Skip to content

Commit

Permalink
feat: can add new options not only tags #150
Browse files Browse the repository at this point in the history
  • Loading branch information
adamberecz committed Dec 16, 2021
1 parent 2e2135f commit 6c57415
Show file tree
Hide file tree
Showing 7 changed files with 580 additions and 46 deletions.
12 changes: 8 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -240,9 +240,12 @@ Join our [Discord channel](https://discord.gg/WhX2nG6GTQ) or [open an issue](htt
| **filterResults** | `boolean` | `true` | Whether option list should be filtered by search query. This may be set to `false` if you are handling filtering manually when returning async options. |
| **minChars** | `number` | `0` | The minimum number of characters that should be typed to refresh async option list. If `0` it will refresh even when the search field becomes empty. |
| **resolveOnLoad** | `boolean` | `true` | Whether async options should be loaded initially (with an empty query). This should be `true` if you are planning to load non-object value(s) initially while using async options (to fetch matching objects for values). |
| **appendNewTag** | `boolean` | `true` | Whether it should append new tag automatically to option list when using `tags` mode with `createTag`. If set to `false` you need to take care of appending a new tag to the provided `:options` list upon `@tag` event. |
| **createTag** | `boolean` | `false` | Whether it should allow creating new tag based on search query when using `tags` mode. |
| **addTagOn** | `array` | `['enter']` | The list of keys that creates a new tag while typing in the search field when having `createTag` enabled. Possible values: `'enter'\|'space'\|'tab'\|';'\|','`. |
| **appendNewTag** | `boolean` | `true` | **Deprecated 2.3.0: use `appendNewOption` instead.**<br>Whether it should append new tag automatically to option list when using `tags` mode with `createTag`. If set to `false` you need to take care of appending a new tag to the provided `:options` list upon `@tag` event. |
| **createTag** | `boolean` | `false` | **Deprecated 2.3.0: use `createOption` instead.**<br>Whether it should allow creating new tags based on search query when using `tags` mode. |
| **addTagOn** | `array` | `['enter']` | **Deprecated 2.3.0: use `addOptionOn` instead.**<br>The list of keys that creates a new tag while typing in the search field when having `createTag` enabled. Possible values: `'enter'\|'space'\|'tab'\|';'\|','`. |
| **appendNewOption** | `boolean` | `true` | Whether it should append new option automatically to option list when `searchable` and `createTag` are enabled. If set to `false` you need to take care of appending a new option to the provided `:options` list upon `@option` event. |
| **createOption** | `boolean` | `false` | Whether it should allow creating new options based on search query when `searchable` is enabled. |
| **addOptionOn** | `array` | `['enter']` | The list of keys that creates a new option while typing in the search field when having `createOption` enabled. Possible values: `'enter'\|'space'\|'tab'\|';'\|','`. |
| **hideSelected** | `boolean` | `true` | Whether selected options should be excluded from the option list when using `multiple` or `tags` mode. |
| **showOptions** | `boolean` | `true` | Whether option list should be displayed. Can be used to create free-typed tags. |
| **object** | `boolean` | `false` | Whether the value should be stored as an object.<br>If **false**:<br>`value: ['js','jsx','ts']`<br>If **true**:<br> `value: [`<br>&nbsp;&nbsp;`{value:'js',label:'Javascript'},`<br>&nbsp;&nbsp;`{value:'jsx',label:'JSX'},`<br>&nbsp;&nbsp;`{value:'ts',label:'Typescript'}`<br>`]` |
Expand Down Expand Up @@ -290,7 +293,8 @@ mounted() {
| **@open** | | Emitted after opening the option list. |
| **@search-change** | `query` | Emitted after a character is typed. |
| **@select** | `option` | Emitted after an option or tag is selected. |
| **@tag** | `query` | Emitted after enter is hit when a new tag is being created. |
| **@tag** | `query` | **Deprecated 2.3.0: use `@option` instead**. Emitted after enter is hit when a new tag is being created. |
| **@option** | `query` | Emitted after enter is hit when a new option is being created. |
| **@clear** | | Emitted when the options are cleared. |
| **@paste** | `Event` | Emitted when value is pasted into the search field. |

Expand Down
4 changes: 4 additions & 0 deletions src/Multiselect.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,11 @@ declare class Multiselect extends Vue {
minChars?: number;
resolveOnLoad?: boolean;
appendNewTag?: boolean;
appendNewOption?: boolean;
createTag?: boolean;
createOption?: boolean;
addTagOn?: string[];
addOptionOn?: string[];
hideSelected?: boolean;
showOptions?: boolean;
object?: boolean;
Expand All @@ -54,6 +57,7 @@ declare class Multiselect extends Vue {
$emit(eventName: 'remove', e: {originalEvent: Event, value: any, option: any}): this;
$emit(eventName: 'search-change', e: {originalEvent: Event, query: string}): this;
$emit(eventName: 'tag', e: {originalEvent: Event, query: string}): this;
$emit(eventName: 'option', e: {originalEvent: Event, query: string}): this;
$emit(eventName: 'paste', e: {originalEvent: Event}): this;
$emit(eventName: 'open'): this;
$emit(eventName: 'close'): this;
Expand Down
31 changes: 23 additions & 8 deletions src/Multiselect.vue
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,7 @@
name: 'Multiselect',
emits: [
'open', 'close', 'select', 'deselect',
'input', 'search-change', 'tag', 'update:modelValue',
'input', 'search-change', 'tag', 'option', 'update:modelValue',
'change', 'clear'
],
props: {
Expand Down Expand Up @@ -281,12 +281,32 @@
createTag: {
type: Boolean,
required: false,
default: false,
default: undefined,
},
createOption: {
type: Boolean,
required: false,
default: undefined,
},
appendNewTag: {
type: Boolean,
required: false,
default: true,
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,
Expand Down Expand Up @@ -367,11 +387,6 @@
required: false,
default: true,
},
addTagOn: {
type: Array,
required: false,
default: () => (['enter']),
},
required: {
type: Boolean,
required: false,
Expand Down
46 changes: 35 additions & 11 deletions src/composables/useKeyboard.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { toRefs } from 'composition-api'
import { toRefs, computed } from 'composition-api'

export default function useKeyboard (props, context, dep)
{
const {
mode, addTagOn, createTag, openDirection, searchable,
mode, addTagOn, openDirection, searchable,
showOptions, valueProp, groups: groupped,
addOptionOn: addOptionOn_, createTag, createOption: createOption_,
} = toRefs(props)

// ============ DEPENDENCIES ============
Expand All @@ -19,6 +20,25 @@ export default function useKeyboard (props, context, dep)
const blur = dep.blur
const fo = dep.fo

// ============== COMPUTED ==============

// no export
const createOption = computed(() => {
return createTag.value || createOption_.value || false
})

// no export
const addOptionOn = computed(() => {
if (addTagOn.value !== undefined) {
return addTagOn.value
}
else if (addOptionOn_.value !== undefined) {
return addOptionOn_.value
}

return ['enter']
})

// =============== METHODS ==============

// no export
Expand All @@ -28,7 +48,7 @@ export default function useKeyboard (props, context, dep)
// 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 && createTag.value && searchable.value && !groupped.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)])
}
}
Expand All @@ -54,7 +74,7 @@ export default function useKeyboard (props, context, dep)
case 'Enter':
e.preventDefault()

if (mode.value === 'tags' && addTagOn.value.indexOf('enter') === -1 && createTag.value) {
if (addOptionOn.value.indexOf('enter') === -1 && createOption.value) {
return
}

Expand All @@ -63,11 +83,19 @@ export default function useKeyboard (props, context, dep)
break

case ' ':
if (searchable.value && mode.value !== 'tags' && !createTag.value) {
if (!createOption.value && !searchable.value) {
e.preventDefault()

preparePointer()
selectPointer()
return
}

if (mode.value === 'tags' && ((addTagOn.value.indexOf('space') === -1 && createTag.value) || !createTag.value)) {
if (!createOption.value) {
return false
}

if (addOptionOn.value.indexOf('space') === -1 && createOption.value) {
return
}

Expand All @@ -80,11 +108,7 @@ export default function useKeyboard (props, context, dep)
case 'Tab':
case ';':
case ',':
if (mode.value !== 'tags') {
return
}

if (addTagOn.value.indexOf(e.key.toLowerCase()) === -1 || !createTag.value) {
if (addOptionOn.value.indexOf(e.key.toLowerCase()) === -1 || !createOption.value) {
return
}

Expand Down
59 changes: 42 additions & 17 deletions src/composables/useOptions.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@ import arraysEqual from './../utils/arraysEqual'
export default function useOptions (props, context, dep)
{
const {
options, mode, trackBy: trackBy_, limit, hideSelected, createTag, label,
appendNewTag, multipleLabel, object, loading, delay, resolveOnLoad,
options, mode, trackBy: trackBy_, limit, hideSelected, createTag, createOption: createOption_, label,
appendNewTag, appendNewOption: appendNewOption_, multipleLabel, object, loading, delay, resolveOnLoad,
minChars, filterResults, clearOnSearch, clearOnSelect, valueProp,
canDeselect, max, strict, closeOnSelect, groups: groupped, groupLabel,
groupOptions, groupHideEmpty, groupSelect,
groupOptions, groupHideEmpty, groupSelect,
} = toRefs(props)

// ============ DEPENDENCIES ============
Expand Down Expand Up @@ -41,6 +41,22 @@ export default function useOptions (props, context, dep)

// ============== COMPUTED ==============

// no export
const createOption = computed(() => {
return createTag.value || createOption_.value || false
})

// no export
const appendNewOption = computed(() => {
if (appendNewTag.value !== undefined) {
return appendNewTag.value
} else if (appendNewOption_.value !== undefined) {
return appendNewOption_.value
}

return true
})

// no export
// extendedOptions
const eo = computed(() => {
Expand Down Expand Up @@ -89,8 +105,8 @@ export default function useOptions (props, context, dep)
const fo = computed(() => {
let options = eo.value

if (createdTag.value.length) {
options = createdTag.value.concat(options)
if (createdOption.value.length) {
options = createdOption.value.concat(options)
}

options = filterOptions(options)
Expand Down Expand Up @@ -120,7 +136,7 @@ export default function useOptions (props, context, dep)
})

const noOptions = computed(() => {
return !eo.value.length && !resolving.value && !createdTag.value.length
return !eo.value.length && !resolving.value && !createdOption.value.length
})


Expand All @@ -129,8 +145,8 @@ export default function useOptions (props, context, dep)
})

// no export
const createdTag = computed(() => {
if (createTag.value === false || !search.value) {
const createdOption = computed(() => {
if (createOption.value === false || !search.value) {
return []
}

Expand Down Expand Up @@ -270,6 +286,8 @@ export default function useOptions (props, context, dep)
return
}

handleOptionAppend(option)

blur()
select(option)
break
Expand All @@ -284,6 +302,8 @@ export default function useOptions (props, context, dep)
return
}

handleOptionAppend(option)

select(option)

if (clearOnSelect.value) {
Expand Down Expand Up @@ -312,15 +332,7 @@ export default function useOptions (props, context, dep)
return
}

if (getOption(option[valueProp.value]) === undefined && createTag.value) {
context.emit('tag', option[valueProp.value])

if (appendNewTag.value) {
appendOption(option)
}

clearSearch()
}
handleOptionAppend(option)

if (clearOnSelect.value) {
clearSearch()
Expand Down Expand Up @@ -373,6 +385,19 @@ export default function useOptions (props, context, dep)
}
}

const handleOptionAppend = (option) => {
if (getOption(option[valueProp.value]) === undefined && createOption.value) {
context.emit('tag', option[valueProp.value])
context.emit('option', option[valueProp.value])

if (appendNewOption.value) {
appendOption(option)
}

clearSearch()
}
}

const selectAll = () => {
if (mode.value === 'single') {
return
Expand Down
Loading

0 comments on commit 6c57415

Please sign in to comment.