function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); enumerableOnly && (symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; })), keys.push.apply(keys, symbols); } return keys; } function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = null != arguments[i] ? arguments[i] : {}; i % 2 ? ownKeys(Object(source), !0).forEach(function (key) { _defineProperty(target, key, source[key]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } return target; } function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } import { extend } from '../vue'; import { EVENT_NAME_BLUR, EVENT_NAME_CHANGE, EVENT_NAME_INPUT, EVENT_NAME_UPDATE } from '../constants/events'; import { PROP_TYPE_BOOLEAN, PROP_TYPE_BOOLEAN_STRING, PROP_TYPE_FUNCTION, PROP_TYPE_NUMBER_STRING, PROP_TYPE_STRING } from '../constants/props'; import { attemptBlur, attemptFocus } from '../utils/dom'; import { stopEvent } from '../utils/events'; import { mathMax } from '../utils/math'; import { makeModelMixin } from '../utils/model'; import { toInteger, toFloat } from '../utils/number'; import { sortKeys } from '../utils/object'; import { hasPropFunction, makeProp, makePropsConfigurable } from '../utils/props'; import { toString } from '../utils/string'; // --- Constants --- var _makeModelMixin = makeModelMixin('value', { type: PROP_TYPE_NUMBER_STRING, defaultValue: '', event: EVENT_NAME_UPDATE }), modelMixin = _makeModelMixin.mixin, modelProps = _makeModelMixin.props, MODEL_PROP_NAME = _makeModelMixin.prop, MODEL_EVENT_NAME = _makeModelMixin.event; export { MODEL_PROP_NAME, MODEL_EVENT_NAME }; // --- Props --- export var props = makePropsConfigurable(sortKeys(_objectSpread(_objectSpread({}, modelProps), {}, { ariaInvalid: makeProp(PROP_TYPE_BOOLEAN_STRING, false), autocomplete: makeProp(PROP_TYPE_STRING), // Debounce timeout (in ms). Not applicable with `lazy` prop debounce: makeProp(PROP_TYPE_NUMBER_STRING, 0), formatter: makeProp(PROP_TYPE_FUNCTION), // Only update the `v-model` on blur/change events lazy: makeProp(PROP_TYPE_BOOLEAN, false), lazyFormatter: makeProp(PROP_TYPE_BOOLEAN, false), number: makeProp(PROP_TYPE_BOOLEAN, false), placeholder: makeProp(PROP_TYPE_STRING), plaintext: makeProp(PROP_TYPE_BOOLEAN, false), readonly: makeProp(PROP_TYPE_BOOLEAN, false), trim: makeProp(PROP_TYPE_BOOLEAN, false) })), 'formTextControls'); // --- Mixin --- // @vue/component export var formTextMixin = extend({ mixins: [modelMixin], props: props, data: function data() { var value = this[MODEL_PROP_NAME]; return { localValue: toString(value), vModelValue: this.modifyValue(value) }; }, computed: { computedClass: function computedClass() { var plaintext = this.plaintext, type = this.type; var isRange = type === 'range'; var isColor = type === 'color'; return [{ // Range input needs class `custom-range` 'custom-range': isRange, // `plaintext` not supported by `type="range"` or `type="color"` 'form-control-plaintext': plaintext && !isRange && !isColor, // `form-control` not used by `type="range"` or `plaintext` // Always used by `type="color"` 'form-control': isColor || !plaintext && !isRange }, this.sizeFormClass, this.stateClass]; }, computedDebounce: function computedDebounce() { // Ensure we have a positive number equal to or greater than 0 return mathMax(toInteger(this.debounce, 0), 0); }, hasFormatter: function hasFormatter() { return hasPropFunction(this.formatter); } }, watch: _defineProperty({}, MODEL_PROP_NAME, function (newValue) { var stringifyValue = toString(newValue); var modifiedValue = this.modifyValue(newValue); if (stringifyValue !== this.localValue || modifiedValue !== 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 = modifiedValue; } }), created: function created() { // Create private non-reactive props this.$_inputDebounceTimer = null; }, beforeDestroy: function beforeDestroy() { this.clearDebounce(); }, methods: { clearDebounce: function clearDebounce() { clearTimeout(this.$_inputDebounceTimer); this.$_inputDebounceTimer = null; }, formatValue: function formatValue(value, event) { var force = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false; value = toString(value); if (this.hasFormatter && (!this.lazyFormatter || force)) { value = this.formatter(value, event); } return value; }, modifyValue: function modifyValue(value) { value = toString(value); // Emulate `.trim` modifier behaviour if (this.trim) { value = value.trim(); } // Emulate `.number` modifier behaviour if (this.number) { value = toFloat(value, value); } 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; } // Make sure to always clear the debounce when `updateValue()` // is called, even when the v-model hasn't changed this.clearDebounce(); // Define the shared update logic in a method to be able to use // it for immediate and debounced value changes var doUpdate = function doUpdate() { value = _this.modifyValue(value); if (value !== _this.vModelValue) { _this.vModelValue = value; _this.$emit(MODEL_EVENT_NAME, value); } 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; } } }; // 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 var debounce = this.computedDebounce; if (debounce > 0 && !lazy && !force) { this.$_inputDebounceTimer = setTimeout(doUpdate, debounce); } else { // Immediately update the v-model doUpdate(); } }, onInput: function onInput(event) { // `event.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 (event.target.composing) { return; } var value = event.target.value; var formattedValue = this.formatValue(value, event); // Exit when the `formatter` function strictly returned `false` // or prevented the input event /* istanbul ignore next */ if (formattedValue === false || event.defaultPrevented) { stopEvent(event, { propagation: false }); return; } this.localValue = formattedValue; this.updateValue(formattedValue); this.$emit(EVENT_NAME_INPUT, formattedValue); }, onChange: function onChange(event) { var value = event.target.value; var formattedValue = this.formatValue(value, event); // Exit when the `formatter` function strictly returned `false` // or prevented the input event /* istanbul ignore next */ if (formattedValue === false || event.defaultPrevented) { stopEvent(event, { propagation: false }); return; } this.localValue = formattedValue; this.updateValue(formattedValue, true); this.$emit(EVENT_NAME_CHANGE, formattedValue); }, onBlur: function onBlur(event) { // Apply the `localValue` on blur to prevent cursor jumps // on mobile browsers (e.g. caused by autocomplete) var value = event.target.value; var formattedValue = this.formatValue(value, event, 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(EVENT_NAME_BLUR, event); }, focus: function focus() { // For external handler that may want a focus method if (!this.disabled) { attemptFocus(this.$el); } }, blur: function blur() { // For external handler that may want a blur method if (!this.disabled) { attemptBlur(this.$el); } } } });