@ishitatsuyuki/oruga-next
Version:
UI components for Vue.js and CSS framework agnostic
1,308 lines (1,294 loc) • 599 kB
JavaScript
/*! Oruga v0.5.5-direct-export | MIT License | github.com/oruga-ui/oruga */
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('vue')) :
typeof define === 'function' && define.amd ? define(['exports', 'vue'], factory) :
(global = global || self, factory(global.Oruga = {}, global.Vue));
}(this, (function (exports, vue) { 'use strict';
/**
* +/- function to native math sign
*/
function signPoly(value) {
if (value < 0)
return -1;
return value > 0 ? 1 : 0;
}
const sign = Math.sign || signPoly;
/**
* Checks if the flag is set
* @param val
* @param flag
* @returns {boolean}
*/
function hasFlag(val, flag) {
return (val & flag) === flag;
}
/**
* Native modulo bug with negative numbers
* @param n
* @param mod
* @returns {number}
*/
function mod(n, mod) {
return ((n % mod) + mod) % mod;
}
/**
* Asserts a value is beetween min and max
* @param val
* @param min
* @param max
* @returns {number}
*/
function bound(val, min, max) {
return Math.max(min, Math.min(max, val));
}
/**
* Get value of an object property/path even if it's nested
*/
function getValueByPath(obj, path, defaultValue = undefined) {
const value = path.split('.').reduce((o, i) => typeof o !== 'undefined' ? o[i] : undefined, obj);
return typeof value !== 'undefined' ? value : defaultValue;
}
/**
* Extension of indexOf method by equality function if specified
*/
function indexOf(array, obj, fn) {
if (!array)
return -1;
if (!fn || typeof fn !== 'function')
return array.indexOf(obj);
for (let i = 0; i < array.length; i++) {
if (fn(array[i], obj)) {
return i;
}
}
return -1;
}
/**
* Merge function to replace Object.assign with deep merging possibility
*/
const isObject = (item) => typeof item === 'object' && !Array.isArray(item);
const mergeFn = (target, source, deep = false) => {
if (deep || !Object.assign) {
const isDeep = (prop) => isObject(source[prop]) &&
target !== null &&
Object.prototype.hasOwnProperty.call(target, prop) &&
isObject(target[prop]);
let replaced;
if (source === null || typeof source === 'undefined') {
replaced = false;
}
else {
replaced = Object.getOwnPropertyNames(source)
.map((prop) => ({ [prop]: isDeep(prop)
? mergeFn(target[prop], source[prop], deep)
: source[prop] }))
.reduce((a, b) => ({ ...a, ...b }), {});
}
return {
...target,
...replaced
};
}
else {
return Object.assign(target, source);
}
};
const merge = mergeFn;
/**
* Mobile detection
* https://www.abeautifulsite.net/detecting-mobile-devices-with-javascript
*/
const isMobile = {
Android: function () {
return (typeof window !== 'undefined' &&
window.navigator.userAgent.match(/Android/i));
},
BlackBerry: function () {
return (typeof window !== 'undefined' &&
window.navigator.userAgent.match(/BlackBerry/i));
},
iOS: function () {
return (typeof window !== 'undefined' &&
window.navigator.userAgent.match(/iPhone|iPad|iPod/i));
},
Opera: function () {
return (typeof window !== 'undefined' &&
window.navigator.userAgent.match(/Opera Mini/i));
},
Windows: function () {
return (typeof window !== 'undefined' &&
window.navigator.userAgent.match(/IEMobile/i));
},
any: function () {
return (isMobile.Android() ||
isMobile.BlackBerry() ||
isMobile.iOS() ||
isMobile.Opera() ||
isMobile.Windows());
}
};
function removeElement(el) {
if (typeof el.remove !== 'undefined') {
el.remove();
}
else if (typeof el.parentNode !== 'undefined' && el.parentNode !== null) {
el.parentNode.removeChild(el);
}
}
function createAbsoluteElement(el) {
const root = document.createElement('div');
root.style.position = 'absolute';
root.style.left = '0px';
root.style.top = '0px';
const wrapper = document.createElement('div');
root.appendChild(wrapper);
wrapper.appendChild(el);
document.body.appendChild(root);
return root;
}
/**
* Escape regex characters
* http://stackoverflow.com/a/6969486
*/
function escapeRegExpChars(value) {
if (!value)
return value;
return value.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&');
}
function toCssDimension(width) {
return width === undefined ? null : (isNaN(width) ? width : width + 'px');
}
function blankIfUndefined(value) {
return typeof value !== 'undefined' && value !== null ? value : '';
}
function defaultIfUndefined(value, defaultValue) {
return typeof value !== 'undefined' && value !== null ? value : defaultValue;
}
function getMonthNames(locale = undefined, format = 'long') {
const dates = [];
for (let i = 0; i < 12; i++) {
dates.push(new Date(2000, i, 15));
}
const dtf = new Intl.DateTimeFormat(locale, {
month: format,
});
return dates.map((d) => dtf.format(d));
}
function getWeekdayNames(locale = undefined, firstDayOfWeek = 0, format = 'narrow') {
const dates = [];
for (let i = 1, j = 0; j < 7; i++) {
const d = new Date(2000, 0, i);
const day = d.getDay();
if (day === firstDayOfWeek || j > 0) {
dates.push(d);
j++;
}
}
const dtf = new Intl.DateTimeFormat(locale, {
weekday: format,
});
return dates.map((d) => dtf.format(d));
}
/**
* Accept a regex with group names and return an object
* ex. matchWithGroups(/((?!=<year>)\d+)\/((?!=<month>)\d+)\/((?!=<day>)\d+)/, '2000/12/25')
* will return { year: 2000, month: 12, day: 25 }
* @param {String} includes injections of (?!={groupname}) for each group
* @param {String} the string to run regex
* @return {Object} an object with a property for each group having the group's match as the value
*/
function matchWithGroups(pattern, str) {
const matches = str.match(pattern);
return pattern
// get the pattern as a string
.toString()
// suss out the groups
.match(/<(.+?)>/g)
// remove the braces
.map((group) => {
const groupMatches = group.match(/<(.+)>/);
if (!groupMatches || groupMatches.length <= 0) {
return null;
}
return group.match(/<(.+)>/)[1];
})
// create an object with a property for each group having the group's match as the value
.reduce((acc, curr, index) => {
if (matches && matches.length > index) {
acc[curr] = matches[index + 1];
}
else {
acc[curr] = null;
}
return acc;
}, {});
}
function debounce(func, wait, immediate) {
let timeout;
return function () {
const context = this;
const args = arguments;
const later = function () {
timeout = null;
if (!immediate)
func.apply(context, args);
};
const callNow = immediate && !timeout;
clearTimeout(timeout);
timeout = setTimeout(later, wait);
if (callNow)
func.apply(context, args);
};
}
function endsWith(str, suffix) {
return str.indexOf(suffix, str.length - suffix.length) !== -1;
}
const isDefined = (d) => d !== undefined;
/**
* Remove accents/diacritics in a string in JavaScript
* https://stackoverflow.com/a/37511463
*/
function removeDiacriticsFromString(value) {
if (!value)
return value;
return value.normalize('NFD').replace(/[\u0300-\u036f]/g, '');
}
let config = {
iconPack: 'mdi',
useHtml5Validation: true,
statusIcon: true,
transformClasses: undefined
};
const setOptions = (options) => { config = options; };
const getOptions = () => { return config; };
let VueInstance;
const setVueInstance = (Vue) => { VueInstance = Vue; };
const Programmatic = {
getOptions,
setOptions(options) {
setOptions(merge(getOptions(), options, true));
}
};
const Plugin = {
install(Vue, options = {}) {
setVueInstance(Vue);
// Options
setOptions(merge(getOptions(), options, true));
}
};
const _defaultSuffixProcessor = (input, suffix) => {
return blankIfUndefined(input)
.split(' ')
.filter((cls) => cls.length > 0)
.map((cls) => cls + suffix)
.join(' ');
};
const _getContext = (vm) => {
const computedNames = vm.$options.computed ? Object.keys(vm.$options.computed) : [];
const computed = computedNames.filter(e => !endsWith(e, 'Classes')).reduce((o, key) => {
o[key] = vm[key];
return o;
}, {});
return { props: vm.$props, data: vm.$data, computed };
};
var BaseComponentMixin = vue.defineComponent({
isOruga: true,
props: {
override: Boolean
},
methods: {
computedClass(field, defaultValue, suffix = '') {
const config = this.$props.override === true ? {} : getOptions();
const override = this.$props.override || getValueByPath(config, `${this.$options.configField}.override`, false);
const overrideClass = getValueByPath(config, `${this.$options.configField}.${field}.override`, override);
const globalTransformClasses = getValueByPath(config, `transformClasses`, undefined);
const localTransformClasses = getValueByPath(config, `${this.$options.configField}.transformClasses`, undefined);
let globalClass = getValueByPath(config, `${this.$options.configField}.${field}.class`, '')
|| getValueByPath(config, `${this.$options.configField}.${field}`, '');
let currentClass = getValueByPath(this.$props, field);
if (Array.isArray(currentClass)) {
currentClass = currentClass.join(' ');
}
if (defaultValue.search("{*}") !== -1) {
defaultValue = defaultValue.replace(/\{\*\}/g, suffix);
}
else {
defaultValue = defaultValue + suffix;
}
let context = null;
if (typeof currentClass === "function") {
context = _getContext(this);
currentClass = currentClass(suffix, context);
}
else {
currentClass = _defaultSuffixProcessor(currentClass, suffix);
}
if (typeof globalClass === "function") {
globalClass = globalClass(suffix, context || _getContext(this));
}
else {
globalClass = _defaultSuffixProcessor(globalClass, suffix);
}
let appliedClasses = (`${(override && !overrideClass) || (!override && !overrideClass) ? defaultValue : ''} `
+ `${blankIfUndefined(globalClass)} `
+ `${blankIfUndefined(currentClass)}`).trim().replace(/\s\s+/g, ' ');
if (localTransformClasses) {
appliedClasses = localTransformClasses(appliedClasses);
}
if (globalTransformClasses) {
appliedClasses = globalTransformClasses(appliedClasses);
}
return appliedClasses;
}
}
});
const mdiIcons = {
sizes: {
'default': 'mdi-24px',
'small': null,
'medium': 'mdi-36px',
'large': 'mdi-48px'
},
iconPrefix: 'mdi-'
};
const faIcons = () => {
const iconComponent = getValueByPath(getOptions(), 'iconComponent');
const faIconPrefix = iconComponent ? '' : 'fa-';
return {
sizes: {
'default': null,
'small': null,
'medium': faIconPrefix + 'lg',
'large': faIconPrefix + '2x'
},
iconPrefix: faIconPrefix,
internalIcons: {
'check': 'check',
'information': 'info-circle',
'alert': 'exclamation-triangle',
'alert-circle': 'exclamation-circle',
'arrow-up': 'arrow-up',
'chevron-right': 'angle-right',
'chevron-left': 'angle-left',
'chevron-down': 'angle-down',
'chevron-up': 'angle-up',
'eye': 'eye',
'eye-off': 'eye-slash',
'caret-down': 'caret-down',
'caret-up': 'caret-up',
'close-circle': 'times-circle',
'close': 'times',
'loading': 'circle-notch'
}
};
};
const getIcons = () => {
let icons = {
mdi: mdiIcons,
fa: faIcons(),
fas: faIcons(),
far: faIcons(),
fad: faIcons(),
fab: faIcons(),
fal: faIcons()
};
const customIconPacks = getValueByPath(getOptions(), 'customIconPacks');
if (customIconPacks) {
icons = merge(icons, customIconPacks, true);
}
return icons;
};
/**
* Icons take an important role of any application
* @displayName Icon
* @style _icon.scss
*/
var script = vue.defineComponent({
name: 'OIcon',
mixins: [BaseComponentMixin],
configField: 'icon',
props: {
/**
* Color of the icon, optional
* @values primary, info, success, warning, danger, and any other custom color
*/
variant: [String, Object],
/**
* Icon component name
*/
component: String,
/**
* Icon pack to use
* @values mdi, fa, fas and any other custom icon pack
*/
pack: String,
/**
* Icon name
*/
icon: String,
/**
* Icon size, optional
* @values small, medium, large
*/
size: String,
/**
* Overrides icon font size, optional
* @values Depends on library: null (smallest), fa-lg, fa-2x, fa-3x, fa-4x, fa-5x, mdi-18px, mdi-24px, mdi-36px, mdi-48px
*/
customSize: String,
/**
* Add class to icon font, optional. See here for MDI, here for FontAwesome 4 and here for FontAwesome 5 custom classes
*/
customClass: String,
/**
* When true makes icon clickable
*/
clickable: Boolean,
/** Enable spin effect on icon */
spin: Boolean,
/** Rotation 0-360 */
rotation: [Number, String],
/** @ignore */
both: Boolean,
rootClass: [String, Function, Array],
clickableClass: [String, Function, Array],
spinClass: [String, Function, Array],
sizeClass: [String, Function, Array],
variantClass: [String, Function, Array]
},
computed: {
rootClasses() {
return [
this.computedClass('rootClass', 'o-icon'),
{ [this.computedClass('clickableClass', 'o-icon--clickable')]: this.clickable },
{ [this.computedClass('spinClass', 'o-icon--spin')]: this.spin },
{ [this.computedClass('sizeClass', 'o-icon--', this.size)]: this.size },
{ [this.computedClass('variantClass', 'o-icon--', this.newVariant)]: this.newVariant }
];
},
rootStyle() {
const style = {};
if (this.rotation) {
style['transform'] = `rotate(${this.rotation}deg)`;
}
return style;
},
iconConfig() {
return getIcons()[this.newPack];
},
iconPrefix() {
if (this.iconConfig && this.iconConfig.iconPrefix) {
return this.iconConfig.iconPrefix;
}
return '';
},
/**
* Internal icon name based on the pack.
* If pack is 'fa', gets the equivalent FA icon name of the MDI,
* internal icons are always MDI.
*/
newIcon() {
return `${this.iconPrefix}${this.getEquivalentIconOf(this.icon)}`;
},
newPack() {
return this.pack || getValueByPath(getOptions(), 'iconPack', 'mdi');
},
newVariant() {
if (!this.variant)
return;
let newVariant = '';
if (typeof this.variant === 'string') {
newVariant = this.variant;
}
else {
newVariant = Object.keys(this.variant).filter(key => this.variant[key])[0];
}
return newVariant;
},
newCustomSize() {
return this.customSize || this.customSizeByPack;
},
customSizeByPack() {
if (this.iconConfig && this.iconConfig.sizes) {
if (this.size && this.iconConfig.sizes[this.size] !== undefined) {
return this.iconConfig.sizes[this.size];
}
else if (this.iconConfig.sizes.default) {
return this.iconConfig.sizes.default;
}
}
return null;
},
useIconComponent() {
if (this.component)
return this.component;
const component = getValueByPath(getOptions(), 'iconComponent');
if (component)
return component;
return null;
}
},
methods: {
/**
* Equivalent icon name of the MDI.
*/
getEquivalentIconOf(value) {
// Only transform the class if the both prop is set to true
if (!this.both) {
return value;
}
if (this.iconConfig &&
this.iconConfig.internalIcons &&
this.iconConfig.internalIcons[value]) {
return this.iconConfig.internalIcons[value];
}
return value;
}
}
});
function render(_ctx, _cache, $props, $setup, $data, $options) {
return vue.openBlock(), vue.createBlock("span", {
class: _ctx.rootClasses,
style: _ctx.rootStyle
}, [!_ctx.useIconComponent ? (vue.openBlock(), vue.createBlock("i", {
key: 0,
class: [_ctx.newPack, _ctx.newIcon, _ctx.newCustomSize, _ctx.customClass]
}, null, 2
/* CLASS */
)) : (vue.openBlock(), vue.createBlock(vue.Fragment, {
key: 1
}, [vue.createCommentVNode(" custom icon component "), (vue.openBlock(), vue.createBlock(vue.resolveDynamicComponent(_ctx.useIconComponent), {
icon: [_ctx.newPack, _ctx.newIcon],
size: _ctx.newCustomSize,
class: [_ctx.customClass]
}, null, 8
/* PROPS */
, ["icon", "size", "class"]))], 64
/* STABLE_FRAGMENT */
))], 6
/* CLASS, STYLE */
);
}
script.render = render;
script.__file = "src/components/icon/Icon.vue";
var FormElementMixin = vue.defineComponent({
inject: {
$field: { from: "$field", default: false }
},
emits: ['blur', 'focus'],
props: {
/**
* Makes input full width when inside a grouped or addon field
*/
expanded: Boolean,
/**
* Makes the element rounded
*/
rounded: Boolean,
/**
* Icon name to be added
*/
icon: String,
/**
* Icon pack to use
* @values mdi, fa, fas and any other custom icon pack
*/
iconPack: String,
/** Native options to use in HTML5 validation */
autocomplete: String,
/** Same as native maxlength, plus character counter */
maxlength: [Number, String],
/** Enable html 5 native validation */
useHtml5Validation: {
type: Boolean,
default: () => {
return getValueByPath(getOptions(), "useHtml5Validation", true);
},
},
/** Show status icon using field and variant prop */
statusIcon: {
type: Boolean,
default: () => {
return getValueByPath(getOptions(), "statusIcon", true);
},
},
/**
* The message which is shown when a validation error occurs
*/
validationMessage: String,
},
data() {
return {
isValid: true,
isFocused: false,
newIconPack: this.iconPack,
};
},
computed: {
parentField() {
return this.$field;
},
/**
* Get the type prop from parent if it's a Field.
*/
statusVariant() {
if (!this.parentField)
return;
if (!this.parentField.newVariant)
return;
if (typeof this.parentField.newVariant === "string") {
return this.parentField.newVariant;
}
else {
for (const key in this.parentField.newVariant) {
if (this.parentField.newVariant[key]) {
return key;
}
}
}
},
/**
* Get the message prop from parent if it's a Field.
*/
statusMessage() {
if (!this.parentField)
return;
return this.parentField.newMessage || this.parentField.hasMessageSlot;
},
/**
* Icon name based on the variant.
*/
statusVariantIcon() {
const statusVariantIcon = getValueByPath(getOptions(), "statusVariantIcon", {
'success': 'check',
'danger': 'alert-circle',
'info': 'information',
'warning': 'alert'
});
return statusVariantIcon[this.statusVariant] || '';
}
},
methods: {
/**
* Focus method that work dynamically depending on the component.
*/
focus() {
const el = this.getElement();
if (!el)
return;
this.$nextTick(() => {
if (el)
el.focus();
});
},
onBlur(event) {
this.isFocused = false;
if (this.parentField) {
this.parentField.isFocused = false;
}
this.$emit("blur", event);
this.checkHtml5Validity();
},
onFocus(event) {
this.isFocused = true;
if (this.parentField) {
this.parentField.isFocused = true;
}
this.$emit("focus", event);
},
getElement() {
let el = this.$refs[this.$elementRef];
while (el && el.$elementRef) {
el = el.$refs[el.$elementRef];
}
return el;
},
setInvalid() {
const variant = "danger";
const message = this.validationMessage || this.getElement().validationMessage;
this.setValidity(variant, message);
},
setValidity(variant, message) {
this.$nextTick(() => {
if (this.parentField) {
// Set type only if not defined
if (!this.parentField.variant) {
this.parentField.newVariant = variant;
}
// Set message only if not defined
if (!this.parentField.message) {
this.parentField.newMessage = message;
}
}
});
},
/**
* Check HTML5 validation, set isValid property.
* If validation fail, send 'danger' type,
* and error message to parent if it's a Field.
*/
checkHtml5Validity() {
if (!this.useHtml5Validation)
return;
const el = this.getElement();
if (!el)
return;
if (!el.checkValidity()) {
this.setInvalid();
this.isValid = false;
}
else {
this.setValidity(null, null);
this.isValid = true;
}
return this.isValid;
},
syncFilled(value) {
if (this.parentField) {
this.parentField.isFilled = !!value;
}
}
}
});
/**
* Get user Input. Use with Field to access all functionalities
* @displayName Input
* @style _input.scss
*/
var script$1 = vue.defineComponent({
name: 'OInput',
components: {
[script.name]: script
},
mixins: [BaseComponentMixin, FormElementMixin],
configField: 'input',
inheritAttrs: false,
emits: ['update:modelValue', 'icon-click', 'icon-right-click'],
props: {
/** @model */
modelValue: [Number, String],
/** Native options to use in HTML5 validation */
autocomplete: String,
/**
* Input type, like native
* @values Any native input type, and textarea
*/
type: {
type: String,
default: 'text'
},
/**
* Vertical size of input, optional
* @values small, medium, large
*/
size: String,
/**
* Color of the control, optional
* @values primary, info, success, warning, danger, and any other custom color
*/
variant: String,
/**
* Adds the reveal password functionality
*/
passwordReveal: Boolean,
/**
* Makes the icon clickable
*/
iconClickable: Boolean,
/**
* Show character counter when maxlength prop is passed
*/
hasCounter: {
type: Boolean,
default: () => { return getValueByPath(getOptions(), 'input.counter', false); }
},
/**
* Automatically adjust height in textarea
*/
autosize: {
type: Boolean,
default: false
},
/**
* Icon name to be added on the right side
*/
iconRight: String,
/**
* Make the icon right clickable
*/
iconRightClickable: Boolean,
/** Variant of right icon */
iconRightVariant: String,
/** Add a button/icon to clear the inputed text */
clearable: {
type: Boolean,
default: () => { return getValueByPath(getOptions(), 'input.clearable', false); }
},
rootClass: [String, Function, Array],
expandedClass: [String, Function, Array],
iconLeftSpaceClass: [String, Function, Array],
iconRightSpaceClass: [String, Function, Array],
inputClass: [String, Function, Array],
roundedClass: [String, Function, Array],
iconLeftClass: [String, Function, Array],
iconRightClass: [String, Function, Array],
counterClass: [String, Function, Array],
sizeClass: [String, Function, Array],
variantClass: [String, Function, Array]
},
data() {
return {
newValue: this.modelValue,
newType: this.type,
// from mixin (ts workaround)
newAutocomplete: this.autocomplete || getValueByPath(getOptions(), 'input.autocompletete', 'off'),
isPasswordVisible: false,
height: 'auto'
};
},
computed: {
rootClasses() {
return [
this.computedClass('rootClass', 'o-ctrl-input'),
{ [this.computedClass('expandedClass', 'o-ctrl-input--expanded')]: this.expanded }
];
},
inputClasses() {
return [
this.computedClass('inputClass', 'o-input'),
{ [this.computedClass('roundedClass', 'o-input--rounded')]: this.rounded },
{ [this.computedClass('sizeClass', 'o-input--', this.size)]: this.size },
{ [this.computedClass('variantClass', 'o-input--', (this.statusVariant || this.variant))]: (this.statusVariant || this.variant) },
{ [this.computedClass('textareaClass', 'o-input__textarea')]: this.type === 'textarea' },
{ [this.computedClass('iconLeftSpaceClass', 'o-input-iconspace-left')]: this.icon },
{ [this.computedClass('iconRightSpaceClass', 'o-input-iconspace-right')]: this.hasIconRight }
];
},
iconLeftClasses() {
return [
this.computedClass('iconLeftClass', 'o-input__icon-left')
];
},
iconRightClasses() {
return [
this.computedClass('iconRightClass', 'o-input__icon-right')
];
},
counterClasses() {
return [
this.computedClass('counterClass', 'o-input__counter')
];
},
computedValue: {
get() {
return this.newValue;
},
set(value) {
this.newValue = value;
this.$emit('update:modelValue', this.newValue);
this.syncFilled(this.newValue);
!this.isValid && this.checkHtml5Validity();
}
},
hasIconRight() {
return this.passwordReveal
|| (this.statusIcon && this.statusVariantIcon)
|| (this.clearable && this.newValue)
|| this.iconRight;
},
rightIcon() {
if (this.passwordReveal) {
return this.passwordVisibleIcon;
}
else if (this.clearable && this.newValue) {
return 'close-circle';
}
else if (this.iconRight) {
return this.iconRight;
}
return this.statusVariantIcon;
},
rightIconVariant() {
if (this.passwordReveal || this.iconRight) {
return this.iconRightVariant || this.variant || null;
}
return this.statusVariant;
},
/**
* Check if have any message prop from parent if it's a Field.
*/
hasMessage() {
return !!this.statusMessage;
},
/**
* Current password-reveal icon name.
*/
passwordVisibleIcon() {
return !this.isPasswordVisible ? 'eye' : 'eye-off';
},
/**
* Get value length
*/
valueLength() {
if (typeof this.computedValue === 'string') {
return this.computedValue.length;
}
else if (typeof this.computedValue === 'number') {
return this.computedValue.toString().length;
}
return 0;
},
/**
* Computed inline styles for autoresize
*/
computedStyles() {
if (!this.autosize)
return {};
return {
resize: 'none',
height: this.height,
overflow: 'hidden'
};
},
$elementRef() {
return this.type === 'textarea'
? 'textarea'
: 'input';
}
},
watch: {
/**
* When v-model is changed:
* 1. Set internal value.
*/
modelValue: {
immediate: true,
handler(value) {
this.newValue = value;
this.syncFilled(this.newValue);
if (this.autosize) {
this.resize();
}
}
}
},
methods: {
/**
* Toggle the visibility of a password-reveal input
* by changing the type and focus the input right away.
*/
togglePasswordVisibility() {
this.isPasswordVisible = !this.isPasswordVisible;
this.newType = this.isPasswordVisible ? 'text' : 'password';
this.$nextTick(() => {
this.focus();
});
},
iconClick(emit, event) {
this.$emit(emit, event);
this.$nextTick(() => {
this.focus();
});
},
rightIconClick(event) {
if (this.passwordReveal) {
this.togglePasswordVisibility();
}
else if (this.clearable) {
this.computedValue = '';
}
else if (this.iconRightClickable) {
this.iconClick('icon-right-click', event);
}
},
resize() {
this.height = 'auto';
this.$nextTick(() => {
let scrollHeight = this.$refs.textarea.scrollHeight;
this.height = scrollHeight + 'px';
});
}
}
});
function render$1(_ctx, _cache, $props, $setup, $data, $options) {
const _component_o_icon = vue.resolveComponent("o-icon");
return vue.openBlock(), vue.createBlock("div", {
class: _ctx.rootClasses
}, [_ctx.type !== 'textarea' ? (vue.openBlock(), vue.createBlock("input", vue.mergeProps({
key: 0
}, _ctx.$attrs, {
ref: "input",
class: _ctx.inputClasses,
type: _ctx.newType,
autocomplete: _ctx.newAutocomplete,
maxlength: _ctx.maxlength,
value: _ctx.computedValue,
onInput: _cache[1] || (_cache[1] = $event => _ctx.computedValue = $event.target.value),
onBlur: _cache[2] || (_cache[2] = (...args) => _ctx.onBlur(...args)),
onFocus: _cache[3] || (_cache[3] = (...args) => _ctx.onFocus(...args))
}), null, 16
/* FULL_PROPS */
, ["type", "autocomplete", "maxlength", "value"])) : (vue.openBlock(), vue.createBlock("textarea", vue.mergeProps({
key: 1
}, _ctx.$attrs, {
ref: "textarea",
class: _ctx.inputClasses,
maxlength: _ctx.maxlength,
value: _ctx.computedValue,
onInput: _cache[4] || (_cache[4] = $event => _ctx.computedValue = $event.target.value),
onBlur: _cache[5] || (_cache[5] = (...args) => _ctx.onBlur(...args)),
onFocus: _cache[6] || (_cache[6] = (...args) => _ctx.onFocus(...args)),
style: _ctx.computedStyles
}), null, 16
/* FULL_PROPS */
, ["maxlength", "value"])), _ctx.icon ? vue.createVNode(_component_o_icon, {
key: 2,
class: _ctx.iconLeftClasses,
clickable: _ctx.iconClickable,
icon: _ctx.icon,
pack: _ctx.iconPack,
size: _ctx.size,
onClick: _cache[7] || (_cache[7] = $event => _ctx.iconClick('icon-click', $event))
}, null, 8
/* PROPS */
, ["class", "clickable", "icon", "pack", "size"]) : vue.createCommentVNode("v-if", true), _ctx.hasIconRight ? vue.createVNode(_component_o_icon, {
key: 3,
class: _ctx.iconRightClasses,
clickable: _ctx.passwordReveal || _ctx.clearable || _ctx.iconRightClickable,
icon: _ctx.rightIcon,
pack: _ctx.iconPack,
size: _ctx.size,
variant: _ctx.rightIconVariant,
both: "",
onClick: _ctx.rightIconClick
}, null, 8
/* PROPS */
, ["class", "clickable", "icon", "pack", "size", "variant", "onClick"]) : vue.createCommentVNode("v-if", true), _ctx.maxlength && _ctx.hasCounter && _ctx.isFocused && _ctx.type !== 'number' ? (vue.openBlock(), vue.createBlock("small", {
key: 4,
class: _ctx.counterClasses
}, vue.toDisplayString(_ctx.valueLength) + " / " + vue.toDisplayString(_ctx.maxlength), 3
/* TEXT, CLASS */
)) : vue.createCommentVNode("v-if", true)], 2
/* CLASS */
);
}
script$1.render = render$1;
script$1.__file = "src/components/input/Input.vue";
/**
* Extended input that provide suggestions while the user types
* @displayName Autocomplete
* @style _autocomplete.scss
*/
var script$2 = vue.defineComponent({
name: 'OAutocomplete',
configField: 'autocomplete',
components: {
[script$1.name]: script$1
},
mixins: [BaseComponentMixin, FormElementMixin],
inheritAttrs: false,
emits: ['update:modelValue', 'select', 'infinite-scroll', 'typing', 'focus', 'blur', 'icon-click', 'icon-right-click'],
props: {
/** @model */
modelValue: [Number, String],
/** Options / suggestions */
data: {
type: Array,
default: () => []
},
/** Native options to use in HTML5 validation */
autocomplete: String,
/**
* Vertical size of input, optional
* @values small, medium, large
*/
size: String,
/** Property of the object (if data is array of objects) to use as display text, and to keep track of selected option */
field: {
type: String,
default: 'value'
},
/** The first option will always be pre-selected (easier to just hit enter or tab) */
keepFirst: Boolean,
/** Clear input text on select */
clearOnSelect: Boolean,
/** Open dropdown list on focus */
openOnFocus: Boolean,
/** Function to format an option to a string for display in the input as alternative to field prop) */
customFormatter: Function,
/** Makes the component check if list reached scroll end and emit infinite-scroll event. */
checkInfiniteScroll: Boolean,
/** Keep open dropdown list after select */
keepOpen: Boolean,
/** Add a button/icon to clear the inputed text */
clearable: Boolean,
/** Max height of dropdown content */
maxHeight: [String, Number],
/**
* Position of dropdown
* @values auto, top, bottom
*/
menuPosition: {
type: String,
default: 'auto'
},
/** Transition name to apply on dropdown list */
animation: {
type: String,
default: () => {
return getValueByPath(getOptions(), 'autocomplete.animation', 'fade');
}
},
/** Property of the object (if <code>data</code> is array of objects) to use as display text of group */
groupField: String,
/** Property of the object (if <code>data</code> is array of objects) to use as key to get items array of each group, optional */
groupOptions: String,
/** Number of milliseconds to delay before to emit typing event */
debounceTyping: Number,
/** Icon name to be added on the right side */
iconRight: String,
/** Clickable icon right if exists */
iconRightClickable: Boolean,
/** Append autocomplete content to body */
appendToBody: Boolean,
/** Array of keys (https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values) which will add a tag when typing (default tab and enter) */
confirmKeys: {
type: Array,
default: () => ['Tab', 'Enter']
},
/** Input type */
type: {
type: String,
default: 'text'
},
/**
* Menu tag name
*/
menuTag: {
type: String,
default: () => {
return getValueByPath(getOptions(), 'autocomplete.menuTag', 'div');
}
},
/**
* Menu item tag name
*/
itemTag: {
type: String,
default: () => {
return getValueByPath(getOptions(), 'autocomplete.itemTag', 'div');
}
},
/** Trigger the select event for the first pre-selected option when clicking outside and <code>keep-first</code> is enabled */
selectOnClickOutside: Boolean,
/** Allows the header in the autocomplete to be selectable */
selectableHeader: Boolean,
/** Allows the footer in the autocomplete to be selectable */
selectableFooter: Boolean,
rootClass: [String, Function, Array],
menuClass: [String, Function, Array],
expandedClass: [String, Function, Array],
menuPositionClass: [String, Function, Array],
itemClass: [String, Function, Array],
itemHoverClass: [String, Function, Array],
itemGroupTitleClass: [String, Function, Array],
itemEmptyClass: [String, Function, Array],
itemHeaderClass: [String, Function, Array],
itemFooterClass: [String, Function, Array],
inputClasses: {
type: Object,
default: () => {
return getValueByPath(getOptions(), 'autocomplete.inputClasses', {});
}
}
},
data() {
return {
selected: null,
hovered: null,
headerHovered: null,
footerHovered: null,
isActive: false,
newValue: this.modelValue,
ariaAutocomplete: this.keepFirst ? 'both' : 'list',
newAutocomplete: this.autocomplete || 'off',
isListInViewportVertically: true,
hasFocus: false,
itemRefs: [],
width: undefined,
bodyEl: undefined,
};
},
computed: {
rootClasses() {
return [
this.computedClass('rootClass', 'o-acp'),
{ [this.computedClass('expandedClass', 'o-acp--expanded')]: this.expanded }
];
},
menuClasses() {
return [
this.computedClass('menuClass', 'o-acp__menu'),
{ [this.computedClass('menuPositionClass', 'o-acp__menu--', this.newDropdownPosition)]: !this.appendToBody },
];
},
itemClasses() {
return [
this.computedClass('itemClass', 'o-acp__item')
];
},
itemEmptyClasses() {
return [
...this.itemClasses,
this.computedClass('itemEmptyClass', 'o-acp__item--empty')
];
},
itemGroupClasses() {
return [
...this.itemClasses,
this.computedClass('itemGroupTitleClass', 'o-acp__item-group-title')
];
},
itemHeaderClasses() {
return [
...this.itemClasses,
this.computedClass('itemHeaderClass', 'o-acp__item-header'),
{ [this.computedClass('itemHoverClass', 'o-acp__item--hover')]: this.headerHovered }
];
},
itemFooterClasses() {
return [
...this.itemClasses,
this.computedClass('itemFooterClass', 'o-acp__item-footer'),
{ [this.computedClass('itemHoverClass', 'o-acp__item--hover')]: this.footerHovered }
];
},
inputBind() {
return {
...this.$attrs,
...this.inputClasses
};
},
computedData() {
if (this.groupField) {
if (this.groupOptions) {
const newData = [];
this.data.forEach((option) => {
const group = getValueByPath(option, this.groupField);
const items = getValueByPath(option, this.groupOptions);
newData.push({ group, items });
});
return newData;
}
else {
const tmp = {};
this.data.forEach((option) => {
const group = getValueByPath(option, this.groupField);
if (!tmp[group])
tmp[group] = [];
tmp[group].push(option);
});
const newData = [];
Object.keys(this.data).forEach((group) => {
newData.push({ group, items: this.data[group] });
});