Skip to content

Instantly share code, notes, and snippets.

@JCKodel
Created February 19, 2019 16:01
Show Gist options
  • Save JCKodel/a8d86f881658aea7d31ab7b770640056 to your computer and use it in GitHub Desktop.
Save JCKodel/a8d86f881658aea7d31ab7b770640056 to your computer and use it in GitHub Desktop.
Stencil/Ionic MaskInput
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();
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();
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