@ishitatsuyuki/oruga-next
Version:
UI components for Vue.js and CSS framework agnostic
452 lines (444 loc) • 17.2 kB
JavaScript
'use strict';
var vue = require('vue');
var helpers = require('./helpers.js');
var config = require('./config.js');
var BaseComponentMixin = require('./BaseComponentMixin-a03c02e3.js');
var Icon = require('./Icon-172f9998.js');
var FormElementMixin = require('./FormElementMixin-facf6e30.js');
var Autocomplete = require('./Autocomplete-1a3987e3.js');
/**
* A simple item input field that can have autocomplete functionality
* @displayName Inputitems
* @style _inputItems.scss
*/
var script = vue.defineComponent({
name: 'OInputitems',
components: {
[Autocomplete.script.name]: Autocomplete.script,
[Icon.script.name]: Icon.script
},
mixins: [FormElementMixin.FormElementMixin, BaseComponentMixin.BaseComponentMixin],
inheritAttrs: false,
configField: 'inputitems',
emits: ['update:modelValue', 'focus', 'blur', 'add', 'remove', 'typing', 'infinite-scroll', 'icon-right-click'],
props: {
/** @model */
modelValue: {
type: Array,
default: () => []
},
/**
* Vertical size of input, optional
* @values small, medium, large
*/
size: String,
/** Items data */
data: {
type: Array,
default: () => []
},
/**
* Color of the each items, optional
* @values primary, info, success, warning, danger, and any other custom color
*/
variant: String,
/** Limits the number of items, plus item counter */
maxitems: {
type: [Number, String],
required: false
},
/** Show counter when maxlength or maxtags props are passed */
hasCounter: {
type: Boolean,
default: () => {
return helpers.getValueByPath(config.getOptions(), 'inputitems.hasCounter', true);
}
},
/** Property of the object (if data is array of objects) to use as display text */
field: {
type: String,
default: 'value'
},
/** Add autocomplete feature (if true, any Autocomplete props may be used too) */
autocomplete: Boolean,
/** Property of the object (if data is array of objects) to use as display text of group */
groupField: String,
/** Property of the object (if data is array of objects) to use as key to get items array of each group, optional */
groupOptions: String,
nativeAutocomplete: String,
/** Opens a dropdown with choices when the input field is focused */
openOnFocus: Boolean,
/** Input will be disabled */
disabled: Boolean,
/** Add close/delete button to the item */
closable: {
type: Boolean,
default: () => {
return helpers.getValueByPath(config.getOptions(), 'inputitems.closable', true);
}
},
/**
* Array of keys
* (https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values)
* which will add a item when typing (default comma, tab and enter)
*/
confirmKeys: {
type: Array,
default: () => {
return helpers.getValueByPath(config.getOptions(), 'inputitems.confirmKeys', [',', 'Tab', 'Enter']);
}
},
/** Allow removing last item when pressing given keys, if input is empty */
removeOnKeys: {
type: Array,
default: () => {
return helpers.getValueByPath(config.getOptions(), 'inputitems.removeOnKeys', ['Backspace']);
}
},
/** When autocomplete, it allow to add new items */
allowNew: Boolean,
/** Array of chars used to split when pasting a new string */
onPasteSeparators: {
type: Array,
default: () => {
return helpers.getValueByPath(config.getOptions(), 'inputitems.onPasteSeparators', [',']);
}
},
/** Function to validate the value of the item before adding */
beforeAdding: {
type: Function,
default: () => true
},
/** Allows adding the same item multiple time */
allowDuplicates: {
type: Boolean,
default: false
},
/** Makes the autocomplete component check if list reached scroll end and emit infinite-scroll event */
checkInfiniteScroll: {
type: Boolean,
default: false
},
/** Function to create a new item to push into v-model (items) */
createItem: {
type: Function,
default: (item) => item
},
/** Icon name of close icon on selected item */
closeIcon: {
type: String,
default: () => {
return helpers.getValueByPath(config.getOptions(), 'inputitems.closeIcon', 'close');
}
},
/** The first option will always be pre-selected (easier to just hit enter or tab) */
keepFirst: Boolean,
/** Accessibility label for the close button */
ariaCloseLabel: String,
/** Append autocomplete content to body */
appendToBody: Boolean,
rootClass: [String, Array, Function],
expandedClass: [String, Array, Function],
variantClass: [String, Array, Function],
closeClass: [String, Array, Function],
itemClass: [String, Array, Function],
counterClass: [String, Array, Function],
autocompleteClasses: {
type: Object,
default: () => {
return helpers.getValueByPath(config.getOptions(), 'inputitems.autocompleteClasses', {});
}
}
},
data() {
return {
items: Array.isArray(this.modelValue) ? this.modelValue.slice(0) : (this.modelValue || []),
newItem: '',
isComposing: false
};
},
computed: {
rootClasses() {
return [
this.computedClass('rootClass', 'o-inputit'),
{ [this.computedClass('expandedClass', 'o-inputit--expanded')]: this.expanded }
];
},
containerClasses() {
return [
this.computedClass('containerClass', 'o-inputit__container'),
{ [this.computedClass('sizeClass', 'o-inputit__container--', this.size)]: this.size },
];
},
itemClasses() {
return [
this.computedClass('itemClass', 'o-inputit__item'),
{ [this.computedClass('variantClass', 'o-inputit__item--', this.variant)]: this.variant }
];
},
closeClasses() {
return [
this.computedClass('closeClass', 'o-inputit__item__close')
];
},
counterClasses() {
return [
this.computedClass('counterClass', 'o-inputit__counter')
];
},
autocompleteBind() {
return {
...this.$attrs,
'root-class': this.computedClass('autocompleteClasses.rootClass', 'o-inputit__autocomplete'),
'input-classes': {
'input-class': this.computedClass('autocompleteClasses.inputClasses.inputClass', 'o-inputit__input'),
},
...this.autocompleteClasses
};
},
valueLength() {
return this.newItem.trim().length;
},
hasDefaultSlot() {
return !!this.$slots.default;
},
hasEmptySlot() {
return !!this.$slots.empty;
},
hasHeaderSlot() {
return !!this.$slots.header;
},
hasFooterSlot() {
return !!this.$slots.footer;
},
/**
* Show the input field if a maxitems hasn't been set or reached.
*/
hasInput() {
return this.maxitems == null || this.itemsLength < this.maxitems;
},
itemsLength() {
return this.items.length;
},
/**
* If input has onPasteSeparators prop,
* returning new RegExp used to split pasted string.
*/
separatorsAsRegExp() {
const sep = this.onPasteSeparators;
return sep.length ? new RegExp(sep.map((s) => {
return s ? s.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&') : null;
}).join('|'), 'g') : null;
},
$elementRef() {
return 'autocomplete';
}
},
watch: {
/**
* When modelValue is changed set internal value.
*/
modelValue(value) {
this.items = Array.isArray(value) ? value.slice(0) : (value || []);
},
hasInput() {
if (!this.hasInput)
this.onBlur();
}
},
methods: {
addItem(item) {
const itemToAdd = item || this.newItem.trim();
if (itemToAdd) {
if (!this.autocomplete) {
const reg = this.separatorsAsRegExp;
if (reg && itemToAdd.match(reg)) {
itemToAdd.split(reg)
.map((t) => t.trim())
.filter((t) => t.length !== 0)
.map(this.addItem);
return;
}
}
// Add the item input if it is not blank
// or previously added (if not allowDuplicates).
const add = !this.allowDuplicates ? this.items.indexOf(this.createItem(itemToAdd)) === -1 : true;
if (add && this.beforeAdding(itemToAdd)) {
this.items.push(this.createItem(itemToAdd));
this.$emit('update:modelValue', this.items);
this.$emit('add', itemToAdd);
}
}
// after autocomplete events
requestAnimationFrame(() => {
this.newItem = '';
this.$emit('typing', '');
});
},
getNormalizedItemText(item) {
if (typeof item === 'object') {
item = helpers.getValueByPath(item, this.field);
}
return `${item}`;
},
customOnBlur(event) {
// Add item on-blur if not select only
if (!this.autocomplete)
this.addItem();
this.onBlur(event);
},
onSelect(option) {
if (!option)
return;
this.addItem(option);
this.$nextTick(() => {
this.newItem = '';
});
},
removeItem(index, event) {
const item = this.items.splice(index, 1)[0];
this.$emit('update:modelValue', this.items);
this.$emit('remove', item);
if (event)
event.stopPropagation();
if (this.openOnFocus && this.$refs.autocomplete) {
this.$refs.autocomplete.focus();
}
return item;
},
removeLastItem() {
if (this.itemsLength > 0) {
this.removeItem(this.itemsLength - 1);
}
},
keydown(event) {
const { key } = event; // cannot destructure preventDefault (https://stackoverflow.com/a/49616808/2774496)
if (this.removeOnKeys.indexOf(key) !== -1 && !this.newItem.length) {
this.removeLastItem();
}
// Stop if is to accept select only
if (this.autocomplete && !this.allowNew)
return;
if (this.confirmKeys.indexOf(key) >= 0) {
// Allow Tab to advance to next field regardless
if (key !== 'Tab')
event.preventDefault();
if (key === 'Enter' && this.isComposing)
return;
this.addItem();
}
},
onTyping(event) {
this.$emit('typing', event.trim());
}
}
});
function render(_ctx, _cache, $props, $setup, $data, $options) {
const _component_o_icon = vue.resolveComponent("o-icon");
const _component_o_autocomplete = vue.resolveComponent("o-autocomplete");
return vue.openBlock(), vue.createBlock("div", {
class: _ctx.rootClasses
}, [vue.createVNode("div", {
class: _ctx.containerClasses,
onClick: _cache[6] || (_cache[6] = $event => _ctx.hasInput && _ctx.focus($event))
}, [vue.renderSlot(_ctx.$slots, "selected", {
items: _ctx.items
}, () => [(vue.openBlock(true), vue.createBlock(vue.Fragment, null, vue.renderList(_ctx.items, (item, index) => {
return vue.openBlock(), vue.createBlock("span", {
key: _ctx.getNormalizedItemText(item) + index,
class: _ctx.itemClasses
}, [vue.createVNode("span", null, vue.toDisplayString(_ctx.getNormalizedItemText(item)), 1
/* TEXT */
), _ctx.closable ? vue.createVNode(_component_o_icon, {
key: 0,
class: _ctx.closeClasses,
clickable: "",
both: "",
icon: _ctx.closeIcon,
onClick: $event => _ctx.removeItem(index, $event),
"aria-label": _ctx.ariaCloseLabel
}, null, 8
/* PROPS */
, ["class", "icon", "onClick", "aria-label"]) : vue.createCommentVNode("v-if", true)], 2
/* CLASS */
);
}), 128
/* KEYED_FRAGMENT */
))]), _ctx.hasInput ? vue.createVNode(_component_o_autocomplete, vue.mergeProps({
key: 0,
ref: "autocomplete",
modelValue: _ctx.newItem,
"onUpdate:modelValue": _cache[1] || (_cache[1] = $event => _ctx.newItem = $event)
}, _ctx.autocompleteBind, {
data: _ctx.data,
field: _ctx.field,
icon: _ctx.icon,
"icon-pack": _ctx.iconPack,
maxlength: _ctx.maxlength,
"has-counter": false,
size: _ctx.size,
disabled: _ctx.disabled,
autocomplete: _ctx.nativeAutocomplete,
"open-on-focus": _ctx.openOnFocus,
"keep-first": _ctx.keepFirst,
"keep-open": _ctx.openOnFocus,
"group-field": _ctx.groupField,
"group-options": _ctx.groupOptions,
"use-html5-validation": _ctx.useHtml5Validation,
"check-infinite-scroll": _ctx.checkInfiniteScroll,
"append-to-body": _ctx.appendToBody,
"confirm-keys": _ctx.confirmKeys,
onTyping: _ctx.onTyping,
onFocus: _ctx.onFocus,
onBlur: _ctx.customOnBlur,
onKeydown: _ctx.keydown,
onCompositionstart: _cache[2] || (_cache[2] = $event => _ctx.isComposing = true),
onCompositionend: _cache[3] || (_cache[3] = $event => _ctx.isComposing = false),
onSelect: _ctx.onSelect,
"onInfinite-scroll": _cache[4] || (_cache[4] = $event => _ctx.$emit('infinite-scroll', $event)),
"onIcon-right-click": _cache[5] || (_cache[5] = $event => _ctx.$emit('icon-right-click', $event))
}), vue.createSlots({
_: 2
}, [_ctx.hasHeaderSlot ? {
name: "header",
fn: vue.withCtx(() => [vue.renderSlot(_ctx.$slots, "header")])
} : undefined, _ctx.hasDefaultSlot ? {
name: "default",
fn: vue.withCtx(props => [vue.renderSlot(_ctx.$slots, "default", {
option: props.option,
index: props.index
})])
} : undefined, _ctx.hasEmptySlot ? {
name: "empty",
fn: vue.withCtx(() => [vue.renderSlot(_ctx.$slots, "empty")])
} : undefined, _ctx.hasFooterSlot ? {
name: "footer",
fn: vue.withCtx(() => [vue.renderSlot(_ctx.$slots, "footer")])
} : undefined]), 1040
/* FULL_PROPS, DYNAMIC_SLOTS */
, ["modelValue", "data", "field", "icon", "icon-pack", "maxlength", "size", "disabled", "autocomplete", "open-on-focus", "keep-first", "keep-open", "group-field", "group-options", "use-html5-validation", "check-infinite-scroll", "append-to-body", "confirm-keys", "onTyping", "onFocus", "onBlur", "onKeydown", "onSelect"]) : vue.createCommentVNode("v-if", true)], 2
/* CLASS */
), _ctx.hasCounter && (_ctx.maxitems || _ctx.maxlength) ? (vue.openBlock(), vue.createBlock("small", {
key: 0,
class: _ctx.counterClasses
}, [_ctx.maxlength && _ctx.valueLength > 0 ? (vue.openBlock(), vue.createBlock(vue.Fragment, {
key: 0
}, [vue.createTextVNode(vue.toDisplayString(_ctx.valueLength) + " / " + vue.toDisplayString(_ctx.maxlength), 1
/* TEXT */
)], 64
/* STABLE_FRAGMENT */
)) : _ctx.maxitems ? (vue.openBlock(), vue.createBlock(vue.Fragment, {
key: 1
}, [vue.createTextVNode(vue.toDisplayString(_ctx.itemsLength) + " / " + vue.toDisplayString(_ctx.maxitems), 1
/* TEXT */
)], 64
/* STABLE_FRAGMENT */
)) : vue.createCommentVNode("v-if", true)], 2
/* CLASS */
)) : vue.createCommentVNode("v-if", true)], 2
/* CLASS */
);
}
script.render = render;
script.__file = "src/components/inputitems/Inputitems.vue";
exports.script = script;