UNPKG

framework7

Version:

Full featured mobile HTML framework for building iOS & Android apps

403 lines (398 loc) 12.7 kB
import $ from '../../shared/dom7.js'; import { extend, deleteProps } from '../../shared/utils.js'; import Framework7Class from '../../shared/class.js'; class Stepper extends Framework7Class { constructor(app, params) { super(params, [app]); const stepper = this; const defaults = { el: null, inputEl: null, valueEl: null, value: 0, formatValue: null, step: 1, min: 0, max: 100, watchInput: true, autorepeat: false, autorepeatDynamic: false, wraps: false, manualInputMode: false, decimalPoint: 4, buttonsEndInputMode: true }; // Extend defaults with modules params stepper.useModulesParams(defaults); stepper.params = extend(defaults, params); if (stepper.params.value < stepper.params.min) { stepper.params.value = stepper.params.min; } if (stepper.params.value > stepper.params.max) { stepper.params.value = stepper.params.max; } const el = stepper.params.el; if (!el) return stepper; const $el = $(el); if ($el.length === 0) return stepper; if ($el[0].f7Stepper) return $el[0].f7Stepper; let $inputEl; if (stepper.params.inputEl) { $inputEl = $(stepper.params.inputEl); } else if ($el.find('.stepper-input-wrap').find('input, textarea').length) { $inputEl = $el.find('.stepper-input-wrap').find('input, textarea').eq(0); } if ($inputEl && $inputEl.length) { 'step min max'.split(' ').forEach(paramName => { if (!params[paramName] && $inputEl.attr(paramName)) { stepper.params[paramName] = parseFloat($inputEl.attr(paramName)); } }); const decimalPoint = parseInt(stepper.params.decimalPoint, 10); if (Number.isNaN(decimalPoint)) { stepper.params.decimalPoint = 0; } else { stepper.params.decimalPoint = decimalPoint; } const inputValue = parseFloat($inputEl.val()); if (typeof params.value === 'undefined' && !Number.isNaN(inputValue) && (inputValue || inputValue === 0)) { stepper.params.value = inputValue; } } let $valueEl; if (stepper.params.valueEl) { $valueEl = $(stepper.params.valueEl); } else if ($el.find('.stepper-value').length) { $valueEl = $el.find('.stepper-value').eq(0); } const $buttonPlusEl = $el.find('.stepper-button-plus'); const $buttonMinusEl = $el.find('.stepper-button-minus'); const { step, min, max, value, decimalPoint } = stepper.params; extend(stepper, { app, $el, el: $el[0], $buttonPlusEl, buttonPlusEl: $buttonPlusEl[0], $buttonMinusEl, buttonMinusEl: $buttonMinusEl[0], $inputEl, inputEl: $inputEl ? $inputEl[0] : undefined, $valueEl, valueEl: $valueEl ? $valueEl[0] : undefined, step, min, max, value, decimalPoint, typeModeChanged: false }); $el[0].f7Stepper = stepper; // Handle Events const touchesStart = {}; let isTouched; let isScrolling; let preventButtonClick; let intervalId; let timeoutId; let autorepeatAction = null; let autorepeatInAction = false; let manualInput = false; function dynamicRepeat(current, progressions, startsIn, progressionStep, repeatEvery, action) { clearTimeout(timeoutId); timeoutId = setTimeout(() => { if (current === 1) { preventButtonClick = true; autorepeatInAction = true; } clearInterval(intervalId); action(); intervalId = setInterval(() => { action(); }, repeatEvery); if (current < progressions) { dynamicRepeat(current + 1, progressions, startsIn, progressionStep, repeatEvery / 2, action); } }, current === 1 ? startsIn : progressionStep); } function onTouchStart(e) { if (isTouched) return; if (manualInput) { return; } if ($(e.target).closest($buttonPlusEl).length) { autorepeatAction = 'increment'; } else if ($(e.target).closest($buttonMinusEl).length) { autorepeatAction = 'decrement'; } if (!autorepeatAction) return; touchesStart.x = e.type === 'touchstart' ? e.targetTouches[0].pageX : e.pageX; touchesStart.y = e.type === 'touchstart' ? e.targetTouches[0].pageY : e.pageY; isTouched = true; isScrolling = undefined; const progressions = stepper.params.autorepeatDynamic ? 4 : 1; dynamicRepeat(1, progressions, 500, 1000, 300, () => { stepper[autorepeatAction](); }); } function onTouchMove(e) { if (!isTouched) return; if (manualInput) { return; } const pageX = e.type === 'touchmove' ? e.targetTouches[0].pageX : e.pageX; const pageY = e.type === 'touchmove' ? e.targetTouches[0].pageY : e.pageY; if (typeof isScrolling === 'undefined' && !autorepeatInAction) { isScrolling = !!(isScrolling || Math.abs(pageY - touchesStart.y) > Math.abs(pageX - touchesStart.x)); } const distance = ((pageX - touchesStart.x) ** 2 + (pageY - touchesStart.y) ** 2) ** 0.5; if (isScrolling || distance > 20) { isTouched = false; clearTimeout(timeoutId); clearInterval(intervalId); } } function onTouchEnd() { clearTimeout(timeoutId); clearInterval(intervalId); autorepeatAction = null; autorepeatInAction = false; isTouched = false; } function onMinusClick() { if (manualInput) { if (stepper.params.buttonsEndInputMode) { manualInput = false; stepper.endTypeMode(true); } return; } if (preventButtonClick) { preventButtonClick = false; return; } stepper.decrement(true); } function onPlusClick() { if (manualInput) { if (stepper.params.buttonsEndInputMode) { manualInput = false; stepper.endTypeMode(true); } return; } if (preventButtonClick) { preventButtonClick = false; return; } stepper.increment(true); } function onInputClick(e) { if (!e.target.readOnly && stepper.params.manualInputMode) { manualInput = true; if (typeof e.target.selectionStart === 'number') { e.target.selectionStart = e.target.value.length; e.target.selectionEnd = e.target.value.length; } } } function onInputKey(e) { if (e.keyCode === 13 || e.which === 13) { e.preventDefault(); manualInput = false; stepper.endTypeMode(); } } function onInputBlur() { manualInput = false; stepper.endTypeMode(true); } function onInput(e) { if (manualInput) { stepper.typeValue(e.target.value); return; } if (e.detail && e.detail.sentByF7Stepper) return; stepper.setValue(e.target.value, true); } stepper.attachEvents = function attachEvents() { $buttonMinusEl.on('click', onMinusClick); $buttonPlusEl.on('click', onPlusClick); if (stepper.params.watchInput && $inputEl && $inputEl.length) { $inputEl.on('input', onInput); $inputEl.on('click', onInputClick); $inputEl.on('blur', onInputBlur); $inputEl.on('keyup', onInputKey); } if (stepper.params.autorepeat) { app.on('touchstart:passive', onTouchStart); app.on('touchmove:active', onTouchMove); app.on('touchend:passive', onTouchEnd); } }; stepper.detachEvents = function detachEvents() { $buttonMinusEl.off('click', onMinusClick); $buttonPlusEl.off('click', onPlusClick); if (stepper.params.watchInput && $inputEl && $inputEl.length) { $inputEl.off('input', onInput); $inputEl.off('click', onInputClick); $inputEl.off('blur', onInputBlur); $inputEl.off('keyup', onInputKey); } }; // Install Modules stepper.useModules(); // Init stepper.init(); return stepper; } minus() { return this.decrement(); } plus() { return this.increment(); } decrement() { const stepper = this; return stepper.setValue(stepper.value - stepper.step, false, true); } increment() { const stepper = this; return stepper.setValue(stepper.value + stepper.step, false, true); } setValue(newValue, forceUpdate, withWraps) { const stepper = this; const { step, min, max } = stepper; const oldValue = stepper.value; let value = Math.round(newValue / step) * step; if (stepper.params.wraps && withWraps) { if (value > max) value = min; if (value < min) value = max; } else { value = Math.max(Math.min(value, max), min); } if (Number.isNaN(value)) { value = oldValue; } stepper.value = value; const valueChanged = oldValue !== value; // Events if (!valueChanged && !forceUpdate) return stepper; stepper.$el.trigger('stepper:change', stepper.value); const formattedValue = stepper.formatValue(stepper.value); if (stepper.$inputEl && stepper.$inputEl.length) { stepper.$inputEl.val(formattedValue); stepper.$inputEl.trigger('input change', { sentByF7Stepper: true }); } if (stepper.$valueEl && stepper.$valueEl.length) { stepper.$valueEl.html(formattedValue); } stepper.emit('local::change stepperChange', stepper, stepper.value); return stepper; } endTypeMode(noBlur) { const stepper = this; const { min, max } = stepper; let value = parseFloat(stepper.value); if (Number.isNaN(value)) value = 0; value = Math.max(Math.min(value, max), min); stepper.value = value; if (!stepper.typeModeChanged) { if (stepper.$inputEl && stepper.$inputEl.length && !noBlur) { stepper.$inputEl.blur(); } return stepper; } stepper.typeModeChanged = false; stepper.$el.trigger('stepper:change', stepper.value); const formattedValue = stepper.formatValue(stepper.value); if (stepper.$inputEl && stepper.$inputEl.length) { stepper.$inputEl.val(formattedValue); stepper.$inputEl.trigger('input change', { sentByF7Stepper: true }); if (!noBlur) stepper.$inputEl.blur(); } if (stepper.$valueEl && stepper.$valueEl.length) { stepper.$valueEl.html(formattedValue); } stepper.emit('local::change stepperChange', stepper, stepper.value); return stepper; } typeValue(value) { const stepper = this; stepper.typeModeChanged = true; let inputTxt = String(value); if (inputTxt.length === 1 && inputTxt === '-') return stepper; if (inputTxt.lastIndexOf('.') + 1 === inputTxt.length || inputTxt.lastIndexOf(',') + 1 === inputTxt.length) { if (inputTxt.lastIndexOf('.') !== inputTxt.indexOf('.') || inputTxt.lastIndexOf(',') !== inputTxt.indexOf(',')) { inputTxt = inputTxt.slice(0, -1); stepper.value = inputTxt; stepper.$inputEl.val(stepper.value); return stepper; } } else { let newValue = parseFloat(inputTxt.replace(',', '.')); if (newValue === 0) { stepper.value = inputTxt.replace(',', '.'); stepper.$inputEl.val(stepper.value); return stepper; } if (Number.isNaN(newValue)) { stepper.value = 0; stepper.$inputEl.val(stepper.value); return stepper; } const powVal = 10 ** stepper.params.decimalPoint; newValue = Math.round(newValue * powVal).toFixed(stepper.params.decimalPoint + 1) / powVal; stepper.value = parseFloat(String(newValue).replace(',', '.')); stepper.$inputEl.val(stepper.value); return stepper; } stepper.value = inputTxt; stepper.$inputEl.val(inputTxt); return stepper; } getValue() { return this.value; } formatValue(value) { const stepper = this; if (!stepper.params.formatValue) return value; return stepper.params.formatValue.call(stepper, value); } init() { const stepper = this; stepper.attachEvents(); if (stepper.$valueEl && stepper.$valueEl.length) { const formattedValue = stepper.formatValue(stepper.value); stepper.$valueEl.html(formattedValue); } return stepper; } destroy() { let stepper = this; stepper.$el.trigger('stepper:beforedestroy'); stepper.emit('local::beforeDestroy stepperBeforeDestroy', stepper); delete stepper.$el[0].f7Stepper; stepper.detachEvents(); deleteProps(stepper); stepper = null; } } export default Stepper;