Comparing version 0.7.0 to 0.8.0
@@ -5,152 +5,142 @@ 'use strict'; | ||
function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; } | ||
var _inheritsLoose = _interopDefault(require('@babel/runtime/helpers/inheritsLoose')); | ||
var React = require('react'); | ||
var Rifm = | ||
/*#__PURE__*/ | ||
function (_React$Component) { | ||
_inheritsLoose(Rifm, _React$Component); | ||
var Rifm = function Rifm(props) { | ||
var _React$useReducer = React.useReducer(function (c) { | ||
return c + 1; | ||
}, 0), | ||
refresh = _React$useReducer[1]; | ||
function Rifm(props) { | ||
var _this; | ||
var valueRef = React.useRef(null); // state of delete button see comments below about inputType support | ||
_this = _React$Component.call(this, props) || this; | ||
_this._state = null; | ||
_this._del = false; | ||
var isDeleleteButtonDownRef = React.useRef(false); | ||
_this._handleChange = function (evt) { | ||
if (process.env.NODE_ENV !== 'production') { | ||
if (evt.target.type === 'number') { | ||
console.error('Rifm does not support input type=number, use type=tel instead.'); | ||
return; | ||
} | ||
} // FUTURE: use evt.nativeEvent.inputType for del event, see comments at onkeydown | ||
var onChange = function onChange(evt) { | ||
if (process.env.NODE_ENV !== 'production') { | ||
if (evt.target.type === 'number') { | ||
console.error('Rifm does not support input type=number, use type=tel instead.'); | ||
return; | ||
} | ||
} | ||
var value = props.value; | ||
var eventValue = evt.target.value; | ||
valueRef.current = [eventValue, // eventValue | ||
evt.target, // input | ||
eventValue.length > value.length, // isSizeIncreaseOperation | ||
isDeleleteButtonDownRef.current, // isDeleleteButtonDown | ||
value === props.format(eventValue)]; // The main trick is to update underlying input with non formatted value (= eventValue) | ||
// that allows us to calculate right cursor position after formatting (see getCursorPosition) | ||
// then we format new value and call props.onChange with masked/formatted value | ||
// and finally we are able to set cursor position into right place | ||
var stateValue = _this.state.value; | ||
var value = evt.target.value; | ||
var input = evt.target; | ||
var op = value.length > stateValue.length; | ||
var del = _this._del; | ||
refresh(); | ||
}; // React prints warn on server in non production mode about useLayoutEffect usage | ||
// in both cases it's noop | ||
var noOp = stateValue === _this.props.format(value); | ||
_this.setState({ | ||
value: value, | ||
local: true | ||
}, function () { | ||
var selectionStart = input.selectionStart; | ||
var refuse = _this.props.refuse || /[^\d]+/g; | ||
var before = value.substr(0, selectionStart).replace(refuse, ''); | ||
_this._state = { | ||
input: input, | ||
before: before, | ||
op: op, | ||
di: del && noOp, | ||
del: del | ||
}; | ||
if (process.env.NODE_ENV === 'production' || typeof window !== 'undefined') { | ||
React.useLayoutEffect(function () { | ||
if (valueRef.current == null) return; | ||
var _valueRef$current = valueRef.current, | ||
eventValue = _valueRef$current[0], | ||
input = _valueRef$current[1], | ||
isSizeIncreaseOperation = _valueRef$current[2], | ||
isDeleleteButtonDown = _valueRef$current[3], | ||
// No operation means that value itself hasn't been changed, BTW cursor, selection etc can be changed | ||
isNoOperation = _valueRef$current[4]; | ||
valueRef.current = null; // this usually occurs on deleting special symbols like ' here 123'123.00 | ||
// in case of isDeleleteButtonDown cursor should move differently vs backspace | ||
if (_this.props.replace && _this.props.replace(stateValue) && op && !noOp) { | ||
var start = -1; | ||
var deleteWasNoOp = isDeleleteButtonDown && isNoOperation; // Create string from only accepted symbols | ||
for (var i = 0; i !== before.length; ++i) { | ||
start = Math.max(start, value.toLowerCase().indexOf(before[i].toLowerCase(), start + 1)); | ||
} | ||
var clean = function clean(str) { | ||
return (str.match(props.accept || /\d/g) || []).join(''); | ||
}; | ||
var c = value.substr(start + 1).replace(refuse, '')[0]; | ||
start = value.indexOf(c, start + 1); | ||
value = "" + value.substr(0, start) + value.substr(start + 1); | ||
} | ||
var valueBeforeSelectionStart = clean(eventValue.substr(0, input.selectionStart)).toLowerCase(); // trying to find cursor position in formatted value having knowledge about valueBeforeSelectionStart | ||
// This works because we assume that format doesn't change the order of accepted symbols. | ||
// Imagine we have formatter which adds ' symbol between numbers, and by default we refuse all non numeric symbols | ||
// for example we had input = 1'2|'4 (| means cursor position) then user entered '3' symbol | ||
// inputValue = 1'23'|4 so valueBeforeSelectionStart = 123 and formatted value = 1'2'3'4 | ||
// calling getCursorPosition("1'2'3'4") will give us position after 3, 1'2'3|'4 | ||
// so for formatting just this function to determine cursor position after formatting is enough | ||
// with masking we need to do some additional checks see `replace` below | ||
var fv = _this.props.format(value); | ||
var getCursorPosition = function getCursorPosition(val) { | ||
var start = 0; | ||
if (stateValue === fv) { | ||
_this.setState({ | ||
value: value | ||
}); | ||
} else { | ||
_this.props.onChange(fv); | ||
for (var i = 0; i !== valueBeforeSelectionStart.length; ++i) { | ||
start = Math.max(start, val.toLowerCase().indexOf(valueBeforeSelectionStart[i], start) + 1); | ||
} | ||
}); | ||
}; | ||
_this._hKD = function (evt) { | ||
if (evt.code === 'Delete') { | ||
_this._del = true; | ||
return start; | ||
}; // Masking part, for masks if size of mask is above some value (props.replace checks that) | ||
// we need to replace symbols instead of do nothing as like in format | ||
if (props.replace && props.replace(props.value) && isSizeIncreaseOperation && !isNoOperation) { | ||
var start = getCursorPosition(eventValue); | ||
var c = clean(eventValue.substr(start))[0]; | ||
start = eventValue.indexOf(c, start); | ||
eventValue = "" + eventValue.substr(0, start) + eventValue.substr(start + 1); | ||
} | ||
}; | ||
_this._hKU = function (evt) { | ||
if (evt.code === 'Delete') { | ||
_this._del = false; | ||
var formattedValue = props.format(eventValue); | ||
if (props.value === formattedValue) { | ||
// if nothing changed for formatted value, just refresh so props.value will be used at render | ||
refresh(); | ||
} else { | ||
props.onChange(formattedValue); | ||
} | ||
}; | ||
_this.state = { | ||
value: props.value, | ||
local: true | ||
}; | ||
return _this; | ||
} | ||
return function () { | ||
var start = getCursorPosition(formattedValue); // Visually improves working with masked values, | ||
// like cursor jumping over refused symbols | ||
// as an example date mask: was "5|1-24-3" then user pressed "6" | ||
// it becomes "56-|12-43" with this code, and "56|-12-43" without | ||
Rifm.getDerivedStateFromProps = function getDerivedStateFromProps(props, state) { | ||
return { | ||
value: state.local ? state.value : props.value, | ||
local: false | ||
}; | ||
}; | ||
if (props.replace && (isSizeIncreaseOperation || isDeleleteButtonDown && !deleteWasNoOp)) { | ||
while (formattedValue[start] && clean(formattedValue[start]) === '') { | ||
start += 1; | ||
} | ||
} | ||
var _proto = Rifm.prototype; | ||
_proto.render = function render() { | ||
var _handleChange = this._handleChange, | ||
value = this.state.value, | ||
children = this.props.children; | ||
return children({ | ||
value: value, | ||
onChange: _handleChange | ||
input.selectionStart = input.selectionEnd = start + (deleteWasNoOp ? 1 : 0); | ||
}; | ||
}); | ||
} // delete when https://developer.mozilla.org/en-US/docs/Web/API/InputEvent/inputType will be supported by all major browsers | ||
; | ||
} | ||
_proto.componentWillUnmount = function componentWillUnmount() { | ||
document.removeEventListener('keydown', this._hKD); | ||
document.removeEventListener('keyup', this._hKU); | ||
} // delete when https://developer.mozilla.org/en-US/docs/Web/API/InputEvent/inputType will be supported by all major browsers | ||
; | ||
React.useEffect(function () { | ||
// until https://developer.mozilla.org/en-US/docs/Web/API/InputEvent/inputType will be supported | ||
// by all major browsers (now supported by: +chrome, +safari, ?edge, !firefox) | ||
// there is no way I found to distinguish in onChange | ||
// backspace or delete was called in some situations | ||
// firefox track https://bugzilla.mozilla.org/show_bug.cgi?id=1447239 | ||
var handleKeyDown = function handleKeyDown(evt) { | ||
if (evt.code === 'Delete') { | ||
isDeleleteButtonDownRef.current = true; | ||
} | ||
}; | ||
_proto.componentDidMount = function componentDidMount() { | ||
document.addEventListener('keydown', this._hKD); | ||
document.addEventListener('keyup', this._hKU); | ||
}; | ||
_proto.componentDidUpdate = function componentDidUpdate() { | ||
var _state = this._state; | ||
if (_state) { | ||
var value = this.state.value; | ||
var start = -1; | ||
for (var i = 0; i !== _state.before.length; ++i) { | ||
start = Math.max(start, value.toLowerCase().indexOf(_state.before[i].toLowerCase(), start + 1)); | ||
} // format usually looks better without this | ||
if (this.props.replace && (_state.op || _state.del && !_state.di)) { | ||
while (value[start + 1] && (this.props.refuse || /[^\d]+/).test(value[start + 1])) { | ||
start += 1; | ||
} | ||
var handleKeyUp = function handleKeyUp(evt) { | ||
if (evt.code === 'Delete') { | ||
isDeleleteButtonDownRef.current = false; | ||
} | ||
}; | ||
_state.input.selectionStart = _state.input.selectionEnd = start + 1 + (_state.di ? 1 : 0); | ||
} | ||
document.addEventListener('keydown', handleKeyDown); | ||
document.addEventListener('keyup', handleKeyUp); | ||
return function () { | ||
document.removeEventListener('keydown', handleKeyDown); | ||
document.removeEventListener('keyup', handleKeyUp); | ||
}; | ||
}, []); | ||
return props.children({ | ||
value: valueRef.current != null ? valueRef.current[0] : props.value, | ||
onChange: onChange | ||
}); | ||
}; | ||
this._state = null; | ||
}; | ||
return Rifm; | ||
}(React.Component); | ||
exports.Rifm = Rifm; |
@@ -1,149 +0,141 @@ | ||
import _inheritsLoose from '@babel/runtime/helpers/esm/inheritsLoose'; | ||
import { Component } from 'react'; | ||
import { useReducer, useRef, useLayoutEffect, useEffect } from 'react'; | ||
var Rifm = | ||
/*#__PURE__*/ | ||
function (_React$Component) { | ||
_inheritsLoose(Rifm, _React$Component); | ||
var Rifm = function Rifm(props) { | ||
var _React$useReducer = useReducer(function (c) { | ||
return c + 1; | ||
}, 0), | ||
refresh = _React$useReducer[1]; | ||
function Rifm(props) { | ||
var _this; | ||
var valueRef = useRef(null); // state of delete button see comments below about inputType support | ||
_this = _React$Component.call(this, props) || this; | ||
_this._state = null; | ||
_this._del = false; | ||
var isDeleleteButtonDownRef = useRef(false); | ||
_this._handleChange = function (evt) { | ||
if (process.env.NODE_ENV !== 'production') { | ||
if (evt.target.type === 'number') { | ||
console.error('Rifm does not support input type=number, use type=tel instead.'); | ||
return; | ||
} | ||
} // FUTURE: use evt.nativeEvent.inputType for del event, see comments at onkeydown | ||
var onChange = function onChange(evt) { | ||
if (process.env.NODE_ENV !== 'production') { | ||
if (evt.target.type === 'number') { | ||
console.error('Rifm does not support input type=number, use type=tel instead.'); | ||
return; | ||
} | ||
} | ||
var value = props.value; | ||
var eventValue = evt.target.value; | ||
valueRef.current = [eventValue, // eventValue | ||
evt.target, // input | ||
eventValue.length > value.length, // isSizeIncreaseOperation | ||
isDeleleteButtonDownRef.current, // isDeleleteButtonDown | ||
value === props.format(eventValue)]; // The main trick is to update underlying input with non formatted value (= eventValue) | ||
// that allows us to calculate right cursor position after formatting (see getCursorPosition) | ||
// then we format new value and call props.onChange with masked/formatted value | ||
// and finally we are able to set cursor position into right place | ||
var stateValue = _this.state.value; | ||
var value = evt.target.value; | ||
var input = evt.target; | ||
var op = value.length > stateValue.length; | ||
var del = _this._del; | ||
refresh(); | ||
}; // React prints warn on server in non production mode about useLayoutEffect usage | ||
// in both cases it's noop | ||
var noOp = stateValue === _this.props.format(value); | ||
_this.setState({ | ||
value: value, | ||
local: true | ||
}, function () { | ||
var selectionStart = input.selectionStart; | ||
var refuse = _this.props.refuse || /[^\d]+/g; | ||
var before = value.substr(0, selectionStart).replace(refuse, ''); | ||
_this._state = { | ||
input: input, | ||
before: before, | ||
op: op, | ||
di: del && noOp, | ||
del: del | ||
}; | ||
if (process.env.NODE_ENV === 'production' || typeof window !== 'undefined') { | ||
useLayoutEffect(function () { | ||
if (valueRef.current == null) return; | ||
var _valueRef$current = valueRef.current, | ||
eventValue = _valueRef$current[0], | ||
input = _valueRef$current[1], | ||
isSizeIncreaseOperation = _valueRef$current[2], | ||
isDeleleteButtonDown = _valueRef$current[3], | ||
// No operation means that value itself hasn't been changed, BTW cursor, selection etc can be changed | ||
isNoOperation = _valueRef$current[4]; | ||
valueRef.current = null; // this usually occurs on deleting special symbols like ' here 123'123.00 | ||
// in case of isDeleleteButtonDown cursor should move differently vs backspace | ||
if (_this.props.replace && _this.props.replace(stateValue) && op && !noOp) { | ||
var start = -1; | ||
var deleteWasNoOp = isDeleleteButtonDown && isNoOperation; // Create string from only accepted symbols | ||
for (var i = 0; i !== before.length; ++i) { | ||
start = Math.max(start, value.toLowerCase().indexOf(before[i].toLowerCase(), start + 1)); | ||
} | ||
var clean = function clean(str) { | ||
return (str.match(props.accept || /\d/g) || []).join(''); | ||
}; | ||
var c = value.substr(start + 1).replace(refuse, '')[0]; | ||
start = value.indexOf(c, start + 1); | ||
value = "" + value.substr(0, start) + value.substr(start + 1); | ||
} | ||
var valueBeforeSelectionStart = clean(eventValue.substr(0, input.selectionStart)).toLowerCase(); // trying to find cursor position in formatted value having knowledge about valueBeforeSelectionStart | ||
// This works because we assume that format doesn't change the order of accepted symbols. | ||
// Imagine we have formatter which adds ' symbol between numbers, and by default we refuse all non numeric symbols | ||
// for example we had input = 1'2|'4 (| means cursor position) then user entered '3' symbol | ||
// inputValue = 1'23'|4 so valueBeforeSelectionStart = 123 and formatted value = 1'2'3'4 | ||
// calling getCursorPosition("1'2'3'4") will give us position after 3, 1'2'3|'4 | ||
// so for formatting just this function to determine cursor position after formatting is enough | ||
// with masking we need to do some additional checks see `replace` below | ||
var fv = _this.props.format(value); | ||
var getCursorPosition = function getCursorPosition(val) { | ||
var start = 0; | ||
if (stateValue === fv) { | ||
_this.setState({ | ||
value: value | ||
}); | ||
} else { | ||
_this.props.onChange(fv); | ||
for (var i = 0; i !== valueBeforeSelectionStart.length; ++i) { | ||
start = Math.max(start, val.toLowerCase().indexOf(valueBeforeSelectionStart[i], start) + 1); | ||
} | ||
}); | ||
}; | ||
_this._hKD = function (evt) { | ||
if (evt.code === 'Delete') { | ||
_this._del = true; | ||
return start; | ||
}; // Masking part, for masks if size of mask is above some value (props.replace checks that) | ||
// we need to replace symbols instead of do nothing as like in format | ||
if (props.replace && props.replace(props.value) && isSizeIncreaseOperation && !isNoOperation) { | ||
var start = getCursorPosition(eventValue); | ||
var c = clean(eventValue.substr(start))[0]; | ||
start = eventValue.indexOf(c, start); | ||
eventValue = "" + eventValue.substr(0, start) + eventValue.substr(start + 1); | ||
} | ||
}; | ||
_this._hKU = function (evt) { | ||
if (evt.code === 'Delete') { | ||
_this._del = false; | ||
var formattedValue = props.format(eventValue); | ||
if (props.value === formattedValue) { | ||
// if nothing changed for formatted value, just refresh so props.value will be used at render | ||
refresh(); | ||
} else { | ||
props.onChange(formattedValue); | ||
} | ||
}; | ||
_this.state = { | ||
value: props.value, | ||
local: true | ||
}; | ||
return _this; | ||
} | ||
return function () { | ||
var start = getCursorPosition(formattedValue); // Visually improves working with masked values, | ||
// like cursor jumping over refused symbols | ||
// as an example date mask: was "5|1-24-3" then user pressed "6" | ||
// it becomes "56-|12-43" with this code, and "56|-12-43" without | ||
Rifm.getDerivedStateFromProps = function getDerivedStateFromProps(props, state) { | ||
return { | ||
value: state.local ? state.value : props.value, | ||
local: false | ||
}; | ||
}; | ||
if (props.replace && (isSizeIncreaseOperation || isDeleleteButtonDown && !deleteWasNoOp)) { | ||
while (formattedValue[start] && clean(formattedValue[start]) === '') { | ||
start += 1; | ||
} | ||
} | ||
var _proto = Rifm.prototype; | ||
_proto.render = function render() { | ||
var _handleChange = this._handleChange, | ||
value = this.state.value, | ||
children = this.props.children; | ||
return children({ | ||
value: value, | ||
onChange: _handleChange | ||
input.selectionStart = input.selectionEnd = start + (deleteWasNoOp ? 1 : 0); | ||
}; | ||
}); | ||
} // delete when https://developer.mozilla.org/en-US/docs/Web/API/InputEvent/inputType will be supported by all major browsers | ||
; | ||
} | ||
_proto.componentWillUnmount = function componentWillUnmount() { | ||
document.removeEventListener('keydown', this._hKD); | ||
document.removeEventListener('keyup', this._hKU); | ||
} // delete when https://developer.mozilla.org/en-US/docs/Web/API/InputEvent/inputType will be supported by all major browsers | ||
; | ||
useEffect(function () { | ||
// until https://developer.mozilla.org/en-US/docs/Web/API/InputEvent/inputType will be supported | ||
// by all major browsers (now supported by: +chrome, +safari, ?edge, !firefox) | ||
// there is no way I found to distinguish in onChange | ||
// backspace or delete was called in some situations | ||
// firefox track https://bugzilla.mozilla.org/show_bug.cgi?id=1447239 | ||
var handleKeyDown = function handleKeyDown(evt) { | ||
if (evt.code === 'Delete') { | ||
isDeleleteButtonDownRef.current = true; | ||
} | ||
}; | ||
_proto.componentDidMount = function componentDidMount() { | ||
document.addEventListener('keydown', this._hKD); | ||
document.addEventListener('keyup', this._hKU); | ||
}; | ||
_proto.componentDidUpdate = function componentDidUpdate() { | ||
var _state = this._state; | ||
if (_state) { | ||
var value = this.state.value; | ||
var start = -1; | ||
for (var i = 0; i !== _state.before.length; ++i) { | ||
start = Math.max(start, value.toLowerCase().indexOf(_state.before[i].toLowerCase(), start + 1)); | ||
} // format usually looks better without this | ||
if (this.props.replace && (_state.op || _state.del && !_state.di)) { | ||
while (value[start + 1] && (this.props.refuse || /[^\d]+/).test(value[start + 1])) { | ||
start += 1; | ||
} | ||
var handleKeyUp = function handleKeyUp(evt) { | ||
if (evt.code === 'Delete') { | ||
isDeleleteButtonDownRef.current = false; | ||
} | ||
}; | ||
_state.input.selectionStart = _state.input.selectionEnd = start + 1 + (_state.di ? 1 : 0); | ||
} | ||
document.addEventListener('keydown', handleKeyDown); | ||
document.addEventListener('keyup', handleKeyUp); | ||
return function () { | ||
document.removeEventListener('keydown', handleKeyDown); | ||
document.removeEventListener('keyup', handleKeyUp); | ||
}; | ||
}, []); | ||
return props.children({ | ||
value: valueRef.current != null ? valueRef.current[0] : props.value, | ||
onChange: onChange | ||
}); | ||
}; | ||
this._state = null; | ||
}; | ||
return Rifm; | ||
}(Component); | ||
export { Rifm }; |
@@ -1,1 +0,1 @@ | ||
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports,require("react")):"function"==typeof define&&define.amd?define(["exports","react"],t):t((e=e||self).Rifm={},e.React)}(this,function(e,t){"use strict";var o=function(e){var t,o;function n(t){var o;return(o=e.call(this,t)||this)._state=null,o._del=!1,o._handleChange=function(e){var t=o.state.value,n=e.target.value,r=e.target,a=n.length>t.length,s=o._del,i=t===o.props.format(n);o.setState({value:n,local:!0},function(){var e=r.selectionStart,u=o.props.refuse||/[^\d]+/g,l=n.substr(0,e).replace(u,"");if(o._state={input:r,before:l,op:a,di:s&&i,del:s},o.props.replace&&o.props.replace(t)&&a&&!i){for(var p=-1,c=0;c!==l.length;++c)p=Math.max(p,n.toLowerCase().indexOf(l[c].toLowerCase(),p+1));var d=n.substr(p+1).replace(u,"")[0];p=n.indexOf(d,p+1),n=""+n.substr(0,p)+n.substr(p+1)}var f=o.props.format(n);t===f?o.setState({value:n}):o.props.onChange(f)})},o._hKD=function(e){"Delete"===e.code&&(o._del=!0)},o._hKU=function(e){"Delete"===e.code&&(o._del=!1)},o.state={value:t.value,local:!0},o}o=e,(t=n).prototype=Object.create(o.prototype),t.prototype.constructor=t,t.__proto__=o,n.getDerivedStateFromProps=function(e,t){return{value:t.local?t.value:e.value,local:!1}};var r=n.prototype;return r.render=function(){var e=this._handleChange,t=this.state.value;return(0,this.props.children)({value:t,onChange:e})},r.componentWillUnmount=function(){document.removeEventListener("keydown",this._hKD),document.removeEventListener("keyup",this._hKU)},r.componentDidMount=function(){document.addEventListener("keydown",this._hKD),document.addEventListener("keyup",this._hKU)},r.componentDidUpdate=function(){var e=this._state;if(e){for(var t=this.state.value,o=-1,n=0;n!==e.before.length;++n)o=Math.max(o,t.toLowerCase().indexOf(e.before[n].toLowerCase(),o+1));if(this.props.replace&&(e.op||e.del&&!e.di))for(;t[o+1]&&(this.props.refuse||/[^\d]+/).test(t[o+1]);)o+=1;e.input.selectionStart=e.input.selectionEnd=o+1+(e.di?1:0)}this._state=null},n}(t.Component);e.Rifm=o,Object.defineProperty(e,"__esModule",{value:!0})}); | ||
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports,require("react")):"function"==typeof define&&define.amd?define(["exports","react"],t):t((e=e||self).Rifm={},e.React)}(this,function(e,t){"use strict";e.Rifm=function(e){var n=t.useReducer(function(e){return e+1},0)[1],r=t.useRef(null),u=t.useRef(!1);return t.useLayoutEffect(function(){if(null!=r.current){var t=r.current,u=t[0],o=t[1],c=t[2],a=t[3],f=t[4];r.current=null;var i=a&&f,l=function(t){return(t.match(e.accept||/\d/g)||[]).join("")},s=l(u.substr(0,o.selectionStart)).toLowerCase(),d=function(e){for(var t=0,n=0;n!==s.length;++n)t=Math.max(t,e.toLowerCase().indexOf(s[n],t)+1);return t};if(e.replace&&e.replace(e.value)&&c&&!f){var v=d(u),m=l(u.substr(v))[0];v=u.indexOf(m,v),u=""+u.substr(0,v)+u.substr(v+1)}var p=e.format(u);return e.value===p?n():e.onChange(p),function(){var t=d(p);if(e.replace&&(c||a&&!i))for(;p[t]&&""===l(p[t]);)t+=1;o.selectionStart=o.selectionEnd=t+(i?1:0)}}}),t.useEffect(function(){var e=function(e){"Delete"===e.code&&(u.current=!0)},t=function(e){"Delete"===e.code&&(u.current=!1)};return document.addEventListener("keydown",e),document.addEventListener("keyup",t),function(){document.removeEventListener("keydown",e),document.removeEventListener("keyup",t)}},[]),e.children({value:null!=r.current?r.current[0]:e.value,onChange:function(t){var o=e.value,c=t.target.value;r.current=[c,t.target,c.length>o.length,u.current,o===e.format(c)],n()}})},Object.defineProperty(e,"__esModule",{value:!0})}); |
@@ -7,153 +7,140 @@ (function (global, factory) { | ||
function _inheritsLoose(subClass, superClass) { | ||
subClass.prototype = Object.create(superClass.prototype); | ||
subClass.prototype.constructor = subClass; | ||
subClass.__proto__ = superClass; | ||
} | ||
var Rifm = function Rifm(props) { | ||
var _React$useReducer = React.useReducer(function (c) { | ||
return c + 1; | ||
}, 0), | ||
refresh = _React$useReducer[1]; | ||
var Rifm = | ||
/*#__PURE__*/ | ||
function (_React$Component) { | ||
_inheritsLoose(Rifm, _React$Component); | ||
var valueRef = React.useRef(null); // state of delete button see comments below about inputType support | ||
function Rifm(props) { | ||
var _this; | ||
var isDeleleteButtonDownRef = React.useRef(false); | ||
_this = _React$Component.call(this, props) || this; | ||
_this._state = null; | ||
_this._del = false; | ||
var onChange = function onChange(evt) { | ||
{ | ||
if (evt.target.type === 'number') { | ||
console.error('Rifm does not support input type=number, use type=tel instead.'); | ||
return; | ||
} | ||
} | ||
_this._handleChange = function (evt) { | ||
{ | ||
if (evt.target.type === 'number') { | ||
console.error('Rifm does not support input type=number, use type=tel instead.'); | ||
return; | ||
} | ||
} // FUTURE: use evt.nativeEvent.inputType for del event, see comments at onkeydown | ||
var value = props.value; | ||
var eventValue = evt.target.value; | ||
valueRef.current = [eventValue, // eventValue | ||
evt.target, // input | ||
eventValue.length > value.length, // isSizeIncreaseOperation | ||
isDeleleteButtonDownRef.current, // isDeleleteButtonDown | ||
value === props.format(eventValue)]; // The main trick is to update underlying input with non formatted value (= eventValue) | ||
// that allows us to calculate right cursor position after formatting (see getCursorPosition) | ||
// then we format new value and call props.onChange with masked/formatted value | ||
// and finally we are able to set cursor position into right place | ||
refresh(); | ||
}; // React prints warn on server in non production mode about useLayoutEffect usage | ||
// in both cases it's noop | ||
var stateValue = _this.state.value; | ||
var value = evt.target.value; | ||
var input = evt.target; | ||
var op = value.length > stateValue.length; | ||
var del = _this._del; | ||
var noOp = stateValue === _this.props.format(value); | ||
if (typeof window !== 'undefined') { | ||
React.useLayoutEffect(function () { | ||
if (valueRef.current == null) return; | ||
var _valueRef$current = valueRef.current, | ||
eventValue = _valueRef$current[0], | ||
input = _valueRef$current[1], | ||
isSizeIncreaseOperation = _valueRef$current[2], | ||
isDeleleteButtonDown = _valueRef$current[3], | ||
// No operation means that value itself hasn't been changed, BTW cursor, selection etc can be changed | ||
isNoOperation = _valueRef$current[4]; | ||
valueRef.current = null; // this usually occurs on deleting special symbols like ' here 123'123.00 | ||
// in case of isDeleleteButtonDown cursor should move differently vs backspace | ||
_this.setState({ | ||
value: value, | ||
local: true | ||
}, function () { | ||
var selectionStart = input.selectionStart; | ||
var refuse = _this.props.refuse || /[^\d]+/g; | ||
var before = value.substr(0, selectionStart).replace(refuse, ''); | ||
_this._state = { | ||
input: input, | ||
before: before, | ||
op: op, | ||
di: del && noOp, | ||
del: del | ||
}; | ||
var deleteWasNoOp = isDeleleteButtonDown && isNoOperation; // Create string from only accepted symbols | ||
if (_this.props.replace && _this.props.replace(stateValue) && op && !noOp) { | ||
var start = -1; | ||
var clean = function clean(str) { | ||
return (str.match(props.accept || /\d/g) || []).join(''); | ||
}; | ||
for (var i = 0; i !== before.length; ++i) { | ||
start = Math.max(start, value.toLowerCase().indexOf(before[i].toLowerCase(), start + 1)); | ||
} | ||
var valueBeforeSelectionStart = clean(eventValue.substr(0, input.selectionStart)).toLowerCase(); // trying to find cursor position in formatted value having knowledge about valueBeforeSelectionStart | ||
// This works because we assume that format doesn't change the order of accepted symbols. | ||
// Imagine we have formatter which adds ' symbol between numbers, and by default we refuse all non numeric symbols | ||
// for example we had input = 1'2|'4 (| means cursor position) then user entered '3' symbol | ||
// inputValue = 1'23'|4 so valueBeforeSelectionStart = 123 and formatted value = 1'2'3'4 | ||
// calling getCursorPosition("1'2'3'4") will give us position after 3, 1'2'3|'4 | ||
// so for formatting just this function to determine cursor position after formatting is enough | ||
// with masking we need to do some additional checks see `replace` below | ||
var c = value.substr(start + 1).replace(refuse, '')[0]; | ||
start = value.indexOf(c, start + 1); | ||
value = "" + value.substr(0, start) + value.substr(start + 1); | ||
var getCursorPosition = function getCursorPosition(val) { | ||
var start = 0; | ||
for (var i = 0; i !== valueBeforeSelectionStart.length; ++i) { | ||
start = Math.max(start, val.toLowerCase().indexOf(valueBeforeSelectionStart[i], start) + 1); | ||
} | ||
var fv = _this.props.format(value); | ||
return start; | ||
}; // Masking part, for masks if size of mask is above some value (props.replace checks that) | ||
// we need to replace symbols instead of do nothing as like in format | ||
if (stateValue === fv) { | ||
_this.setState({ | ||
value: value | ||
}); | ||
} else { | ||
_this.props.onChange(fv); | ||
} | ||
}); | ||
}; | ||
_this._hKD = function (evt) { | ||
if (evt.code === 'Delete') { | ||
_this._del = true; | ||
if (props.replace && props.replace(props.value) && isSizeIncreaseOperation && !isNoOperation) { | ||
var start = getCursorPosition(eventValue); | ||
var c = clean(eventValue.substr(start))[0]; | ||
start = eventValue.indexOf(c, start); | ||
eventValue = "" + eventValue.substr(0, start) + eventValue.substr(start + 1); | ||
} | ||
}; | ||
_this._hKU = function (evt) { | ||
if (evt.code === 'Delete') { | ||
_this._del = false; | ||
var formattedValue = props.format(eventValue); | ||
if (props.value === formattedValue) { | ||
// if nothing changed for formatted value, just refresh so props.value will be used at render | ||
refresh(); | ||
} else { | ||
props.onChange(formattedValue); | ||
} | ||
}; | ||
_this.state = { | ||
value: props.value, | ||
local: true | ||
}; | ||
return _this; | ||
} | ||
return function () { | ||
var start = getCursorPosition(formattedValue); // Visually improves working with masked values, | ||
// like cursor jumping over refused symbols | ||
// as an example date mask: was "5|1-24-3" then user pressed "6" | ||
// it becomes "56-|12-43" with this code, and "56|-12-43" without | ||
Rifm.getDerivedStateFromProps = function getDerivedStateFromProps(props, state) { | ||
return { | ||
value: state.local ? state.value : props.value, | ||
local: false | ||
}; | ||
}; | ||
if (props.replace && (isSizeIncreaseOperation || isDeleleteButtonDown && !deleteWasNoOp)) { | ||
while (formattedValue[start] && clean(formattedValue[start]) === '') { | ||
start += 1; | ||
} | ||
} | ||
var _proto = Rifm.prototype; | ||
_proto.render = function render() { | ||
var _handleChange = this._handleChange, | ||
value = this.state.value, | ||
children = this.props.children; | ||
return children({ | ||
value: value, | ||
onChange: _handleChange | ||
input.selectionStart = input.selectionEnd = start + (deleteWasNoOp ? 1 : 0); | ||
}; | ||
}); | ||
} // delete when https://developer.mozilla.org/en-US/docs/Web/API/InputEvent/inputType will be supported by all major browsers | ||
; | ||
} | ||
_proto.componentWillUnmount = function componentWillUnmount() { | ||
document.removeEventListener('keydown', this._hKD); | ||
document.removeEventListener('keyup', this._hKU); | ||
} // delete when https://developer.mozilla.org/en-US/docs/Web/API/InputEvent/inputType will be supported by all major browsers | ||
; | ||
React.useEffect(function () { | ||
// until https://developer.mozilla.org/en-US/docs/Web/API/InputEvent/inputType will be supported | ||
// by all major browsers (now supported by: +chrome, +safari, ?edge, !firefox) | ||
// there is no way I found to distinguish in onChange | ||
// backspace or delete was called in some situations | ||
// firefox track https://bugzilla.mozilla.org/show_bug.cgi?id=1447239 | ||
var handleKeyDown = function handleKeyDown(evt) { | ||
if (evt.code === 'Delete') { | ||
isDeleleteButtonDownRef.current = true; | ||
} | ||
}; | ||
_proto.componentDidMount = function componentDidMount() { | ||
document.addEventListener('keydown', this._hKD); | ||
document.addEventListener('keyup', this._hKU); | ||
}; | ||
_proto.componentDidUpdate = function componentDidUpdate() { | ||
var _state = this._state; | ||
if (_state) { | ||
var value = this.state.value; | ||
var start = -1; | ||
for (var i = 0; i !== _state.before.length; ++i) { | ||
start = Math.max(start, value.toLowerCase().indexOf(_state.before[i].toLowerCase(), start + 1)); | ||
} // format usually looks better without this | ||
if (this.props.replace && (_state.op || _state.del && !_state.di)) { | ||
while (value[start + 1] && (this.props.refuse || /[^\d]+/).test(value[start + 1])) { | ||
start += 1; | ||
} | ||
var handleKeyUp = function handleKeyUp(evt) { | ||
if (evt.code === 'Delete') { | ||
isDeleleteButtonDownRef.current = false; | ||
} | ||
}; | ||
_state.input.selectionStart = _state.input.selectionEnd = start + 1 + (_state.di ? 1 : 0); | ||
} | ||
document.addEventListener('keydown', handleKeyDown); | ||
document.addEventListener('keyup', handleKeyUp); | ||
return function () { | ||
document.removeEventListener('keydown', handleKeyDown); | ||
document.removeEventListener('keyup', handleKeyUp); | ||
}; | ||
}, []); | ||
return props.children({ | ||
value: valueRef.current != null ? valueRef.current[0] : props.value, | ||
onChange: onChange | ||
}); | ||
}; | ||
this._state = null; | ||
}; | ||
return Rifm; | ||
}(React.Component); | ||
exports.Rifm = Rifm; | ||
@@ -160,0 +147,0 @@ |
{ | ||
"name": "rifm", | ||
"version": "0.7.0", | ||
"version": "0.8.0", | ||
"description": "Tiny react input formatter and mask", | ||
@@ -17,11 +17,24 @@ "author": "istarkov", | ||
"build": "rimraf dist && npm run build:code && npm run build:flow", | ||
"dev": "NODE_ICU_DATA=`yarn -s run node-full-icu-path` next dev", | ||
"jest": "NODE_ICU_DATA=`yarn -s run node-full-icu-path` jest", | ||
"test": "eslint ./ && flow check && yarn test:ts && yarn jest", | ||
"test:ts": "tslint -p tsconfig.json", | ||
"docz:build": "docz build", | ||
"docz:dev": "docz dev", | ||
"deploy:docz": "yarn docz:build && cp .docz/dist/index.html .docz/dist/404.html && gh-pages -d .docz/dist", | ||
"copy-sandbox-examples": "mkdir -p ./out/codesandboxes && find ./pages -type d -mindepth 1 -maxdepth 1 -exec cp -R \\{\\} ./out/codesandboxes \\;", | ||
"export": "rimraf ./out && next build && NODE_ICU_DATA=`yarn -s run node-full-icu-path` next export && yarn copy-sandbox-examples", | ||
"deploy": "yarn export && gh-pages -d out", | ||
"precommit": "lint-staged", | ||
"prepublishOnly": "yarn test && yarn build" | ||
}, | ||
"babel": { | ||
"presets": [ | ||
[ | ||
"@babel/env", | ||
{ | ||
"loose": true | ||
} | ||
], | ||
"@babel/flow", | ||
"@babel/react" | ||
] | ||
}, | ||
"lint-staged": { | ||
@@ -35,3 +48,11 @@ "*.{js,md,ts,tsx}": [ | ||
"singleQuote": true, | ||
"trailingComma": "es5" | ||
"trailingComma": "es5", | ||
"overrides": [ | ||
{ | ||
"files": "*.test.js", | ||
"options": { | ||
"printWidth": 120 | ||
} | ||
} | ||
] | ||
}, | ||
@@ -68,3 +89,4 @@ "eslintConfig": { | ||
} | ||
] | ||
], | ||
"react/prop-types": "off" | ||
} | ||
@@ -74,3 +96,2 @@ }, | ||
"@babel/core": "^7.3.3", | ||
"@babel/plugin-proposal-class-properties": "^7.3.3", | ||
"@babel/plugin-transform-runtime": "^7.0.0", | ||
@@ -80,3 +101,4 @@ "@babel/preset-env": "^7.3.1", | ||
"@babel/preset-react": "^7.0.0", | ||
"@material-ui/core": "^1.3.1", | ||
"@material-ui/core": "^4.0.0", | ||
"@material-ui/styles": "^4.0.0", | ||
"alea": "^0.0.9", | ||
@@ -86,4 +108,2 @@ "babel-core": "^7.0.0-bridge.0", | ||
"babel-jest": "^23.2.0", | ||
"docz": "^0.13.7", | ||
"docz-theme-default": "^0.13.7", | ||
"dtslint": "^0.4.2", | ||
@@ -97,3 +117,3 @@ "emotion": "^9.2.4", | ||
"flow-bin": "^0.93.0", | ||
"full-icu": "^1.2.1", | ||
"full-icu": "^1.3.0", | ||
"gh-pages": "^1.2.0", | ||
@@ -104,3 +124,4 @@ "husky": "^0.14.3", | ||
"lint-staged": "^7.2.0", | ||
"prettier": "^1.10.2", | ||
"next": "^8.1.0", | ||
"prettier": "^1.17.1", | ||
"react": "^16.8.2", | ||
@@ -117,3 +138,2 @@ "react-dom": "^16.8.2", | ||
"rollup-plugin-terser": "^4.0.4", | ||
"string-mask": "^0.3.0", | ||
"tslint": "^5.12.0" | ||
@@ -120,0 +140,0 @@ }, |
@@ -6,3 +6,3 @@ # RIFM - React Input Format & Mask | ||
[Demo](https://istarkov.github.io/rifm/docs-readme) | ||
[Demo](https://istarkov.github.io/rifm) | ||
@@ -14,3 +14,3 @@ ## Highlights | ||
- Tiny (≈ 800b) | ||
- Supports any [input](https://istarkov.github.io/rifm/docs-readme#material-ui). | ||
- Supports any [input](https://istarkov.github.io/rifm#material-ui). | ||
- Can mask input and format | ||
@@ -20,5 +20,4 @@ | ||
```javascript | ||
```js | ||
import { Rifm } from 'rifm'; | ||
import { Value } from 'react-powerplug'; | ||
import TextField from '@material-ui/core/TextField'; | ||
@@ -34,21 +33,19 @@ import { css } from 'emotion'; | ||
<Value initial={''}> | ||
{text => ( | ||
<Rifm | ||
value={text.value} | ||
onChange={text.set} | ||
format={numberFormat} | ||
> | ||
{({ value, onChange }) => ( | ||
<TextField | ||
value={value} | ||
label={'Float'} | ||
onChange={onChange} | ||
className={css({input: {textAlign:"right"}})} | ||
type="tel" | ||
/> | ||
)} | ||
</Rifm> | ||
const [value, setValue] = React.useState('') | ||
<Rifm | ||
value={value} | ||
onChange={setValue} | ||
format={numberFormat} | ||
> | ||
{({ value, onChange }) => ( | ||
<TextField | ||
value={value} | ||
label={'Float'} | ||
onChange={onChange} | ||
className={css({input: {textAlign:"right"}})} | ||
type="tel" | ||
/> | ||
)} | ||
</Value> | ||
</Rifm> | ||
@@ -58,18 +55,10 @@ ... | ||
[Demo](https://istarkov.github.io/rifm/docs-readme) | ||
[Demo source](https://github.com/istarkov/rifm/blob/master/docs/readme.mdx) | ||
## Install | ||
```shell | ||
yarn add rifm | ||
```sh | ||
yarn add rifm | ||
``` | ||
```shell | ||
npm -i rifm | ||
``` | ||
## Thanks | ||
[@TrySound](https://github.com/TrySound) for incredible help and support on this |
@@ -13,3 +13,3 @@ import * as React from 'react'; | ||
replace?: (str: string) => boolean; | ||
refuse?: RegExp; | ||
accept?: RegExp; | ||
children: (args: RifmRenderArgs<E>) => React.ReactNode; | ||
@@ -16,0 +16,0 @@ } |
249
src/Rifm.js
@@ -10,3 +10,3 @@ /* @flow */ | ||
replace?: string => boolean, | ||
refuse?: RegExp, | ||
accept?: RegExp, | ||
children: ({ | ||
@@ -20,27 +20,10 @@ value: string, | ||
type State = {| | ||
value: string, | ||
local: boolean, | ||
|}; | ||
export const Rifm = (props: Props) => { | ||
const [, refresh] = React.useReducer(c => c + 1, 0); | ||
const valueRef = React.useRef(null); | ||
export class Rifm extends React.Component<Props, State> { | ||
constructor(props: Props) { | ||
super(props); | ||
this.state = { | ||
value: props.value, | ||
local: true, | ||
}; | ||
} | ||
// state of delete button see comments below about inputType support | ||
const isDeleleteButtonDownRef = React.useRef(false); | ||
_state: ?{| | ||
before: string, | ||
input: HTMLInputElement, | ||
op: boolean, | ||
di: boolean, | ||
del: boolean, | ||
|} = null; | ||
_del: boolean = false; | ||
_handleChange = ( | ||
const onChange = ( | ||
evt: SyntheticInputEvent<HTMLInputElement | HTMLTextAreaElement> | ||
@@ -56,125 +39,147 @@ ) => { | ||
} | ||
// FUTURE: use evt.nativeEvent.inputType for del event, see comments at onkeydown | ||
const stateValue = this.state.value; | ||
let value = evt.target.value; | ||
const input = evt.target; | ||
const op = value.length > stateValue.length; | ||
const del = this._del; | ||
const noOp = stateValue === this.props.format(value); | ||
this.setState({ value, local: true }, () => { | ||
const { selectionStart } = input; | ||
const refuse = this.props.refuse || /[^\d]+/g; | ||
const value = props.value; | ||
const eventValue = evt.target.value; | ||
const before = value.substr(0, selectionStart).replace(refuse, ''); | ||
valueRef.current = [ | ||
eventValue, // eventValue | ||
evt.target, // input | ||
eventValue.length > value.length, // isSizeIncreaseOperation | ||
isDeleleteButtonDownRef.current, // isDeleleteButtonDown | ||
value === props.format(eventValue), // isNoOperation | ||
]; | ||
this._state = { | ||
// The main trick is to update underlying input with non formatted value (= eventValue) | ||
// that allows us to calculate right cursor position after formatting (see getCursorPosition) | ||
// then we format new value and call props.onChange with masked/formatted value | ||
// and finally we are able to set cursor position into right place | ||
refresh(); | ||
}; | ||
// React prints warn on server in non production mode about useLayoutEffect usage | ||
// in both cases it's noop | ||
if (process.env.NODE_ENV === 'production' || typeof window !== 'undefined') { | ||
React.useLayoutEffect(() => { | ||
if (valueRef.current == null) return; | ||
let [ | ||
eventValue, | ||
input, | ||
before, | ||
op, | ||
di: del && noOp, | ||
del, | ||
}; | ||
isSizeIncreaseOperation, | ||
isDeleleteButtonDown, | ||
// No operation means that value itself hasn't been changed, BTW cursor, selection etc can be changed | ||
isNoOperation, | ||
] = valueRef.current; | ||
valueRef.current = null; | ||
if (this.props.replace && this.props.replace(stateValue) && op && !noOp) { | ||
let start = -1; | ||
for (let i = 0; i !== before.length; ++i) { | ||
// this usually occurs on deleting special symbols like ' here 123'123.00 | ||
// in case of isDeleleteButtonDown cursor should move differently vs backspace | ||
const deleteWasNoOp = isDeleleteButtonDown && isNoOperation; | ||
// Create string from only accepted symbols | ||
const clean = str => (str.match(props.accept || /\d/g) || []).join(''); | ||
const valueBeforeSelectionStart = clean( | ||
eventValue.substr(0, input.selectionStart) | ||
).toLowerCase(); | ||
// trying to find cursor position in formatted value having knowledge about valueBeforeSelectionStart | ||
// This works because we assume that format doesn't change the order of accepted symbols. | ||
// Imagine we have formatter which adds ' symbol between numbers, and by default we refuse all non numeric symbols | ||
// for example we had input = 1'2|'4 (| means cursor position) then user entered '3' symbol | ||
// inputValue = 1'23'|4 so valueBeforeSelectionStart = 123 and formatted value = 1'2'3'4 | ||
// calling getCursorPosition("1'2'3'4") will give us position after 3, 1'2'3|'4 | ||
// so for formatting just this function to determine cursor position after formatting is enough | ||
// with masking we need to do some additional checks see `replace` below | ||
const getCursorPosition = val => { | ||
let start = 0; | ||
for (let i = 0; i !== valueBeforeSelectionStart.length; ++i) { | ||
start = Math.max( | ||
start, | ||
value.toLowerCase().indexOf(before[i].toLowerCase(), start + 1) | ||
val.toLowerCase().indexOf(valueBeforeSelectionStart[i], start) + 1 | ||
); | ||
} | ||
return start; | ||
}; | ||
const c = value.substr(start + 1).replace(refuse, '')[0]; | ||
start = value.indexOf(c, start + 1); | ||
// Masking part, for masks if size of mask is above some value (props.replace checks that) | ||
// we need to replace symbols instead of do nothing as like in format | ||
if ( | ||
props.replace && | ||
props.replace(props.value) && | ||
isSizeIncreaseOperation && | ||
!isNoOperation | ||
) { | ||
let start = getCursorPosition(eventValue); | ||
value = `${value.substr(0, start)}${value.substr(start + 1)}`; | ||
const c = clean(eventValue.substr(start))[0]; | ||
start = eventValue.indexOf(c, start); | ||
eventValue = `${eventValue.substr(0, start)}${eventValue.substr( | ||
start + 1 | ||
)}`; | ||
} | ||
const fv = this.props.format(value); | ||
const formattedValue = props.format(eventValue); | ||
if (stateValue === fv) { | ||
this.setState({ value }); | ||
if (props.value === formattedValue) { | ||
// if nothing changed for formatted value, just refresh so props.value will be used at render | ||
refresh(); | ||
} else { | ||
this.props.onChange(fv); | ||
props.onChange(formattedValue); | ||
} | ||
}); | ||
}; | ||
// until https://developer.mozilla.org/en-US/docs/Web/API/InputEvent/inputType will be supported | ||
// by all major browsers (now supported by: +chrome, +safari, ?edge, !firefox) | ||
// there is no way I found to distinguish in onChange | ||
// backspace or delete was called in some situations | ||
// firefox track https://bugzilla.mozilla.org/show_bug.cgi?id=1447239 | ||
_hKD = (evt: KeyboardEvent) => { | ||
if (evt.code === 'Delete') { | ||
this._del = true; | ||
} | ||
}; | ||
return () => { | ||
let start = getCursorPosition(formattedValue); | ||
_hKU = (evt: KeyboardEvent) => { | ||
if (evt.code === 'Delete') { | ||
this._del = false; | ||
} | ||
}; | ||
// Visually improves working with masked values, | ||
// like cursor jumping over refused symbols | ||
// as an example date mask: was "5|1-24-3" then user pressed "6" | ||
// it becomes "56-|12-43" with this code, and "56|-12-43" without | ||
if ( | ||
props.replace && | ||
(isSizeIncreaseOperation || (isDeleleteButtonDown && !deleteWasNoOp)) | ||
) { | ||
while (formattedValue[start] && clean(formattedValue[start]) === '') { | ||
start += 1; | ||
} | ||
} | ||
static getDerivedStateFromProps(props: Props, state: State) { | ||
return { | ||
value: state.local ? state.value : props.value, | ||
local: false, | ||
}; | ||
input.selectionStart = input.selectionEnd = | ||
start + (deleteWasNoOp ? 1 : 0); | ||
}; | ||
}); | ||
} | ||
render() { | ||
const { | ||
_handleChange, | ||
state: { value }, | ||
props: { children }, | ||
} = this; | ||
return children({ value, onChange: _handleChange }); | ||
} | ||
// delete when https://developer.mozilla.org/en-US/docs/Web/API/InputEvent/inputType will be supported by all major browsers | ||
componentWillUnmount() { | ||
document.removeEventListener('keydown', this._hKD); | ||
document.removeEventListener('keyup', this._hKU); | ||
} | ||
// delete when https://developer.mozilla.org/en-US/docs/Web/API/InputEvent/inputType will be supported by all major browsers | ||
componentDidMount() { | ||
document.addEventListener('keydown', this._hKD); | ||
document.addEventListener('keyup', this._hKU); | ||
} | ||
componentDidUpdate() { | ||
const { _state } = this; | ||
if (_state) { | ||
const value = this.state.value; | ||
let start = -1; | ||
for (let i = 0; i !== _state.before.length; ++i) { | ||
start = Math.max( | ||
start, | ||
value.toLowerCase().indexOf(_state.before[i].toLowerCase(), start + 1) | ||
); | ||
React.useEffect(() => { | ||
// until https://developer.mozilla.org/en-US/docs/Web/API/InputEvent/inputType will be supported | ||
// by all major browsers (now supported by: +chrome, +safari, ?edge, !firefox) | ||
// there is no way I found to distinguish in onChange | ||
// backspace or delete was called in some situations | ||
// firefox track https://bugzilla.mozilla.org/show_bug.cgi?id=1447239 | ||
const handleKeyDown = (evt: KeyboardEvent) => { | ||
if (evt.code === 'Delete') { | ||
isDeleleteButtonDownRef.current = true; | ||
} | ||
}; | ||
// format usually looks better without this | ||
if (this.props.replace && (_state.op || (_state.del && !_state.di))) { | ||
while ( | ||
value[start + 1] && | ||
(this.props.refuse || /[^\d]+/).test(value[start + 1]) | ||
) { | ||
start += 1; | ||
} | ||
const handleKeyUp = (evt: KeyboardEvent) => { | ||
if (evt.code === 'Delete') { | ||
isDeleleteButtonDownRef.current = false; | ||
} | ||
}; | ||
_state.input.selectionStart = _state.input.selectionEnd = | ||
start + 1 + (_state.di ? 1 : 0); | ||
} | ||
document.addEventListener('keydown', handleKeyDown); | ||
document.addEventListener('keyup', handleKeyUp); | ||
this._state = null; | ||
} | ||
} | ||
return () => { | ||
document.removeEventListener('keydown', handleKeyDown); | ||
document.removeEventListener('keyup', handleKeyUp); | ||
}; | ||
}, []); | ||
return props.children({ | ||
value: valueRef.current != null ? valueRef.current[0] : props.value, | ||
onChange, | ||
}); | ||
}; |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
39488
39
12
646
60
6
1