taiga-family / maskito

Collection of libraries to create an input mask which ensures that user types value according to predefined format.
https://maskito.dev
Apache License 2.0
1.42k stars 33 forks source link

šŸš€ - Allow for async maskito element predicate #300

Closed sean-perkins closed 1 year ago

sean-perkins commented 1 year ago

Which package(s) are relevant/related to the feature request?

@maskito/angular, @maskito/react

Description

The predicate behavior for maskitoElement is currently synchronous.

For example:

<my-component [maskito]="maskitoOptions" [maskitoElement]="predicate"></my-component>

Where predicate is typically querying the desired input/textarea node:

readonly predicate: MaskitoElementPredicate = (element) => element.querySelector('input')!;

This works great in a variety of scenarios, but is currently not compatible with Stencil hydrated web components and I would suspect could be problematic in scenarios where the input/textarea is rendered after the host is mounted.

If MaskitoElementPredicate additionally supported Promise<HTMLInputElement | HTMLTextareaElement>, then these scenarios could be supported.

Use cases

  1. Ionic Framework (Angular)

In the Angular implementation of Ionic Framework, we use Stencil web components. The template has not mounted at the time of ngOnChanges firing in the MaskitoDirective.

Desired behavior:

<ion-input [maskito]="maskitoOptions" [maskitoElement]="predicate"></ion-input>
readonly predicate: MaskitoElementPredicate = async element => await element.getInputElement();

getInputElement() is a method on the web component that queries and returns the native input element.

Repo of problematic behavior: https://stackblitz.com/edit/angular-gvhxc7?file=package.json,src%2Fapp%2Fexample.component.ts,src%2Fapp%2Fexample.component.html

You can observe the mask does not initially load. Interacting in the editor will cause the iframe to reload and the mask will work as expected.

Alternatives

  1. Ionic Framework (Angular)

We can manually invoke a new Maskito instance from @maskito/core when we return the result from our web component's API. This however is a more involved approach for Angular developers.

Additional Information

Let me know if this is a feature you would consider. I'm happy to contribute the work involved to support this if so.

nsbarsukov commented 1 year ago

@sean-perkins Sounds great.

Let's extend MaskitoElementPredicate with Promise<HTMLInputElement | HTMLTextareaElement> šŸ‘

I'm happy to contribute the work involved to support this if so.

We'd love all and any contributions! Don't hesitate to open your PR!

waterplea commented 1 year ago

I think it's better to do MaskitoElementPredicate: element => HTMLInputElement | HTMLTextAreaElement and MaskitoElementAsyncPredicate: element => Promise<HTMLInputElement | HTMLTextAreaElement. Also we gotta be wary of race conditions. Perhaps wrapping it into Observable inside would be a good idea to deal with those easily with switchMap and combineLatest can be used to take options changes into account.

nsbarsukov commented 1 year ago

@waterplea I think that the single interface MaskitoElementPredicate is enough.

User (developer who uses Maskito) should not care about the internal implementation of @maskito/angularĀ orĀ @maskito/react. The only required knowledge is the single universal type MaskitoElementPredicate.

const syncPredicate: MaskitoElementPredicate = (element) => element.querySelector('input')!;
const asyncPredicate: MaskitoElementPredicate = async element => await element.getInputElement();

But the main point, that we should add support of async predicate for both @maskito/angularĀ andĀ @maskito/react at the same release (because they use the same type MaskitoElementPredicate).