Skip to content

Commit

Permalink
feat(stark-ui): replace "angular2-text-mask" library by "imaskjs"
Browse files Browse the repository at this point in the history
Add support for `min` and `max` date validation on
typing
for
`starkTimestampMask`
directive.
Add
support for filtering date input when
typing
on
`starkTimestampMask` directive.
Built-in Stark
filters:
-
`OnlyWeekends`
-
`OnlyWeekdays`
BREAKING
CHANGE:
The pipe function
in
`StarkTextMaskConfig`
interface is not
supported anymore
ISSUES CLOSED:
ISSUES CLOSED: NationalBankBelgium#2564
  • Loading branch information
mhenkens committed Jul 27, 2022
1 parent cedddaa commit 40c4413
Show file tree
Hide file tree
Showing 15 changed files with 379 additions and 316 deletions.
2 changes: 0 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -102,8 +102,6 @@
"stylelint": "^13.11.0",
"stylelint-config-prettier": "^8.0.2",
"stylelint-webpack-plugin": "~2.1.0",
"text-mask-addons": "^3.8.0",
"text-mask-core": "^5.1.2",
"tslib": "^2.3.0",
"tslint": "^6.1.3",
"tslint-config-prettier": "^1.18.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,6 @@ export type StarkDatePickerFilter = "OnlyWeekends" | "OnlyWeekdays" | ((date: Da
*/
export type StarkDatePickerMaskConfig = StarkTimestampMaskConfig | boolean;

/**
* Type expected by [StarkDatePickerComponent max]{@link StarkDatePickerComponent#max} and
* [StarkDatePickerComponent min]{@link StarkDatePickerComponent#min} inputs.
*/

/**
* Default date mask configuration used by the {@link StarkDatePickerComponent}
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import {
import { StarkTimestampMaskConfig } from "./timestamp-mask-config.intf";
import { COMPOSITION_BUFFER_MODE } from "@angular/forms";

export type MaskConfigType = StarkTextMaskBaseConfig | StarkNumberMaskConfig | StarkTimestampMaskConfig | string | boolean;
export type StarkMaskConfigType = StarkTextMaskBaseConfig | StarkNumberMaskConfig | StarkTimestampMaskConfig | string | boolean;

/**
* Base class of the InputMask directive
Expand All @@ -35,22 +35,16 @@ export abstract class AbstractStarkTextMaskBaseDirective<
/**
* Configuration object for the mask to be displayed in the input field.
*/
public maskConfig?: MaskConfigType;
/**
* @ignore
*/
private elementRef: ElementRef;
/**
* @ignore
*/
private listener?: EventListener;
public maskConfig?: StarkMaskConfigType;

private shouldShowGuide = false;

/**
*
* @param _renderer - Angular `Renderer2` wrapper for DOM manipulations
* @param _elementRef - Reference to the DOM element where this directive is applied to.
* @param _factory - ´IMaskFactory` for the imaskjs library {@link https://github.com/uNmAnNeR/imaskjs/blob/master/packages/angular-imask/src/imask-factory.ts | imask-factory}
* @param _platformId - Angular ´PlatformId´ needed for imaskJs
* @param _factory - {@link https://github.com/uNmAnNeR/imaskjs/blob/master/packages/angular-imask/src/imask-factory.ts | imask-factory} used by angular-imask to communicate with imaskjs
* @param _platformId - Angular `PLATFORM_ID` which indicates an opaque platform ID about the platform: `browser`, `server`, `browserWorkerApp` or `browserWorkerUi`
* @param _compositionMode - Injected token to control if form directives buffer IME input until the "compositionend" event occurs.
*/
protected constructor(
Expand All @@ -61,104 +55,109 @@ export abstract class AbstractStarkTextMaskBaseDirective<
@Optional() @Inject(COMPOSITION_BUFFER_MODE) _compositionMode: boolean
) {
super(_elementRef, _renderer, _factory, _platformId, _compositionMode);
}

/**
*
*/
// tslint:disable-next-line:contextual-lifecycle
public override ngAfterViewInit(): void {
super.ngAfterViewInit();
if (this.isConfigValid(this.maskConfig)) {
this.imask = this.normalizeMaskConfig(this.maskConfig, this.defaultMask());
this.imask = this.normalizeMaskConfig(this.maskConfig);
}
this.elementRef = _elementRef;
}

/**
* Component lifecycle hook
* the base diretive listen for the change on the imask property.
* if it's needed to rebuild the `imask` property, we rebuild it at add the property `imask` in the changes array.
* The base {@link https://github.com/uNmAnNeR/imaskjs/blob/master/packages/angular-imask/src/imask.directive.ts|IMaskDirective} directive listens for the change on the `imask` property.
* If there is a change on the `maskConfig` input then the `angular-imask` ngOnChanges hook will be triggered
* in order to rebuild the `imask`.
*/
// tslint:disable-next-line:contextual-lifecycle
public override ngOnChanges(changes: SimpleChanges): void {
const maskRefDefine = !!this.maskRef;
// if maskConfig changes then apply the change to the imask and propagate changes.
const unmaskedValue = this.maskRef ? this.maskRef.unmaskedValue : "";
if (this.rebuildMaskNgOnChanges(changes)) {
const oldValue = this.imask;
if (this.isConfigValid(this.maskConfig)) {
this.imask = this.normalizeMaskConfig(this.maskConfig, this.defaultMask());
this.imask = this.normalizeMaskConfig(this.maskConfig);
this.shouldShowGuide = !!this.mergeMaskConfig(this.maskConfig, this.defaultMask)["guide"];
} else {
this.imask = undefined;
this.shouldShowGuide = false;
}
changes = { ...changes, imask: new SimpleChange(oldValue, this.imask, true) };
}
super.ngOnChanges(changes);

// Add input event and must be handled after the maskRef therefor it will be register after an maskRef has been created
if (maskRefDefine !== !!this.maskRef) {
if (maskRefDefine) {
this.elementRef.nativeElement.removeEventListener("input", this.inputAfterMaskRef.bind(this));
} else {
this.elementRef.nativeElement.addEventListener("input", this.inputAfterMaskRef.bind(this));
if (this.maskRef && this.maskRef.unmaskedValue !== unmaskedValue && changes["imask"]) {
this.maskRef.unmaskedValue = unmaskedValue + " ";
if (this.shouldShowGuide && this.maskRef.value) {
this.maskRef.updateOptions({ lazy: true });
}
}
}

/**
* Add the event input listener after the mask has been created
*/
// tslint:disable-next-line:contextual-lifecycle
public override ngAfterViewInit(): void {
super.ngAfterViewInit();
if (this.maskRef) {
this.elementRef.nativeElement.addEventListener("input", this.inputAfterMaskRef.bind(this));
public override _handleInput(event: any): void {
if (!(event instanceof Event) || !event.target) {
return;
}
}

/**
* remove the event listener when destroy the directive
*/
public override ngOnDestroy(): void {
super.ngOnDestroy();
if (this.listener) {
this.elementRef.nativeElement.removeEventListener("input", this.inputAfterMaskRef.bind(this));
let value = event.target["value"];
event.stopImmediatePropagation();
event.stopPropagation();
if (this.maskRef) {
// show the mask before entering value
if (this.shouldShowGuide) {
this.maskRef.updateOptions({ lazy: false });
}
// call the event handler of iMaskJS
event.target["value"] = value;
(<any>this.maskRef)._onInput(event);
if (!this.shouldShowGuide || !this.maskRef.unmaskedValue) {
this.maskRef.updateOptions({ lazy: true });
if (!this.maskRef.unmaskedValue) {
this.maskRef.value = "";
}
}
value = this.maskRef.value;
}
}

public override _handleInput(value: any): void {
super._handleInput(value);
}

/**
* Show the guide after the mask has been updated
* @param _value
*/
public inputAfterMaskRef(_value: any): void {
if (this.isConfigValid(this.maskConfig) && this.maskRef) {
const mergerConfig: MaskConfig = this.mergeMaskConfig(this.maskConfig, this.defaultMask());
if (mergerConfig["guide"]) {
this.maskRef.updateOptions({ lazy: this.maskRef.unmaskedValue === "" });
public override writeValue(value: any): void {
super.writeValue(value);
if (this.maskRef) {
if (this.shouldShowGuide && this.maskRef.unmaskedValue) {
this.maskRef.updateOptions({ lazy: false });
} else {
this.maskRef.updateOptions({ lazy: true });
}
}
}

/**
* Check if it is needed to rebuild the mask on ngOnChanges
* @param changes list of changes provided by the ngOnChange
* @return - true if the mask must be rebuilt
* - false if not
* @protected
* @param changes - The list of changes provided by the `ngOnChange` lifecycle hook
* @return - `true` if the mask must be rebuilt
* - `false` if not
*/
protected rebuildMaskNgOnChanges(changes: SimpleChanges): boolean {
return !!changes["maskConfig"];
}

protected abstract defaultMask(): MaskConfig;
protected abstract defaultMask: MaskConfig;

/**
* merger default mask and current mask and transform option from StarkTextMaskConfig to maskOption for imaskjs
* @param maskConfig
* @param defaultMask
* @protected
* @param maskConfig - The input maskConfig
* @Return - The mask configuration for IMaskJs {@link https://imask.js.org/guide.html}
*/
protected abstract normalizeMaskConfig(maskConfig: MaskConfigType, defaultMask: MaskConfig): Opts;
protected abstract normalizeMaskConfig(maskConfig: StarkMaskConfigType): Opts;

protected abstract mergeMaskConfig(maskConfig: MaskConfigType, defaultMask: MaskConfig): MaskConfig;
protected abstract mergeMaskConfig(maskConfig: StarkMaskConfigType, defaultMask: MaskConfig): MaskConfig;

protected isConfigValid(config: MaskConfigType | undefined): config is MaskConfigType {
protected isConfigValid(config: StarkMaskConfigType | undefined): config is StarkMaskConfigType {
if (!config) {
return false;
}
Expand Down
Loading

0 comments on commit 40c4413

Please sign in to comment.