Created
February 19, 2019 16:01
-
-
Save JCKodel/a8d86f881658aea7d31ab7b770640056 to your computer and use it in GitHub Desktop.
Stencil/Ionic MaskInput
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { IDictionary } from "../index"; | |
import { InputChangeEventDetail } from "@ionic/core"; | |
export interface ITokens | |
{ | |
pattern?: RegExp; | |
transform?: (v: string) => string; | |
escape?: boolean; | |
} | |
export class MaskInput | |
{ | |
private tokens: IDictionary<ITokens> = | |
{ | |
"#": { pattern: /\d/ }, | |
"X": { pattern: /[0-9a-zA-Z]/ }, | |
"S": { pattern: /[a-zA-Z]/ }, | |
"A": { pattern: /[a-zA-Z]/, transform: (v) => v.toLocaleUpperCase() }, | |
"a": { pattern: /[a-zA-Z]/, transform: (v) => v.toLocaleLowerCase() }, | |
"!": { escape: true }, | |
}; | |
private maskit(value: string, mask: string | string[], masked = true, tokens: IDictionary<ITokens>) | |
{ | |
value = value || ""; | |
mask = mask || ""; | |
let iMask = 0; | |
let iValue = 0; | |
let output = ""; | |
while(iMask < mask.length && iValue < value.length) | |
{ | |
let cMask = mask[iMask]; | |
const masker = tokens[cMask]; | |
const cValue = value[iValue]; | |
if(masker && !masker.escape) | |
{ | |
if(masker.pattern.test(cValue)) | |
{ | |
output += masker.transform ? masker.transform(cValue) : cValue; | |
iMask++; | |
} | |
iValue++; | |
} | |
else | |
{ | |
if(masker && masker.escape) | |
{ | |
iMask++; | |
cMask = mask[iMask]; | |
} | |
if(masked) | |
{ | |
output += cMask; | |
} | |
if(cValue === cMask) | |
{ | |
iValue++; | |
} | |
iMask++; | |
} | |
} | |
let restOutput = ""; | |
while(iMask < mask.length && masked) | |
{ | |
const ccMask = mask[iMask]; | |
if(tokens[ccMask]) | |
{ | |
restOutput = ""; | |
break; | |
} | |
restOutput += ccMask; | |
iMask++; | |
} | |
return output + restOutput; | |
} | |
private masker(value: string, mask: string | string[], masked = true, tokens: IDictionary<ITokens>) | |
{ | |
return Array.isArray(mask) | |
? this.dynamicMask(mask, tokens)(value, mask, masked) | |
: this.maskit(value, mask, masked, tokens); | |
} | |
private dynamicMask(masks: string[], tokens: IDictionary<ITokens>) | |
{ | |
masks = masks.sort((a, b) => a.length - b.length); | |
return (value, mask, masked = true) => | |
{ | |
let i = 0; | |
while(i < masks.length) | |
{ | |
const currentMask = masks[i]; | |
i++; | |
const nextMask = masks[i]; | |
if(!(nextMask && this.maskit(value, nextMask, true, tokens).length > currentMask.length)) | |
{ | |
return this.maskit(value, currentMask, masked, tokens); | |
} | |
} | |
return ""; | |
}; | |
} | |
private event(name: string) | |
{ | |
const evt = document.createEvent("Event"); | |
evt.initEvent(name, true, true); | |
return evt; | |
} | |
public mask(inputElement: HTMLIonInputElement, config: { mask: string | string[], tokens?: IDictionary<ITokens> }) | |
{ | |
if(!config.tokens) | |
{ | |
config.tokens = this.tokens; | |
} | |
inputElement.getInputElement().then((el: HTMLInputElement) => | |
{ | |
el.oninput = (evt) => | |
{ | |
if(!evt.isTrusted) | |
{ | |
return; | |
} | |
let position = el.selectionEnd; | |
const digit = el.value[position - 1]; | |
el.value = this.masker(el.value, config.mask, true, config.tokens); | |
while(position < el.value.length && el.value.charAt(position - 1) !== digit) | |
{ | |
position++; | |
} | |
if(el === document.activeElement) | |
{ | |
el.setSelectionRange(position, position); | |
window.setTimeout(() => | |
{ | |
el.setSelectionRange(position, position); | |
}, 0); | |
} | |
el.dispatchEvent(this.event("input")); | |
inputElement.dispatchEvent(new CustomEvent<InputChangeEventDetail>("ionchange", { detail: { value: el.value } })); | |
}; | |
const newDisplay = this.masker(el.value, config.mask, true, config.tokens); | |
if(newDisplay !== el.value) | |
{ | |
el.value = newDisplay; | |
el.dispatchEvent(this.event("input")); | |
inputElement.dispatchEvent(new CustomEvent<InputChangeEventDetail>("ionchange", { detail: { value: newDisplay } })); | |
} | |
}); | |
} | |
} | |
export default new MaskInput(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
export interface IMoneyOptions | |
{ | |
prefix?: string; | |
suffix?: string; | |
thousands?: string; | |
decimal?: string; | |
precision?: number; | |
} | |
export class MoneyInput | |
{ | |
private defaults: IMoneyOptions = { | |
prefix: "R$ ", | |
suffix: "", | |
thousands: ".", | |
decimal: ",", | |
precision: 2, | |
}; | |
private assign(defaults: IMoneyOptions, extras: IMoneyOptions): IMoneyOptions | |
{ | |
defaults = defaults || {}; | |
extras = extras || {}; | |
return Object.keys(defaults).concat(Object.keys(extras)).reduce((acc, val) => | |
{ | |
acc[val] = extras[val] === undefined ? defaults[val] : extras[val]; | |
return acc; | |
}, {}) as IMoneyOptions; | |
} | |
private format(input: number | string, opt?: IMoneyOptions) | |
{ | |
if(!opt) | |
{ | |
opt = this.defaults; | |
} | |
if(typeof input === "number") | |
{ | |
input = input.toFixed(this.fixed(opt.precision)); | |
} | |
const negative = input.indexOf("-") >= 0 ? "-" : ""; | |
const numbers = this.onlyNumbers(input); | |
const currency = this.numbersToCurrency(numbers, opt.precision); | |
const parts = this.toStr(currency).split("."); | |
let integer = parts[0]; | |
const decimal = parts[1]; | |
integer = this.addThousandSeparator(integer, opt.thousands); | |
return opt.prefix + negative + this.joinIntegerAndDecimal(integer, decimal, opt.decimal) + opt.suffix; | |
} | |
private unformat(input: string, precision: number) | |
{ | |
const negative = input.indexOf("-") >= 0 ? -1 : 1; | |
const numbers = this.onlyNumbers(input); | |
const currency = this.numbersToCurrency(numbers, precision); | |
return parseFloat(currency) * negative; | |
} | |
private onlyNumbers(input: string) | |
{ | |
return this.toStr(input).replace(/\D+/g, "") || "0"; | |
} | |
private fixed(precision: number) | |
{ | |
return this.between(0, precision, 20); | |
} | |
private between(min: number, n: number, max: number) | |
{ | |
return Math.max(min, Math.min(n, max)); | |
} | |
private numbersToCurrency(numbers: string, precision: number) | |
{ | |
const exp = Math.pow(10, precision); | |
const float = parseFloat(numbers) / exp; | |
return float.toFixed(this.fixed(precision)); | |
} | |
private addThousandSeparator(integer: string, separator: string) | |
{ | |
return integer.replace(/(\d)(?=(?:\d{3})+\b)/gm, `$1${separator}`); | |
} | |
private currencyToIntegerAndDecimal(float: number) | |
{ | |
return this.toStr(float).split("."); | |
} | |
private joinIntegerAndDecimal(integer: string, decimal: string, separator: string) | |
{ | |
return decimal ? integer + separator + decimal : integer; | |
} | |
private toStr(value: string | number) | |
{ | |
return value ? value.toString() : ""; | |
} | |
private setCursor(el: HTMLInputElement, position: number) | |
{ | |
const setSelectionRange = () => el.setSelectionRange(position, position); | |
if(el === document.activeElement) | |
{ | |
setSelectionRange(); | |
setTimeout(setSelectionRange, 1); | |
} | |
} | |
private event(name: string) | |
{ | |
const evt = document.createEvent("Event"); | |
evt.initEvent(name, true, true); | |
return evt; | |
} | |
public mask(inputElement: HTMLIonInputElement, config?: IMoneyOptions) | |
{ | |
const opt = this.assign(this.defaults, config); | |
inputElement.getInputElement().then((el: HTMLInputElement) => | |
{ | |
const ai = inputElement as any; | |
ai.input = el; | |
ai.opt = opt; | |
el.oninput = () => | |
{ | |
let positionFromEnd = el.value.length - el.selectionEnd; | |
el.value = this.format(el.value, opt); | |
positionFromEnd = Math.max(positionFromEnd, opt.suffix.length); | |
positionFromEnd = el.value.length - positionFromEnd; | |
positionFromEnd = Math.max(positionFromEnd, opt.prefix.length + 1); | |
this.setCursor(el, positionFromEnd + 1); | |
el.dispatchEvent(this.event("change")); | |
}; | |
el.onfocus = () => | |
{ | |
this.setCursor(el, el.value.length - opt.suffix.length); | |
}; | |
el.oninput(null); | |
el.dispatchEvent(this.event("input")); | |
}); | |
} | |
public getValue(inputElement: HTMLIonInputElement) | |
{ | |
const ai = inputElement as any; | |
const input = ai.input as HTMLInputElement; | |
const opt = ai.opt as IMoneyOptions; | |
const numbers = this.onlyNumbers(input.value); | |
const exp = Math.pow(10, opt.precision); | |
return parseFloat(numbers) / exp; | |
} | |
} | |
export default new MoneyInput(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
MaskInput.mask(this.documentNumberInput, { mask: this.isLegalEntity ? "##.###.###/####-##" : "###.###.###-##" }); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment