Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: new Time-mask (kit) + new overwriteMode-mode (core) #37

Merged
merged 5 commits into from
Dec 14, 2022
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/deploy-preview.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ jobs:
- name: Setup Node.js and Cache
uses: ./.github/actions/nodejs

- run: npx nx build --base-href='/'
- run: npm run build

- name: Debug output
run: tree dist/demo/browser -P '*.html'
Expand Down
11 changes: 11 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@
"@taiga-ui/core": "^3.11.0",
"@taiga-ui/icons": "^3.11.0",
"@taiga-ui/kit": "^3.11.0",
"@taiga-ui/styles": "^3.11.0",
"core-js": "^2.6.12",
"express": "^4.17.3",
"rxjs": "~6.6.7",
Expand Down
11 changes: 8 additions & 3 deletions projects/core/src/lib/classes/mask-model/mask-model.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {ElementState, MaskExpression, MaskitoOptions, SelectionRange} from '../../types';
import {removeFixedMaskCharacters} from './utils/remove-fixed-mask-characters';
import {applyOverwriteMode} from './utils/apply-overwrite-mode';
import {calibrateValueByMask} from './utils/calibrate-value-by-mask';
import {removeFixedMaskCharacters} from './utils/remove-fixed-mask-characters';

export class MaskModel implements ElementState {
value = '';
Expand Down Expand Up @@ -31,7 +32,11 @@ export class MaskModel implements ElementState {
{value, selection},
maskExpression,
);
const [unmaskedFrom, unmaskedTo] = unmaskedElementState.selection;
const [unmaskedFrom, unmaskedTo] =
newCharacters.length <= 1
? applyOverwriteMode(unmaskedElementState, this.maskOptions.overwriteMode)
.selection
: unmaskedElementState.selection;
const newUnmaskedValue =
unmaskedElementState.value.slice(0, unmaskedFrom) +
newCharacters +
Expand Down Expand Up @@ -61,7 +66,7 @@ export class MaskModel implements ElementState {
}

if (from === to) {
throw new Error('MaskModel.removeCharacters() accepts only not-empty range');
return;
}

const {maskExpression, value} = this;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import {ElementState, MaskitoOptions} from '../../../types';

export function applyOverwriteMode(
{value, selection}: ElementState,
mode: MaskitoOptions['overwriteMode'],
): ElementState {
const [from, to] = selection;

if (from !== to || to >= value.length) {
return {value, selection};
}

return {value, selection: mode === 'replace' ? [from, to + 1] : [from, to]};
}
1 change: 1 addition & 0 deletions projects/core/src/lib/constants/default-options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@ export const MASKITO_DEFAULT_OPTIONS: Required<MaskitoOptions> = {
mask: /^.*$/,
preprocessor: identity,
postprocessor: identity,
overwriteMode: 'shift',
};
25 changes: 18 additions & 7 deletions projects/core/src/lib/mask.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,8 @@ export class Maskito extends MaskHistory {
private conformValueToMask(): void {
const {elementState} = this.options.preprocessor({
elementState: this.elementState,
data: '',
actionType: 'validation',
});
const maskModel = new MaskModel(elementState, this.options);
const {value, selection} = this.options.postprocessor(maskModel);
Expand All @@ -130,25 +132,35 @@ export class Maskito extends MaskHistory {
}

private handleDelete(event: Event, isForward: boolean): void {
const initialState: ElementState = {
value: this.elementState.value,
selection: extendToNotEmptyRange(this.elementState.selection, isForward),
};
const [initialFrom, initialTo] = initialState.selection;
const {elementState} = this.options.preprocessor({
elementState: this.elementState,
elementState: initialState,
data: '',
actionType: isForward ? 'deleteForward' : 'deleteBackward',
});
const [from, to] = extendToNotEmptyRange(elementState.selection, isForward);
const maskModel = new MaskModel(elementState, this.options);
const [from, to] = elementState.selection;

maskModel.deleteCharacters([from, to]);

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

if (newPossibleValue === newElementState.value) {
return;
}

event.preventDefault();

if (areElementValuesEqual(elementState, maskModel, newElementState)) {
if (
areElementValuesEqual(initialState, elementState, maskModel, newElementState)
nsbarsukov marked this conversation as resolved.
Show resolved Hide resolved
) {
// User presses Backspace/Delete for the fixed value
return this.updateSelectionRange(isForward ? [to, to] : [from, from]);
}
Expand All @@ -162,6 +174,7 @@ export class Maskito extends MaskHistory {
const {elementState, data: insertedText = data} = this.options.preprocessor({
data,
elementState: this.elementState,
actionType: 'insert',
});
const maskModel = new MaskModel(elementState, this.options);

Expand All @@ -173,9 +186,7 @@ export class Maskito extends MaskHistory {

const [from, to] = elementState.selection;
const newPossibleValue =
elementState.value.slice(0, from) +
insertedText +
elementState.value.slice(to);
elementState.value.slice(0, from) + data + elementState.value.slice(to);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the difference between insertedText and data in this case?

Copy link
Member Author

@nsbarsukov nsbarsukov Dec 13, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • data - inserted characters BEFORE preprocessing (raw data from Event.data).
  • insertedText - inserted characters AFTER preprocessing

We calculate newPossibleValue to decide if we have to prevent native event? Will natively typed character make mask valid (without programatic interference)?

It is especially useful for RegExp-mask (without fixed characters masks).
The majority of all typed characters can be inserted natively (without programatic value patching) ?

const {value, selection} = this.options.postprocessor(maskModel);

if (newPossibleValue !== value) {
Expand Down
1 change: 1 addition & 0 deletions projects/core/src/lib/types/mask-options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ export interface MaskitoOptions {
mask: MaskExpression | (() => MaskExpression);
preprocessor?: MaskPreprocessor;
postprocessor?: MaskPostprocessor;
overwriteMode?: 'shift' | 'replace';
}
6 changes: 5 additions & 1 deletion projects/core/src/lib/types/mask-processors.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import {ElementState} from './element-state';

export type MaskPreprocessor = (_: {elementState: ElementState; data?: string}) => {
export type MaskPreprocessor = (_: {
elementState: ElementState;
data: string;
actionType: 'insert' | 'deleteForward' | 'deleteBackward' | 'validation';
}) => {
elementState: ElementState;
data?: string;
};
Expand Down
Loading