bootstrap-vue
Version:
BootstrapVue, with over 40 plugins and more than 80 custom components, custom directives, and over 300 icons, provides one of the most comprehensive implementations of Bootstrap v4 components and grid system for Vue.js. With extensive and automated WAI-AR
277 lines (242 loc) • 8.36 kB
JavaScript
import { isFunction } from '../utils/inspect';
import { toInteger, toFloat } from '../utils/number';
import { toString } from '../utils/string'; // @vue/component
export default {
model: {
prop: 'value',
event: 'update'
},
props: {
value: {
type: [String, Number],
default: ''
},
ariaInvalid: {
type: [Boolean, String],
default: false
},
readonly: {
type: Boolean,
default: false
},
plaintext: {
type: Boolean,
default: false
},
autocomplete: {
type: String,
default: null
},
placeholder: {
type: String,
default: null
},
formatter: {
type: Function,
default: null
},
lazyFormatter: {
type: Boolean,
default: false
},
trim: {
type: Boolean,
default: false
},
number: {
type: Boolean,
default: false
},
lazy: {
// Only update the `v-model` on blur/change events
type: Boolean,
default: false
},
debounce: {
// Debounce timout (in ms). Not applicable with `lazy` prop
type: [Number, String],
default: 0
}
},
data: function data() {
return {
localValue: toString(this.value),
vModelValue: this.value
};
},
computed: {
computedClass: function computedClass() {
return [{
// Range input needs class `custom-range`
'custom-range': this.type === 'range',
// `plaintext` not supported by `type="range"` or `type="color"`
'form-control-plaintext': this.plaintext && this.type !== 'range' && this.type !== 'color',
// `form-control` not used by `type="range"` or `plaintext`
// Always used by `type="color"`
'form-control': !this.plaintext && this.type !== 'range' || this.type === 'color'
}, this.sizeFormClass, this.stateClass];
},
computedAriaInvalid: function computedAriaInvalid() {
if (!this.ariaInvalid || this.ariaInvalid === 'false') {
// `this.ariaInvalid` is `null` or `false` or 'false'
return this.computedState === false ? 'true' : null;
}
if (this.ariaInvalid === true) {
// User wants explicit `:aria-invalid="true"`
return 'true';
} // Most likely a string value (which could be the string 'true')
return this.ariaInvalid;
},
computedDebounce: function computedDebounce() {
// Ensure we have a positive number equal to or greater than 0
return Math.max(toInteger(this.debounce) || 0, 0);
},
hasFormatter: function hasFormatter() {
return isFunction(this.formatter);
}
},
watch: {
value: function value(newVal) {
var stringifyValue = toString(newVal);
if (stringifyValue !== this.localValue && newVal !== this.vModelValue) {
// Clear any pending debounce timeout, as we are overwriting the user input
this.clearDebounce(); // Update the local values
this.localValue = stringifyValue;
this.vModelValue = newVal;
}
}
},
mounted: function mounted() {
// Create non-reactive property and set up destroy handler
this.$_inputDebounceTimer = null;
this.$on('hook:beforeDestroy', this.clearDebounce); // Preset the internal state
var value = this.value;
var stringifyValue = toString(value);
/* istanbul ignore next */
if (stringifyValue !== this.localValue && value !== this.vModelValue) {
this.localValue = stringifyValue;
this.vModelValue = value;
}
},
methods: {
clearDebounce: function clearDebounce() {
clearTimeout(this.$_inputDebounceTimer);
this.$_inputDebounceTimer = null;
},
formatValue: function formatValue(value, evt) {
var force = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
value = toString(value);
if (this.hasFormatter && (!this.lazyFormatter || force)) {
value = this.formatter(value, evt);
}
return value;
},
modifyValue: function modifyValue(value) {
// Emulate `.trim` modifier behaviour
if (this.trim) {
value = value.trim();
} // Emulate `.number` modifier behaviour
if (this.number) {
var number = toFloat(value);
value = isNaN(number) ? value : number;
}
return value;
},
updateValue: function updateValue(value) {
var _this = this;
var force = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
var lazy = this.lazy;
if (lazy && !force) {
return;
}
value = this.modifyValue(value);
if (value !== this.vModelValue) {
this.clearDebounce();
var doUpdate = function doUpdate() {
_this.vModelValue = value;
_this.$emit('update', value);
};
var debounce = this.computedDebounce; // Only debounce the value update when a value greater than `0`
// is set and we are not in lazy mode or this is a forced update
if (debounce > 0 && !lazy && !force) {
this.$_inputDebounceTimer = setTimeout(doUpdate, debounce);
} else {
// Immediately update the v-model
doUpdate();
}
} else if (this.hasFormatter) {
// When the `vModelValue` hasn't changed but the actual input value
// is out of sync, make sure to change it to the given one
// Usually caused by browser autocomplete and how it triggers the
// change or input event, or depending on the formatter function
// https://github.com/bootstrap-vue/bootstrap-vue/issues/2657
// https://github.com/bootstrap-vue/bootstrap-vue/issues/3498
/* istanbul ignore next: hard to test */
var $input = this.$refs.input;
/* istanbul ignore if: hard to test out of sync value */
if ($input && value !== $input.value) {
$input.value = value;
}
}
},
onInput: function onInput(evt) {
// `evt.target.composing` is set by Vue
// https://github.com/vuejs/vue/blob/dev/src/platforms/web/runtime/directives/model.js
// TODO: Is this needed now with the latest Vue?
/* istanbul ignore if: hard to test composition events */
if (evt.target.composing) {
return;
}
var value = evt.target.value;
var formattedValue = this.formatValue(value, evt); // Exit when the `formatter` function strictly returned `false`
// or prevented the input event
/* istanbul ignore next */
if (formattedValue === false || evt.defaultPrevented) {
evt.preventDefault();
return;
}
this.localValue = formattedValue;
this.updateValue(formattedValue);
this.$emit('input', formattedValue);
},
onChange: function onChange(evt) {
var value = evt.target.value;
var formattedValue = this.formatValue(value, evt); // Exit when the `formatter` function strictly returned `false`
// or prevented the input event
/* istanbul ignore next */
if (formattedValue === false || evt.defaultPrevented) {
evt.preventDefault();
return;
}
this.localValue = formattedValue;
this.updateValue(formattedValue, true);
this.$emit('change', formattedValue);
},
onBlur: function onBlur(evt) {
// Apply the `localValue` on blur to prevent cursor jumps
// on mobile browsers (e.g. caused by autocomplete)
var value = evt.target.value;
var formattedValue = this.formatValue(value, evt, true);
if (formattedValue !== false) {
// We need to use the modified value here to apply the
// `.trim` and `.number` modifiers properly
this.localValue = toString(this.modifyValue(formattedValue)); // We pass the formatted value here since the `updateValue` method
// handles the modifiers itself
this.updateValue(formattedValue, true);
} // Emit native blur event
this.$emit('blur', evt);
},
focus: function focus() {
// For external handler that may want a focus method
if (!this.disabled) {
this.$el.focus();
}
},
blur: function blur() {
// For external handler that may want a blur method
if (!this.disabled) {
this.$el.blur();
}
}
}
};