diff --git a/core/src/components.d.ts b/core/src/components.d.ts
index 4943d8862a..d911a97431 100644
--- a/core/src/components.d.ts
+++ b/core/src/components.d.ts
@@ -17,7 +17,7 @@ import { CheckboxChangeEventDetail } from "./components/checkbox/checkbox-interf
import { ScrollBaseDetail, ScrollDetail } from "./components/content/content-interface";
import { DatetimeChangeEventDetail, DatetimeHighlight, DatetimeHighlightCallback, DatetimePresentation, TitleSelectedDatesFormatter } from "./components/datetime/datetime-interface";
import { SpinnerTypes } from "./components/spinner/spinner-configs";
-import { MaskExpression, MaskPlaceholder, MaskVisibility } from "./utils/input-masking";
+import { MaskExpression, MaskPlaceholder, MaskVisibility } from "./utils/input-masking/public-api";
import { InputChangeEventDetail, InputInputEventDetail } from "./components/input/input-interface";
import { CounterFormatter } from "./components/item/item-interface";
import { MenuChangeEventDetail, Side } from "./components/menu/menu-interface";
@@ -54,7 +54,7 @@ export { CheckboxChangeEventDetail } from "./components/checkbox/checkbox-interf
export { ScrollBaseDetail, ScrollDetail } from "./components/content/content-interface";
export { DatetimeChangeEventDetail, DatetimeHighlight, DatetimeHighlightCallback, DatetimePresentation, TitleSelectedDatesFormatter } from "./components/datetime/datetime-interface";
export { SpinnerTypes } from "./components/spinner/spinner-configs";
-export { MaskExpression, MaskPlaceholder, MaskVisibility } from "./utils/input-masking";
+export { MaskExpression, MaskPlaceholder, MaskVisibility } from "./utils/input-masking/public-api";
export { InputChangeEventDetail, InputInputEventDetail } from "./components/input/input-interface";
export { CounterFormatter } from "./components/item/item-interface";
export { MenuChangeEventDetail, Side } from "./components/menu/menu-interface";
diff --git a/core/src/components/input/input.tsx b/core/src/components/input/input.tsx
index b05fd693ea..9acfcc246a 100644
--- a/core/src/components/input/input.tsx
+++ b/core/src/components/input/input.tsx
@@ -8,7 +8,8 @@ import type { LegacyFormController } from '../../utils/forms';
import { createLegacyFormController } from '../../utils/forms';
import type { Attributes } from '../../utils/helpers';
import { inheritAriaAttributes, debounceEvent, findItemLabel, inheritAttributes } from '../../utils/helpers';
-import type { MaskExpression, MaskPlaceholder, MaskVisibility } from '../../utils/input-masking';
+import { MaskController } from '../../utils/input-masking';
+import type { MaskExpression, MaskPlaceholder, MaskVisibility } from '../../utils/input-masking/public-api';
import { printIonWarning } from '../../utils/logging';
import { createColorClasses, hostContext } from '../../utils/theme';
@@ -32,6 +33,7 @@ export class Input implements ComponentInterface {
private inheritedAttributes: Attributes = {};
private isComposing = false;
private legacyFormController!: LegacyFormController;
+ private maskController?: MaskController;
// This flag ensures we log the deprecation warning at most once.
private hasLoggedDeprecationWarning = false;
@@ -373,9 +375,11 @@ export class Input implements ComponentInterface {
}
componentWillLoad() {
+ const { el } = this;
+
this.inheritedAttributes = {
- ...inheritAriaAttributes(this.el),
- ...inheritAttributes(this.el, ['tabindex', 'title', 'data-form-type']),
+ ...inheritAriaAttributes(el),
+ ...inheritAttributes(el, ['tabindex', 'title', 'data-form-type']),
};
}
@@ -396,7 +400,15 @@ export class Input implements ComponentInterface {
}
componentDidLoad() {
+ const { mask, nativeInput } = this;
+
this.originalIonInput = this.ionInput;
+
+ if (mask !== undefined && nativeInput) {
+ this.maskController = new MaskController(nativeInput, {
+ mask,
+ });
+ }
}
disconnectedCallback() {
@@ -407,6 +419,9 @@ export class Input implements ComponentInterface {
})
);
}
+
+ // Todo - need to evaluate if I need to recreate this in connectedCallback after first load
+ this.maskController?.destroy();
}
/**
diff --git a/core/src/components/input/test/input.spec.tsx b/core/src/components/input/test/input.spec.tsx
index 84637011b8..8eb6417fbc 100644
--- a/core/src/components/input/test/input.spec.tsx
+++ b/core/src/components/input/test/input.spec.tsx
@@ -1,4 +1,5 @@
import { newSpecPage } from '@stencil/core/testing';
+
import { Input } from '../input';
it('should render bottom content when helper text is defined', async () => {
diff --git a/core/src/components/input/test/mask/index.html b/core/src/components/input/test/mask/index.html
new file mode 100644
index 0000000000..7a8354dcae
--- /dev/null
+++ b/core/src/components/input/test/mask/index.html
@@ -0,0 +1,81 @@
+
+
+
+
+ Input - Mask
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Input - Item
+
+
+
+
+
+
+
US Phone Number
+
+
+
+
+
+
+
+
+
+
diff --git a/core/src/interface.d.ts b/core/src/interface.d.ts
index be14e3f26a..7a7aed756d 100644
--- a/core/src/interface.d.ts
+++ b/core/src/interface.d.ts
@@ -43,7 +43,7 @@ export {
AnimationKeyFrames,
AnimationLifecycle,
} from './utils/animation/animation-interface';
-export { MaskExpression, MaskPlaceholder, MaskVisibility } from './utils/input-masking';
+export { MaskExpression, MaskPlaceholder, MaskVisibility } from './utils/input-masking/public-api';
export { HTMLIonOverlayElement, OverlayController, OverlayInterface } from './utils/overlays-interface';
export { Config, config } from './global/config';
export { Gesture, GestureConfig, GestureDetail } from './utils/gesture';
diff --git a/core/src/utils/input-masking/classes/index.ts b/core/src/utils/input-masking/classes/index.ts
new file mode 100644
index 0000000000..5ac33d2a44
--- /dev/null
+++ b/core/src/utils/input-masking/classes/index.ts
@@ -0,0 +1 @@
+export { MaskHistory } from './mask-history';
diff --git a/core/src/utils/input-masking/classes/mask-history.ts b/core/src/utils/input-masking/classes/mask-history.ts
new file mode 100644
index 0000000000..e3bebc45b6
--- /dev/null
+++ b/core/src/utils/input-masking/classes/mask-history.ts
@@ -0,0 +1,63 @@
+import type { ElementState, TypedInputEvent } from '../types/mask-interface';
+
+/**
+ * The mask history class. It is used to store the previous and next states of the element.
+ * @internal
+ *
+ * Original design by Tinkoff:
+ * @see https://github.com/Tinkoff/maskito/blob/main/projects/core/src/lib/classes/mask-history.ts
+ */
+export abstract class MaskHistory {
+ private now: ElementState | null = null;
+ private readonly past: ElementState[] = [];
+ private future: ElementState[] = [];
+
+ protected abstract updateElementState(
+ state: ElementState,
+ eventInit: Pick
+ ): void;
+
+ protected undo(): void {
+ const state = this.past.pop();
+
+ if (state && this.now) {
+ this.future.push(this.now);
+ this.updateElement(state, 'historyUndo');
+ }
+ }
+
+ protected redo(): void {
+ const state = this.future.pop();
+
+ if (state && this.now) {
+ this.past.push(this.now);
+ this.updateElement(state, 'historyRedo');
+ }
+ }
+
+ protected updateHistory(state: ElementState): void {
+ if (!this.now) {
+ this.now = state;
+ return;
+ }
+
+ const isValueChanged = this.now.value !== state.value;
+ const isSelectionChanged = this.now.selection.some((item, index) => item !== state.selection[index]);
+
+ if (!isValueChanged && !isSelectionChanged) {
+ return;
+ }
+
+ if (isValueChanged) {
+ this.past.push(this.now);
+ this.future = [];
+ }
+
+ this.now = state;
+ }
+
+ private updateElement(state: ElementState, inputType: TypedInputEvent['inputType']): void {
+ this.now = state;
+ this.updateElementState(state, { inputType, data: null });
+ }
+}
diff --git a/core/src/utils/input-masking/constants/default-options.ts b/core/src/utils/input-masking/constants/default-options.ts
new file mode 100644
index 0000000000..31b276aad7
--- /dev/null
+++ b/core/src/utils/input-masking/constants/default-options.ts
@@ -0,0 +1,9 @@
+import type { MaskOptions } from '../types/mask-interface';
+import { identity } from '../utils';
+
+export const MASK_DEFAULT_OPTIONS: Required = {
+ mask: /^.*$/,
+ preprocessor: identity,
+ postprocessor: identity,
+ overwriteMode: 'shift',
+};
diff --git a/core/src/utils/input-masking/constants/index.ts b/core/src/utils/input-masking/constants/index.ts
new file mode 100644
index 0000000000..2276938968
--- /dev/null
+++ b/core/src/utils/input-masking/constants/index.ts
@@ -0,0 +1 @@
+export * from './default-options';
diff --git a/core/src/utils/input-masking/dom/event-listener.ts b/core/src/utils/input-masking/dom/event-listener.ts
new file mode 100644
index 0000000000..2f2d11ba7c
--- /dev/null
+++ b/core/src/utils/input-masking/dom/event-listener.ts
@@ -0,0 +1,30 @@
+import type { TypedInputEvent } from '../types/mask-interface';
+
+/**
+ * Event listener utility class that simplifies teardown of
+ * manually added event listeners.
+ * @internal
+ *
+ * Original design by Tinkoff:
+ * @see https://github.com/Tinkoff/maskito/blob/main/projects/core/src/lib/utils/dom/event-listener.ts
+ */
+export class EventListener {
+ private readonly listeners: (() => void)[] = [];
+
+ constructor(private readonly element: HTMLElement) {}
+
+ listen(
+ eventType: E,
+ fn: (event: E extends 'beforeinput' ? TypedInputEvent : HTMLElementEventMap[E]) => unknown,
+ options?: AddEventListenerOptions
+ ): void {
+ const untypedFn = fn as (event: HTMLElementEventMap[E]) => unknown;
+
+ this.element.addEventListener(eventType, untypedFn, options);
+ this.listeners.push(() => this.element.removeEventListener(eventType, untypedFn));
+ }
+
+ destroy(): void {
+ this.listeners.forEach((removeListener) => removeListener());
+ }
+}
diff --git a/core/src/utils/input-masking/dom/index.ts b/core/src/utils/input-masking/dom/index.ts
new file mode 100644
index 0000000000..eef5f8c881
--- /dev/null
+++ b/core/src/utils/input-masking/dom/index.ts
@@ -0,0 +1,3 @@
+export * from './event-listener';
+export * from './is-event-producing-character';
+export * from './is-before-input-event-supported';
diff --git a/core/src/utils/input-masking/dom/is-before-input-event-supported.ts b/core/src/utils/input-masking/dom/is-before-input-event-supported.ts
new file mode 100644
index 0000000000..0e394ac79e
--- /dev/null
+++ b/core/src/utils/input-masking/dom/is-before-input-event-supported.ts
@@ -0,0 +1,14 @@
+/**
+ * "beforeinput" is the appropriate event for preprocessing of the input masking (rather than `keydown`):
+ * - `keydown` is not triggered by predictive text from native mobile keyboards.
+ * - `keydown` is triggered by system key combinations (we don't need them, and they should be manually filtered).
+ * - Dropping text inside input triggers `beforeinput` (but not `keydown`).
+ * ___
+ * "beforeinput" is not supported by Chrome 49+ (only from 60+) and by Firefox 52+ (only from 87+).
+ *
+ * @see https://caniuse.com/?search=beforeinput
+ * @see https://ionicframework.com/docs/reference/browser-support
+ */
+export function isBeforeInputEventSupported(element: HTMLInputElement | HTMLTextAreaElement): boolean {
+ return 'onbeforeinput' in element;
+}
diff --git a/core/src/utils/input-masking/dom/is-event-producing-character.ts b/core/src/utils/input-masking/dom/is-event-producing-character.ts
new file mode 100644
index 0000000000..51057eb958
--- /dev/null
+++ b/core/src/utils/input-masking/dom/is-event-producing-character.ts
@@ -0,0 +1,9 @@
+/**
+ * Checks if the event is a character that should be inserted into the input.
+ */
+export function isEventProducingCharacter({ key, ctrlKey, metaKey, altKey }: KeyboardEvent): boolean {
+ const isSystemKeyCombinations = ctrlKey || metaKey || altKey;
+ const isSingleUnicodeChar = /^.$/u.test(key); // 4-byte characters case (e.g. smile)
+
+ return !isSystemKeyCombinations && key !== 'Backspace' && isSingleUnicodeChar;
+}
diff --git a/core/src/utils/input-masking/index.ts b/core/src/utils/input-masking/index.ts
index 79c5527fd3..cbe72c6a33 100644
--- a/core/src/utils/input-masking/index.ts
+++ b/core/src/utils/input-masking/index.ts
@@ -1 +1 @@
-export * from './input-masking-interface';
+export { MaskController } from './mask-controller';
diff --git a/core/src/utils/input-masking/input-masking-interface.ts b/core/src/utils/input-masking/input-masking-interface.ts
deleted file mode 100644
index 9f2fae70c0..0000000000
--- a/core/src/utils/input-masking/input-masking-interface.ts
+++ /dev/null
@@ -1,5 +0,0 @@
-export type MaskVisibility = 'always' | 'focus' | 'never';
-
-export type MaskExpression = RegExp | (string | RegExp)[];
-
-export type MaskPlaceholder = string | null | undefined;
diff --git a/core/src/utils/input-masking/mask-controller.ts b/core/src/utils/input-masking/mask-controller.ts
new file mode 100644
index 0000000000..ec30a6c136
--- /dev/null
+++ b/core/src/utils/input-masking/mask-controller.ts
@@ -0,0 +1,158 @@
+import { MaskHistory } from './classes';
+import { MASK_DEFAULT_OPTIONS } from './constants';
+import { isBeforeInputEventSupported, isEventProducingCharacter, EventListener } from './dom';
+import type { ElementState, MaskOptions, SelectionRange, TypedInputEvent } from './types/mask-interface';
+import { getNotEmptySelection } from './utils';
+
+/**
+ * The mask controller class. It is used to control the mask of the element.
+ *
+ * @internal
+ *
+ * Original design by Tinkoff:
+ * @see https://github.com/Tinkoff/maskito/blob/main/projects/core/src/lib/mask.ts
+ */
+export class MaskController extends MaskHistory {
+ private readonly eventListener = new EventListener(this.element);
+
+ private readonly options: Required = {
+ ...MASK_DEFAULT_OPTIONS,
+ ...this.maskOptions,
+ };
+
+ constructor(private readonly element: HTMLInputElement, private readonly maskOptions: MaskOptions) {
+ super();
+ console.debug('MaskController', {
+ element,
+ maskOptions,
+ options: this.options,
+ });
+ this.ensureValueFitsMask();
+ this.updateHistory(this.elementState);
+
+ this.eventListener.listen('keydown', (event) => {
+ console.debug('keydown', event);
+ });
+
+ if (isBeforeInputEventSupported(element)) {
+ this.eventListener.listen('beforeinput', (event) => {
+ console.debug('beforeinput', event);
+ });
+ } else {
+ /**
+ * TODO: Remove once Firefox 87+ is the minimum supported version
+ * Also, replace union types `Event | TypedInputEvent` with `TypedInputEvent` inside:
+ *** {@link handleDelete}
+ *** {@link handleInsert}
+ */
+ this.eventListener.listen('keydown', (event) => this.handleKeyDown(event));
+ this.eventListener.listen('paste', (event) =>
+ this.handleInsert(event, event.clipboardData?.getData('text/plain') || '')
+ );
+ }
+
+ this.eventListener.listen('input', () => {
+ console.debug('input');
+ });
+ }
+
+ private get elementState(): ElementState {
+ const { value, selectionStart, selectionEnd } = this.element;
+ return {
+ value,
+ selection: [selectionStart ?? 0, selectionEnd ?? 0],
+ };
+ }
+
+ destroy(): void {
+ // Remove all event listeners
+ this.eventListener.destroy();
+ }
+
+ protected updateElementState(
+ { value, selection }: ElementState,
+ eventInit: Pick = {
+ inputType: 'insertText',
+ data: null,
+ }
+ ): void {
+ const initialValue = this.elementState.value;
+
+ this.updateValue(value);
+ this.updateSelectionRange(selection);
+
+ if (initialValue !== value) {
+ this.dispatchInputEvent(eventInit);
+ }
+ }
+
+ private handleKeyDown(event: KeyboardEvent): void {
+ const pressedKey = event.key;
+ const isForward = pressedKey === 'Delete';
+
+ switch (pressedKey) {
+ case 'Backspace':
+ case 'Delete':
+ return this.handleDelete({
+ event,
+ isForward,
+ selection: getNotEmptySelection(this.elementState, isForward),
+ });
+ }
+
+ if (!isEventProducingCharacter(event)) {
+ return;
+ }
+
+ this.handleInsert(event, pressedKey);
+ }
+
+ private ensureValueFitsMask(): void {
+ // TODO implementation
+ }
+
+ private handleDelete({
+ event,
+ selection,
+ isForward,
+ force = false,
+ }: {
+ event: Event | TypedInputEvent;
+ selection: SelectionRange;
+ isForward: boolean;
+ force?: boolean;
+ }): void {
+ // TODO implementation
+ console.debug('handleDelete', {
+ event,
+ selection,
+ isForward,
+ force,
+ });
+ }
+
+ private handleInsert(event: Event | TypedInputEvent, data: string): void {
+ // TODO implementation
+ console.debug('handleInsert', event, data);
+ }
+
+ private updateSelectionRange([from, to]: SelectionRange): void {
+ // TODO implementation
+ console.debug('updateSelectionRange', [from, to]);
+ }
+
+ private updateValue(newValue: string): void {
+ // TODO implementation
+ console.debug('updateValue', newValue);
+ }
+
+ private dispatchInputEvent(
+ eventInit: Pick = {
+ inputType: 'insertText',
+ data: null,
+ }
+ ): void {
+ // TODO implementation
+ console.debug('dispatchInputEvent', eventInit);
+ }
+}
diff --git a/core/src/utils/input-masking/public-api.ts b/core/src/utils/input-masking/public-api.ts
new file mode 100644
index 0000000000..565d9f4eb1
--- /dev/null
+++ b/core/src/utils/input-masking/public-api.ts
@@ -0,0 +1 @@
+export { MaskVisibility, MaskExpression, MaskPlaceholder } from './types/mask-interface';
diff --git a/core/src/utils/input-masking/types/mask-interface.ts b/core/src/utils/input-masking/types/mask-interface.ts
new file mode 100644
index 0000000000..2ddb4c3ddd
--- /dev/null
+++ b/core/src/utils/input-masking/types/mask-interface.ts
@@ -0,0 +1,53 @@
+export interface TypedInputEvent extends InputEvent {
+ inputType:
+ | 'deleteByCut' // Ctrl (Command) + X
+ | 'deleteContentBackward' // Backspace
+ | 'deleteContentForward' // Delete (Fn + Backspace)
+ | 'deleteHardLineBackward' // Ctrl (Command) + Backspace
+ | 'deleteHardLineForward'
+ | 'deleteSoftLineBackward' // Ctrl (Command) + Backspace
+ | 'deleteSoftLineForward'
+ | 'deleteWordBackward' // Alt (Option) + Backspace
+ | 'deleteWordForward' // Alt (Option) + Delete (Fn + Backspace)
+ | 'historyRedo' // Ctrl (Command) + Shift + Z
+ | 'historyUndo' // Ctrl (Command) + Z
+ | 'insertCompositionText'
+ | 'insertFromDrop'
+ | 'insertFromPaste' // Ctrl (Command) + V
+ | 'insertLineBreak'
+ | 'insertReplacementText'
+ | 'insertText';
+}
+
+export type SelectionRange = [from: number, to: number];
+
+export interface ElementState {
+ readonly value: string;
+ readonly selection: SelectionRange;
+}
+
+export type MaskExpression = (RegExp | string)[] | RegExp;
+
+export type MaskPreprocessor = (
+ _: {
+ elementState: ElementState;
+ data: string;
+ },
+ actionType: 'deleteBackward' | 'deleteForward' | 'insert' | 'validation'
+) => {
+ elementState: ElementState;
+ data?: string;
+};
+
+export type MaskPostprocessor = (elementState: ElementState, initialElementState: ElementState) => ElementState;
+
+export interface MaskOptions {
+ readonly mask: MaskExpression | ((elementState: ElementState) => MaskExpression);
+ readonly preprocessor?: MaskPreprocessor;
+ readonly postprocessor?: MaskPostprocessor;
+ readonly overwriteMode?: 'replace' | 'shift' | ((elementState: ElementState) => 'replace' | 'shift');
+}
+
+export type MaskVisibility = 'always' | 'focus' | 'never';
+
+export type MaskPlaceholder = string | null | undefined;
diff --git a/core/src/utils/input-masking/utils/get-not-empty-selection.spec.ts b/core/src/utils/input-masking/utils/get-not-empty-selection.spec.ts
new file mode 100644
index 0000000000..ea50b08049
--- /dev/null
+++ b/core/src/utils/input-masking/utils/get-not-empty-selection.spec.ts
@@ -0,0 +1,64 @@
+import type { ElementState } from '../types/mask-interface';
+
+import { getNotEmptySelection } from './get-not-empty-selection';
+
+describe('getNotEmptySelection', () => {
+ it('should return the same selection when selection positions are not equal', () => {
+ const elementStateStub: ElementState = {
+ value: 'testValue',
+ selection: [1, 3],
+ };
+
+ expect(getNotEmptySelection(elementStateStub, true)).toEqual(elementStateStub.selection);
+ expect(getNotEmptySelection(elementStateStub, false)).toEqual(elementStateStub.selection);
+ });
+
+ describe('backward direction', () => {
+ it('should not change when start value is 0', () => {
+ const elementStateStub: ElementState = {
+ value: 'testValue',
+ selection: [0, 4],
+ };
+
+ expect(getNotEmptySelection(elementStateStub, false)).toEqual([0, 4]);
+ });
+
+ it('should decrease by one start value', () => {
+ const elementStateStub: ElementState = {
+ value: 'testValue',
+ selection: [1, 1],
+ };
+
+ expect(getNotEmptySelection(elementStateStub, false)).toEqual([0, 1]);
+ });
+ });
+
+ describe('forward direction', () => {
+ it('should increase by one end position, when value`s length is more than the end position', () => {
+ const elementStateStub: ElementState = {
+ value: 'testValue',
+ selection: [2, 2],
+ };
+
+ expect(getNotEmptySelection(elementStateStub, true)).toEqual([2, 3]);
+ });
+
+ it('should return value length as end position, when value`s length is less or equal to the end position ', () => {
+ const elementStateStub: ElementState = {
+ value: 'sx',
+ selection: [4, 4],
+ };
+
+ expect(getNotEmptySelection(elementStateStub, true)).toEqual([2, 2]);
+ });
+
+ it('should increase by one end position, when value`s length equal end position is increased by one', () => {
+ const elementStateStub: ElementState = {
+ value: 'test1',
+ selection: [4, 4],
+ };
+
+ expect(getNotEmptySelection(elementStateStub, true)).toEqual([4, 5]);
+ });
+ });
+});
diff --git a/core/src/utils/input-masking/utils/get-not-empty-selection.ts b/core/src/utils/input-masking/utils/get-not-empty-selection.ts
new file mode 100644
index 0000000000..f1f7e207cd
--- /dev/null
+++ b/core/src/utils/input-masking/utils/get-not-empty-selection.ts
@@ -0,0 +1,19 @@
+import type { ElementState, SelectionRange } from '../types/mask-interface';
+
+/**
+ * Returns a selection that is not empty from the given element state.
+ * @param elementState The element state to get the selection from.
+ * @param isForward Whether the selection is forward or not.
+ * @returns The non-empty selection.
+ */
+export function getNotEmptySelection({ value, selection }: ElementState, isForward: boolean): SelectionRange {
+ const [from, to] = selection;
+
+ if (from !== to) {
+ return [from, to];
+ }
+
+ const notEmptySelection = isForward ? [from, to + 1] : [from - 1, to];
+
+ return notEmptySelection.map((index) => Math.min(Math.max(index, 0), value.length)) as [number, number];
+}
diff --git a/core/src/utils/input-masking/utils/identity.ts b/core/src/utils/input-masking/utils/identity.ts
new file mode 100644
index 0000000000..6589842c03
--- /dev/null
+++ b/core/src/utils/input-masking/utils/identity.ts
@@ -0,0 +1,3 @@
+export function identity(x: T): T {
+ return x;
+}
diff --git a/core/src/utils/input-masking/utils/index.ts b/core/src/utils/input-masking/utils/index.ts
new file mode 100644
index 0000000000..52d5758636
--- /dev/null
+++ b/core/src/utils/input-masking/utils/index.ts
@@ -0,0 +1,2 @@
+export * from './get-not-empty-selection';
+export * from './identity';