nice-numeric-input
Version:
A Vue input component for numbers with realtime formating and currency support.
640 lines (590 loc) • 25.9 kB
JavaScript
'use strict';var Vue=require('vue');function _interopDefaultLegacy(e){return e&&typeof e==='object'&&'default'in e?e:{'default':e}}var Vue__default=/*#__PURE__*/_interopDefaultLegacy(Vue);function _slicedToArray(arr, i) {
return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest();
}
function _arrayWithHoles(arr) {
if (Array.isArray(arr)) return arr;
}
function _iterableToArrayLimit(arr, i) {
var _i = arr == null ? null : typeof Symbol !== "undefined" && arr[Symbol.iterator] || arr["@@iterator"];
if (_i == null) return;
var _arr = [];
var _n = true;
var _d = false;
var _s, _e;
try {
for (_i = _i.call(arr); !(_n = (_s = _i.next()).done); _n = true) {
_arr.push(_s.value);
if (i && _arr.length === i) break;
}
} catch (err) {
_d = true;
_e = err;
} finally {
try {
if (!_n && _i["return"] != null) _i["return"]();
} finally {
if (_d) throw _e;
}
}
return _arr;
}
function _unsupportedIterableToArray(o, minLen) {
if (!o) return;
if (typeof o === "string") return _arrayLikeToArray(o, minLen);
var n = Object.prototype.toString.call(o).slice(8, -1);
if (n === "Object" && o.constructor) n = o.constructor.name;
if (n === "Map" || n === "Set") return Array.from(o);
if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen);
}
function _arrayLikeToArray(arr, len) {
if (len == null || len > arr.length) len = arr.length;
for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i];
return arr2;
}
function _nonIterableRest() {
throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
}var script = Vue__default['default'].extend({
props: {
value: {
type: Number,
default: 0
},
id: {
type: String,
default: "nice-numeric-input"
},
name: {
type: String,
default: "nice-numeric-input"
},
label: {
type: String,
required: true
},
placeholder: {
type: String,
default: ""
},
step: {
type: Number,
default: 1
},
min: {
type: Number,
default: Number.NEGATIVE_INFINITY
},
max: {
type: Number,
default: Number.POSITIVE_INFINITY
},
isValid: {
type: Boolean,
default: false,
required: false
},
disabled: {
type: Boolean,
default: false
},
locale: {
type: String,
default: null
},
currency: {
type: String,
default: null
},
minDecimalPlaces: {
type: Number,
default: 0
},
maxDecimalPlaces: {
type: Number,
default: 2
},
integerOnly: {
type: Boolean,
default: false
},
noControls: {
type: Boolean,
default: false
},
hideLabel: {
type: Boolean,
default: false
},
decreaseTitle: {
type: String,
default: "Increase"
},
increaseTitle: {
type: String,
default: "Decrease"
},
increaseText: {
type: String,
default: "+"
},
decreaseText: {
type: String,
default: "-"
},
superIncreaseText: {
type: String,
default: "++"
},
superDecreaseText: {
type: String,
default: "--"
},
ultraIncreaseText: {
type: String,
default: "+++"
},
ultraDecreaseText: {
type: String,
default: "---"
},
superStep: {
type: Number,
default: 10
},
ultraStep: {
type: Number,
default: 100
},
labelClass: {
type: String,
default: null
},
inputClass: {
type: String,
default: null
},
decreaseButtonClass: {
type: String,
default: null
},
increaseButtonClass: {
type: String,
default: null
},
wrapperClass: {
type: String,
default: null
},
superStepClass: {
type: String,
default: ""
},
ultraStepClass: {
type: String,
default: ""
}
},
data: function data() {
return {
internalValue: null,
ctrlActive: false,
shiftActive: false,
internalLocale: "en-US"
};
},
computed: {
labelId: function labelId() {
return this.id + '-label';
},
canIncrease: function canIncrease() {
return this.internalValueIsNotDefined || this.internalValue + this.internalStep <= this.max;
},
canDecrease: function canDecrease() {
return this.internalValueIsNotDefined || this.internalValue - this.internalStep >= this.min;
},
displayString: function displayString() {
if (this.internalValueIsNotDefined) {
if (this.value) {
this.setInternalValue(this.value);
} else {
this.setToDefaultValue();
}
}
var minDecimals = 0,
maxDecimals = 0;
if (!this.integerOnly) {
minDecimals = this.minDecimalPlaces;
maxDecimals = this.maxDecimalPlaces;
}
return this.internalValue.toLocaleString(this.internalLocale, {
style: this.currency ? "currency" : "decimal",
currency: this.currency || undefined,
minimumFractionDigits: minDecimals,
maximumFractionDigits: maxDecimals
});
},
internalValueIsNotDefined: function internalValueIsNotDefined() {
return this.internalValue == null || Number.isNaN(this.internalValue);
},
isValidComputed: {
get: function get() {
return this.isValid;
},
set: function set(val) {
this.$emit('update:isValid', val);
}
},
isError: function isError() {
var error = false;
if (this.internalValue == null || this.internalValue > this.max || this.internalValue < this.min) {
error = true;
}
this.isValidComputed = !error;
return error;
},
isUltraChangeActive: function isUltraChangeActive() {
return this.ctrlActive && this.shiftActive;
},
isSuperChangeActive: function isSuperChangeActive() {
// Equivalent of ctrlActive XOR shiftActive for booleans.
return this.ctrlActive != this.shiftActive;
},
internalIncreaseText: function internalIncreaseText() {
if (this.isUltraChangeActive) {
return this.ultraIncreaseText;
} else if (this.isSuperChangeActive) {
return this.superIncreaseText;
} else {
return this.increaseText;
}
},
internalDecreaseText: function internalDecreaseText() {
if (this.isUltraChangeActive) {
return this.ultraDecreaseText;
} else if (this.isSuperChangeActive) {
return this.superDecreaseText;
} else {
return this.decreaseText;
}
},
changeButtonClass: function changeButtonClass() {
if (this.isUltraChangeActive) {
return this.ultraStepClass || "much-smaller-padding";
} else if (this.isSuperChangeActive) {
return this.superStepClass || "smaller-padding";
} else {
return "";
}
},
internalStep: function internalStep() {
if (this.isUltraChangeActive) {
return this.ultraStep;
} else if (this.isSuperChangeActive) {
return this.superStep;
} else {
return this.step;
}
}
},
methods: {
getDefaultValue: function getDefaultValue() {
if (this.min != Number.NEGATIVE_INFINITY) {
return this.min;
}
return 0;
},
handlePaste: function handlePaste(e) {
var clipboardData = e.clipboardData || window.clipboardData;
var pastedData = clipboardData.getData("Text");
if (pastedData) {
e.stopPropagation();
e.preventDefault();
this.valueChanged(pastedData, null);
}
},
handleInput: function handleInput(e) {
var target = e.target;
this.valueChanged(target.value, e.data);
},
handleChange: function handleChange(e) {
var target = e.target;
this.valueChanged(target.value, null, true);
},
valueChanged: function valueChanged(newValue, newInput) {
var strictValidation = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
var possibleRecurse = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : true;
var decimalNumbersRegex = /[+-]?\d+(\.\d+)?/g;
var normalisedInput = this.normaliseInput(newValue, this.internalLocale); // Match to find any numbers.
var matches = normalisedInput.match(decimalNumbersRegex);
var result = null;
var isValidNonNumeric = false;
if (!strictValidation && (this.isEmptyInput(newValue) || this.isStartingSignedInput(newValue) || this.isAddingDecimalPlaces(newValue))) {
isValidNonNumeric = true;
} else if (matches != null && matches.length > 0) {
// Parse the first match.
result = parseFloat(matches[0]);
} else {
if (possibleRecurse && newValue.length > 0) {
this.valueChanged("".concat(newValue[0]), newInput, strictValidation, false);
return;
} // Manually clear the invalid input to cover edge cases where computed properties don't update because the internal value hasn't changed value.
this.$refs.numberInput.value = null;
} // Don't reset to 0 when we have a valid non-numeric edge case.
if (!isValidNonNumeric) {
if (this.integerOnly) {
result = Math.round(result);
}
if (newInput === "-") {
result = -result;
} else if (newInput === "+") {
result = Math.abs(result);
}
this.setInternalValue(result);
}
},
normaliseInput: function normaliseInput(value, locale) {
var example = Intl.NumberFormat(locale).format(1.1);
var cleanRegExp = new RegExp("[^-+0-9".concat(example.charAt(1), "]"), "g");
var cleanValue = value.replace(cleanRegExp, "");
var normalised = cleanValue.replace(example.charAt(1), ".");
return normalised;
},
isStartingSignedInput: function isStartingSignedInput(input) {
return input.length === 1 && (input === "+" || input === "-");
},
isEmptyInput: function isEmptyInput(input) {
return input.length === 0;
},
isAddingDecimalPlaces: function isAddingDecimalPlaces(input) {
return input.endsWith(".") || input.endsWith(",") || input.endsWith(" ");
},
increase: function increase() {
if (this.canIncrease) {
if (this.internalValueIsNotDefined) {
this.setToDefaultValue();
}
this.setInternalValue(this.internalValue + this.internalStep);
}
},
decrease: function decrease() {
if (this.canDecrease) {
if (this.internalValueIsNotDefined) {
this.setToDefaultValue();
}
this.setInternalValue(this.internalValue - this.internalStep);
}
},
setToDefaultValue: function setToDefaultValue() {
var newVal = 0;
if (this.min != Number.NEGATIVE_INFINITY) {
newVal = this.min;
}
this.setInternalValue(newVal);
},
setInternalValue: function setInternalValue(val) {
// Wipe out the value to force an update even if the value hasn't changed - ensures extra characters that don't affect the parsed value are removed from display.
this.internalValue = null;
this.internalValue = val;
this.$emit('input', this.internalValue);
},
keychange: function keychange(e) {
this.ctrlActive = e.ctrlKey;
this.shiftActive = e.shiftKey;
}
},
created: function created() {
if (this.locale === null) {
if (typeof window !== 'undefined' && window) {
this.internalLocale = window.navigator.language;
document.addEventListener("keydown", this.keychange);
document.addEventListener("keyup", this.keychange);
}
} else {
this.internalLocale = this.locale;
}
if (Vue__default['default'].config.devtools) {
// Validate props that depend on each other in development mode.
if (this.min > this.max) {
console.error("nice-numeric-input Prop Error: Min [".concat(this.min, "] cannot be greater than Max [").concat(this.max, "]"));
}
if (this.$listeners && !this.$listeners.input) {
console.warn("nice-numeric-input Warning: There is no input event listener attached, use v-model or bind one directly to the input event.");
}
}
},
beforeDestroy: function beforeDestroy() {
document.removeEventListener('keydown', this.keychange);
document.removeEventListener('keyup', this.keychange);
},
watch: {
value: function value(newVal) {
this.internalValue = newVal;
}
}
});function normalizeComponent(template, style, script, scopeId, isFunctionalTemplate, moduleIdentifier /* server only */, shadowMode, createInjector, createInjectorSSR, createInjectorShadow) {
if (typeof shadowMode !== 'boolean') {
createInjectorSSR = createInjector;
createInjector = shadowMode;
shadowMode = false;
}
// Vue.extend constructor export interop.
const options = typeof script === 'function' ? script.options : script;
// render functions
if (template && template.render) {
options.render = template.render;
options.staticRenderFns = template.staticRenderFns;
options._compiled = true;
// functional template
if (isFunctionalTemplate) {
options.functional = true;
}
}
// scopedId
if (scopeId) {
options._scopeId = scopeId;
}
let hook;
if (moduleIdentifier) {
// server build
hook = function (context) {
// 2.3 injection
context =
context || // cached call
(this.$vnode && this.$vnode.ssrContext) || // stateful
(this.parent && this.parent.$vnode && this.parent.$vnode.ssrContext); // functional
// 2.2 with runInNewContext: true
if (!context && typeof __VUE_SSR_CONTEXT__ !== 'undefined') {
context = __VUE_SSR_CONTEXT__;
}
// inject component styles
if (style) {
style.call(this, createInjectorSSR(context));
}
// register component module identifier for async chunk inference
if (context && context._registeredComponents) {
context._registeredComponents.add(moduleIdentifier);
}
};
// used by ssr in case component is cached and beforeCreate
// never gets called
options._ssrRegister = hook;
}
else if (style) {
hook = shadowMode
? function (context) {
style.call(this, createInjectorShadow(context, this.$root.$options.shadowRoot));
}
: function (context) {
style.call(this, createInjector(context));
};
}
if (hook) {
if (options.functional) {
// register for functional component in vue file
const originalRender = options.render;
options.render = function renderWithStyleInjection(h, context) {
hook.call(context);
return originalRender(h, context);
};
}
else {
// inject component registration as beforeCreate hook
const existing = options.beforeCreate;
options.beforeCreate = existing ? [].concat(existing, hook) : [hook];
}
}
return script;
}function createInjectorSSR(context) {
if (!context && typeof __VUE_SSR_CONTEXT__ !== 'undefined') {
context = __VUE_SSR_CONTEXT__;
}
if (!context)
return () => { };
if (!('styles' in context)) {
context._styles = context._styles || {};
Object.defineProperty(context, 'styles', {
enumerable: true,
get: () => context._renderStyles(context._styles)
});
context._renderStyles = context._renderStyles || renderStyles;
}
return (id, style) => addStyle(id, style, context);
}
function addStyle(id, css, context) {
const group = css.media || 'default' ;
const style = context._styles[group] || (context._styles[group] = { ids: [], css: '' });
if (!style.ids.includes(id)) {
style.media = css.media;
style.ids.push(id);
let code = css.source;
style.css += code + '\n';
}
}
function renderStyles(styles) {
let css = '';
for (const key in styles) {
const style = styles[key];
css +=
'<style data-vue-ssr-id="' +
Array.from(style.ids).join(' ') +
'"' +
(style.media ? ' media="' + style.media + '"' : '') +
'>' +
style.css +
'</style>';
}
return css;
}/* script */
var __vue_script__ = script;
/* template */
var __vue_render__ = function __vue_render__() {
var _vm = this;
var _h = _vm.$createElement;
var _c = _vm._self._c || _h;
return _c('div', {
staticClass: "input-wrapper",
class: [_vm.noControls ? '' : 'controls', _vm.isError ? 'error' : '', _vm.wrapperClass]
}, [_vm._ssrNode((!_vm.hideLabel ? "<label" + _vm._ssrAttr("id", _vm.labelId) + _vm._ssrAttr("for", _vm.id) + _vm._ssrClass("input-label", _vm.labelClass) + " data-v-89fe9580>" + _vm._ssrEscape("\n " + _vm._s(_vm.label) + "\n ") + "</label>" : "<!---->") + " " + (!_vm.noControls ? "<button" + _vm._ssrAttr("disabled", _vm.disabled || !_vm.canDecrease) + _vm._ssrAttr("title", _vm.decreaseTitle) + _vm._ssrClass("left-control", [_vm.changeButtonClass, _vm.decreaseButtonClass]) + " data-v-89fe9580>" + _vm._ssrEscape("\n " + _vm._s(_vm.internalDecreaseText) + "\n ") + "</button>" : "<!---->") + " <input" + _vm._ssrAttr("id", _vm.id) + _vm._ssrAttr("name", _vm.name) + _vm._ssrAttr("disabled", _vm.disabled) + " type=\"text\"" + _vm._ssrAttr("placeholder", _vm.placeholder) + _vm._ssrAttr("aria-labelledby", !_vm.hideLabel ? _vm.labelId : false) + _vm._ssrAttr("aria-label", _vm.hideLabel ? _vm.label : false) + _vm._ssrAttr("value", _vm.displayString) + _vm._ssrClass(null, [_vm.noControls ? 'no-controls-input' : 'double-controls-input', _vm.inputClass]) + " data-v-89fe9580> " + (!_vm.noControls ? "<button" + _vm._ssrAttr("disabled", _vm.disabled || !_vm.canIncrease) + _vm._ssrAttr("title", _vm.increaseTitle) + _vm._ssrClass("right-control", [_vm.changeButtonClass, _vm.increaseButtonClass]) + " data-v-89fe9580>" + _vm._ssrEscape("\n " + _vm._s(_vm.internalIncreaseText) + "\n ") + "</button>" : "<!---->"))]);
};
var __vue_staticRenderFns__ = [];
/* style */
var __vue_inject_styles__ = function __vue_inject_styles__(inject) {
if (!inject) return;
inject("data-v-89fe9580_0", {
source: ".input-wrapper[data-v-89fe9580]{position:relative;font-weight:400;font-style:normal;display:-webkit-box;display:-ms-flexbox;display:flex;color:rgba(0,0,0,.9)}.input-wrapper>input[data-v-89fe9580]{width:100%;margin:0;max-width:100%;-webkit-box-flex:1;-ms-flex:1 0 auto;flex:1 0 auto;outline:0;-webkit-tap-highlight-color:transparent;text-align:left;line-height:1.2em;font-family:\"Helvetica Neue\",Arial,Helvetica,sans-serif;padding:.66em 1em;background:#fff;border:1px solid rgba(34,36,38,.2);color:rgba(0,0,0,.9);border-radius:.3rem;-webkit-transition:border-color .1s ease,-webkit-box-shadow .1s ease;transition:border-color .1s ease,-webkit-box-shadow .1s ease;transition:box-shadow .1s ease,border-color .1s ease;transition:box-shadow .1s ease,border-color .1s ease,-webkit-box-shadow .1s ease;-webkit-box-shadow:none;box-shadow:none}.input-wrapper input[disabled][data-v-89fe9580],.input-wrapper.disabled[data-v-89fe9580]{opacity:.4}.input-wrapper.disabled>input[data-v-89fe9580]{pointer-events:none}.input-wrapper>input[data-v-89fe9580]:active{border-color:rgba(0,0,0,.4);background:#fafafa}.input-wrapper>input[data-v-89fe9580]:focus{border-color:#85b7d9;background:#fff;color:rgba(0,0,0,.8)}.input-wrapper.error>input[data-v-89fe9580]{background-color:#ffd7d7;border-color:#dba8a8;color:#9b2d2b}.input-wrapper>input[data-v-89fe9580]::-webkit-input-placeholder{color:rgba(191,191,191,.87)}.input-wrapper>input[data-v-89fe9580]::-moz-placeholder{color:rgba(191,191,191,.87)}.input-wrapper>input[data-v-89fe9580]:-ms-input-placeholder{color:rgba(191,191,191,.87)}.input-wrapper.error>input[data-v-89fe9580]::-webkit-input-placeholder{color:#e7bdbc}.input-wrapper.error>input[data-v-89fe9580]::-moz-placeholder{color:#e7bdbc}.input-wrapper.error>input[data-v-89fe9580]::-ms-input-placeholder{color:#e7bdbc!important}.input-wrapper.error>input[data-v-89fe9580]:focus::-webkit-input-placeholder{color:#da9796}.input-wrapper.error>input[data-v-89fe9580]:focus::-moz-placeholder{color:#da9796}.input-wrapper.error>input[data-v-89fe9580]:focus::-ms-input-placeholder{color:#da9796!important}.input-label[data-v-89fe9580]{-webkit-box-flex:0;-ms-flex:0 0 auto;flex:0 0 auto;margin:0;font-size:1em;padding-right:1em;font-weight:700;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center}button[data-v-89fe9580]{cursor:pointer;display:inline-block;min-height:1em;outline:0;border:none;vertical-align:baseline;background:#e0e1e2 none;color:rgba(0,0,0,.7);font-family:\"Helvetica Neue\",Arial,Helvetica,sans-serif;margin:0 .25em 0 0;padding:.75em 1.5em .75em}button.smaller-padding[data-v-89fe9580]{padding:.75em 1.25em .75em}button.much-smaller-padding[data-v-89fe9580]{padding:.75em 1em .75em}button[data-v-89fe9580]:hover{background-color:#cacbcd;-webkit-box-shadow:0 0 0 1px transparent inset,0 0 0 0 rgba(34,36,38,.15) inset;box-shadow:0 0 0 1px transparent inset,0 0 0 0 rgba(34,36,38,.15) inset;color:rgba(0,0,0,.8)}button[data-v-89fe9580]:focus{background-color:#cacbcd;color:rgba(0,0,0,.8);background-image:\"\"!important;-webkit-box-shadow:\"\"!important;box-shadow:\"\"!important}button[data-v-89fe9580]:active{background-color:#babbbc;background-image:\"\";color:rgba(0,0,0,.9);-webkit-box-shadow:0 0 0 1px transparent inset,none;box-shadow:0 0 0 1px transparent inset,none}button[data-v-89fe9580]:disabled{cursor:default;opacity:.4!important;-webkit-box-shadow:none!important;box-shadow:none!important;pointer-events:none!important}.input-wrapper.controls>button[data-v-89fe9580]{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;-webkit-box-flex:0;-ms-flex:0 0 auto;flex:0 0 auto;margin:0;text-transform:none;text-shadow:none;font-weight:700;line-height:1em;font-style:normal;text-align:center;text-decoration:none;-webkit-box-shadow:0 0 0 1px transparent inset,0 0 0 0 rgba(34,36,38,.15) inset;box-shadow:0 0 0 1px transparent inset,0 0 0 0 rgba(34,36,38,.15) inset;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-transition:opacity .1s ease,background-color .1s ease,color .1s ease,background .1s ease,-webkit-box-shadow .1s ease;transition:opacity .1s ease,background-color .1s ease,color .1s ease,background .1s ease,-webkit-box-shadow .1s ease;transition:opacity .1s ease,background-color .1s ease,color .1s ease,box-shadow .1s ease,background .1s ease;transition:opacity .1s ease,background-color .1s ease,color .1s ease,box-shadow .1s ease,background .1s ease,-webkit-box-shadow .1s ease;-webkit-tap-highlight-color:transparent}button.left-control[data-v-89fe9580]{border-radius:3px 0 0 3px}button.right-control[data-v-89fe9580]{border-radius:0 3px 3px 0}.double-controls-input[data-v-89fe9580]{border-radius:0!important}.no-controls-input[data-v-89fe9580]{border-radius:3px!important;border-left-color:rgba(34,36,38,.15)!important}",
map: undefined,
media: undefined
});
};
/* scoped */
var __vue_scope_id__ = "data-v-89fe9580";
/* module identifier */
var __vue_module_identifier__ = "data-v-89fe9580";
/* functional template */
var __vue_is_functional_template__ = false;
/* style inject shadow dom */
var __vue_component__ = /*#__PURE__*/normalizeComponent({
render: __vue_render__,
staticRenderFns: __vue_staticRenderFns__
}, __vue_inject_styles__, __vue_script__, __vue_scope_id__, __vue_is_functional_template__, __vue_module_identifier__, false, undefined, createInjectorSSR, undefined);
var component$1 = __vue_component__;// Import vue component
// Default export is installable instance of component.
// IIFE injects install function into component, allowing component
// to be registered via Vue.use() as well as Vue.component(),
var component = /*#__PURE__*/(function () {
// Assign InstallableComponent type
var installable = component$1; // Attach install function executed by Vue.use()
installable.install = function (Vue) {
Vue.component('NiceNumericInput', installable);
};
return installable;
})(); // It's possible to expose named exports when writing components that can
// also be used as directives, etc. - eg. import { RollupDemoDirective } from 'rollup-demo';
// export const RollupDemoDirective = directive;
var namedExports=/*#__PURE__*/Object.freeze({__proto__:null,'default': component});// only expose one global var, with named exports exposed as properties of
// that global var (eg. plugin.namedExport)
Object.entries(namedExports).forEach(function (_ref) {
var _ref2 = _slicedToArray(_ref, 2),
exportName = _ref2[0],
exported = _ref2[1];
if (exportName !== 'default') component[exportName] = exported;
});module.exports=component;