UNPKG

@ishitatsuyuki/oruga-next

Version:

UI components for Vue.js and CSS framework agnostic

452 lines (444 loc) 17.2 kB
'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;