Skip to content

Commit

Permalink
feat(angular): create new library @maskito/angular (#35)
Browse files Browse the repository at this point in the history
* feat(angular): create new library `@maskito/angular`

* chore: fix previous review comments
  • Loading branch information
nsbarsukov authored Dec 7, 2022
1 parent ab9c0aa commit 1855c35
Show file tree
Hide file tree
Showing 30 changed files with 359 additions and 264 deletions.
1 change: 1 addition & 0 deletions angular.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"version": 2,
"projects": {
"angular": "projects/angular",
"core": "projects/core",
"demo": "projects/demo",
"kit": "projects/kit"
Expand Down
16 changes: 14 additions & 2 deletions nx.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,25 @@
"config": "project"
},
"@nrwl/angular:library": {
"buildable": true,
"compilationMode": "partial",
"style": "less",
"linter": "none",
"unitTestRunner": "jest",
"buildable": true,
"compilationMode": "partial",
"strict": true,
"skipModule": true,
"standaloneConfig": true
},
"@nrwl/angular:application": {
"style": "less",
"linter": "none",
"unitTestRunner": "jest"
},
"@nrwl/angular:component": {
"style": "less"
}
},
"cli": {
"defaultCollection": "@nrwl/js"
}
}
259 changes: 70 additions & 189 deletions package-lock.json

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,10 @@
"jest-preset-angular": "^11.1.1",
"lint-staged": "^12.3.7",
"ng-packagr": "^12.2.7",
"postcss": "^8.3.9",
"postcss-import": "^14.0.2",
"postcss-preset-env": "^6.7.0",
"postcss-url": "^10.1.1",
"prettier": "^2.6.2",
"standard-version": "^9.3.2",
"ts-jest": "27.0.5",
Expand Down
7 changes: 7 additions & 0 deletions projects/angular/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# angular

This library was generated with [Nx](https://nx.dev).

## Running unit tests

Run `nx test angular` to execute the unit tests.
20 changes: 20 additions & 0 deletions projects/angular/jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
module.exports = {
displayName: 'angular',
preset: '../../jest.preset.js',
setupFilesAfterEnv: ['<rootDir>/src/test-setup.ts'],
globals: {
'ts-jest': {
tsconfig: '<rootDir>/tsconfig.spec.json',
stringifyContentPathRegex: '\\.(html|svg)$',
},
},
coverageDirectory: '../../coverage/projects/angular',
transform: {
'^.+\\.(ts|js|html)$': 'jest-preset-angular',
},
snapshotSerializers: [
'jest-preset-angular/build/serializers/no-ng-attributes',
'jest-preset-angular/build/serializers/ng-snapshot',
'jest-preset-angular/build/serializers/html-comment',
],
};
7 changes: 7 additions & 0 deletions projects/angular/ng-package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"$schema": "../../node_modules/ng-packagr/ng-package.schema.json",
"dest": "../../dist/projects/angular",
"lib": {
"entryFile": "src/index.ts"
}
}
12 changes: 12 additions & 0 deletions projects/angular/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"name": "@maskito/angular",
"version": "0.0.1",
"dependencies": {
"tslib": "^2.3.0"
},
"peerDependencies": {
"@angular/common": ">=12.0.0",
"@angular/core": ">=12.0.0",
"@maskito/core": "^0.0.1"
}
}
33 changes: 33 additions & 0 deletions projects/angular/project.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{
"projectType": "library",
"root": "projects/angular",
"sourceRoot": "projects/angular/src",
"prefix": "maskito",
"targets": {
"build": {
"executor": "@nrwl/angular:ng-packagr-lite",
"outputs": ["dist/projects/angular"],
"options": {
"project": "projects/angular/ng-package.json"
},
"configurations": {
"production": {
"tsConfig": "projects/angular/tsconfig.lib.prod.json"
},
"development": {
"tsConfig": "projects/angular/tsconfig.lib.json"
}
},
"defaultConfiguration": "production"
},
"test": {
"executor": "@nrwl/jest:jest",
"outputs": ["coverage/projects/angular"],
"options": {
"jestConfig": "projects/angular/jest.config.js",
"passWithNoTests": true
}
}
},
"tags": []
}
3 changes: 3 additions & 0 deletions projects/angular/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * from './lib/maskito.directive';
export * from './lib/maskito.module';
export * from './lib/maskito-options';
9 changes: 9 additions & 0 deletions projects/angular/src/lib/maskito-options.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import {InjectionToken} from '@angular/core';
import {MASKITO_DEFAULT_OPTIONS, MaskitoOptions} from '@maskito/core';

export const MASKITO_OPTIONS = new InjectionToken<MaskitoOptions>(
'[MASKITO_OPTIONS] Default parameters for MaskitoDirective',
{
factory: () => MASKITO_DEFAULT_OPTIONS,
},
);
53 changes: 53 additions & 0 deletions projects/angular/src/lib/maskito.directive.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import {Directive, ElementRef, Inject, Input, OnChanges, OnDestroy} from '@angular/core';
import {Maskito, MaskitoOptions} from '@maskito/core';
import {MASKITO_OPTIONS} from './maskito-options';

@Directive({
selector: 'input[maskito], textarea[maskito]',
})
export class MaskitoDirective implements OnChanges, OnDestroy {
private maskedElement: Maskito | null = null;

/**
* When developer has direct access to native input element:
* ```
* <input [maskito]="options" />
* ```
* ___
* When native input element is hidden somewhere deep inside another component:
* ```
* @Component({
* template: `
* <your-custom-input>
* <!-- <input maskito /> is somewhere inside -->
* </your-custom-input>
* `,
* providers: [{provide: MASKITO_OPTIONS, useValue: {mask: /^\d+$/}}],
* })
* export class YourComponent {}
* ```
* WARNING! This approach is acceptable only for the static mask (no mask option changes).
*/
@Input('maskito')
options: MaskitoOptions | '' = this.defaultOptions;

constructor(
@Inject(ElementRef)
private readonly elementRef: ElementRef<HTMLInputElement | HTMLTextAreaElement>,
@Inject(MASKITO_OPTIONS)
private readonly defaultOptions: MaskitoOptions,
) {}

ngOnChanges() {
this.maskedElement?.destroy();

this.maskedElement = new Maskito(
this.elementRef.nativeElement,
this.options || this.defaultOptions,
);
}

ngOnDestroy() {
this.maskedElement?.destroy();
}
}
8 changes: 8 additions & 0 deletions projects/angular/src/lib/maskito.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import {NgModule} from '@angular/core';
import {MaskitoDirective} from './maskito.directive';

@NgModule({
declarations: [MaskitoDirective],
exports: [MaskitoDirective],
})
export class MaskitoModule {}
1 change: 1 addition & 0 deletions projects/angular/src/test-setup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
import 'jest-preset-angular/setup-jest';
24 changes: 24 additions & 0 deletions projects/angular/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"extends": "../../tsconfig.base.json",
"files": [],
"include": [],
"references": [
{
"path": "./tsconfig.lib.json"
},
{
"path": "./tsconfig.spec.json"
}
],
"compilerOptions": {
"forceConsistentCasingInFileNames": true,
"strict": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true
},
"angularCompilerOptions": {
"strictInjectionParameters": true,
"strictInputAccessModifiers": true,
"strictTemplates": true
}
}
14 changes: 14 additions & 0 deletions projects/angular/tsconfig.lib.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../../dist/out-tsc",
"target": "es2015",
"declaration": true,
"declarationMap": true,
"inlineSources": true,
"types": [],
"lib": ["dom", "es2018"]
},
"exclude": ["src/test-setup.ts", "**/*.spec.ts", "**/*.test.ts"],
"include": ["**/*.ts"]
}
10 changes: 10 additions & 0 deletions projects/angular/tsconfig.lib.prod.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/* To learn more about this file see: https://angular.io/config/tsconfig. */
{
"extends": "./tsconfig.lib.json",
"compilerOptions": {
"declarationMap": false
},
"angularCompilerOptions": {
"compilationMode": "partial"
}
}
10 changes: 10 additions & 0 deletions projects/angular/tsconfig.spec.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../../dist/out-tsc",
"module": "commonjs",
"types": ["jest", "node"]
},
"files": ["src/test-setup.ts"],
"include": ["**/*.test.ts", "**/*.spec.ts", "**/*.d.ts"]
}
1 change: 1 addition & 0 deletions projects/core/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export {Maskito} from './lib/mask';
export {MaskitoOptions} from './lib/types';
export {MASKITO_DEFAULT_OPTIONS} from './lib/constants';
1 change: 1 addition & 0 deletions projects/core/src/lib/classes/mask-history.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export abstract class MaskHistory {
protected updateHistory(state: ElementState): void {
if (!this.now) {
this.now = state;

return;
}

Expand Down
8 changes: 8 additions & 0 deletions projects/core/src/lib/constants/default-options.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import {MaskitoOptions} from '@maskito/core';
import {identity} from '../utils';

export const MASKITO_DEFAULT_OPTIONS: Required<MaskitoOptions> = {
mask: /^.*$/,
preprocessor: identity,
postprocessor: identity,
};
1 change: 1 addition & 0 deletions projects/core/src/lib/constants/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './default-options';
37 changes: 16 additions & 21 deletions projects/core/src/lib/mask.ts
Original file line number Diff line number Diff line change
@@ -1,30 +1,21 @@
import {MaskHistory, MaskModel} from './classes';
import {
ElementState,
MaskitoOptions,
MaskPostprocessor,
MaskPreprocessor,
SelectionRange,
} from './types';
import {MASKITO_DEFAULT_OPTIONS} from './constants';
import {ElementState, MaskitoOptions, SelectionRange} from './types';
import {
areElementValuesEqual,
EventListener,
extendToNotEmptyRange,
identity,
isBeforeInputEventSupported,
isEventProducingCharacter,
} from './utils';

export class Maskito extends MaskHistory {
private readonly eventListener = new EventListener(this.element);
private readonly preprocessor: MaskPreprocessor =
this.options.preprocessor || identity;
private readonly postprocessor: MaskPostprocessor =
this.options.postprocessor || identity;
private readonly options = {...MASKITO_DEFAULT_OPTIONS, ...this.maskitoOptions};

constructor(
private readonly element: HTMLInputElement | HTMLTextAreaElement,
private readonly options: MaskitoOptions,
private readonly maskitoOptions: MaskitoOptions,
) {
super();
this.conformValueToMask();
Expand Down Expand Up @@ -124,26 +115,30 @@ export class Maskito extends MaskHistory {
return;
}

return this.handleInsert(event, pressedKey);
this.handleInsert(event, pressedKey);
}

private conformValueToMask(): void {
const {elementState} = this.preprocessor({elementState: this.elementState});
const {elementState} = this.options.preprocessor({
elementState: this.elementState,
});
const maskModel = new MaskModel(elementState, this.options);
const {value, selection} = this.postprocessor(maskModel);
const {value, selection} = this.options.postprocessor(maskModel);

this.updateValue(value);
this.updateSelectionRange(selection);
}

private handleDelete(event: Event, isForward: boolean): void {
const {elementState} = this.preprocessor({elementState: this.elementState});
const {elementState} = this.options.preprocessor({
elementState: this.elementState,
});
const [from, to] = extendToNotEmptyRange(elementState.selection, isForward);
const maskModel = new MaskModel(elementState, this.options);

maskModel.deleteCharacters([from, to]);

const newElementState = this.postprocessor(maskModel);
const newElementState = this.options.postprocessor(maskModel);
const newPossibleValue =
elementState.value.slice(0, from) + elementState.value.slice(to);

Expand All @@ -164,7 +159,7 @@ export class Maskito extends MaskHistory {
}

private handleInsert(event: Event, data: string): void {
const {elementState, data: insertedText = data} = this.preprocessor({
const {elementState, data: insertedText = data} = this.options.preprocessor({
data,
elementState: this.elementState,
});
Expand All @@ -181,7 +176,7 @@ export class Maskito extends MaskHistory {
elementState.value.slice(0, from) +
insertedText +
elementState.value.slice(to);
const {value, selection} = this.postprocessor(maskModel);
const {value, selection} = this.options.postprocessor(maskModel);

if (newPossibleValue !== value) {
event.preventDefault();
Expand All @@ -194,7 +189,7 @@ export class Maskito extends MaskHistory {

private handleEnter(event: Event): void {
if (this.isTextArea) {
return this.handleInsert(event, '\n');
this.handleInsert(event, '\n');
}
}

Expand Down
Loading

0 comments on commit 1855c35

Please sign in to comment.