@v4fire/client
Version:
V4Fire client core library
429 lines (365 loc) • 10.1 kB
text/typescript
/*!
* V4Fire Client Core
* https://github.com/V4Fire/Client
*
* Released under the MIT license
* https://github.com/V4Fire/Client/blob/master/LICENSE
*/
/**
* [[include:super/i-input-text/README.md]]
* @packageDocumentation
*/
import iWidth from 'traits/i-width/i-width';
import iSize from 'traits/i-size/i-size';
import iInput, {
component,
prop,
system,
computed,
wait,
ModsDecl,
UnsafeGetter
} from 'super/i-input/i-input';
//#if runtime has iInputText/mask
import * as mask from 'super/i-input-text/modules/mask';
//#endif
import { $$ } from 'super/i-input-text/const';
import type { CompiledMask, SyncMaskWithTextOptions, UnsafeIInputText } from 'super/i-input-text/interface';
export * from 'super/i-input/i-input';
export * from 'super/i-input-text/const';
export * from 'super/i-input-text/interface';
export * from 'super/i-input-text/modules/validators';
export { default as TextValidators } from 'super/i-input-text/modules/validators';
export { $$ };
/**
* Superclass to create text inputs
*/
export default class iInputText extends iInput implements iWidth, iSize {
/**
* Initial text value of the input
*/
readonly textProp?: string;
/**
* UI type of the input
* @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/Input#input_types
*/
readonly type: string = 'text';
/**
* Autocomplete mode of the input
* @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/Input#htmlattrdefautocomplete
*/
readonly autocomplete: string = 'off';
/**
* Placeholder text of the input
* @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/Input#htmlattrdefplaceholder
*/
readonly placeholder?: string;
/**
* The minimum text value length of the input.
* The option will be ignored if provided `mask`.
*
* @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/Input#htmlattrdefminlength
*/
readonly minLength?: number;
/**
* The maximum text value length of the input.
* The option will be ignored if provided `mask`.
*
* @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/Input#htmlattrdefmaxlength
*/
readonly maxLength?: number;
/**
* A value of the input's mask.
*
* The mask is used when you need to "decorate" some input value,
* like a phone number or credit card number. The mask can contain terminal and non-terminal symbols.
* The terminal symbols will be shown as they are written.
* The non-terminal symbols should start with `%` and one more symbol. For instance, `%d` means that it can be
* replaced by a numeric character (0-9).
*
* Supported non-terminal symbols:
*
* `%d` - is equivalent RegExp' `\d`
* `%w` - is equivalent RegExp' `\w`
* `%s` - is equivalent RegExp' `\s`
*
* @example
* ```
* < b-input :mask = '+%d% (%d%d%d) %d%d%d-%d%d-%d%d'
* ```
*/
readonly mask?: string;
/**
* A value of the mask placeholder.
* All non-terminal symbols from the mask without the specified value will have this placeholder.
*
* @example
* ```
* /// A user will see an input element with a value:
* /// +_ (___) ___-__-__
* /// When it starts typing, the value will be automatically changed, like,
* /// +7 (49_) ___-__-__
* < b-input :mask = '+%d% (%d%d%d) %d%d%d-%d%d-%d%d' | :maskPlaceholder = '_'
* ```
*/
readonly maskPlaceholder: string = '_';
/**
* Number of mask repetitions.
* This parameter allows you to specify how many times the mask pattern needs to apply to the input value.
* The `true` value means that the pattern can be repeated infinitely.
*
* @example
* ```
* /// A user will see an input element with a value:
* /// _-_
* /// When it starts typing, the value will be automatically changed, like,
* /// 2-3 1-_
* < b-input :mask = '%d-%d' | :maskRepetitions = 2
* ```
*/
readonly maskRepetitionsProp?: number | boolean;
/**
* A delimiter for a mask value. This parameter is used when you are using the `maskRepetitions` prop.
* Every next chunk of the mask will have the delimiter as a prefix.
*
* @example
* ```
* /// A user will see an input element with a value:
* /// _-_
* /// When it starts typing, the value will be automatically changed, like,
* /// 2-3@1-_
* < b-input :mask = '%d-%d' | :maskRepetitions = 2 | :maskDelimiter = '@'
* ```
*/
readonly maskDelimiter: string = ' ';
/**
* A dictionary with RegExp-s as values.
* Keys of the dictionary are interpreted as non-terminal symbols for the component mask, i.e.,
* you can add new non-terminal symbols.
*
* @example
* ```
* < b-input :mask = '%l%l%l' | :regExps = {l: /[a-z]/i}
* ```
*/
readonly regExps?: Dictionary<RegExp>;
override get unsafe(): UnsafeGetter<UnsafeIInputText<this>> {
return Object.cast(this);
}
/**
* Text value of the input
* @see [[iInputText.textStore]]
*/
get text(): string {
const
v = this.field.get<string>('textStore') ?? '';
// If the input is empty, don't return the empty mask
if (this.compiledMask?.placeholder === v) {
return '';
}
return v;
}
/**
* Sets a new text value of the input
* @param value
*/
set text(value: string) {
if (this.mask != null) {
void this.syncMaskWithText(value);
return;
}
this.updateTextStore(value);
}
/**
* True, if the mask is repeated infinitely
*/
get isMaskInfinite(): boolean {
return this.maskRepetitionsProp === true;
}
static override readonly mods: ModsDecl = {
...iWidth.mods,
...iSize.mods,
empty: [
'true',
'false'
],
readonly: [
'true',
['false']
]
};
/**
* Text value store of the input
* @see [[iInputText.textProp]]
*/
protected textStore!: string;
/**
* Object of the compiled mask
* @see [[iInputText.mask]]
*/
protected compiledMask?: CompiledMask;
/**
* Number of mask repetitions
* @see [[iInputText.maskRepetitionsProp]]
*/
protected maskRepetitions: number = 1;
protected override readonly $refs!: {input: HTMLInputElement};
/**
* Selects all content of the input
* @emits `selectText()`
*/
selectText(): CanPromise<boolean> {
const
{input} = this.$refs;
if (input.selectionStart !== 0 || input.selectionEnd !== input.value.length) {
input.select();
this.emit('selectText');
return true;
}
return false;
}
/**
* Clears content of the input
* @emits `clearText()`
*/
clearText(): CanPromise<boolean> {
if (this.text === '') {
return false;
}
if (this.mask != null) {
void this.syncMaskWithText('');
} else {
this.text = '';
}
this.emit('clearText');
return true;
}
/**
* Initializes the component mask
*/
protected initMask(): CanPromise<void> {
return mask.init(this);
}
/**
* Compiles the component mask.
* The method saves the compiled mask object and other properties within the component.
*/
protected compileMask(): CanUndef<CompiledMask> {
if (this.mask == null) {
return;
}
this.compiledMask = mask.compile(this, this.mask);
return this.compiledMask;
}
/**
* Synchronizes the component mask with the specified text value
*
* @param [text] - text to synchronize or a list of Unicode symbols
* @param [opts] - additional options
*/
protected syncMaskWithText(
text: CanArray<string> = this.text,
opts?: SyncMaskWithTextOptions
): CanPromise<void> {
mask.syncWithText(this, text, opts);
}
/**
* Updates the component text store with the provided value
* @param value
*/
protected updateTextStore(value: string): void {
const
{input} = this.$refs;
// Force to set a value to the input
if (Object.isTruly(input)) {
input.value = value;
}
this.field.set('textStore', value);
}
protected override normalizeAttrs(attrs: Dictionary = {}): Dictionary {
attrs = {
...attrs,
type: this.type,
placeholder: this.placeholder,
autocomplete: this.autocomplete,
readonly: Object.parse(this.mods.readonly),
minlength: this.minLength,
maxlength: this.maxLength
};
return attrs;
}
protected override initModEvents(): void {
super.initModEvents();
this.sync.mod('empty', 'text', (v) => v === '');
}
protected mounted(): void {
this.updateTextStore(this.text);
}
/**
* Handler: the input with a mask has lost the focus
*/
protected onMaskBlur(): boolean {
return mask.syncInputWithField(this);
}
/**
* Handler: value of the masked input has been changed and can be saved
*/
protected onMaskValueReady(): boolean {
return mask.saveSnapshot(this);
}
/**
* Handler: there is occurred an input action on the masked input
*/
protected onMaskInput(): Promise<boolean> {
return mask.syncFieldWithInput(this);
}
/**
* Handler: there is occurred a keypress action on the masked input
* @param e
*/
protected onMaskKeyPress(e: KeyboardEvent): boolean {
return mask.onKeyPress(this, e);
}
/**
* Handler: removing characters from the mask via `backspace/delete` buttons
* @param e
*/
protected onMaskDelete(e: KeyboardEvent): boolean {
return mask.onDelete(this, e);
}
/**
* Handler: "navigation" over the mask via "arrow" buttons or click events
* @param e
*/
protected onMaskNavigate(e: KeyboardEvent | MouseEvent): boolean {
return mask.onNavigate(this, e);
}
}