primevue-mirror/components/chips/Chips.vue

363 lines
10 KiB
Vue
Raw Normal View History

2022-09-06 12:03:37 +00:00
<template>
<div :class="containerClass">
2022-09-14 11:26:01 +00:00
<ul
ref="container"
class="p-inputtext p-chips-multiple-container"
tabindex="-1"
role="listbox"
aria-orientation="horizontal"
:aria-labelledby="ariaLabelledby"
:aria-label="ariaLabel"
:aria-activedescendant="focused ? focusedOptionId : undefined"
@click="onWrapperClick()"
@focus="onContainerFocus"
@blur="onContainerBlur"
@keydown="onContainerKeyDown"
>
<li
v-for="(val, i) of modelValue"
:key="`${i}_${val}`"
:id="id + '_chips_item_' + i"
role="option"
:class="['p-chips-token', { 'p-focus': focusedIndex === i }]"
:aria-label="val"
:aria-selected="true"
:aria-setsize="modelValue.length"
:aria-posinset="i + 1"
>
2022-09-06 12:03:37 +00:00
<slot name="chip" :value="val">
2022-09-14 11:26:01 +00:00
<span class="p-chips-token-label">{{ val }}</span>
2022-09-06 12:03:37 +00:00
</slot>
2022-12-08 11:04:25 +00:00
<span :class="['p-chips-token-icon', removeTokenIcon]" @click="removeItem($event, i)" aria-hidden="true"></span>
2022-09-06 12:03:37 +00:00
</li>
<li class="p-chips-input-token" role="option">
2022-09-14 11:26:01 +00:00
<input
ref="input"
:id="inputId"
type="text"
:class="inputClass"
:style="inputStyle"
:disabled="disabled || maxedOut"
:placeholder="placeholder"
@focus="onFocus($event)"
@blur="onBlur($event)"
@input="onInput"
@keydown="onKeyDown($event)"
@paste="onPaste($event)"
v-bind="inputProps"
/>
2022-09-06 12:03:37 +00:00
</li>
</ul>
</div>
</template>
<script>
2022-09-14 11:26:01 +00:00
import { UniqueComponentId } from 'primevue/utils';
2022-09-06 12:03:37 +00:00
export default {
name: 'Chips',
emits: ['update:modelValue', 'add', 'remove', 'focus', 'blur'],
props: {
modelValue: {
type: Array,
default: null
},
max: {
type: Number,
default: null
},
separator: {
2022-12-08 11:04:25 +00:00
type: [String, Object],
2022-09-06 12:03:37 +00:00
default: null
},
addOnBlur: {
type: Boolean,
default: null
},
allowDuplicate: {
type: Boolean,
default: true
},
placeholder: {
type: String,
default: null
},
disabled: {
type: Boolean,
default: false
},
2022-09-14 11:26:01 +00:00
inputId: {
type: String,
default: null
},
inputClass: {
type: String,
default: null
},
inputStyle: {
type: null,
default: null
},
inputProps: {
type: null,
default: null
},
2022-12-08 11:04:25 +00:00
removeTokenIcon: {
type: String,
default: 'pi pi-times-circle'
},
2022-09-06 12:03:37 +00:00
'aria-labelledby': {
type: String,
2022-09-14 11:26:01 +00:00
default: null
2022-09-06 12:03:37 +00:00
},
'aria-label': {
type: String,
default: null
}
},
data() {
return {
id: UniqueComponentId(),
inputValue: null,
focused: false,
focusedIndex: null
};
},
methods: {
onWrapperClick() {
this.$refs.input.focus();
},
onInput(event) {
this.inputValue = event.target.value;
this.focusedIndex = null;
},
onFocus(event) {
this.focused = true;
this.focusedIndex = null;
this.$emit('focus', event);
},
onBlur(event) {
this.focused = false;
this.focusedIndex = null;
2022-09-14 11:26:01 +00:00
2022-09-06 12:03:37 +00:00
if (this.addOnBlur) {
this.addItem(event, event.target.value, false);
}
2022-09-14 11:26:01 +00:00
2022-09-06 12:03:37 +00:00
this.$emit('blur', event);
},
onKeyDown(event) {
const inputValue = event.target.value;
2022-09-14 11:26:01 +00:00
switch (event.code) {
2022-09-06 12:03:37 +00:00
case 'Backspace':
if (inputValue.length === 0 && this.modelValue && this.modelValue.length > 0) {
if (this.focusedIndex !== null) {
this.removeItem(event, this.focusedIndex);
2022-09-14 11:26:01 +00:00
} else this.removeItem(event, this.modelValue.length - 1);
2022-09-06 12:03:37 +00:00
}
2022-09-14 11:26:01 +00:00
break;
2022-09-06 12:03:37 +00:00
case 'Enter':
if (inputValue && inputValue.trim().length && !this.maxedOut) {
this.addItem(event, inputValue, true);
}
2022-09-14 11:26:01 +00:00
break;
2022-09-06 12:03:37 +00:00
case 'ArrowLeft':
if (inputValue.length === 0 && this.modelValue && this.modelValue.length > 0) {
this.$refs.container.focus();
}
2022-09-14 11:26:01 +00:00
break;
2022-09-06 12:03:37 +00:00
case 'ArrowRight':
event.stopPropagation();
2022-09-14 11:26:01 +00:00
break;
2022-09-06 12:03:37 +00:00
default:
if (this.separator) {
2022-12-08 11:04:25 +00:00
if (this.separator === event.key || event.key.match(this.separator)) {
2022-09-06 12:03:37 +00:00
this.addItem(event, inputValue, true);
}
}
2022-09-14 11:26:01 +00:00
break;
2022-09-06 12:03:37 +00:00
}
},
onPaste(event) {
if (this.separator) {
let pastedData = (event.clipboardData || window['clipboardData']).getData('Text');
2022-09-14 11:26:01 +00:00
2022-09-06 12:03:37 +00:00
if (pastedData) {
let value = this.modelValue || [];
let pastedValues = pastedData.split(this.separator);
2022-09-14 11:26:01 +00:00
pastedValues = pastedValues.filter((val) => this.allowDuplicate || value.indexOf(val) === -1);
2022-09-06 12:03:37 +00:00
value = [...value, ...pastedValues];
this.updateModel(event, value, true);
}
}
},
onContainerFocus() {
this.focused = true;
},
onContainerBlur() {
this.focusedIndex = -1;
this.focused = false;
},
onContainerKeyDown(event) {
switch (event.code) {
case 'ArrowLeft':
this.onArrowLeftKeyOn(event);
break;
case 'ArrowRight':
this.onArrowRightKeyOn(event);
break;
case 'Backspace':
this.onBackspaceKeyOn(event);
break;
default:
break;
}
},
onArrowLeftKeyOn() {
if (this.inputValue.length === 0 && this.modelValue && this.modelValue.length > 0) {
this.focusedIndex = this.focusedIndex === null ? this.modelValue.length - 1 : this.focusedIndex - 1;
if (this.focusedIndex < 0) this.focusedIndex = 0;
}
},
onArrowRightKeyOn() {
if (this.inputValue.length === 0 && this.modelValue && this.modelValue.length > 0) {
if (this.focusedIndex === this.modelValue.length - 1) {
this.focusedIndex = null;
this.$refs.input.focus();
2022-09-14 11:26:01 +00:00
} else {
2022-09-06 12:03:37 +00:00
this.focusedIndex++;
}
}
},
onBackspaceKeyOn(event) {
if (this.focusedIndex !== null) {
this.removeItem(event, this.focusedIndex);
}
},
updateModel(event, value, preventDefault) {
this.$emit('update:modelValue', value);
this.$emit('add', {
originalEvent: event,
value: value
});
this.$refs.input.value = '';
this.inputValue = '';
2022-12-08 11:04:25 +00:00
setTimeout(() => {
this.maxedOut && (this.focused = false);
}, 0);
2022-09-06 12:03:37 +00:00
if (preventDefault) {
event.preventDefault();
}
},
addItem(event, item, preventDefault) {
if (item && item.trim().length) {
2022-09-14 11:26:01 +00:00
let value = this.modelValue ? [...this.modelValue] : [];
2022-09-06 12:03:37 +00:00
if (this.allowDuplicate || value.indexOf(item) === -1) {
value.push(item);
this.updateModel(event, value, preventDefault);
}
}
},
removeItem(event, index) {
if (this.disabled) {
return;
}
let values = [...this.modelValue];
const removedItem = values.splice(index, 1);
2022-09-14 11:26:01 +00:00
2022-09-06 12:03:37 +00:00
this.focusedIndex = null;
this.$refs.input.focus();
this.$emit('update:modelValue', values);
this.$emit('remove', {
originalEvent: event,
value: removedItem
});
}
},
computed: {
maxedOut() {
return this.max && this.modelValue && this.max === this.modelValue.length;
},
containerClass() {
2022-09-14 11:26:01 +00:00
return [
'p-chips p-component p-inputwrapper',
{
'p-disabled': this.disabled,
'p-focus': this.focused,
'p-inputwrapper-filled': (this.modelValue && this.modelValue.length) || (this.inputValue && this.inputValue.length),
'p-inputwrapper-focus': this.focused
}
];
2022-09-06 12:03:37 +00:00
},
focusedOptionId() {
return this.focusedIndex !== null ? `${this.id}_chips_item_${this.focusedIndex}` : null;
}
}
2022-09-14 11:26:01 +00:00
};
2022-09-06 12:03:37 +00:00
</script>
<style>
.p-chips {
display: inline-flex;
}
.p-chips-multiple-container {
margin: 0;
padding: 0;
list-style-type: none;
cursor: text;
overflow: hidden;
display: flex;
align-items: center;
flex-wrap: wrap;
}
.p-chips-token {
cursor: default;
display: inline-flex;
align-items: center;
flex: 0 0 auto;
}
.p-chips-input-token {
flex: 1 1 auto;
display: inline-flex;
}
.p-chips-token-icon {
cursor: pointer;
}
.p-chips-input-token input {
border: 0 none;
outline: 0 none;
background-color: transparent;
margin: 0;
padding: 0;
box-shadow: none;
border-radius: 0;
width: 100%;
}
.p-fluid .p-chips {
display: flex;
}
</style>