Remove deprecated scripts for adding head-matter to wt_config.xml, including Python and Bash implementations, to streamline configuration management.

This commit is contained in:
Torsten Schulz (local)
2025-12-04 16:34:45 +01:00
parent 4b674c7c60
commit 6e9116e819
13187 changed files with 1493219 additions and 337 deletions

View File

@@ -0,0 +1,21 @@
---
name: "Bug report"
about: "If you've found a bug."
---
<!--
PLEASE READ: HELP US SO WE CAN HELP YOU, BY FILLING OUT THIS TEMPLATE
Issues that do not include enough information might not be picked up and closed.
-->
### Version
* Vue version: <!-- 2 or 3 -->
### Description
<!-- Describe the issue -->
### Demo
Please use our [JSFiddle template](https://jsfiddle.net/5yxpmw4v/) to reproduce the bug. Issues without working reproduction might be ignored.

View File

@@ -0,0 +1,4 @@
---
name: "Other"
about: "Open an issue about anything else than a bug."
---

View File

@@ -0,0 +1,16 @@
# Number of days of inactivity before an issue becomes stale
daysUntilStale: 360
# Number of days of inactivity before a stale issue is closed
daysUntilClose: 15
# Issues with these labels will never be considered stale
exemptLabels:
- pinned
- security
- upcoming
- enhancement
# Label to use when marking an issue as stale
staleLabel: stale
# Comment to post when marking an issue as stale. Set to `false` to disable
markComment: Hi 👋 this issue has been automatically marked as `stale` 📌 because it has not had recent activity 😴. It will be closed if no further activity occurs.
# Comment to post when closing a stale issue. Set to `false` to disable
closeComment: Hi again 👋 we would like to inform you that this issue has been automatically closed 🔒 because it had not recent activity during the stale period.

571
client/node_modules/@vueform/multiselect/CHANGELOG.md generated vendored Normal file
View File

@@ -0,0 +1,571 @@
## v2.6.11
> `2024-11-23`
### 🐞 Bug Fixes
- Don't throw error when endpoint type `options` is undefined
## v2.6.10
> `2024-09-16`
### 🐞 Bug Fixes
- Option should display as selected even when value is an object
## v2.6.9
> `2024-07-29`
### 🐞 Bug Fixes
- Removed optional chaining, fixes #420
## v2.6.8
> `2024-06-14`
### 🎉 Feature
- export `resolvedOptions`
### 🐞 Bug Fixes
- Typing fixes (Thanks to @nurbek0298 🙏)
- Always scroll to selected option on open #406
- Use `click` instead of `mousedown` event #387
### 🧹 Chore
- Performance optimization (Thanks to @negezor 🙏)
## v2.6.7
> `2024-03-20`
### 🐞 Bug Fixes
- Clicking on scroll bar closes the dropdown fix #383
- Types for scoped slots #391
- Missing method types and typo fixes #376 #392
- Don't show dropdown id when id is not defined
- Avoid error when multiselect does not exist #394
## v2.6.6
> `2023-10-18`
### 🎉 Feature
- Added `appendTo` option.
- Added `${id}-dropdown` to dropdown DOM.
### 🐞 Bug Fixes
- Included `@popperjs/core` for `appendToBody` / `appendTo` position fixes.
## v2.6.5
> `2023-10-16`
### 🐞 Bug Fixes
- Type fixes.
## v2.6.4
> `2023-10-14`
### 🐞 Bug Fixes
- Type fixes.
## v2.6.3
> `2023-10-07`
### 🎉 Feature
- Added `appendToBody` **experimental** feature for **Vue.js 3 only**. #133 #341
- `trackBy` can now accept array. #314
- Auto truncate long tags. Added `breakTags` prop. #346
- Added `handleCaretClick` and `isOpen` to `caret` slot. #320
- The externalValue (from `v-model`) is now reactive. #356
### 🐞 Bug Fixes
- `limit` prop was not reactive. #342
- Trigger `deselect` on tags backspace. #335
- Keyboard nav on group select fix. #354
- TypeScript fixes. Thanks @antpngl92 @Adeiko @mathildaax 🙏 #287 #282 #260 #230
- `searchFilter` did not receive proper args. #338 #337
- Open dropdown when it has search, it's focued and dropdown is closed. #333
- Options are cleared before `clear` event is emitted. #332
## v2.6.2
> `2023-04-17`
### 🐞 Bug Fixes
- Removed `sideEffects: false` from `package.json`.
## v2.6.1
> `2023-03-14`
### 🐞 Bug Fixes
- Use `.mjs` for `import`.
## v2.6.0
> `2023-03-11`
### 🎉 Feature
- All texts including, option & group labels can now be provided in multiple languages. Added `locale` and `fallbackLocale` props.
- Added `searchFilter` prop that allow to provide a custom search algorithm #313.
- Added `allowAbsent` option to allow adding values that are not among the options.
- Added `closeOnDeselect` prop.
- Deprecated `option` event, added `create` instead (`option` still works).
- Tags that added can also be disabled, which will prevent their removal.
### 🐞 Bug Fixes
- Fix for new option display when using `groups`, `createOption` and `tags` #254 #291.
- String pointer equality #316.
- Disabled tags will not be removed on backspace #318.
- Added `.mjs` extension build and referenced `module` to that #290 #258.
- The `selectAll()` now does not select disabled options and does not duplicate already selected options.
## v2.5.8
> `2022-12-21`
### 🎉 Feature
- Added `--ms-border-width-active` and `--ms-border-color-active` CSS vars #213.
- Added `@max` event #269.
- Added `clearOnBlur` option #251.
### 🐞 Bug Fixes
- Removed `max-height` duplicate from default theme #240.
- Norwegian chars fix #243.
- Trigger `@change` event on updating external value #259.
- Docs fix for 2.7 installation instructions #294.
- Docs fix fiddle url.
- Tags dropdown focus fix #286 #300.
- Stop propagation on tag remove click #295.
## v2.5.7
> `2022-11-21`
### 🎉 Feature
- **BREAKING**: added a wrapper `div` and related classes inside the main container next to the dropdown container.
- Accessibility improvements.
### 🐞 Bug Fixes
- Don't select new tag on IME enter #226.
- Removed `v-html` from option & single label for XSS security #278.
- Arrow left should not throw error when not using tags #271.
## v2.5.6
> `2022-09-28`
### 🐞 Bug Fixes
- Async options resolve fix [#266](https://github.com/vueform/multiselect/issues/266).
## v2.5.5
> `2022-09-26`
### 🎉 Feature
- Unnecessary ES6 feature removed.
## v2.5.4
> `2022-09-26`
### 🎉 Feature
- A11y improvements.
## v2.5.3
> `2022-09-22`
### 🎉 Feature
- A11y improvements.
## v2.5.2
> `2022-07-22`
### 🐞 Bug Fixes
- Fix for `tailwind.css`.
## v2.5.1
> `2022-07-11`
### 🎉 Feature
- Vue `2.7` compatibility.
## v2.5.0
> `2022-07-11`
### 🎉 Feature
- Vue `2.7` compatibility.
## v2.4.2
> `2022-05-31`
### 🐞 Bug Fixes
- Hotfix for ES6 [#235](https://github.com/vueform/multiselect/issues/235)
-
## v2.4.1
> `2022-05-31`
### 🐞 Bug Fixes
- Hotfix for SSR [#235](https://github.com/vueform/multiselect/issues/235)
## v2.4.0
> `2022-05-30`
### 🎉 Feature
- 🎉 Added accessibility (a11y) support [#22](https://github.com/vueform/multiselect/issues/22), [#179](https://github.com/vueform/multiselect/issues/179).
- 🎉 Added infinite scroll [#76](https://github.com/vueform/multiselect/issues/76), [#165](https://github.com/vueform/multiselect/issues/165), [#198](https://github.com/vueform/multiselect/issues/198).
- 🎉 Added RTL support [#206](https://github.com/vueform/multiselect/issues/206).
- 🎉 Close on click if opened [#162](https://github.com/vueform/multiselect/issues/162).
- Added `id` to input when searchable.
- Re-open input on arrows & search type if closed.
- Close dropdown instead of blur on select.
- Added `regex` option [#138](https://github.com/vueform/multiselect/issues/138).
- Scroll to first selected on open [#168](https://github.com/vueform/multiselect/issues/168).
- Options are not reversed when `openPosition: true`.
- Added `reverse` option.
- Added `searchStart` option [#169](https://github.com/vueform/multiselect/issues/169).
- Added `disabledProp` option [#202](https://github.com/vueform/multiselect/issues/202).
- Added `onCreate` option [#204](https://github.com/vueform/multiselect/issues/204).
- Added `select$` as second param to events and async options.
- Added `isSelected` & `isPointed` to `option` slot scope [#195](https://github.com/vueform/multiselect/issues/195).
### 🐞 Bug Fixes
- Headless UI conflict resolved [#182](https://github.com/vueform/multiselect/issues/182).
- Keep selected options when async [#228](https://github.com/vueform/multiselect/issues/228).
- Show spinner even when not active [#223](https://github.com/vueform/multiselect/issues/223).
- Allow `false` value [#222](https://github.com/vueform/multiselect/issues/222).
- Resolve options on `minChars: 0` too [#230](https://github.com/vueform/multiselect/issues/230).
- Added `keyup`, `keydown` events.
- Resolved number tag creation duplicate bug.
- Input height fix when `searchable` for FF.
- CSS: moved max height to dropdown container from wrapper.
- Vite & Nuxt 3 build warn fixes.).
## v2.3.4
> `2022-05-11`
### 🎉 Feature
- Async options change detectiion.
- Label prop change detection.
- Option & label texts can contain HTML.
## v2.3.3
> `2022-02-26`
### 🎉 Feature
- Added `attrs` prop.
## v2.3.2
> `2022-02-06`
### 🐞 Bug Fixes
- Removed `@apply` from default theme.
## v2.3.1
> `2021-12-16`
### 🐞 Bug Fixes
- Removed `exports` from `package.json` [#178](https://github.com/vueform/multiselect/issues/178).
## v2.3.0
> `2021-12-16`
### 🎉 Feature
- **Deprecated:** `appendNewTag`, `createTag`, `addTagOn` props and `@tag` event.
- Added `appendNewOption`, `createOption`, `addOptionOn` props and `@option` event [#150](https://github.com/vueform/multiselect/issues/150).
- Added `selectAll` method [#172](https://github.com/vueform/multiselect/issues/172).
- The `trackBy` prop now defaults to `label` [#175](https://github.com/vueform/multiselect/issues/175).
- Replaces focus on search when an option is selected [#163](https://github.com/vueform/multiselect/issues/163).
- Added `<span>` wrapper for single label with `singleLabelText` class key [#157](https://github.com/vueform/multiselect/issues/157).
### 🐞 Bug Fixes
- Don't show spinner when not active [#156](https://github.com/vueform/multiselect/issues/156).
- Tailwind CSS 3 compatibility issue fix [#176](https://github.com/vueform/multiselect/issues/176).
- Don't show caret when `showOptions` are disabled [#173](https://github.com/vueform/multiselect/issues/173).
- Resolved headless UI modal click issue [#148](https://github.com/vueform/multiselect/issues/148).
- Resolved Tailwind CSS/form ring issue [#135](https://github.com/vueform/multiselect/issues/135).
- Made classes reactive [#126](https://github.com/vueform/multiselect/issues/126).
- The `addTagOn` prop uses `key` instead of `keyCode` internally [#125](https://github.com/vueform/multiselect/issues/125).
## v2.2.1
> `2021-11-23`
### 🐞 Bug Fixes
- Added missing CSS vars.
## v2.2.0
> `2021-09-09`
### 🎉 Feature
- 🎉🎉 Added `groups` and related props which allow groupping options. 🎉🎉
- Added `tailwind.scss` theme to use instead of `classes` if needed.
- Added support for case sensitive tags when `createTag` is `true` [#119](https://github.com/vueform/multiselect/issues/119).
- Added `inputType` prop [#108](https://github.com/vueform/multiselect/issues/116), [#116](https://github.com/vueform/multiselect/issues/116).
- Added `@paste` event [#105](https://github.com/vueform/multiselect/issues/105).
- Added `tab` as option for `addTagOn` [#117](https://github.com/vueform/multiselect/issues/117).
- Updated default `max-height` for dropdown (to `15rem`).
### 🐞 Bug Fixes
- When `closeOnSelect` is `true` in `searchable` `tags` and `multiple` mode the input now blurs upon selecting an option.
- Fix for empty dropdown when async options are loading [#115](https://github.com/vueform/multiselect/issues/115).
- Fixed dropdown auto-scrolling when using arrows.
## v2.1.2
> `2021-08-09`
### 🐞 Bug Fixes
- Removed async/await.
## v2.1.1
> `2021-08-09`
### 🎉 Feature
- Added `closeOnSelect` prop.
### 🐞 Bug Fixes
- Clear search on single option select [#99](https://github.com/vueform/multiselect/issues/99) and [#106](https://github.com/vueform/multiselect/issues/106).
- No blur when tags are being removed.
## v2.1.0
> `2021-07-26`
### 🎉 Feature
- **BREAKING**: `dropdown` class now has `dropdownHidden` when it is closed instead of using `v-show` (requires using 2.1.0's `themes/default.css`)
- **BREAKING**: removed `:maxHeight` prop. Use `var(--ms-max-height)` instead.
- **BREAKING**: tags search layout has changed -> added a wrapper div and an extra span to calculate input width.
- Dropddown can be closed on caret click [#88](https://github.com/vueform/multiselect/issues/88).
- Added `:strict` prop to achieve accent-free search [#82](https://github.com/vueform/multiselect/issues/82).
- Removed inline styles, CSP compilance [#84](https://github.com/vueform/multiselect/issues/84).
- Background images are now customizable via `background-color` [#85](https://github.com/vueform/multiselect/issues/85).
### 🐞 Bug Fixes
- Free typed tags fix [#96](https://github.com/vueform/multiselect/issues/96).
- Tabindex becomes `-1` when `:disabled`.
## v2.0.1
> `2021-06-27`
### 🎉 Feature
- Classname fixes.
- Readme update.
## v2.0.0
> `2021-06-20`
### 🎉 Feature
- **BREAKING**: Completely rewritten `<template>` and `default.css`.
- Caret is now always displayed when `caret: true` regardless if the multiselect has selected option(s).
- Added `canDeselect` and `classes` prop.
- Added `;` and `,` options to `addTagOn` prop.
## v1.5.0
> `2021-05-17`
### 🎉 Feature
- Added native input support [#48](https://github.com/vueform/multiselect/issues/48).
- Added `openDirection` prop [#52](https://github.com/vueform/multiselect/issues/52).
- Added `option` as second param for `select` and `deselect` events.
- Added `@clear` event [#68](https://github.com/vueform/multiselect/issues/68).
### 🐞 Bug Fixes
- Clear icon CSS fix.
- Fixed unintendeed clear button showing up when `canDeselect` is `false` [#61](https://github.com/vueform/multiselect/issues/61).
## v1.4.0
> `2021-04-06`
### 🎉 Feature
- **BREAKING**: array `options` are no longer treated as objects, but both option value and label will equal to array item value. Eg. `option: ['v1','v2']` used to be equal to `{0: 'v1', 1: 'v2'}`, now they're equal to `{v1: 'v1', v2: 'v2'}`.
- Optimized variable names for lower package size.
## v1.3.7
> `2021-04-06`
### 🐞 Bug Fixes
- Async options fix for [#39](https://github.com/vueform/multiselect/issues/39). Refreshing non-async options now will only be reflected after a tick.
### 🎉 Feature
- Added caret, remove and spinner slots.
## v1.3.6
> `2021-03-30`
### 🐞 Bug Fixes
- Remove extra space when creating a tag with space ([#46](https://github.com/vueform/multiselect/issues/46)).
- Fixed issues around refreshing async options ([#45](https://github.com/vueform/multiselect/issues/45)).
- Android keyboard fix ([#49](https://github.com/vueform/multiselect/issues/49) & [#50](https://github.com/vueform/multiselect/issues/50)).
### 🎉 Feature
- Added installation guide for Nuxt.js.
## v1.3.5
> `2021-03-20`
### 🐞 Bug Fixes
- Fix for unintended side effect on space when using single mode ([#42](https://github.com/vueform/multiselect/issues/42)).
## v1.3.4
> `2021-03-13`
### 🐞 Bug Fixes
- Recursion error when accessing `value` inside computed `options` ([#39](https://github.com/vueform/multiselect/issues/39)).
- Set initial value when options are loaded later ([#40](https://github.com/vueform/multiselect/issues/40)).
## v1.3.3
> `2021-03-12`
### 🐞 Bug Fixes
- Selected items' label update when options label change ([#39](https://github.com/vueform/multiselect/issues/39)).
- Horizontal scroll fix ([#31](https://github.com/vueform/multiselect/issues/31)).
### 🎉 Feature
- Added `addTagOn` that can enable `enter` and/or `space` key to create a tag.
- Added `required` prop that renders a HTML5 required attribute on a fake input next to multiselect.
- Added `showOptions` prop that hide options list if somebody wants to have only a free-type tag list.
## v1.3.2
> `2021-02-05`
### 🐞 Bug Fixes
- Tags slots scope updated with `handleTagRemove` instead of `remove`.
## v1.3.1
> `2021-02-05`
### 🐞 Bug Fixes
- Readme API fix.
## v1.3.0
> `2021-02-05`
### 🎉 Feature
- Added Typescript definitions based on [#20](https://github.com/vueform/multiselect/pull/20).
- Added Clear button for `multiple` and `tags` mode.
- Added `placeholder` slot.
- Added proper `open` and `close` methods.
- Hide options when resolving with `clearOnSearch` `true`.
- Added `refreshOptions` method to refresh async options.
- Added API docs.
### 🐞 Bug Fixes
- Added fix for #26. The value now can be set the same time that `options` change.
- Added fix for #28. Right mouse click no longer removes tag.
- Added fix for #29. Focus is no longer trapped to option list when using search.
## v1.2.5
> `2021-01-17`
### 🐞 Bug Fixes
- Close open dropdown on input click
- Select first option after async search fix #18
- Update options when `:options` property changes #16 #17
## v1.2.4
> `2021-01-12`
### 🎉 Feature
- Added `:max` property
### 🐞 Bug Fixes
- Backspace issue fix #9
- Custom label issue fix #13
## v1.2.3
> `2020-12-29`
### 🐞 Bug Fixes
- `v-model` deep sync
## v1.2.2
> `2020-12-28`
### 🎉 Feature
- Added `canDeselect` option
### 🐞 Bug Fixes
- Set internalValue on init when using async options with `:object` `true` and `resolveOnLoad` `false`
## v1.2.1
> `2020-12-23`
### 🐞 Bug Fixes
- Changes in `v-model` will sync with multiselect value
## v1.2.0
> `2020-12-23`
### 🎉 Feature
- Options now can be disabled using an array of objects as options, with `disabled` property being set to `true`
- The `value` property of an object option now can be customized with `:valueProp`
## v1.1.3
> `2020-12-19`
### 🐞 Bug Fixes
- **Breaking**: Renamed slots to lowercase instead of camel case because of [DOM issue](https://github.com/vuejs/vue/issues/9449#issuecomment-461170017)
### 🎉 Feature
- Added `change` event
- **Breaking**: renamed `hideSelectedTag` to `hideSelected`
## v1.1.2
> `2020-12-19`
### 🎉 Feature
- Async/await eliminated from code thus reducing bundle size without runtimeRegenerator
- UMD bundle replaced with global
## v1.1.1
> `2020-12-19`
### 🐞 Bug Fixes
- RuntimeRegenerator added to esm builds
## v1.1.0
> `2020-12-18`
### 🎉 Feature
- Options now can be defined as a sync or async function. This opens up the capabilities to load options from remote data source and/or to implement autocomplete behaviour.

21
client/node_modules/@vueform/multiselect/LICENSE.md generated vendored Normal file
View File

@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2020-2021 Adam Berecz <adam@vueform.com>.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

1165
client/node_modules/@vueform/multiselect/README.md generated vendored Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

92
client/node_modules/@vueform/multiselect/package.json generated vendored Normal file
View File

@@ -0,0 +1,92 @@
{
"name": "@vueform/multiselect",
"version": "2.6.11",
"private": false,
"description": "Vue 3 multiselect component with single select, multiselect and tagging options.",
"license": "MIT",
"author": "Adam Berecz <adam@vueform.com>",
"main": "./dist/multiselect.js",
"types": "./src/index.d.ts",
"module": "./dist/multiselect.mjs",
"unpkg": "./dist/multiselect.global.js",
"jsdelivr": "./dist/multiselect.global.js",
"style": "./themes/default.css",
"exports": {
".": {
"types": "./src/index.d.ts",
"node": {
"import": {
"production": "./dist/multiselect.mjs",
"development": "./dist/multiselect.mjs",
"default": "./dist/multiselect.mjs"
},
"require": {
"production": "./dist/multiselect.js",
"development": "./dist/multiselect.js",
"default": "./dist/multiselect.js"
}
},
"import": "./dist/multiselect.mjs",
"require": "./dist/multiselect.js"
},
"./src/*": "./src/*",
"./dist/*": "./dist/*",
"./themes/*": "./themes/*",
"./package.json": "./package.json"
},
"repository": {
"type": "git",
"url": "git+https://github.com/vueform/multiselect.git"
},
"bugs": {
"url": "https://github.com/vueform/multiselect/issues"
},
"scripts": {
"build": "npm run build:vue2; npm run build:vue3; npm run build:themes",
"build:vue2": "rollup --config build/vue2.rollup.config.js",
"build:vue3": "rollup --config build/vue3.rollup.config.js",
"build:themes": "rollup --config build/themes.rollup.config.js",
"test": "jest --config=jest/jest.config.vue3.js; jest --config=jest/jest.config.vue2.js;",
"test:vue2": "jest --verbose --config=jest/jest.config.vue2.js",
"test:vue3": "jest --verbose --config=jest/jest.config.vue3.js"
},
"devDependencies": {
"@babel/core": "^7.12.10",
"@babel/plugin-transform-modules-umd": "^7.12.1",
"@babel/preset-env": "^7.12.10",
"@popperjs/core": "^2.11.8",
"@rollup/plugin-alias": "^3.1.1",
"@rollup/plugin-babel": "^5.2.2",
"@rollup/plugin-node-resolve": "^15.2.3",
"@testing-library/jest-dom": "^5.11.5",
"@vue/compiler-sfc": "^3.0.4",
"autoprefixer": "^10.3.1",
"babel-core": "^7.0.0-bridge.0",
"babel-jest": "^27.3.1",
"babel-plugin-rename-umd-globals": "^1.0.0",
"flush-promises": "^1.0.2",
"fraction.js": "^4.1.1",
"jest": "^27.3.1",
"jest-environment-jsdom-sixteen": "^1.0.3",
"node-sass": "^7.0.0",
"postcss": "^8.4.31",
"rollup": "^2.34.2",
"rollup-plugin-postcss": "^4.0.0",
"rollup-plugin-terser": "^7.0.2",
"typescript": "^4.1.2",
"vue-next": "npm:vue@^3.2.20",
"vue-next-jest": "npm:@vue/vue3-jest@^27.0.0-alpha.1",
"vue-next-rollup-plugin-vue": "npm:rollup-plugin-vue@^6.0.0",
"vue-next-test-utils": "npm:@vue/test-utils@2.0.0-rc.16",
"vue-prev": "npm:vue@^2.7.8",
"vue-prev-jest": "npm:@vue/vue2-jest@^27.0.0-alpha.2",
"vue-prev-rollup-plugin-vue": "npm:rollup-plugin-vue@^5.1.9",
"vue-prev-test-utils": "npm:@vue/test-utils@1.2.2",
"vue-template-compiler": "^2.7.8"
},
"browserslist": [
"> 1%",
"last 2 versions",
"not dead"
]
}

View File

@@ -0,0 +1 @@
module.exports = {}

View File

@@ -0,0 +1,297 @@
import { VNode, defineComponent } from 'vue';
interface ClassList {
assist: string;
caret: Array<string>;
clear: string;
clearIcon: string;
container: Array<string>;
dropdown: Array<string>;
fakeInput: string;
group: string;
groupLabel: (g: any) => any;
groupOptions: string;
inifinite: string;
inifiniteSpinner: string;
multipleLabel: string;
noOptions: string;
noResults: string;
option: (o:any, g:any) => any;
options: Array<string>;
placeholder: string;
search: string;
singleLabel: string;
singleLabelText: string;
spacer: string;
spinner: string;
tag: Array<string>
tagDisabled: string;
tagRemove: string;
tagRemoveIcon: string;
tags: string;
tagsSearch: string;
tagsSearchCopy: string;
tagsSearchWrapper: string;
wrapper: string;
}
interface MultiselectProps {
modelValue?: any;
value?: any;
mode?: 'single' | 'multiple' | 'tags';
options?: any[] | object | Function;
searchable?: boolean;
valueProp?: string;
trackBy?: string | string[];
label?: string;
placeholder?: string | null;
multipleLabel?: any; // Function
disabled?: boolean;
max?: number;
limit?: number;
loading?: boolean;
id?: string;
caret?: boolean;
maxHeight?: string | number;
noOptionsText?: string | object;
noResultsText?: string | object;
canDeselect?: boolean;
canClear?: boolean;
clearOnSearch?: boolean;
clearOnSelect?: boolean;
delay?: number;
filterResults?: boolean;
minChars?: number;
resolveOnLoad?: boolean;
appendNewTag?: boolean;
appendNewOption?: boolean;
createTag?: boolean;
createOption?: boolean;
addTagOn?: string[];
addOptionOn?: string[];
hideSelected?: boolean;
showOptions?: boolean;
object?: boolean;
required?: boolean;
openDirection?: 'top' | 'bottom';
nativeSupport?: boolean;
classes?: object;
strict?: boolean;
closeOnSelect?: boolean;
closeOnDeselect?: boolean;
autocomplete?: string;
groups?: boolean;
groupLabel?: string;
groupOptions?: string;
groupHideEmpty?: boolean;
groupSelect?: boolean;
inputType?: string;
attrs?: object;
onCreate?: Function;
searchStart?: boolean;
reverse?: boolean;
regex?: string | object;
rtl?: boolean;
infinite?: boolean;
aria?: object;
clearOnBlur?: boolean;
locale?: string;
fallbackLocale?: string;
searchFilter?: Function;
allowAbsent?: boolean;
appendToBody?: boolean;
closeOnScroll?: boolean;
breakTags?: boolean;
appendTo?: string;
}
declare class Multiselect implements ReturnType<typeof defineComponent> {
modelValue: MultiselectProps['modelValue'];
value: MultiselectProps['value'];
mode: MultiselectProps['mode'];
options: MultiselectProps['options'];
searchable: MultiselectProps['searchable'];
valueProp: MultiselectProps['valueProp'];
trackBy: MultiselectProps['trackBy'];
label: MultiselectProps['label'];
placeholder: MultiselectProps['placeholder'];
multipleLabel: MultiselectProps['multipleLabel'];
disabled: MultiselectProps['disabled'];
max: MultiselectProps['max'];
limit: MultiselectProps['limit'];
loading: MultiselectProps['loading'];
id: MultiselectProps['id'];
caret: MultiselectProps['caret'];
maxHeight: MultiselectProps['maxHeight'];
noOptionsText: MultiselectProps['noOptionsText'];
noResultsText: MultiselectProps['noResultsText'];
canDeselect: MultiselectProps['canDeselect'];
canClear: MultiselectProps['canClear'];
clearOnSearch: MultiselectProps['clearOnSearch'];
clearOnSelect: MultiselectProps['clearOnSelect'];
delay: MultiselectProps['delay'];
filterResults: MultiselectProps['filterResults'];
minChars: MultiselectProps['minChars'];
resolveOnLoad: MultiselectProps['resolveOnLoad'];
appendNewTag: MultiselectProps['appendNewTag'];
appendNewOption: MultiselectProps['appendNewOption'];
createTag: MultiselectProps['createTag'];
createOption: MultiselectProps['createOption'];
addTagOn: MultiselectProps['addTagOn'];
addOptionOn: MultiselectProps['addOptionOn'];
hideSelected: MultiselectProps['hideSelected'];
showOptions: MultiselectProps['showOptions'];
object: MultiselectProps['object'];
required: MultiselectProps['required'];
openDirection: MultiselectProps['openDirection'];
nativeSupport: MultiselectProps['nativeSupport'];
classes: MultiselectProps['classes'];
strict: MultiselectProps['strict'];
closeOnSelect: MultiselectProps['closeOnSelect'];
closeOnDeselect: MultiselectProps['closeOnDeselect'];
autocomplete: MultiselectProps['autocomplete'];
groups: MultiselectProps['groups'];
groupLabel: MultiselectProps['groupLabel'];
groupOptions: MultiselectProps['groupOptions'];
groupHideEmpty: MultiselectProps['groupHideEmpty'];
groupSelect: MultiselectProps['groupSelect'];
inputType: MultiselectProps['inputType'];
attrs: MultiselectProps['attrs'];
onCreate: MultiselectProps['onCreate'];
searchStart: MultiselectProps['searchStart'];
reverse: MultiselectProps['reverse'];
regex: MultiselectProps['regex'];
rtl: MultiselectProps['rtl'];
infinite: MultiselectProps['infinite'];
aria: MultiselectProps['aria'];
clearOnBlur: MultiselectProps['clearOnBlur'];
locale: MultiselectProps['locale'];
fallbackLocale: MultiselectProps['fallbackLocale'];
searchFilter: MultiselectProps['searchFilter'];
allowAbsent: MultiselectProps['allowAbsent'];
appendToBody: MultiselectProps['appendToBody'];
closeOnScroll: MultiselectProps['closeOnScroll'];
breakTags: MultiselectProps['breakTags'];
appendTo: MultiselectProps['appendTo'];
$props: MultiselectProps;
$emit(eventName: 'change', value: any, instance: this): this | void;
$emit(eventName: 'select', value: any, option: any, instance:this): this | void;
$emit(eventName: 'deselect', value: any, option: any, instance:this): this | void;
$emit(eventName: 'search-change', query: string, instance: this): this | void;
$emit(eventName: 'tag', option: any, instance: this): this | void;
$emit(eventName: 'option', option: any, instance: this): this | void;
$emit(eventName: 'create', option: any, instance: this): this | void;
$emit(eventName: 'paste', e: Event, instance: this): this | void;
$emit(eventName: 'keydown', e: Event, instance: this): this | void;
$emit(eventName: 'keyup', e: Event, instance: this): this | void;
$emit(eventName: 'open', instance: this): this | void;
$emit(eventName: 'close', instance: this): this | void;
$emit(eventName: 'clear', instance: this): this | void;
$emit(eventName: 'max', instance: this): this | void;
$slots: {
placeholder: VNode[];
afterlist: (props: { options: any[] }) => VNode[];
beforelist: (props: { options: any[] }) => VNode[];
multiplelabel: (props: { values: any[] | object }) => VNode[];
singlelabel: (props: { value: any }) => VNode[];
option: (props: { option: any, isSelected: (option: any) => boolean, isPointed: (option: any) => boolean, search: null | string }) => VNode[];
grouplabel: (props: { group: any, isSelected: (option: any) => boolean, isPointed: (option: any) => boolean }) => VNode[];
tag: (props: { option: any, handleTagRemove: (option: any, e: Event) => void, disabled: boolean, }) => VNode[];
infinite: VNode[];
nooptions: VNode[];
noresults: VNode[];
caret: (props: { handleCaretClick: () => void, isOpen: boolean, }) => VNode[];
clear: (props: { clear: () => void }) => VNode[];
spinner: VNode[];
};
activate: (shouldOpen?: boolean) => void;
ariaActiveDescendant: string | undefined;
ariaAssist: string;
ariaControls: string;
ariaGroupId: (option : any) => string;
ariaGroupLabel: (label: any) => string;
ariaLabel: string;
ariaMultiselectable: boolean;
ariaOptionId: (option: any) => string;
ariaOptionLabel: (label: any) => string;
ariaPlaceholder: any;
ariaTagLabel: (label: any) => string;
arias: object;
backwardPointer: any;
blur: () => void;
busy: boolean;
canPointGroups: boolean;
classList: ClassList;
clear: () => void;
clearPointer: () => void;
clearSearch: () => void;
close: () => void;
deactivate: () => void;
deselect: (option: any) => void;
disabledProp?: string;
extendedGroups: Array<any>
extendedOptions: Array<any>
externalValue: any;
filteredGroups: Array<any>
filteredOptions: any;
focus: () => void;
forwardPointer: () => void;
getOption: (val: any) => any
handleCaretClick: () => void;
handleFocusIn: (e: any) => void;
handleFocusOut: () => void;
handleGroupClick: (group: any) => void;
handleKeydown: (e: Event) => void;
handleKeypress: (e: Event) => void;
handleKeyup: (e: Event) => void;
handleMousedown: (e: Event) => void;
handleOptionClic: (option: any) => void;
handlePaste: (e: Event) => void;
handleSearchInput: (e: Event) => void;
handleTagRemove: (option: any, e: Event) => void;
hasMore: boolean;
hasSelected: boolean;
infiniteLoader: any;
input: HTMLInputElement;
internalValue: any;
isActive: boolean;
isDisabled: boolean;
isMax: () => boolean;
isOpen: boolean;
isPointed: (option: any) => boolean | undefined;
isSelected: (option: any) => boolean;
localize: (target: any) => any;
mouseClicked: boolean;
multipleLabelText: string;
multiselect: any;
offset: number;
open: () => void;
plainValue: any;
pointer: any;
placement: string;
popper: object;
preparePointer: () => void;
refreshLabels: () => void;
refreshOptions: (callback: any) => void;
remove: (option: any) => void;
resolveOptions: (callback: any) => void;
resolving: boolean;
search: any;
select: (option: any) => void;
selectAll: () => void;
selectPointer: () => void;
setPointer: (option: any) => void;
setPointerFirst: () => void;
showDropdown: boolean;
tabindex: number;
tags: any;
textValue: any;
update: (val: any, triggerInput?: boolean) => void;
updatePopper: () => void;
}
export default Multiselect;

View File

@@ -0,0 +1,691 @@
<template>
<div
ref="multiselect"
:class="classList.container"
:id="searchable ? undefined : id"
:dir="rtl ? 'rtl' : undefined"
@focusin="handleFocusIn"
@focusout="handleFocusOut"
@keyup="handleKeyup"
@keydown="handleKeydown"
>
<div
:class="classList.wrapper"
@mousedown="handleMousedown"
ref="wrapper"
:tabindex="tabindex"
:aria-controls="!searchable ? ariaControls : undefined"
:aria-placeholder="!searchable ? ariaPlaceholder : undefined"
:aria-expanded="!searchable ? isOpen : undefined"
:aria-activedescendant="!searchable ? ariaActiveDescendant : undefined"
:aria-multiselectable="!searchable ? ariaMultiselectable : undefined"
:role="!searchable ? 'combobox' : undefined"
v-bind="!searchable ? arias : {}"
>
<!-- Search -->
<template v-if="mode !== 'tags' && searchable && !disabled">
<input
:type="inputType"
:modelValue="search"
:value="search"
:class="classList.search"
:autocomplete="autocomplete"
:id="searchable ? id : undefined"
@input="handleSearchInput"
@keypress="handleKeypress"
@paste.stop="handlePaste"
ref="input"
:aria-controls="ariaControls"
:aria-placeholder="ariaPlaceholder"
:aria-expanded="isOpen"
:aria-activedescendant="ariaActiveDescendant"
:aria-multiselectable="ariaMultiselectable"
role="combobox"
v-bind="{
...attrs,
...arias,
}"
/>
</template>
<!-- Tags (with search) -->
<template v-if="mode == 'tags'">
<div :class="classList.tags" data-tags>
<slot
v-for="(option, i, key) in iv"
name="tag"
:option="option"
:handleTagRemove="handleTagRemove"
:disabled="disabled"
>
<span
:class="[
classList.tag,
option.disabled ? classList.tagDisabled : null,
]"
tabindex="-1"
@keyup.enter="handleTagRemove(option, $event)"
:key="key"
:aria-label="ariaTagLabel(localize(option[label]))"
>
<span :class="classList.tagWrapper">{{ localize(option[label]) }}</span>
<span
v-if="!disabled && !option.disabled"
:class="classList.tagRemove"
@click.stop="handleTagRemove(option, $event)"
>
<span :class="classList.tagRemoveIcon"></span>
</span>
</span>
</slot>
<div :class="classList.tagsSearchWrapper" ref="tags">
<!-- Used for measuring search width -->
<span :class="classList.tagsSearchCopy">{{ search }}</span>
<!-- Actual search input -->
<input
v-if="searchable && !disabled"
:type="inputType"
:modelValue="search"
:value="search"
:class="classList.tagsSearch"
:id="searchable ? id : undefined"
:autocomplete="autocomplete"
@input="handleSearchInput"
@keypress="handleKeypress"
@paste.stop="handlePaste"
ref="input"
:aria-controls="ariaControls"
:aria-placeholder="ariaPlaceholder"
:aria-expanded="isOpen"
:aria-activedescendant="ariaActiveDescendant"
:aria-multiselectable="ariaMultiselectable"
role="combobox"
v-bind="{
...attrs,
...arias,
}"
/>
</div>
</div>
</template>
<!-- Single label -->
<template v-if="mode == 'single' && hasSelected && !search && iv">
<slot name="singlelabel" :value="iv">
<div :class="classList.singleLabel">
<span :class="classList.singleLabelText">{{ localize(iv[label]) }}</span>
</div>
</slot>
</template>
<!-- Multiple label -->
<template v-if="mode == 'multiple' && hasSelected && !search">
<slot name="multiplelabel" :values="iv">
<div :class="classList.multipleLabel" v-html="multipleLabelText"></div>
</slot>
</template>
<!-- Placeholder -->
<template v-if="placeholder && !hasSelected && !search">
<slot name="placeholder">
<div :class="classList.placeholder" aria-hidden="true">
{{ placeholder }}
</div>
</slot>
</template>
<!-- Spinner -->
<slot v-if="loading || resolving" name="spinner">
<span :class="classList.spinner" aria-hidden="true"></span>
</slot>
<!-- Clear -->
<slot v-if="hasSelected && !disabled && canClear && !busy" name="clear" :clear="clear">
<span
aria-hidden="true"
tabindex="0"
role="button"
data-clear
aria-roledescription="❎"
:class="classList.clear"
@click="clear"
@keyup.enter="clear"
><span :class="classList.clearIcon"></span></span>
</slot>
<!-- Caret -->
<slot v-if="caret && showOptions" name="caret" :handle-caret-click="handleCaretClick" :is-open="isOpen">
<span :class="classList.caret" @click="handleCaretClick" aria-hidden="true"></span>
</slot>
</div>
<!-- Options -->
<Teleport :to="appendTo || 'body'" :disabled="!appendToBody && !appendTo">
<div
:id="id ? `${id}-dropdown` : undefined"
:class="classList.dropdown"
tabindex="-1"
ref="dropdown"
@focusin="handleFocusIn"
@focusout="handleFocusOut"
>
<slot name="beforelist" :options="fo"></slot>
<ul :class="classList.options" :id="ariaControls" role="listbox">
<template v-if="groups">
<li
v-for="(group, i, key) in fg"
:class="classList.group"
:key="key"
:id="ariaGroupId(group)"
:aria-label="ariaGroupLabel(localize(group[groupLabel]))"
:aria-selected="isSelected(group)"
role="option"
>
<div
v-if="!group.__CREATE__"
:class="classList.groupLabel(group)"
:data-pointed="isPointed(group)"
@mouseenter="setPointer(group, i)"
@click="handleGroupClick(group)"
>
<slot name="grouplabel" :group="group" :is-selected="isSelected" :is-pointed="isPointed">
<span v-html="localize(group[groupLabel])"></span>
</slot>
</div>
<ul
:class="classList.groupOptions"
:aria-label="ariaGroupLabel(localize(group[groupLabel]))"
role="group"
>
<li
v-for="(option, i, key) in group.__VISIBLE__"
:class="classList.option(option, group)"
:data-pointed="isPointed(option)"
:data-selected="isSelected(option) || undefined"
:key="key"
@mouseenter="setPointer(option)"
@click="handleOptionClick(option)"
:id="ariaOptionId(option)"
:aria-selected="isSelected(option)"
:aria-label="ariaOptionLabel(localize(option[label]))"
role="option"
>
<slot name="option" :option="option" :is-selected="isSelected" :is-pointed="isPointed" :search="search">
<span>{{ localize(option[label]) }}</span>
</slot>
</li>
</ul>
</li>
</template>
<template v-else>
<li
v-for="(option, i, key) in fo"
:class="classList.option(option)"
:data-pointed="isPointed(option)"
:data-selected="isSelected(option) || undefined"
:key="key"
@mouseenter="setPointer(option)"
@click="handleOptionClick(option)"
:id="ariaOptionId(option)"
:aria-selected="isSelected(option)"
:aria-label="ariaOptionLabel(localize(option[label]))"
role="option"
>
<slot name="option" :option="option" :isSelected="isSelected" :is-pointed="isPointed" :search="search">
<span>{{ localize(option[label]) }}</span>
</slot>
</li>
</template>
</ul>
<slot v-if="noOptions" name="nooptions">
<div :class="classList.noOptions" v-html="localize(noOptionsText)"></div>
</slot>
<slot v-if="noResults" name="noresults">
<div :class="classList.noResults" v-html="localize(noResultsText)"></div>
</slot>
<div v-if="infinite && hasMore" :class="classList.inifinite" ref="infiniteLoader">
<slot name="infinite">
<span :class="classList.inifiniteSpinner"></span>
</slot>
</div>
<slot name="afterlist" :options="fo"></slot>
</div>
</Teleport>
<!-- Hacky input element to show HTML5 required warning -->
<input v-if="required" :class="classList.fakeInput" tabindex="-1" :value="textValue" required/>
<!-- Native input support -->
<template v-if="nativeSupport">
<input v-if="mode == 'single'" type="hidden" :name="name" :value="plainValue !== undefined ? plainValue : ''" />
<template v-else>
<input v-for="(v, i) in plainValue" type="hidden" :name="`${name}[]`" :value="v" :key="i" />
</template>
</template>
<!-- Screen reader assistive text -->
<div v-if="searchable && hasSelected" :class="classList.assist" :id="ariaAssist" aria-hidden="true">
{{ ariaLabel }}
</div>
<!-- Create height for empty input -->
<div :class="classList.spacer"></div>
</div>
</template>
<script>
/* istanbul ignore file */
import useData from './composables/useData'
import useValue from './composables/useValue'
import useSearch from './composables/useSearch'
import usePointer from './composables/usePointer'
import useOptions from './composables/useOptions'
import usePointerAction from './composables/usePointerAction'
import useDropdown from './composables/useDropdown'
import useMultiselect from './composables/useMultiselect'
import useKeyboard from './composables/useKeyboard'
import useClasses from './composables/useClasses'
import useScroll from './composables/useScroll'
import useA11y from './composables/useA11y'
import useI18n from './composables/useI18n'
import useRefs from './composables/useRefs'
import resolveDeps from './utils/resolveDeps'
export default {
name: 'Multiselect',
emits: [
'paste', 'open', 'close', 'select', 'deselect',
'input', 'search-change', 'tag', 'option', 'update:modelValue',
'change', 'clear', 'keydown', 'keyup', 'max', 'create',
],
props: {
value: {
required: false,
},
modelValue: {
required: false,
},
options: {
type: [Array, Object, Function],
required: false,
default: () => ([])
},
id: {
type: [String, Number],
required: false,
default: undefined,
},
name: {
type: [String, Number],
required: false,
default: 'multiselect',
},
disabled: {
type: Boolean,
required: false,
default: false,
},
label: {
type: String,
required: false,
default: 'label',
},
trackBy: {
type: [String, Array],
required: false,
default: undefined,
},
valueProp: {
type: String,
required: false,
default: 'value',
},
placeholder: {
type: String,
required: false,
default: null,
},
mode: {
type: String,
required: false,
default: 'single', // single|multiple|tags
},
searchable: {
type: Boolean,
required: false,
default: false,
},
limit: {
type: Number,
required: false,
default: -1,
},
hideSelected: {
type: Boolean,
required: false,
default: true,
},
createTag: {
type: Boolean,
required: false,
default: undefined,
},
createOption: {
type: Boolean,
required: false,
default: undefined,
},
appendNewTag: {
type: Boolean,
required: false,
default: undefined,
},
appendNewOption: {
type: Boolean,
required: false,
default: undefined,
},
addTagOn: {
type: Array,
required: false,
default: undefined,
},
addOptionOn: {
type: Array,
required: false,
default: undefined,
},
caret: {
type: Boolean,
required: false,
default: true,
},
loading: {
type: Boolean,
required: false,
default: false,
},
noOptionsText: {
type: [String, Object],
required: false,
default: 'The list is empty',
},
noResultsText: {
type: [String, Object],
required: false,
default: 'No results found',
},
multipleLabel: {
type: Function,
required: false,
default: undefined,
},
object: {
type: Boolean,
required: false,
default: false,
},
delay: {
type: Number,
required: false,
default: -1,
},
minChars: {
type: Number,
required: false,
default: 0,
},
resolveOnLoad: {
type: Boolean,
required: false,
default: true,
},
filterResults: {
type: Boolean,
required: false,
default: true,
},
clearOnSearch: {
type: Boolean,
required: false,
default: false,
},
clearOnSelect: {
type: Boolean,
required: false,
default: true,
},
canDeselect: {
type: Boolean,
required: false,
default: true,
},
canClear: {
type: Boolean,
required: false,
default: true,
},
max: {
type: Number,
required: false,
default: -1,
},
showOptions: {
type: Boolean,
required: false,
default: true,
},
required: {
type: Boolean,
required: false,
default: false,
},
openDirection: {
type: String,
required: false,
default: 'bottom',
},
nativeSupport: {
type: Boolean,
required: false,
default: false,
},
classes: {
type: Object,
required: false,
default: () => ({})
},
strict: {
type: Boolean,
required: false,
default: true,
},
closeOnSelect: {
type: Boolean,
required: false,
default: true,
},
closeOnDeselect: {
type: Boolean,
required: false,
default: false,
},
autocomplete: {
type: String,
required: false,
default: undefined,
},
groups: {
type: Boolean,
required: false,
default: false,
},
groupLabel: {
type: String,
required: false,
default: 'label',
},
groupOptions: {
type: String,
required: false,
default: 'options',
},
groupHideEmpty: {
type: Boolean,
required: false,
default: false,
},
groupSelect: {
type: Boolean,
required: false,
default: true,
},
inputType: {
type: String,
required: false,
default: 'text',
},
attrs: {
required: false,
type: Object,
default: () => ({}),
},
onCreate: {
required: false,
type: Function,
default: undefined,
},
disabledProp: {
type: String,
required: false,
default: 'disabled',
},
searchStart: {
type: Boolean,
required: false,
default: false,
},
reverse: {
type: Boolean,
required: false,
default: false,
},
regex: {
type: [Object, String, RegExp],
required: false,
default: undefined,
},
rtl: {
type: Boolean,
required: false,
default: false,
},
infinite: {
type: Boolean,
required: false,
default: false,
},
aria: {
required: false,
type: Object,
default: () => ({}),
},
clearOnBlur: {
required: false,
type: Boolean,
default: true,
},
locale: {
required: false,
type: String,
default: null,
},
fallbackLocale: {
required: false,
type: String,
default: 'en',
},
searchFilter: {
required: false,
type: Function,
default: null,
},
allowAbsent: {
required: false,
type: Boolean,
default: false,
},
appendToBody: {
required: false,
type: Boolean,
default: false,
},
closeOnScroll: {
required: false,
type: Boolean,
default: false,
},
breakTags: {
required: false,
type: Boolean,
default: false,
},
appendTo: {
required: false,
type: String,
default: undefined,
},
},
setup(props, context)
{
return resolveDeps(props, context, [
useRefs,
useI18n,
useValue,
usePointer,
useDropdown,
useSearch,
useData,
useMultiselect,
useOptions,
useScroll,
usePointerAction,
useKeyboard,
useClasses,
useA11y,
])
},
beforeMount() {
if ((this.$root.constructor && this.$root.constructor.version && this.$root.constructor.version.match(/^2\./)) || this.vueVersionMs === 2) {
if (!this.$options.components.Teleport) {
this.$options.components.Teleport = {
render() {
return this.$slots.default ? this.$slots.default[0] : null
}
}
}
}
}
}
</script>

View File

@@ -0,0 +1,127 @@
import { toRefs, onMounted, ref, computed } from 'vue'
import toRef from './../utils/toRef'
export default function useA11y (props, context, dep)
{
const {
placeholder, id, valueProp, label: labelProp, mode, groupLabel, aria, searchable ,
} = toRefs(props)
// ============ DEPENDENCIES ============
const pointer = dep.pointer
const iv = dep.iv
const hasSelected = dep.hasSelected
const multipleLabelText = dep.multipleLabelText
// ================ DATA ================
const label = ref(null)
// ============== COMPUTED ==============
const ariaAssist = toRef(() => (
`${id.value ? id.value + '-' : ''}assist`
))
const ariaControls = toRef(() => (
`${id.value ? id.value + '-' : ''}multiselect-options`
))
const ariaActiveDescendant = toRef(() => {
if (pointer.value) {
let texts = id.value
? `${id.value}-`
: '';
texts += `${pointer.value.group ? 'multiselect-group' : 'multiselect-option'}-`
texts += pointer.value.group ? pointer.value.index : pointer.value[valueProp.value]
return texts
}
})
const ariaPlaceholder = toRef(() => {
return placeholder.value
})
const ariaMultiselectable = toRef(() => {
return mode.value !== 'single'
})
const ariaLabel = computed(() => {
if (mode.value === 'single' && hasSelected.value) {
return iv.value[labelProp.value]
}
if (mode.value === 'multiple' && hasSelected.value) {
return multipleLabelText.value
}
if (mode.value === 'tags' && hasSelected.value) {
return iv.value.map(v => v[labelProp.value]).join(', ')
}
return ''
})
const arias = computed(() => {
let arias = { ...aria.value }
// Need to add manually because focusing
// the input won't read the selected value
if (searchable.value) {
arias['aria-labelledby'] = arias['aria-labelledby']
? `${ariaAssist.value} ${arias['aria-labelledby']}`
: ariaAssist.value
if (ariaLabel.value && arias['aria-label']) {
arias['aria-label'] = `${ariaLabel.value}, ${arias['aria-label']}`
}
}
return arias
})
// =============== METHODS ==============
const ariaOptionId = (option) => (
`${id.value ? id.value + '-' : ''}multiselect-option-${option[valueProp.value]}`
)
const ariaGroupId = (option) => (
`${id.value ? id.value + '-' : ''}multiselect-group-${option.index}`
)
const ariaOptionLabel = (label) => `${label}`
const ariaGroupLabel = (label) => `${label}`
const ariaTagLabel = (label) => `${label}`
// =============== HOOKS ================
onMounted(() => {
/* istanbul ignore next */
if (id.value && document && document.querySelector) {
let forTag = document.querySelector(`[for="${id.value}"]`)
label.value = forTag ? forTag.innerText : null
}
})
return {
arias,
ariaLabel,
ariaAssist,
ariaControls,
ariaPlaceholder,
ariaMultiselectable,
ariaActiveDescendant,
ariaOptionId,
ariaOptionLabel,
ariaGroupId,
ariaGroupLabel,
ariaTagLabel,
}
}

View File

@@ -0,0 +1,165 @@
import { computed, toRefs } from 'vue'
import toRef from './../utils/toRef'
export default function useClasses (props, context, dependencies)
{const {
classes: classes_, disabled, showOptions, breakTags
} = toRefs(props)
// ============ DEPENDENCIES ============
const isOpen = dependencies.isOpen
const isPointed = dependencies.isPointed
const isSelected = dependencies.isSelected
const isDisabled = dependencies.isDisabled
const isActive = dependencies.isActive
const canPointGroups = dependencies.canPointGroups
const resolving = dependencies.resolving
const fo = dependencies.fo
const placement = dependencies.placement
// ============== COMPUTED ==============
const classes = toRef(() => ({
container: 'multiselect',
containerDisabled: 'is-disabled',
containerOpen: 'is-open',
containerOpenTop: 'is-open-top',
containerActive: 'is-active',
wrapper: 'multiselect-wrapper',
singleLabel: 'multiselect-single-label',
singleLabelText: 'multiselect-single-label-text',
multipleLabel: 'multiselect-multiple-label',
search: 'multiselect-search',
tags: 'multiselect-tags',
tag: 'multiselect-tag',
tagWrapper: 'multiselect-tag-wrapper',
tagWrapperBreak: 'multiselect-tag-wrapper-break',
tagDisabled: 'is-disabled',
tagRemove: 'multiselect-tag-remove',
tagRemoveIcon: 'multiselect-tag-remove-icon',
tagsSearchWrapper: 'multiselect-tags-search-wrapper',
tagsSearch: 'multiselect-tags-search',
tagsSearchCopy: 'multiselect-tags-search-copy',
placeholder: 'multiselect-placeholder',
caret: 'multiselect-caret',
caretOpen: 'is-open',
clear: 'multiselect-clear',
clearIcon: 'multiselect-clear-icon',
spinner: 'multiselect-spinner',
inifinite: 'multiselect-inifite',
inifiniteSpinner: 'multiselect-inifite-spinner',
dropdown: 'multiselect-dropdown',
dropdownTop: 'is-top',
dropdownHidden: 'is-hidden',
options: 'multiselect-options',
optionsTop: 'is-top',
group: 'multiselect-group',
groupLabel: 'multiselect-group-label',
groupLabelPointable: 'is-pointable',
groupLabelPointed: 'is-pointed',
groupLabelSelected: 'is-selected',
groupLabelDisabled: 'is-disabled',
groupLabelSelectedPointed: 'is-selected is-pointed',
groupLabelSelectedDisabled: 'is-selected is-disabled',
groupOptions: 'multiselect-group-options',
option: 'multiselect-option',
optionPointed: 'is-pointed',
optionSelected: 'is-selected',
optionDisabled: 'is-disabled',
optionSelectedPointed: 'is-selected is-pointed',
optionSelectedDisabled: 'is-selected is-disabled',
noOptions: 'multiselect-no-options',
noResults: 'multiselect-no-results',
fakeInput: 'multiselect-fake-input',
assist: 'multiselect-assistive-text',
spacer: 'multiselect-spacer',
...classes_.value,
}))
const showDropdown = toRef(() => {
return !!(isOpen.value && showOptions.value && (!resolving.value || (resolving.value && fo.value.length)))
})
const classList = computed(() => {
const c = classes.value
return {
container: [c.container]
.concat(disabled.value ? c.containerDisabled : [])
.concat(showDropdown.value && placement.value === 'top' ? c.containerOpenTop : [])
.concat(showDropdown.value && placement.value !== 'top' ? c.containerOpen : [])
.concat(isActive.value ? c.containerActive : []),
wrapper: c.wrapper,
spacer: c.spacer,
singleLabel: c.singleLabel,
singleLabelText: c.singleLabelText,
multipleLabel: c.multipleLabel,
search: c.search,
tags: c.tags,
tag: [c.tag]
.concat(disabled.value ? c.tagDisabled : []),
tagWrapper: [c.tagWrapper, breakTags.value ? c.tagWrapperBreak : null],
tagDisabled: c.tagDisabled,
tagRemove: c.tagRemove,
tagRemoveIcon: c.tagRemoveIcon,
tagsSearchWrapper: c.tagsSearchWrapper,
tagsSearch: c.tagsSearch,
tagsSearchCopy: c.tagsSearchCopy,
placeholder: c.placeholder,
caret: [c.caret]
.concat(isOpen.value ? c.caretOpen : []),
clear: c.clear,
clearIcon: c.clearIcon,
spinner: c.spinner,
inifinite: c.inifinite,
inifiniteSpinner: c.inifiniteSpinner,
dropdown: [c.dropdown]
.concat(placement.value === 'top' ? c.dropdownTop : [])
.concat(!isOpen.value || !showOptions.value || !showDropdown.value ? c.dropdownHidden : []),
options: [c.options]
.concat(placement.value === 'top' ? c.optionsTop : []),
group: c.group,
groupLabel: (g) => {
let groupLabel = [c.groupLabel]
if (isPointed(g)) {
groupLabel.push(isSelected(g) ? c.groupLabelSelectedPointed : c.groupLabelPointed)
} else if (isSelected(g) && canPointGroups.value) {
groupLabel.push(isDisabled(g) ? c.groupLabelSelectedDisabled : c.groupLabelSelected)
} else if (isDisabled(g)) {
groupLabel.push(c.groupLabelDisabled)
}
if (canPointGroups.value) {
groupLabel.push(c.groupLabelPointable)
}
return groupLabel
},
groupOptions: c.groupOptions,
option: (o, g) => {
let option = [c.option]
if (isPointed(o)) {
option.push(isSelected(o) ? c.optionSelectedPointed : c.optionPointed)
} else if (isSelected(o)) {
option.push(isDisabled(o) ? c.optionSelectedDisabled : c.optionSelected)
} else if (isDisabled(o) || (g && isDisabled(g))) {
option.push(c.optionDisabled)
}
return option
},
noOptions: c.noOptions,
noResults: c.noResults,
assist: c.assist,
fakeInput: c.fakeInput,
}
})
return {
classList,
showDropdown,
}
}

View File

@@ -0,0 +1,62 @@
import { toRefs, getCurrentInstance } from 'vue'
import isNullish from './../utils/isNullish'
export default function useData (props, context, dep)
{
const { object, valueProp, mode } = toRefs(props)
const $this = getCurrentInstance().proxy
// ============ DEPENDENCIES ============
const iv = dep.iv
// =============== METHODS ==============
const update = (val, triggerInput = true) => {
// Setting object(s) as internal value
iv.value = makeInternal(val)
// Setting object(s) or plain value as external
// value based on `option` setting
const externalVal = makeExternal(val)
context.emit('change', externalVal, $this)
if (triggerInput) {
context.emit('input', externalVal)
context.emit('update:modelValue', externalVal)
}
}
// no export
const makeExternal = (val) => {
// If external value should be object
// no transformation is required
if (object.value) {
return val
}
// No need to transform if empty value
if (isNullish(val)) {
return val
}
// If external should be plain transform
// value object to plain values
return !Array.isArray(val) ? val[valueProp.value] : val.map(v => v[valueProp.value])
}
// no export
const makeInternal = (val) => {
if (isNullish(val)) {
return mode.value === 'single' ? {} : []
}
return val
}
return {
update,
}
}

View File

@@ -0,0 +1,158 @@
import { ref, toRefs, getCurrentInstance, onMounted, onBeforeUnmount, nextTick } from 'vue'
import { createPopper } from '@popperjs/core/lib/popper-lite'
import preventOverflow from '@popperjs/core/lib/modifiers/preventOverflow'
import flip from '@popperjs/core/lib/modifiers/flip'
import toRef from './../utils/toRef'
export default function useDropdown (props, context, dep)
{
const { disabled, appendTo, appendToBody, openDirection } = toRefs(props)
const $this = getCurrentInstance().proxy
// ============ DEPENDENCIES ============
const multiselect = dep.multiselect
const dropdown = dep.dropdown
// ================ DATA ================
const isOpen = ref(false)
const popper = ref(null)
const forcedPlacement = ref(null)
// ============== COMPUTED ==============
const appended = toRef(() => {
return appendTo.value || appendToBody.value
})
const placement = toRef(() => {
return (openDirection.value === 'top' && forcedPlacement.value === 'bottom') ||
(openDirection.value === 'bottom' && forcedPlacement.value !== 'top')
? 'bottom'
: 'top'
})
// =============== METHODS ==============
const open = () => {
if (isOpen.value || disabled.value) {
return
}
isOpen.value = true
context.emit('open', $this)
if (appended.value) {
nextTick(() => {
updatePopper()
})
}
}
const close = () => {
if (!isOpen.value) {
return
}
isOpen.value = false
context.emit('close', $this)
}
const updatePopper = () => {
if (!popper.value) {
return
}
let borderTopWidth = parseInt(window.getComputedStyle(dropdown.value).borderTopWidth.replace('px', ''))
let borderBottomWidth = parseInt(window.getComputedStyle(dropdown.value).borderBottomWidth.replace('px', ''))
popper.value.setOptions((options) => ({
...options,
modifiers: [
...options.modifiers,
{
name: 'offset',
options: {
offset: [0, (placement.value === 'top' ? borderTopWidth : borderBottomWidth) * -1],
},
},
]
}))
popper.value.update()
}
/* istanbul ignore next: UI feature */
const hasFixedParent = (element) => {
while (element && element !== document.body) {
const style = getComputedStyle(element)
if (style.position === 'fixed') {
return true
}
element = element.parentElement
}
return false
}
onMounted(() => {
if (!appended.value) {
return
}
/* istanbul ignore next: popper mock */
popper.value = createPopper(multiselect.value, dropdown.value, {
strategy: hasFixedParent(multiselect.value) ? /* istanbul ignore next: UI feature */ 'fixed' : undefined,
placement: openDirection.value,
modifiers: [
preventOverflow,
flip,
{
name: 'sameWidth',
enabled: true,
phase: 'beforeWrite',
requires: ['computeStyles'],
fn: ({ state }) => {
state.styles.popper.width = `${state.rects.reference.width}px`
},
effect: ({ state }) => {
state.elements.popper.style.width = `${
state.elements.reference.offsetWidth
}px`
}
},
{
name: 'toggleClass',
enabled: true,
phase: 'write',
fn({ state }) {
forcedPlacement.value = state.placement
},
},
]
})
})
onBeforeUnmount(() => {
if (!appended.value || !popper.value) {
return
}
popper.value.destroy()
popper.value = null
})
return {
popper,
isOpen,
open,
close,
placement,
updatePopper,
}
}

View File

@@ -0,0 +1,34 @@
import { toRefs } from 'vue'
export default function useI18n (props, context, dep)
{
const {
locale, fallbackLocale,
} = toRefs(props)
// =============== METHODS ==============
const localize = (target) => {
if (!target || typeof target !== 'object') {
return target
}
if (target && target[locale.value]) {
return target[locale.value]
} else if (target && locale.value && target[locale.value.toUpperCase()]) {
return target[locale.value.toUpperCase()]
} else if (target && target[fallbackLocale.value]) {
return target[fallbackLocale.value]
} else if (target && fallbackLocale.value && target[fallbackLocale.value.toUpperCase()]) {
return target[fallbackLocale.value.toUpperCase()]
} else if (target && Object.keys(target)[0]) {
return target[Object.keys(target)[0]]
} else {
return ''
}
}
return {
localize,
}
}

View File

@@ -0,0 +1,247 @@
import { toRefs, getCurrentInstance } from 'vue'
import toRef from './../utils/toRef'
export default function useKeyboard (props, context, dep)
{
const {
mode, addTagOn, openDirection, searchable,
showOptions, valueProp, groups: groupped,
addOptionOn: addOptionOn_, createTag, createOption: createOption_,
reverse,
} = toRefs(props)
const $this = getCurrentInstance().proxy
// ============ DEPENDENCIES ============
const iv = dep.iv
const update = dep.update
const deselect = dep.deselect
const search = dep.search
const setPointer = dep.setPointer
const selectPointer = dep.selectPointer
const backwardPointer = dep.backwardPointer
const forwardPointer = dep.forwardPointer
const multiselect = dep.multiselect
const wrapper = dep.wrapper
const tags = dep.tags
const isOpen = dep.isOpen
const open = dep.open
const blur = dep.blur
const fo = dep.fo
// ============== COMPUTED ==============
// no export
const createOption = toRef(() => {
return createTag.value || createOption_.value || false
})
// no export
const addOptionOn = toRef(() => {
if (addTagOn.value !== undefined) {
return addTagOn.value
}
else if (addOptionOn_.value !== undefined) {
return addOptionOn_.value
}
return ['enter']
})
// =============== METHODS ==============
// no export
const preparePointer = () => {
// When options are hidden and creating tags is allowed
// no pointer will be set (because options are hidden).
// In such case we need to set the pointer manually to the
// first option, which equals to the option created from
// the search value.
if (mode.value === 'tags' && !showOptions.value && createOption.value && searchable.value && !groupped.value) {
setPointer(fo.value[fo.value.map(o => o[valueProp.value]).indexOf(search.value)])
}
}
const handleKeydown = (e) => {
context.emit('keydown', e, $this)
let tagList
let activeIndex
if (['ArrowLeft', 'ArrowRight', 'Enter'].indexOf(e.key) !== -1 && mode.value === 'tags') {
tagList = [...(multiselect.value.querySelectorAll(`[data-tags] > *`))].filter(e => e !== tags.value)
activeIndex = tagList.findIndex(e => e === document.activeElement)
}
switch (e.key) {
case 'Backspace':
if (mode.value === 'single') {
return
}
if (searchable.value && [null, ''].indexOf(search.value) === -1) {
return
}
if (iv.value.length === 0) {
return
}
let deselectables = iv.value.filter(v=>!v.disabled && v.remove !== false)
if (deselectables.length) {
deselect(deselectables[deselectables.length - 1])
}
break
case 'Enter':
e.preventDefault()
if (e.keyCode === 229) {
// ignore IME confirmation
return
}
if (activeIndex !== -1 && activeIndex !== undefined) {
update([...iv.value].filter((v, k) => k !== activeIndex))
if (activeIndex === tagList.length - 1) {
if (tagList.length - 1) {
tagList[tagList.length - 2].focus()
} else if (searchable.value) {
tags.value.querySelector('input').focus()
} else {
wrapper.value.focus()
}
}
return
}
if (addOptionOn.value.indexOf('enter') === -1 && createOption.value) {
return
}
preparePointer()
selectPointer()
break
case ' ':
if (!createOption.value && !searchable.value) {
e.preventDefault()
preparePointer()
selectPointer()
return
}
if (!createOption.value) {
return false
}
if (addOptionOn.value.indexOf('space') === -1 && createOption.value) {
return
}
e.preventDefault()
preparePointer()
selectPointer()
break
case 'Tab':
case ';':
case ',':
if (addOptionOn.value.indexOf(e.key.toLowerCase()) === -1 || !createOption.value) {
return
}
preparePointer()
selectPointer()
e.preventDefault()
break
case 'Escape':
blur()
break
case 'ArrowUp':
e.preventDefault()
if (!showOptions.value) {
return
}
/* istanbul ignore else */
if (!isOpen.value) {
open()
}
backwardPointer()
break
case 'ArrowDown':
e.preventDefault()
if (!showOptions.value) {
return
}
/* istanbul ignore else */
if (!isOpen.value) {
open()
}
forwardPointer()
break
case 'ArrowLeft':
if (
(searchable.value && tags.value && tags.value.querySelector('input').selectionStart)
|| e.shiftKey || mode.value !== 'tags' || !iv.value || !iv.value.length
) {
return
}
e.preventDefault()
if (activeIndex === -1) {
tagList[tagList.length-1].focus()
}
else if (activeIndex > 0) {
tagList[activeIndex-1].focus()
}
break
case 'ArrowRight':
if (activeIndex === -1 || e.shiftKey || mode.value !== 'tags' || !iv.value || !iv.value.length) {
return
}
e.preventDefault()
/* istanbul ignore else */
if (tagList.length > activeIndex + 1) {
tagList[activeIndex+1].focus()
}
else if (searchable.value) {
tags.value.querySelector('input').focus()
}
else if (!searchable.value) {
wrapper.value.focus()
}
break
}
}
const handleKeyup = (e) => {
context.emit('keyup', e, $this)
}
return {
handleKeydown,
handleKeyup,
preparePointer,
}
}

View File

@@ -0,0 +1,121 @@
import { ref, toRefs } from 'vue'
import toRef from './../utils/toRef'
export default function useMultiselect (props, context, dep)
{
const { searchable, disabled, clearOnBlur } = toRefs(props)
// ============ DEPENDENCIES ============
const input = dep.input
const open = dep.open
const close = dep.close
const clearSearch = dep.clearSearch
const isOpen = dep.isOpen
const wrapper = dep.wrapper
const tags = dep.tags
// ================ DATA ================
const isActive = ref(false)
const mouseClicked = ref(false)
// ============== COMPUTED ==============
const tabindex = toRef(() => {
return searchable.value || disabled.value ? -1 : 0
})
// =============== METHODS ==============
const blur = () => {
if (searchable.value) {
input.value.blur()
}
wrapper.value.blur()
}
const focus = () => {
if (searchable.value && !disabled.value) {
input.value.focus()
}
}
const activate = (shouldOpen = true) => {
if (disabled.value) {
return
}
isActive.value = true
if (shouldOpen) {
open()
}
}
const deactivate = () => {
isActive.value = false
setTimeout(() => {
if (!isActive.value) {
close()
if (clearOnBlur.value) {
clearSearch()
}
}
}, 1)
}
const handleFocusIn = (e) => {
if ((e.target.closest('[data-tags]') && e.target.nodeName !== 'INPUT') || e.target.closest('[data-clear]')) {
return
}
activate(mouseClicked.value)
}
const handleFocusOut = () => {
deactivate()
}
const handleCaretClick = () => {
deactivate()
blur()
}
/* istanbul ignore next */
const handleMousedown = (e) => {
mouseClicked.value = true
if (isOpen.value && (e.target.isEqualNode(wrapper.value) || e.target.isEqualNode(tags.value))) {
setTimeout(() => {
deactivate()
}, 0)
} else if (!isOpen.value
&& (document.activeElement.isEqualNode(wrapper.value)
|| document.activeElement.isEqualNode(input.value))) {
activate()
}
setTimeout(() => {
mouseClicked.value = false
}, 0)
}
return {
tabindex,
isActive,
mouseClicked,
blur,
focus,
activate,
deactivate,
handleFocusIn,
handleFocusOut,
handleCaretClick,
handleMousedown,
}
}

View File

@@ -0,0 +1,888 @@
import { ref, toRefs, computed, watch, getCurrentInstance } from 'vue'
import normalize from './../utils/normalize'
import isObject from './../utils/isObject'
import isNullish from './../utils/isNullish'
import arraysEqual from './../utils/arraysEqual'
import objectsEqual from './../utils/objectsEqual'
import toRef from './../utils/toRef'
export default function useOptions (props, context, dep)
{
const {
options, mode, trackBy: trackBy_, limit, hideSelected, createTag, createOption: createOption_, label,
appendNewTag, appendNewOption: appendNewOption_, multipleLabel, object, loading, delay, resolveOnLoad,
minChars, filterResults, clearOnSearch, clearOnSelect, valueProp, allowAbsent, groupLabel,
canDeselect, max, strict, closeOnSelect, closeOnDeselect, groups: groupped, reverse, infinite,
groupOptions, groupHideEmpty, groupSelect, onCreate, disabledProp, searchStart, searchFilter,
} = toRefs(props)
const $this = getCurrentInstance().proxy
// ============ DEPENDENCIES ============
const iv = dep.iv
const ev = dep.ev
const search = dep.search
const clearSearch = dep.clearSearch
const update = dep.update
const pointer = dep.pointer
const setPointer = dep.setPointer
const clearPointer = dep.clearPointer
const focus = dep.focus
const deactivate = dep.deactivate
const close = dep.close
const localize = dep.localize
// ================ DATA ================
// no export
// appendedOptions
const ap = ref([])
// no export
// resolvedOptions
const ro = ref([])
const resolving = ref(false)
// no export
const searchWatcher = ref(null)
const offset = ref(infinite.value && limit.value === -1 ? 10 : limit.value)
// ============== COMPUTED ==============
const resolvedOptions = computed({
get: () => ro.value,
set: (v) => ro.value = v
})
// no export
const createOption = toRef(() => {
return createTag.value || createOption_.value || false
})
// no export
const appendNewOption = toRef(() => {
if (appendNewTag.value !== undefined) {
return appendNewTag.value
} else if (appendNewOption_.value !== undefined) {
return appendNewOption_.value
}
return true
})
// no export
// extendedOptions
const eo = computed(() => {
if (groupped.value) {
let groups = eg.value || /* istanbul ignore next */ []
let eo = []
groups.forEach((group) => {
optionsToArray(group[groupOptions.value]).forEach((option) => {
eo.push(Object.assign({}, option, group[disabledProp.value] ? { [disabledProp.value]: true } : {}))
})
})
return eo
} else {
let eo = optionsToArray(ro.value || /* istanbul ignore next */ [])
if (ap.value.length) {
eo = eo.concat(ap.value)
}
return eo
}
})
// preFilteredOptions
const pfo = computed(() => {
let options = eo.value
if (reverse.value) {
options = options.reverse()
}
if (createdOption.value.length) {
options = createdOption.value.concat(options)
}
return filterOptions(options)
})
// filteredOptions
const fo = computed(() => {
let options = pfo.value
if (offset.value > 0) {
options = options.slice(0, offset.value)
}
return options
})
// no export
// extendedGroups
const eg = computed(() => {
if (!groupped.value) {
return []
}
let eg = []
let groups = ro.value || /* istanbul ignore next */ []
if (ap.value.length) {
eg.push({
[groupLabel.value]: ' ',
[groupOptions.value]: [...ap.value],
__CREATE__: true
})
}
return eg.concat(groups)
})
// preFilteredGroups
const pfg = computed(() => {
let groups = [...eg.value].map(g => ({...g}))
if (createdOption.value.length) {
if (groups[0] && groups[0].__CREATE__) {
groups[0][groupOptions.value] = [...createdOption.value, ...groups[0][groupOptions.value]]
} else {
groups = [{
[groupLabel.value]: ' ',
[groupOptions.value]: [...createdOption.value],
__CREATE__: true
}].concat(groups)
}
}
return groups
})
// filteredGroups
const fg = computed(() => {
if (!groupped.value) {
return []
}
let options = pfg.value
return filterGroups((options || /* istanbul ignore next */ []).map((group, index) => {
const arrayOptions = optionsToArray(group[groupOptions.value])
return {
...group,
index,
group: true,
[groupOptions.value]: filterOptions(arrayOptions, false).map(o => Object.assign({}, o, group[disabledProp.value] ? { [disabledProp.value]: true } : {})),
__VISIBLE__: filterOptions(arrayOptions).map(o => Object.assign({}, o, group[disabledProp.value] ? { [disabledProp.value]: true } : {})),
}
// Difference between __VISIBLE__ and {groupOptions}: visible does not contain selected options when hideSelected=true
}))
})
const hasSelected = computed(() => {
switch (mode.value) {
case 'single':
return !isNullish(iv.value[valueProp.value])
case 'multiple':
case 'tags':
return !isNullish(iv.value) && iv.value.length > 0
}
})
const multipleLabelText = computed(() => {
return multipleLabel.value !== undefined
? multipleLabel.value(iv.value, $this)
: (iv.value && iv.value.length > 1 ? `${iv.value.length} options selected` : `1 option selected`)
})
const noOptions = toRef(() => {
return !eo.value.length && !resolving.value && !createdOption.value.length
})
const noResults = toRef(() => {
return eo.value.length > 0 && fo.value.length == 0 && ((search.value && groupped.value) || !groupped.value)
})
// no export
const createdOption = computed(() => {
if (createOption.value === false || !search.value) {
return []
}
if (getOptionByTrackBy(search.value) !== -1) {
return []
}
return [{
[valueProp.value]: search.value,
[trackBy.value[0]]: search.value,
[label.value]: search.value,
__CREATE__: true,
}]
})
const trackBy = computed(() => {
return trackBy_.value ? (Array.isArray(trackBy_.value) ? trackBy_.value : [trackBy_.value]) : [label.value]
})
// no export
const nullValue = toRef(() => {
switch (mode.value) {
case 'single':
return null
case 'multiple':
case 'tags':
return []
}
})
const busy = toRef(() => {
return loading.value || resolving.value
})
// =============== METHODS ==============
/**
* @param {array|object|string|number} option
*/
const select = (option) => {
if (typeof option !== 'object') {
option = getOption(option)
}
switch (mode.value) {
case 'single':
update(option)
break
case 'multiple':
case 'tags':
update((iv.value).concat(option))
break
}
context.emit('select', finalValue(option), option, $this)
}
const deselect = (option) => {
if (typeof option !== 'object') {
option = getOption(option)
}
switch (mode.value) {
case 'single':
clear()
break
case 'tags':
case 'multiple':
update(Array.isArray(option)
? iv.value.filter(v => option.map(o => o[valueProp.value]).indexOf(v[valueProp.value]) === -1)
: iv.value.filter(v => v[valueProp.value] != option[valueProp.value]))
break
}
context.emit('deselect', finalValue(option), option, $this)
}
// no export
const finalValue = (option) => {
return object.value ? option : option[valueProp.value]
}
const remove = (option) => {
deselect(option)
}
const handleTagRemove = (option, e) => {
if (e.button !== 0) {
e.preventDefault()
return
}
remove(option)
}
const clear = () => {
update(nullValue.value)
context.emit('clear', $this)
}
const isSelected = (option) => {
if (option.group !== undefined) {
return mode.value === 'single' ? false : areAllSelected(option[groupOptions.value]) && option[groupOptions.value].length
}
switch (mode.value) {
case 'single':
return !isNullish(iv.value) && (
iv.value[valueProp.value] == option[valueProp.value] ||
(typeof iv.value[valueProp.value] === 'object' && typeof option[valueProp.value] === 'object' && objectsEqual(iv.value[valueProp.value], option[valueProp.value]))
)
case 'tags':
case 'multiple':
return !isNullish(iv.value) && iv.value.map(o => o[valueProp.value]).indexOf(option[valueProp.value]) !== -1
}
}
const isDisabled = (option) => {
return option[disabledProp.value] === true
}
const isMax = () => {
if (max === undefined || max.value === -1 || (!hasSelected.value && max.value > 0)) {
return false
}
return iv.value.length >= max.value
}
const handleOptionClick = (option) => {
if (isDisabled(option)) {
return
}
if (onCreate.value && !isSelected(option) && option.__CREATE__) {
option = { ...option }
delete option.__CREATE__
option = onCreate.value(option, $this)
if (option instanceof Promise) {
resolving.value = true
option.then((result) => {
resolving.value = false
handleOptionSelect(result)
})
return
}
}
handleOptionSelect(option)
}
const handleOptionSelect = (option) => {
if (option.__CREATE__) {
option = { ...option }
delete option.__CREATE__
}
switch (mode.value) {
case 'single':
if (option && isSelected(option)) {
if (canDeselect.value) {
deselect(option)
}
if (closeOnDeselect.value) {
clearPointer()
close()
}
return
}
if (option) {
handleOptionAppend(option)
}
/* istanbul ignore else */
if (clearOnSelect.value) {
clearSearch()
}
if (closeOnSelect.value) {
clearPointer()
close()
}
if (option) {
select(option)
}
break
case 'multiple':
if (option && isSelected(option)) {
deselect(option)
if (closeOnDeselect.value) {
clearPointer()
close()
}
return
}
if (isMax()) {
context.emit('max', $this)
return
}
if (option) {
handleOptionAppend(option)
select(option)
}
if (clearOnSelect.value) {
clearSearch()
}
if (hideSelected.value) {
clearPointer()
}
if (closeOnSelect.value) {
close()
}
break
case 'tags':
if (option && isSelected(option)) {
deselect(option)
if (closeOnDeselect.value) {
clearPointer()
close()
}
return
}
if (isMax()) {
context.emit('max', $this)
return
}
if (option) {
handleOptionAppend(option)
}
if (clearOnSelect.value) {
clearSearch()
}
if (option) {
select(option)
}
if (hideSelected.value) {
clearPointer()
}
if (closeOnSelect.value) {
close()
}
break
}
if (!closeOnSelect.value) {
focus()
}
}
const handleGroupClick = (group) => {
if (isDisabled(group) || mode.value === 'single' || !groupSelect.value) {
return
}
switch (mode.value) {
case 'multiple':
case 'tags':
if (areAllEnabledSelected(group[groupOptions.value])) {
deselect(group[groupOptions.value])
} else {
select(group[groupOptions.value]
.filter(o => iv.value.map(v => v[valueProp.value]).indexOf(o[valueProp.value]) === -1)
.filter(o => !o[disabledProp.value])
.filter((o, k) => iv.value.length + 1 + k <= max.value || max.value === -1)
)
}
if (hideSelected.value && pointer.value) {
// Refresh pointer because pointer.__VISIBLE__ are not reactive #354
setPointer(fg.value.filter(g => !g[disabledProp.value])[pointer.value.index])
}
break
}
if (closeOnSelect.value) {
deactivate()
}
}
const handleOptionAppend = (option) => {
if (getOption(option[valueProp.value]) === undefined && createOption.value) {
context.emit('tag', option[valueProp.value], $this)
context.emit('option', option[valueProp.value], $this)
context.emit('create', option[valueProp.value], $this)
if (appendNewOption.value) {
appendOption(option)
}
clearSearch()
}
}
const selectAll = () => {
if (mode.value === 'single') {
return
}
select(fo.value.filter(o => !o.disabled && !isSelected(o)))
}
// no export
const areAllEnabledSelected = (options) => {
return options.find(o => !isSelected(o) && !o[disabledProp.value]) === undefined
}
// no export
const areAllSelected = (options) => {
return options.find(o => !isSelected(o)) === undefined
}
const getOption = (val) => {
return eo.value[eo.value.map(o => String(o[valueProp.value])).indexOf(String(val))]
}
// no export
const getOptionByTrackBy = (val) => {
return eo.value.findIndex((o) => {
return trackBy.value.some((track) => {
return (parseInt(o[track]) == o[track] ? parseInt(o[track]) : o[track]) === (parseInt(val) == val ? parseInt(val) : val)
})
})
}
// no export
const shouldHideOption = (option) => {
return ['tags', 'multiple'].indexOf(mode.value) !== -1 && hideSelected.value && isSelected(option)
}
// no export
const appendOption = (option) => {
ap.value.push(option)
}
// no export
const filterGroups = (groups) => {
// If the search has value we need to filter among
// the ones that are visible to the user to avoid
// displaying groups which technically have options
// based on search but that option is already selected.
return groupHideEmpty.value
? groups.filter(g => search.value
? g.__VISIBLE__.length
: g[groupOptions.value].length
)
: groups.filter(g => search.value ? g.__VISIBLE__.length : true)
}
// no export
const filterOptions = (options, excludeHideSelected = true) => {
let fo = options
if (search.value && filterResults.value) {
let filter = searchFilter.value
if (!filter) {
filter = (option, query, $this) => {
return trackBy.value.some(track => {
let target = normalize(localize(option[track]), strict.value);
return searchStart.value
? target.startsWith(normalize(query, strict.value))
: target.indexOf(normalize(query, strict.value)) !== -1;
})
}
}
fo = fo.filter((o) => {
return filter(o, search.value, $this)
})
}
if (hideSelected.value && excludeHideSelected) {
fo = fo.filter((option) => !shouldHideOption(option))
}
return fo
}
// no export
const optionsToArray = (options) => {
let uo = options
// Transforming an object to an array of objects
if (isObject(uo)) {
uo = Object.keys(uo).map((key) => {
let val = uo[key]
return { [valueProp.value]: key, [trackBy.value[0]]: val, [label.value]: val}
})
}
// Transforming an plain arrays to an array of objects
/* istanbul ignore else */
if (uo && Array.isArray(uo)) {
uo = uo.map((val) => {
return typeof val === 'object' ? val : { [valueProp.value]: val, [trackBy.value[0]]: val, [label.value]: val}
})
} else {
uo = []
}
return uo
}
// no export
const initInternalValue = () => {
if (!isNullish(ev.value)) {
iv.value = makeInternal(ev.value)
}
}
const resolveOptions = (callback) => {
resolving.value = true
return new Promise((resolve, reject) => {
options.value(search.value, $this).then((response) => {
ro.value = response || []
if (typeof callback == 'function') {
callback(response)
}
resolving.value = false
}).catch((e) => {
console.error(e)
ro.value = []
resolving.value = false
}).finally(() => {
resolve()
})
})
}
// no export
const refreshLabels = () => {
if (!hasSelected.value) {
return
}
if (mode.value === 'single') {
let option = getOption(iv.value[valueProp.value])
/* istanbul ignore else */
if (option !== undefined) {
let newLabel = option[label.value]
iv.value[label.value] = newLabel
if (object.value) {
ev.value[label.value] = newLabel
}
}
} else {
iv.value.forEach((val, i) => {
let option = getOption(iv.value[i][valueProp.value])
/* istanbul ignore else */
if (option !== undefined) {
let newLabel = option[label.value]
iv.value[i][label.value] = newLabel
if (object.value) {
ev.value[i][label.value] = newLabel
}
}
})
}
}
const refreshOptions = (callback) => {
resolveOptions(callback)
}
// no export
const makeInternal = (val) => {
if (isNullish(val)) {
return mode.value === 'single' ? {} : []
}
if (object.value) {
return val
}
// If external should be plain transform value object to plain values
return mode.value === 'single' ? getOption(val) || (allowAbsent.value ? {
[label.value]: val,
[valueProp.value]: val,
[trackBy.value[0]]: val,
} : {}) : val.filter(v => !!getOption(v) || allowAbsent.value).map(v => getOption(v) || {
[label.value]: v,
[valueProp.value]: v,
[trackBy.value[0]]: v,
})
}
// no export
const initSearchWatcher = () => {
searchWatcher.value = watch(search, (query) => {
if (query.length < minChars.value || (!query && minChars.value !== 0)) {
return
}
resolving.value = true
if (clearOnSearch.value) {
ro.value = []
}
setTimeout(() => {
if (query != search.value) {
return
}
options.value(search.value, $this).then((response) => {
if (query == search.value || !search.value) {
ro.value = response
pointer.value = fo.value.filter(o => o[disabledProp.value] !== true)[0] || null
resolving.value = false
}
}).catch( /* istanbul ignore next */ (e) => {
console.error(e)
})
}, delay.value)
}, { flush: 'sync' })
}
// ================ HOOKS ===============
if (mode.value !== 'single' && !isNullish(ev.value) && !Array.isArray(ev.value)) {
throw new Error(`v-model must be an array when using "${mode.value}" mode`)
}
if (options && typeof options.value == 'function') {
if (resolveOnLoad.value) {
resolveOptions(initInternalValue)
} else if (object.value == true) {
initInternalValue()
}
}
else {
ro.value = options.value
initInternalValue()
}
// ============== WATCHERS ==============
if (delay.value > -1) {
initSearchWatcher()
}
watch(delay, (value, old) => {
/* istanbul ignore else */
if (searchWatcher.value) {
searchWatcher.value()
}
if (value >= 0) {
initSearchWatcher()
}
})
watch(ev, (newValue) => {
if (isNullish(newValue)) {
update(makeInternal(newValue), false)
return
}
switch (mode.value) {
case 'single':
if (object.value ? newValue[valueProp.value] != iv.value[valueProp.value] : newValue != iv.value[valueProp.value]) {
update(makeInternal(newValue), false)
}
break
case 'multiple':
case 'tags':
if (!arraysEqual(object.value ? newValue.map(o => o[valueProp.value]) : newValue, iv.value.map(o => o[valueProp.value]))) {
update(makeInternal(newValue), false)
}
break
}
}, { deep: true })
watch(options, (n, o) => {
if (typeof props.options === 'function') {
if (resolveOnLoad.value && (!o || (n && n.toString() !== o.toString()))) {
resolveOptions()
}
} else {
ro.value = props.options
if (!Object.keys(iv.value).length) {
initInternalValue()
}
refreshLabels()
}
})
watch(label, refreshLabels)
watch(limit, (n,o) => {
offset.value = infinite.value && n === -1 ? 10 : n
})
return {
resolvedOptions,
pfo,
fo,
filteredOptions: fo,
hasSelected,
multipleLabelText,
eo,
extendedOptions: eo,
eg,
extendedGroups: eg,
fg,
filteredGroups: fg,
noOptions,
noResults,
resolving,
busy,
offset,
select,
deselect,
remove,
selectAll,
clear,
isSelected,
isDisabled,
isMax,
getOption,
handleOptionClick,
handleGroupClick,
handleTagRemove,
refreshOptions,
resolveOptions,
refreshLabels,
}
}

View File

@@ -0,0 +1,34 @@
import { ref, toRefs } from 'vue'
export default function usePointer (props, context, dep)
{
const { groupSelect, mode, groups, disabledProp } = toRefs(props)
// ================ DATA ================
const pointer = ref(null)
// =============== METHODS ==============
const setPointer = (option) => {
if (option === undefined || (option !== null && option[disabledProp.value])) {
return
}
if (groups.value && option && option.group && (mode.value === 'single' || !groupSelect.value)) {
return
}
pointer.value = option
}
const clearPointer = () => {
setPointer(null)
}
return {
pointer,
setPointer,
clearPointer,
}
}

View File

@@ -0,0 +1,274 @@
import { toRefs, watch, nextTick, computed } from 'vue'
import toRef from './../utils/toRef'
export default function usePointer (props, context, dep)
{
const {
valueProp, showOptions, searchable, groupLabel,
groups: groupped, mode, groupSelect, disabledProp,
groupOptions,
} = toRefs(props)
// ============ DEPENDENCIES ============
const fo = dep.fo
const fg = dep.fg
const handleOptionClick = dep.handleOptionClick
const handleGroupClick = dep.handleGroupClick
const search = dep.search
const pointer = dep.pointer
const setPointer = dep.setPointer
const clearPointer = dep.clearPointer
const multiselect = dep.multiselect
const isOpen = dep.isOpen
// ============== COMPUTED ==============
// no export
const options = computed(() => {
return fo.value.filter(o => !o[disabledProp.value])
})
const groups = computed(() => {
return fg.value.filter(g => !g[disabledProp.value])
})
const canPointGroups = toRef(() => {
return mode.value !== 'single' && groupSelect.value
})
const isPointerGroup = toRef(() => {
return pointer.value && pointer.value.group
})
const currentGroup = computed(() => {
return getParentGroup(pointer.value)
})
const prevGroup = computed(() => {
const group = isPointerGroup.value ? pointer.value : /* istanbul ignore next */ getParentGroup(pointer.value)
const groupIndex = groups.value.map(g => g[groupLabel.value]).indexOf(group[groupLabel.value])
let prevGroup = groups.value[groupIndex - 1]
if (prevGroup === undefined) {
prevGroup = lastGroup.value
}
return prevGroup
})
const nextGroup = computed(() => {
let nextIndex = groups.value.map(g => g.label).indexOf(isPointerGroup.value
? pointer.value[groupLabel.value]
: getParentGroup(pointer.value)[groupLabel.value]) + 1
if (groups.value.length <= nextIndex) {
nextIndex = 0
}
return groups.value[nextIndex]
})
const lastGroup = computed(() => {
return [...groups.value].slice(-1)[0]
})
const currentGroupFirstEnabledOption = computed(() => {
return pointer.value.__VISIBLE__.filter(o => !o[disabledProp.value])[0]
})
const currentGroupPrevEnabledOption = computed(() => {
const options = currentGroup.value.__VISIBLE__.filter(o => !o[disabledProp.value])
return options[options.map(o => o[valueProp.value]).indexOf(pointer.value[valueProp.value]) - 1]
})
const currentGroupNextEnabledOption = computed(() => {
const options = getParentGroup(pointer.value).__VISIBLE__.filter(o => !o[disabledProp.value])
return options[options.map(o => o[valueProp.value]).indexOf(pointer.value[valueProp.value]) + 1]
})
const prevGroupLastEnabledOption = computed(() => {
return [...prevGroup.value.__VISIBLE__.filter(o => !o[disabledProp.value])].slice(-1)[0]
})
const lastGroupLastEnabledOption = computed(() => {
return [...lastGroup.value.__VISIBLE__.filter(o => !o[disabledProp.value])].slice(-1)[0]
})
// =============== METHODS ==============
const isPointed = (option) => {
return (!!pointer.value && (
(!option.group && pointer.value[valueProp.value] === option[valueProp.value]) ||
(option.group !== undefined && pointer.value[groupLabel.value] === option[groupLabel.value])
)) ? true : undefined
}
const setPointerFirst = () => {
setPointer(options.value[0] || null)
}
const selectPointer = () => {
if (!pointer.value || pointer.value[disabledProp.value] === true) {
return
}
if (isPointerGroup.value) {
handleGroupClick(pointer.value)
} else {
handleOptionClick(pointer.value)
}
}
const forwardPointer = () => {
if (pointer.value === null) {
setPointer((groupped.value && canPointGroups.value ? (!groups.value[0].__CREATE__ ? groups.value[0] : options.value[0]) : options.value[0]) || null)
}
else if (groupped.value && canPointGroups.value) {
let nextPointer = isPointerGroup.value ? currentGroupFirstEnabledOption.value : currentGroupNextEnabledOption.value
if (nextPointer === undefined) {
nextPointer = nextGroup.value
if (nextPointer.__CREATE__) {
nextPointer = nextPointer[groupOptions.value][0]
}
}
setPointer(nextPointer || /* istanbul ignore next */ null)
} else {
let next = options.value.map(o => o[valueProp.value]).indexOf(pointer.value[valueProp.value]) + 1
if (options.value.length <= next) {
next = 0
}
setPointer(options.value[next] || null)
}
nextTick(() => {
adjustWrapperScrollToPointer()
})
}
const backwardPointer = () => {
if (pointer.value === null) {
let prevPointer = options.value[options.value.length - 1]
if (groupped.value && canPointGroups.value) {
prevPointer = lastGroupLastEnabledOption.value
if (prevPointer === undefined) {
prevPointer = lastGroup.value
}
}
setPointer(prevPointer || null)
}
else if (groupped.value && canPointGroups.value) {
let prevPointer = isPointerGroup.value ? prevGroupLastEnabledOption.value : currentGroupPrevEnabledOption.value
if (prevPointer === undefined) {
prevPointer = isPointerGroup.value ? prevGroup.value : currentGroup.value
if (prevPointer.__CREATE__) {
prevPointer = prevGroupLastEnabledOption.value
if (prevPointer === undefined) {
prevPointer = prevGroup.value
}
}
}
setPointer(prevPointer || /* istanbul ignore next */ null)
} else {
let prevIndex = options.value.map(o => o[valueProp.value]).indexOf(pointer.value[valueProp.value]) - 1
if (prevIndex < 0) {
prevIndex = options.value.length - 1
}
setPointer(options.value[prevIndex] || null)
}
nextTick(() => {
adjustWrapperScrollToPointer()
})
}
const getParentGroup = (option) => {
return groups.value.find((group) => {
return group.__VISIBLE__.map(o => o[valueProp.value]).indexOf(option[valueProp.value]) !== -1
})
}
// no export
/* istanbul ignore next */
const adjustWrapperScrollToPointer = () => {
let pointedOption = multiselect.value.querySelector(`[data-pointed]`)
if (!pointedOption) {
return
}
let wrapper = pointedOption.parentElement.parentElement
if (groupped.value) {
wrapper = isPointerGroup.value
? pointedOption.parentElement.parentElement.parentElement
: pointedOption.parentElement.parentElement.parentElement.parentElement
}
if (pointedOption.offsetTop + pointedOption.offsetHeight > wrapper.clientHeight + wrapper.scrollTop) {
wrapper.scrollTop = pointedOption.offsetTop + pointedOption.offsetHeight - wrapper.clientHeight
}
if (pointedOption.offsetTop < wrapper.scrollTop) {
wrapper.scrollTop = pointedOption.offsetTop
}
}
// ============== WATCHERS ==============
watch(search, (val) => {
if (searchable.value) {
if (val.length && showOptions.value) {
setPointerFirst()
} else {
clearPointer()
}
}
})
watch(isOpen, (val) => {
if (val && multiselect && multiselect.value) {
let firstSelected = multiselect.value.querySelectorAll(`[data-selected]`)[0]
if (!firstSelected) {
return
}
let wrapper = firstSelected.parentElement.parentElement
nextTick(() => {
// Removed because of #406
/* istanbul ignore next */
// if (wrapper.scrollTop > 0) {
// return
// }
wrapper.scrollTop = firstSelected.offsetTop
})
}
})
return {
pointer,
canPointGroups,
isPointed,
setPointerFirst,
selectPointer,
forwardPointer,
backwardPointer,
}
}

View File

@@ -0,0 +1,24 @@
import { shallowRef } from 'vue'
export default function useRefs (props, context, dep)
{
// ================ DATA ================
const multiselect = shallowRef(null)
const wrapper = shallowRef(null)
const tags = shallowRef(null)
const input = shallowRef(null)
const dropdown = shallowRef(null)
return {
multiselect,
wrapper,
tags,
input,
dropdown,
}
}

View File

@@ -0,0 +1,100 @@
import { toRefs, watch, nextTick, onMounted, ref, shallowRef, computed } from 'vue'
import toRef from '../utils/toRef'
export default function useScroll (props, context, dep)
{
const {
limit, infinite,
} = toRefs(props)
// ============ DEPENDENCIES ============
const isOpen = dep.isOpen
const offset = dep.offset
const search = dep.search
const pfo = dep.pfo
const eo = dep.eo
// ================ DATA ================
// no export
const observer = ref(null)
const infiniteLoader = shallowRef(null)
// ============== COMPUTED ==============
const hasMore = toRef(() => {
return offset.value < pfo.value.length
})
// =============== METHODS ==============
// no export
/* istanbul ignore next */
const handleIntersectionObserver = (entries) => {
const { isIntersecting, target } = entries[0]
if (isIntersecting) {
const parent = target.offsetParent
const scrollTop = parent.scrollTop
offset.value += limit.value == -1 ? 10 : limit.value
nextTick(() => {
parent.scrollTop = scrollTop
})
}
}
const observe = () => {
/* istanbul ignore else */
if (isOpen.value && offset.value < pfo.value.length) {
observer.value.observe(infiniteLoader.value)
} else if (!isOpen.value && observer.value) {
observer.value.disconnect()
}
}
// ============== WATCHERS ==============
watch(isOpen, () => {
if (!infinite.value) {
return
}
observe()
})
watch(search, () => {
if (!infinite.value) {
return
}
offset.value = limit.value
observe()
}, { flush: 'post' })
watch(eo, () => {
if (!infinite.value) {
return
}
observe()
}, { immediate: false, flush: 'post' })
// ================ HOOKS ===============
onMounted(() => {
/* istanbul ignore else */
if (window && window.IntersectionObserver) {
observer.value = new IntersectionObserver(handleIntersectionObserver)
}
})
return {
hasMore,
infiniteLoader,
}
}

View File

@@ -0,0 +1,78 @@
import { ref, getCurrentInstance, watch, toRefs } from 'vue'
export default function useSearch (props, context, dep)
{
const { regex } = toRefs(props)
const $this = getCurrentInstance().proxy
// ============ DEPENDENCIES ============
const isOpen = dep.isOpen
const open = dep.open
// ================ DATA ================
const search = ref(null)
// =============== METHODS ==============
const clearSearch = () => {
search.value = ''
}
const handleSearchInput = (e) => {
search.value = e.target.value
}
const handleKeypress = (e) => {
if (regex.value) {
let regexp = regex.value
if (typeof regexp === 'string') {
regexp = new RegExp(regexp)
}
if (!e.key.match(regexp)) {
e.preventDefault()
}
}
}
const handlePaste = (e) => {
if (regex.value) {
let clipboardData = e.clipboardData || /* istanbul ignore next */ window.clipboardData
let pastedData = clipboardData.getData('Text')
let regexp = regex.value
if (typeof regexp === 'string') {
regexp = new RegExp(regexp)
}
if (!pastedData.split('').every(c => !!c.match(regexp))) {
e.preventDefault()
}
}
context.emit('paste', e, $this)
}
// ============== WATCHERS ==============
watch(search, (val) => {
if (!isOpen.value && val) {
open()
}
context.emit('search-change', val, $this)
})
return {
search,
clearSearch,
handleSearchInput,
handleKeypress,
handlePaste,
}
}

View File

@@ -0,0 +1,37 @@
import { computed, toRefs, ref } from 'vue'
import toRef from '../utils/toRef'
export default function useValue (props, context)
{
const { value, modelValue, mode, valueProp } = toRefs(props)
// ================ DATA ================
// internalValue
const iv = ref(mode.value !== 'single' ? [] : {})
// ============== COMPUTED ==============
/* istanbul ignore next */
// externalValue
const ev = toRef(() => {
return modelValue.value !== undefined ? modelValue.value : value.value
})
const plainValue = computed(() => {
return mode.value === 'single' ? iv.value[valueProp.value] : iv.value.map(v=>v[valueProp.value])
})
const textValue = toRef(() => {
return mode.value !== 'single' ? iv.value.map(v=>v[valueProp.value]).join(',') : iv.value[valueProp.value]
})
return {
iv,
internalValue: iv,
ev,
externalValue: ev,
textValue,
plainValue,
}
}

View File

@@ -0,0 +1 @@
export { default } from './Multiselect';

View File

@@ -0,0 +1,11 @@
export default function arraysEqual (array1, array2) {
if (array1.length !== array2.length) {
return false;
}
const array2Sorted = array2.slice().sort()
return array1.slice().sort().every(function(value, index) {
return value === array2Sorted[index];
})
}

View File

@@ -0,0 +1,3 @@
export default function isNullish (val) {
return val === null || val === undefined
}

View File

@@ -0,0 +1,3 @@
export default function isObject (variable) {
return Object.prototype.toString.call(variable) === '[object Object]'
}

View File

@@ -0,0 +1,11 @@
export default function normalize (str, strict = true) {
return strict
? String(str).toLowerCase().trim()
: String(str).toLowerCase()
.normalize('NFD')
.trim()
.replace(/æ/g, 'ae')
.replace(/œ/g, 'oe')
.replace(/ø/g, 'o')
.replace(/\p{Diacritic}/gu, '')
}

View File

@@ -0,0 +1,38 @@
/* istanbul ignore next */
const objectsEqual = (obj1, obj2) => {
// If both are strictly equal, return true
if (obj1 === obj2) {
return true
}
// If either is not an object or is null, return false (handles primitive types and null)
if (typeof obj1 !== 'object' || obj1 === null || typeof obj2 !== 'object' || obj2 === null) {
return false
}
// Get the keys of both objects
const keys1 = Object.keys(obj1)
const keys2 = Object.keys(obj2)
// If they have a different number of keys, they're not equal
if (keys1.length !== keys2.length) {
return false
}
// Compare each key-value pair recursively
for (let key of keys1) {
// Check if both objects have the same key
if (!keys2.includes(key)) {
return false
}
// Recursively compare the values
if (!objectsEqual(obj1[key], obj2[key])) {
return false
}
}
return true
}
export default objectsEqual

View File

@@ -0,0 +1,10 @@
export default function (props, context, features, deps = {}) {
features.forEach((composable) => {
deps = {
...deps,
...composable(props, context, deps)
}
})
return deps
}

View File

@@ -0,0 +1,7 @@
import { customRef } from 'vue'
// Polyfill for Vue <3.3 for getters only
// https://vuejs.org/api/reactivity-utilities.html#toref
export default function toRef (get) {
return customRef(() => ({ get, set: /* istanbul ignore next */ () => { } }))
}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,525 @@
.multiselect {
position: relative;
margin: 0 auto;
width: 100%;
display: flex;
align-items: center;
justify-content: flex-end;
box-sizing: border-box;
cursor: pointer;
outline: none;
border: var(--ms-border-width, 1px) solid var(--ms-border-color, #D1D5DB);
border-radius: var(--ms-radius, 4px);
background: var(--ms-bg, #FFFFFF);
font-size: var(--ms-font-size, 1rem);
min-height: calc(2 * var(--ms-border-width, 1px) + var(--ms-font-size, 1rem) * var(--ms-line-height, 1.375) + 2 * var(--ms-py, 0.5rem));
&.is-open {
border-radius: var(--ms-radius, 4px) var(--ms-radius, 4px) 0 0;
}
&.is-open-top {
border-radius: 0 0 var(--ms-radius, 4px) var(--ms-radius, 4px);
}
&.is-disabled {
cursor: default;
background: var(--ms-bg-disabled, #F3F4F6);
}
&.is-active {
border: var(--ms-border-width-active, var(--ms-border-width, 1px)) solid var(--ms-border-color-active, var(--ms-border-color, #D1D5DB));
box-shadow: 0 0 0 var(--ms-ring-width, 3px) var(--ms-ring-color, #10B98130);
}
}
.multiselect-wrapper {
position: relative;
margin: 0 auto;
width: 100%;
display: flex;
align-items: center;
justify-content: flex-end;
box-sizing: border-box;
cursor: pointer;
outline: none;
min-height: calc(2 * var(--ms-border-width, 1px) + var(--ms-font-size, 1rem) * var(--ms-line-height, 1.375) + 2 * var(--ms-py, 0.5rem));
}
.multiselect-multiple-label,
.multiselect-single-label,
.multiselect-placeholder {
display: flex;
align-items: center;
height: 100%;
position: absolute;
left: 0;
top: 0;
pointer-events: none;
background: transparent;
line-height: var(--ms-line-height, 1.375);
padding-left: var(--ms-px, 0.875rem);
padding-right: calc(1.25rem + var(--ms-px, 0.875rem) * 3);
box-sizing: border-box;
max-width: 100%;
}
.multiselect-placeholder {
color: var(--ms-placeholder-color, #9CA3AF);
}
.multiselect-single-label-text {
overflow: hidden;
display: block;
white-space: nowrap;
text-overflow: ellipsis;
max-width: 100%;
}
.multiselect-search {
width: 100%;
height: 100%; // for FF
position: absolute;
top: 0px;
bottom: 0px;
left: 0px;
right: 0px;
outline: none;
box-sizing: border-box;
border: 0;
appearance: none;
font-size: inherit;
font-family: inherit;
background: var(--ms-bg, #FFFFFF);
border-radius: var(--ms-radius, 4px);
padding-left: var(--ms-px, 0.875rem);
&::-webkit-search-decoration,
&::-webkit-search-cancel-button,
&::-webkit-search-results-button,
&::-webkit-search-results-decoration {
-webkit-appearance:none;
}
}
.multiselect-tags {
flex-grow: 1;
flex-shrink: 1;
display: flex;
flex-wrap: wrap;
margin: var(--ms-tag-my, 0.25rem) 0 0;
padding-left: var(--ms-py, 0.5rem);
align-items: center;
min-width: 0;
}
.multiselect-tag {
background: var(--ms-tag-bg, #10B981);
color: var(--ms-tag-color, #FFFFFF);
font-size: var(--ms-tag-font-size, 0.875rem);
line-height: var(--ms-tag-line-height, 1.25rem);
font-weight: var(--ms-tag-font-weight, 600);
padding: var(--ms-tag-py, 0.125rem) 0 var(--ms-tag-py, 0.125rem) var(--ms-tag-px, 0.5rem);
border-radius: var(--ms-tag-radius, 4px);
margin-right: var(--ms-tag-mx, 0.25rem);
margin-bottom: var(--ms-tag-my, 0.25rem);
display: flex;
align-items: center;
white-space: nowrap;
min-width: 0;
&.is-disabled {
padding-right: var(--ms-tag-px, 0.5rem);
background: var(--ms-tag-bg-disabled, #9CA3AF);
color: var(--ms-tag-color-disabled, #FFFFFF);
}
}
.multiselect-tag-wrapper {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.multiselect-tag-wrapper-break {
white-space: normal;
word-break: break-all;
}
.multiselect-tag-remove {
display: flex;
align-items: center;
justify-content: center;
padding: var(--ms-tag-remove-py, 0.25rem) var(--ms-tag-remove-px, 0.25rem);
margin: var(--ms-tag-remove-my, 0rem) var(--ms-tag-remove-mx, 0.125rem);
border-radius: var(--ms-tag-remove-radius, 4px);
&:hover {
background: #00000010;
}
}
.multiselect-tag-remove-icon {
-webkit-mask-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 320 512' fill='currentColor' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M207.6 256l107.72-107.72c6.23-6.23 6.23-16.34 0-22.58l-25.03-25.03c-6.23-6.23-16.34-6.23-22.58 0L160 208.4 52.28 100.68c-6.23-6.23-16.34-6.23-22.58 0L4.68 125.7c-6.23 6.23-6.23 16.34 0 22.58L112.4 256 4.68 363.72c-6.23 6.23-6.23 16.34 0 22.58l25.03 25.03c6.23 6.23 16.34 6.23 22.58 0L160 303.6l107.72 107.72c6.23 6.23 16.34 6.23 22.58 0l25.03-25.03c6.23-6.23 6.23-16.34 0-22.58L207.6 256z'%3E%3C/path%3E%3C/svg%3E");
mask-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 320 512' fill='currentColor' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M207.6 256l107.72-107.72c6.23-6.23 6.23-16.34 0-22.58l-25.03-25.03c-6.23-6.23-16.34-6.23-22.58 0L160 208.4 52.28 100.68c-6.23-6.23-16.34-6.23-22.58 0L4.68 125.7c-6.23 6.23-6.23 16.34 0 22.58L112.4 256 4.68 363.72c-6.23 6.23-6.23 16.34 0 22.58l25.03 25.03c6.23 6.23 16.34 6.23 22.58 0L160 303.6l107.72 107.72c6.23 6.23 16.34 6.23 22.58 0l25.03-25.03c6.23-6.23 6.23-16.34 0-22.58L207.6 256z'%3E%3C/path%3E%3C/svg%3E");
-webkit-mask-position: center;
mask-position: center;
-webkit-mask-repeat: no-repeat;
mask-repeat: no-repeat;
-webkit-mask-size: contain;
mask-size: contain;
background-color: currentColor;
opacity: 0.8;
display: inline-block;
width: 0.75rem;
height: 0.75rem;
}
.multiselect-tags-search-wrapper {
display: inline-block;
position: relative;
margin: 0 var(--ms-tag-mx, 4px) var(--ms-tag-my, 4px);
flex-grow: 1;
flex-shrink: 1;
height: 100%;
}
.multiselect-tags-search-copy {
visibility: hidden;
white-space: pre-wrap;
display: inline-block;
height: 1px;
width: 100%;
}
.multiselect-tags-search {
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
border: 0;
appearance: none;
outline: none;
padding: 0;
font-size: inherit;
font-family: inherit;
box-sizing: border-box;
width: 100%;
appearance: none;
&::-webkit-search-decoration,
&::-webkit-search-cancel-button,
&::-webkit-search-results-button,
&::-webkit-search-results-decoration {
-webkit-appearance:none;
}
}
.multiselect-inifite {
display: flex;
width: 100%;
justify-content: center;
align-items: center;
min-height: calc(2 * var(--ms-border-width, 1px) + var(--ms-font-size, 1rem) * var(--ms-line-height, 1.375) + 2 * var(--ms-py, 0.5rem));
}
.multiselect-spinner,
.multiselect-inifite-spinner {
-webkit-mask-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 512 512' fill='currentColor' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M456.433 371.72l-27.79-16.045c-7.192-4.152-10.052-13.136-6.487-20.636 25.82-54.328 23.566-118.602-6.768-171.03-30.265-52.529-84.802-86.621-144.76-91.424C262.35 71.922 256 64.953 256 56.649V24.56c0-9.31 7.916-16.609 17.204-15.96 81.795 5.717 156.412 51.902 197.611 123.408 41.301 71.385 43.99 159.096 8.042 232.792-4.082 8.369-14.361 11.575-22.424 6.92z'%3E%3C/path%3E%3C/svg%3E");
mask-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 512 512' fill='currentColor' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M456.433 371.72l-27.79-16.045c-7.192-4.152-10.052-13.136-6.487-20.636 25.82-54.328 23.566-118.602-6.768-171.03-30.265-52.529-84.802-86.621-144.76-91.424C262.35 71.922 256 64.953 256 56.649V24.56c0-9.31 7.916-16.609 17.204-15.96 81.795 5.717 156.412 51.902 197.611 123.408 41.301 71.385 43.99 159.096 8.042 232.792-4.082 8.369-14.361 11.575-22.424 6.92z'%3E%3C/path%3E%3C/svg%3E");
-webkit-mask-position: center;
mask-position: center;
-webkit-mask-repeat: no-repeat;
mask-repeat: no-repeat;
-webkit-mask-size: contain;
mask-size: contain;
background-color: var(--ms-spinner-color, #10B981);
width: 1rem;
height: 1rem;
z-index: 10;
animation: multiselect-spin 1s linear infinite;
flex-shrink: 0;
flex-grow: 0;
}
.multiselect-spinner {
margin: 0 var(--ms-px, 0.875rem) 0 0;
}
.multiselect-clear {
padding: 0 var(--ms-px, 0.875rem) 0 0px;
position: relative;
z-index: 10;
opacity: 1;
transition: .3s;
flex-shrink: 0;
flex-grow: 0;
display: flex;
&:hover .multiselect-clear-icon {
background-color: var(--ms-clear-color-hover, #000000);
}
}
.multiselect-clear-icon {
-webkit-mask-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 320 512' fill='currentColor' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M207.6 256l107.72-107.72c6.23-6.23 6.23-16.34 0-22.58l-25.03-25.03c-6.23-6.23-16.34-6.23-22.58 0L160 208.4 52.28 100.68c-6.23-6.23-16.34-6.23-22.58 0L4.68 125.7c-6.23 6.23-6.23 16.34 0 22.58L112.4 256 4.68 363.72c-6.23 6.23-6.23 16.34 0 22.58l25.03 25.03c6.23 6.23 16.34 6.23 22.58 0L160 303.6l107.72 107.72c6.23 6.23 16.34 6.23 22.58 0l25.03-25.03c6.23-6.23 6.23-16.34 0-22.58L207.6 256z'%3E%3C/path%3E%3C/svg%3E");
mask-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 320 512' fill='currentColor' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M207.6 256l107.72-107.72c6.23-6.23 6.23-16.34 0-22.58l-25.03-25.03c-6.23-6.23-16.34-6.23-22.58 0L160 208.4 52.28 100.68c-6.23-6.23-16.34-6.23-22.58 0L4.68 125.7c-6.23 6.23-6.23 16.34 0 22.58L112.4 256 4.68 363.72c-6.23 6.23-6.23 16.34 0 22.58l25.03 25.03c6.23 6.23 16.34 6.23 22.58 0L160 303.6l107.72 107.72c6.23 6.23 16.34 6.23 22.58 0l25.03-25.03c6.23-6.23 6.23-16.34 0-22.58L207.6 256z'%3E%3C/path%3E%3C/svg%3E");
-webkit-mask-position: center;
mask-position: center;
-webkit-mask-repeat: no-repeat;
mask-repeat: no-repeat;
-webkit-mask-size: contain;
mask-size: contain;
background-color: var(--ms-clear-color, #999999);
width: 0.625rem;
height: 1.125rem;
display: inline-block;
transition: .3s;
}
.multiselect-caret {
transform: rotate(0deg);
transition: .3s transform;
-webkit-mask-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 320 512' fill='currentColor' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M31.3 192h257.3c17.8 0 26.7 21.5 14.1 34.1L174.1 354.8c-7.8 7.8-20.5 7.8-28.3 0L17.2 226.1C4.6 213.5 13.5 192 31.3 192z'%3E%3C/path%3E%3C/svg%3E");
mask-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 320 512' fill='currentColor' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M31.3 192h257.3c17.8 0 26.7 21.5 14.1 34.1L174.1 354.8c-7.8 7.8-20.5 7.8-28.3 0L17.2 226.1C4.6 213.5 13.5 192 31.3 192z'%3E%3C/path%3E%3C/svg%3E");
-webkit-mask-position: center;
mask-position: center;
-webkit-mask-repeat: no-repeat;
mask-repeat: no-repeat;
-webkit-mask-size: contain;
mask-size: contain;
background-color: var(--ms-caret-color, #999999);
width: 0.625rem;
height: 1.125rem;
margin: 0 var(--ms-px, 0.875rem) 0 0;
position: relative;
z-index: 10;
flex-shrink: 0;
flex-grow: 0;
pointer-events: none;
&.is-open {
transform: rotate(180deg);
pointer-events: auto;
}
}
.multiselect-dropdown {
position: absolute;
left: calc(var(--ms-border-width, 1px) * -1);
right: calc(var(--ms-border-width, 1px) * -1);
bottom: 0;
transform: translateY(100%);
border: var(--ms-dropdown-border-width, 1px) solid var(--ms-dropdown-border-color, #D1D5DB);
margin-top: calc(var(--ms-border-width, 1px) * -1);
overflow-y: scroll;
-webkit-overflow-scrolling: touch;
z-index: 100;
background: var(--ms-dropdown-bg, #FFFFFF);
display: flex;
flex-direction: column;
border-radius: 0 0 var(--ms-dropdown-radius, 4px) var(--ms-dropdown-radius, 4px);
outline: none;
max-height: var(--ms-max-height, 10rem);
&.is-top {
transform: translateY(-100%);
top: var(--ms-border-width, 1px);
bottom: auto;
border-radius: var(--ms-dropdown-radius, 4px) var(--ms-dropdown-radius, 4px) 0 0;
}
&.is-hidden {
display: none;
}
}
.multiselect-options {
padding: 0;
margin: 0;
list-style: none;
display: flex;
flex-direction: column;
}
.multiselect-group {
padding: 0;
margin: 0;
}
.multiselect-group-label {
padding: var(--ms-group-label-py, 0.3rem) var(--ms-group-label-px, 0.75rem);
font-size: 0.875rem;
font-weight: 600;
background: var(--ms-group-label-bg, #E5E7EB);
color: var(--ms-group-label-color, #374151);
cursor: default;
line-height: var(--ms-group-label-line-height, 1.375);
display: flex;
box-sizing: border-box;
text-decoration: none;
align-items: center;
justify-content: flex-start;
text-align: left;
&.is-pointable {
cursor: pointer;
}
&.is-pointed {
background: var(--ms-group-label-bg-pointed, #D1D5DB);
color: var(--ms-group-label-color-pointed, #374151);
}
&.is-selected {
background: var(--ms-group-label-bg-selected, #059669);
color: var(--ms-group-label-color-selected, #FFFFFF);
}
&.is-disabled {
background: var(--ms-group-label-bg-disabled, #F3F4F6);
color: var(--ms-group-label-color-disabled, #D1D5DB);
cursor: not-allowed;
}
&.is-selected.is-pointed {
background: var(--ms-group-label-bg-selected-pointed, #0c9e70);
color: var(--ms-group-label-color-selected-pointed, #FFFFFF);
}
&.is-selected.is-disabled {
background: var(--ms-group-label-bg-selected-disabled, #75cfb1);
color: var(--ms-group-label-color-selected-disabled, #D1FAE5);
}
}
.multiselect-group-options {
padding: 0;
margin: 0;
}
.multiselect-option {
padding: var(--ms-option-py, 0.5rem) var(--ms-option-px, 0.75rem);
font-size: var(--ms-option-font-size, 1rem);
line-height: var(--ms-option-line-height, 1.375);
cursor: pointer;
display: flex;
box-sizing: border-box;
text-decoration: none;
align-items: center;
justify-content: flex-start;
text-align: left;
&.is-pointed {
background: var(--ms-option-bg-pointed, #F3F4F6);
color: var(--ms-option-color-pointed, #1F2937);
}
&.is-selected {
background: var(--ms-option-bg-selected, #10B981);
color: var(--ms-option-color-selected, #FFFFFF);
}
&.is-disabled {
background: var(--ms-option-bg-disabled, #FFFFFF);
color: var(--ms-option-color-disabled, #D1D5DB);
cursor: not-allowed;
}
&.is-selected.is-pointed {
background: var(--ms-option-bg-selected-pointed, #26c08e);
color: var(--ms-option-color-selected-pointed, #FFFFFF);
}
&.is-selected.is-disabled {
background: var(--ms-option-bg-selected-disabled, #87dcc0);
color: var(--ms-option-color-selected-disabled, #D1FAE5);
}
}
.multiselect-no-options,
.multiselect-no-results {
padding: var(--ms-option-py, 0.5rem) var(--ms-option-px, 0.75rem);
color: var(--ms-empty-color, #4B5563);
}
.multiselect-fake-input {
background: transparent;
position: absolute;
left: 0;
right: 0;
bottom: -1px;
width: 100%;
height: 1px;
border: 0;
padding: 0;
font-size: 0;
outline: none;
&:active, &:focus {
outline: none;
}
}
.multiselect-assistive-text {
position: absolute;
margin: -1px;
width: 1px;
height: 1px;
overflow: hidden;
clip: rect(0 0 0 0);
}
.multiselect-spacer {
display: none;
}
[dir="rtl"] {
.multiselect-multiple-label,
.multiselect-single-label,
.multiselect-placeholder {
padding-right: var(--ms-px, 0.875rem);
padding-left: calc(1.25rem + var(--ms-px, 0.875rem) * 3);
left: auto;
right: 0;
}
.multiselect-search {
padding-left: 0;
padding-right: var(--ms-px, 0.875rem);
}
.multiselect-tags {
padding-left: 0;
padding-right: var(--ms-py, 0.5rem);
}
.multiselect-tag {
padding: var(--ms-tag-py, 0.125rem) var(--ms-tag-px, 0.5rem) var(--ms-tag-py, 0.125rem) 0;
margin-right: 0;
margin-left: var(--ms-tag-mx, 0.25rem);
&.is-disabled {
padding-left: var(--ms-tag-px, 0.5rem);
}
}
.multiselect-spinner,
.multiselect-caret {
margin: 0 0 0 var(--ms-px, 0.875rem);
}
.multiselect-clear {
padding: 0 0 0 var(--ms-px, 0.875rem);
}
}
@keyframes multiselect-spin {
from {
transform: rotate(0);
}
to {
transform: rotate(360deg);
}
}

View File

@@ -0,0 +1,193 @@
.multiselect {
@apply relative mx-auto w-full flex items-center justify-end box-border cursor-pointer border border-gray-300 rounded bg-white text-base leading-snug outline-none;
}
.multiselect.is-disabled {
@apply cursor-default bg-gray-100;
}
.multiselect.is-open {
@apply rounded-b-none;
}
.multiselect.is-open-top {
@apply rounded-t-none;
}
.multiselect.is-active {
@apply ring ring-green-500 ring-opacity-30;
}
.multiselect-wrapper {
@apply relative mx-auto w-full flex items-center justify-end box-border cursor-pointer outline-none;
}
.multiselect-single-label {
@apply flex items-center h-full max-w-full absolute left-0 top-0 pointer-events-none bg-transparent leading-snug pl-3.5 pr-16 box-border rtl:left-auto rtl:right-0 rtl:pl-0 rtl:pr-3.5;
}
.multiselect-single-label-text {
@apply overflow-ellipsis overflow-hidden block whitespace-nowrap max-w-full;
}
.multiselect-multiple-label {
@apply flex items-center h-full absolute left-0 top-0 pointer-events-none bg-transparent leading-snug pl-3.5 rtl:left-auto rtl:right-0 rtl:pl-0 rtl:pr-3.5;
}
.multiselect-search {
@apply w-full absolute inset-0 outline-none focus:ring-0 appearance-none box-border border-0 text-base font-sans bg-white rounded pl-3.5 rtl:pl-0 rtl:pr-3.5;
}
.multiselect-tags {
@apply flex-grow flex-shrink flex flex-wrap items-center mt-1 pl-2 min-w-0 rtl:pl-0 rtl:pr-2;
}
.multiselect-tag {
@apply bg-green-500 text-white text-sm font-semibold py-0.5 pl-2 rounded mr-1 mb-1 flex items-center whitespace-nowrap min-w-0 rtl:pl-0 rtl:pr-2 rtl:mr-0 rtl:ml-1;
}
.multiselect-tag.is-disabled {
@apply pr-2 opacity-50 rtl:pl-2;
}
.multiselect-tag-wrapper {
@apply whitespace-nowrap overflow-hidden overflow-ellipsis;
}
.multiselect-tag-wrapper-break {
@apply whitespace-normal break-all;
}
.multiselect-tag-remove {
@apply flex items-center justify-center p-1 mx-0.5 rounded-sm hover:bg-black hover:bg-opacity-10;
}
.multiselect-tag-remove-icon {
@apply bg-multiselect-remove bg-center bg-no-repeat opacity-30 inline-block w-3 h-3;
}
.multiselect-tag-remove:hover .multiselect-tag-remove-icon {
@apply opacity-60;
}
.multiselect-tags-search-wrapper {
@apply inline-block relative mx-1 mb-1 flex-grow flex-shrink h-full;
}
.multiselect-tags-search {
@apply absolute inset-0 border-0 outline-none focus:ring-0 appearance-none p-0 text-base font-sans box-border w-full;
}
.multiselect-tags-search-copy {
@apply invisible whitespace-pre-wrap inline-block h-px;
}
.multiselect-placeholder {
@apply flex items-center h-full absolute left-0 top-0 pointer-events-none bg-transparent leading-snug pl-3.5 text-gray-400 rtl:left-auto rtl:right-0 rtl:pl-0 rtl:pr-3.5;
}
.multiselect-caret {
@apply bg-multiselect-caret bg-center bg-no-repeat w-2.5 h-4 py-px box-content mr-3.5 relative z-10 opacity-40 flex-shrink-0 flex-grow-0 transition-transform transform pointer-events-none rtl:mr-0 rtl:ml-3.5;
}
.multiselect-caret.is-open {
@apply rotate-180 pointer-events-auto;
}
.multiselect-clear {
@apply pr-3.5 relative z-10 opacity-40 transition duration-300 flex-shrink-0 flex-grow-0 flex hover:opacity-80 rtl:pr-0 rtl:pl-3.5;
}
.multiselect-clear-icon {
@apply bg-multiselect-remove bg-center bg-no-repeat w-2.5 h-4 py-px box-content inline-block;
}
.multiselect-spinner {
@apply bg-multiselect-spinner bg-center bg-no-repeat w-4 h-4 z-10 mr-3.5 animate-spin flex-shrink-0 flex-grow-0 rtl:mr-0 rtl:ml-3.5;
}
.multiselect-inifite {
@apply flex items-center justify-center w-full;
}
.multiselect-inifite-spinner {
@apply bg-multiselect-spinner bg-center bg-no-repeat w-4 h-4 z-10 animate-spin flex-shrink-0 flex-grow-0 m-3.5;
}
.multiselect-dropdown {
@apply max-h-60 absolute -left-px -right-px bottom-0 transform translate-y-full border border-gray-300 -mt-px overflow-y-scroll z-50 bg-white flex flex-col rounded-b;
}
.multiselect-dropdown.is-top {
@apply -translate-y-full top-px bottom-auto rounded-b-none rounded-t;
}
.multiselect-dropdown.is-hidden {
@apply hidden;
}
.multiselect-options {
@apply flex flex-col p-0 m-0 list-none;
}
.multiselect-group {
@apply p-0 m-0;
}
.multiselect-group-label {
@apply flex text-sm box-border items-center justify-start text-left py-1 px-3 font-semibold bg-gray-200 cursor-default leading-normal;
}
.multiselect-group-label.is-pointable {
@apply cursor-pointer;
}
.multiselect-group-label.is-pointed {
@apply bg-gray-300 text-gray-700;
}
.multiselect-group-label.is-selected {
@apply bg-green-600 text-white;
}
.multiselect-group-label.is-disabled {
@apply bg-gray-100 text-gray-300 cursor-not-allowed;
}
.multiselect-group-label.is-selected.is-pointed {
@apply bg-green-600 text-white opacity-90;
}
.multiselect-group-label.is-selected.is-disabled {
@apply text-green-100 bg-green-600 bg-opacity-50 cursor-not-allowed;
}
.multiselect-group-options {
@apply p-0 m-0;
}
.multiselect-option {
@apply flex items-center justify-start box-border text-left cursor-pointer text-base leading-snug py-2 px-3;
}
.multiselect-option.is-pointed {
@apply text-gray-800 bg-gray-100;
}
.multiselect-option.is-selected {
@apply text-white bg-green-500;
}
.multiselect-option.is-disabled {
@apply text-gray-300 cursor-not-allowed;
}
.multiselect-option.is-selected.is-pointed {
@apply text-white bg-green-500 opacity-90;
}
.multiselect-option.is-selected.is-disabled {
@apply text-green-100 bg-green-500 bg-opacity-50 cursor-not-allowed;
}
.multiselect-no-options {
@apply py-2 px-3 text-gray-600 bg-white text-left rtl:text-right;
}
.multiselect-no-results {
@apply py-2 px-3 text-gray-600 bg-white text-left rtl:text-right;
}
.multiselect-fake-input {
@apply bg-transparent absolute left-0 right-0 -bottom-px w-full h-px border-0 p-0 appearance-none outline-none text-transparent;
}
.multiselect-assistive-text {
@apply absolute -m-px w-px h-px overflow-hidden;
clip: rect(0 0 0 0);
}
.multiselect-spacer {
@apply h-9 py-px box-content;
}

View File

@@ -0,0 +1,212 @@
.multiselect {
@apply relative mx-auto w-full flex items-center justify-end box-border cursor-pointer border border-gray-300 rounded bg-white text-base leading-snug outline-none;
&.is-disabled {
@apply cursor-default bg-gray-100;
}
&.is-open {
@apply rounded-b-none;
}
&.is-open-top {
@apply rounded-t-none;
}
&.is-active {
@apply ring ring-green-500 ring-opacity-30;
}
}
.multiselect-wrapper {
@apply relative mx-auto w-full flex items-center justify-end box-border cursor-pointer outline-none;
}
.multiselect-single-label {
@apply flex items-center h-full max-w-full absolute left-0 top-0 pointer-events-none bg-transparent leading-snug pl-3.5 pr-16 box-border rtl:left-auto rtl:right-0 rtl:pl-0 rtl:pr-3.5;
}
.multiselect-single-label-text {
@apply overflow-ellipsis overflow-hidden block whitespace-nowrap max-w-full;
}
.multiselect-multiple-label {
@apply flex items-center h-full absolute left-0 top-0 pointer-events-none bg-transparent leading-snug pl-3.5 rtl:left-auto rtl:right-0 rtl:pl-0 rtl:pr-3.5;
}
.multiselect-search {
@apply w-full absolute inset-0 outline-none focus:ring-0 appearance-none box-border border-0 text-base font-sans bg-white rounded pl-3.5 rtl:pl-0 rtl:pr-3.5;
}
.multiselect-tags {
@apply flex-grow flex-shrink flex flex-wrap items-center mt-1 pl-2 min-w-0 rtl:pl-0 rtl:pr-2;
}
.multiselect-tag {
@apply bg-green-500 text-white text-sm font-semibold py-0.5 pl-2 rounded mr-1 mb-1 flex items-center whitespace-nowrap min-w-0 rtl:pl-0 rtl:pr-2 rtl:mr-0 rtl:ml-1;
&.is-disabled {
@apply pr-2 opacity-50 rtl:pl-2;
}
}
.multiselect-tag-wrapper {
@apply whitespace-nowrap overflow-hidden overflow-ellipsis;
}
.multiselect-tag-wrapper-break {
@apply whitespace-normal break-all;
}
.multiselect-tag-remove {
@apply flex items-center justify-center p-1 mx-0.5 rounded-sm hover:bg-black hover:bg-opacity-10;
}
.multiselect-tag-remove-icon {
@apply bg-multiselect-remove bg-center bg-no-repeat opacity-30 inline-block w-3 h-3;
}
.multiselect-tag-remove:hover .multiselect-tag-remove-icon {
@apply opacity-60;
}
.multiselect-tags-search-wrapper {
@apply inline-block relative mx-1 mb-1 flex-grow flex-shrink h-full;
}
.multiselect-tags-search {
@apply absolute inset-0 border-0 outline-none focus:ring-0 appearance-none p-0 text-base font-sans box-border w-full;
}
.multiselect-tags-search-copy {
@apply invisible whitespace-pre-wrap inline-block h-px;
}
.multiselect-placeholder {
@apply flex items-center h-full absolute left-0 top-0 pointer-events-none bg-transparent leading-snug pl-3.5 text-gray-400 rtl:left-auto rtl:right-0 rtl:pl-0 rtl:pr-3.5;
}
.multiselect-caret {
@apply bg-multiselect-caret bg-center bg-no-repeat w-2.5 h-4 py-px box-content mr-3.5 relative z-10 opacity-40 flex-shrink-0 flex-grow-0 transition-transform transform pointer-events-none rtl:mr-0 rtl:ml-3.5;
&.is-open {
@apply rotate-180 pointer-events-auto;
}
}
.multiselect-clear {
@apply pr-3.5 relative z-10 opacity-40 transition duration-300 flex-shrink-0 flex-grow-0 flex hover:opacity-80 rtl:pr-0 rtl:pl-3.5;
}
.multiselect-clear-icon {
@apply bg-multiselect-remove bg-center bg-no-repeat w-2.5 h-4 py-px box-content inline-block;
}
.multiselect-spinner {
@apply bg-multiselect-spinner bg-center bg-no-repeat w-4 h-4 z-10 mr-3.5 animate-spin flex-shrink-0 flex-grow-0 rtl:mr-0 rtl:ml-3.5;
}
.multiselect-inifite {
@apply flex items-center justify-center w-full;
}
.multiselect-inifite-spinner {
@apply bg-multiselect-spinner bg-center bg-no-repeat w-4 h-4 z-10 animate-spin flex-shrink-0 flex-grow-0 m-3.5;
}
.multiselect-dropdown {
@apply max-h-60 absolute -left-px -right-px bottom-0 transform translate-y-full border border-gray-300 -mt-px overflow-y-scroll z-50 bg-white flex flex-col rounded-b;
&.is-top {
@apply -translate-y-full top-px bottom-auto rounded-b-none rounded-t;
}
&.is-hidden {
@apply hidden;
}
}
.multiselect-options {
@apply flex flex-col p-0 m-0 list-none;
}
.multiselect-group {
@apply p-0 m-0;
}
.multiselect-group-label {
@apply flex text-sm box-border items-center justify-start text-left py-1 px-3 font-semibold bg-gray-200 cursor-default leading-normal;
&.is-pointable {
@apply cursor-pointer;
}
&.is-pointed {
@apply bg-gray-300 text-gray-700;
}
&.is-selected {
@apply bg-green-600 text-white;
}
&.is-disabled {
@apply bg-gray-100 text-gray-300 cursor-not-allowed;
}
&.is-selected.is-pointed {
@apply bg-green-600 text-white opacity-90;
}
&.is-selected.is-disabled {
@apply text-green-100 bg-green-600 bg-opacity-50 cursor-not-allowed;
}
}
.multiselect-group-options {
@apply p-0 m-0;
}
.multiselect-option {
@apply flex items-center justify-start box-border text-left cursor-pointer text-base leading-snug py-2 px-3;
&.is-pointed {
@apply text-gray-800 bg-gray-100;
}
&.is-selected {
@apply text-white bg-green-500;
}
&.is-disabled {
@apply text-gray-300 cursor-not-allowed;
}
&.is-selected.is-pointed {
@apply text-white bg-green-500 opacity-90;
}
&.is-selected.is-disabled {
@apply text-green-100 bg-green-500 bg-opacity-50 cursor-not-allowed;
}
}
.multiselect-no-options {
@apply py-2 px-3 text-gray-600 bg-white text-left rtl:text-right;
}
.multiselect-no-results {
@apply py-2 px-3 text-gray-600 bg-white text-left rtl:text-right;
}
.multiselect-fake-input {
@apply bg-transparent absolute left-0 right-0 -bottom-px w-full h-px border-0 p-0 appearance-none outline-none text-transparent;
}
.multiselect-assistive-text {
@apply absolute -m-px w-px h-px overflow-hidden;
clip: rect(0 0 0 0);
}
.multiselect-spacer {
@apply h-9 py-px box-content;
}