Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
4 changes: 2 additions & 2 deletions src/FormGroup/FormGroup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,12 +51,12 @@ export class FormGroup extends React.Component<FormGroupProps, FormGroupState> {
this.context.onUnmount(this.props.name);
}

public handleChange = (value: any) => this.context.onChange(this.props.name, value);
public handleChange = async (value: any) => this.context.onChange(this.props.name, value);

public handleBlur = () => this.setState({isFocused: false});
public handleFocus = () => this.setState({isFocused: true});

public handleMount = (ref: HTMLElement) => this.context.onMount(this.props.name, ref);
public handleMount = async (ref: HTMLElement) => this.context.onMount(this.props.name, ref);

public get value(): ModelValue | undefined {
return this.context.values.find(
Expand Down
1 change: 1 addition & 0 deletions src/Input/BaseInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export class BaseInput<T extends HTMLElement> extends React.Component<React.HTML
ref: this.context.onMount,

name: this.context.name,
value: this.context.value,

onChange: this.handleChange,
onBlur: this.handleBlur,
Expand Down
2 changes: 0 additions & 2 deletions src/Input/Input.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
import * as React from "react";
import {FormGroupContext, FormGroupContextTypes} from "../FormGroup/FormGroupContext";
import {InputContext, InputContextTypes} from "./InputContext";
import {BaseInput} from "./BaseInput";

export class Input extends BaseInput<HTMLInputElement> {
Expand Down
4 changes: 2 additions & 2 deletions src/Input/InputContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@ export interface InputContext {
name: string;
value: any;

onChange: (value: any) => void;
onChange: (value: any) => Promise<void>;
onFocus: () => void;
onBlur: () => void;
onMount: (ref: HTMLElement) => void;
onMount: (ref: HTMLElement) => Promise<void>;
}

export const InputContextTypes = {
Expand Down
15 changes: 15 additions & 0 deletions src/MultiplePatternInput/MultiplePatternInputContext.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import * as PropTypes from "prop-types";

export interface MultiplePatternInputContext {
onChange: (event: string) => void
onFocus: () => void
onBlur: () => void
onMount: (ref: HTMLInputElement) => void
}

export const MultiplePatternInputContextTypes = {
onChange: PropTypes.func.isRequired,
onFocus: PropTypes.func.isRequired,
onBlur: PropTypes.func.isRequired,
onMount: PropTypes.func.isRequired,
};
19 changes: 19 additions & 0 deletions src/MultiplePatternInput/MultiplePatternInputProps.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import * as React from "react";
import * as PropTypes from "prop-types";
import {Pattern} from "./Pattern";

export interface MultiplePatternInputProps extends React.HTMLProps<HTMLElement> {
patterns: Array<string|Pattern>;
}

export const MultiplePatternInputPropTypes = {
patterns: PropTypes.oneOfType([
PropTypes.instanceOf(RegExp),
PropTypes.arrayOf(
PropTypes.oneOfType([
PropTypes.instanceOf(Pattern),
PropTypes.string,
])
),
]),
};
209 changes: 209 additions & 0 deletions src/MultiplePatternInput/MultlplePatternInput.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
import * as React from "react";
import * as PropTypes from "prop-types";
import {Input} from "../Input/Input";
import {MultiplePatternInputProps, MultiplePatternInputPropTypes} from "./MultiplePatternInputProps";
import {InputContext, InputContextTypes} from "../Input/InputContext";
import {MultiplePatternInputContext, MultiplePatternInputContextTypes} from "./MultiplePatternInputContext";
import {Pattern} from "./Pattern";

export class MultiplePatternInput extends React.Component<MultiplePatternInputProps> {
public static propTypes = MultiplePatternInputPropTypes;
public static childContextTypes = MultiplePatternInputContextTypes;
public static contextTypes = InputContextTypes;
public context: InputContext;

protected input: HTMLInputElement;

public escapeRegExp(text) {
return text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
}

public get patternsLength() {
return this.props.patterns
.reduce((carry, current) => {
return carry + current.length;
}, 0);
}

public get patternsGroup() {
return new RegExp(
"["
+ this.props.patterns
.filter((pattern) => pattern instanceof Pattern)
.reduce((carry: string, current: Pattern) => {
return carry + current.regex.source;
}, "")
+ "]"
);
}

public getChildContext(): MultiplePatternInputContext {
return {
onChange: this.handleChange,
onFocus: this.handleFocus,
onBlur: this.handleBlur,
onMount: this.handleMount,
};
}

public render(): JSX.Element {
return <Input/>;
}

protected handleMount = async (ref: HTMLElement) => {
this.input = ref as HTMLInputElement;
await this.context.onMount(ref);
};

protected handleChange = async (sourceValue: string) => {
if (sourceValue === "") {
return;
}

const value = this.clearValue(sourceValue);
let targetValue = "";

for (const char of value || [""]) {
const result = this.process(targetValue + char, sourceValue);
if (result === false) {
continue;
}
targetValue = result;
}

const cursorPosition = this.getCursorPosition(targetValue);

await this.context.onChange(targetValue.substr(0, this.patternsLength));

if (this.input) {
this.input.setSelectionRange(cursorPosition, cursorPosition);
}
};

protected process = (value: string, sourceValue: string) => {
let targetPattern = /^/;
let endOfStringReached = false;

const patterns: Array<string | Pattern> = this.props.patterns;

for (const pattern of patterns) {
if (!(pattern instanceof Pattern)) {
if (value === "") {
return pattern;
}

const prevPattern = targetPattern;
/* Update target pattern using passed string. */
targetPattern = new RegExp(targetPattern.source + this.escapeRegExp(pattern));

/* If value can be concatenated, then there is no more characters in target value. */
/* Add passed string and call onChange. */
if (endOfStringReached) {
if (
sourceValue.length < this.context.value.length
&& this.context.value.endsWith(pattern)
) {
return sourceValue.substr(0, this.context.value.length - pattern.length - 1);
}
return value + pattern;
}
/* If passed string was ignored, add it to the target value. */
if (!value.match(targetPattern) && !(`${value}${pattern}`).match(targetPattern)) {
/* Get string that satisfies the prev pattern. */
const match = value.match(prevPattern);
const {length} = match[0];
/* Insert passed string on position that equals to length of matched string. */
value = value.slice(0, length) + pattern + value.slice(length);
}

continue;
}

for (let i = 1; i <= pattern.length; i++) {
if (endOfStringReached) {
return value;
}

targetPattern = new RegExp(targetPattern.source + pattern.regex.source);
if (!value.match(targetPattern)) {
return false;
}

if (value.match(new RegExp(targetPattern.source + "$"))) {
endOfStringReached = true;
}
}
}

return value;
};

protected getCursorPosition = (value: string): number => {
const currentLength = this.context.value.length;

let start: any = false;

for (let i = 0; i < currentLength; i++) {
if (value[i] !== this.context.value[i]) {
start = i;
break;
}
}
const maxLength = Math.max(currentLength, value.length);
if (start === false) {
return maxLength;
}

let count = 1;
if (value.length < currentLength) {
return start;
}

while (
(value[start + count] !== this.context.value[start])
&& (start + count < maxLength)
) {
count++;
}
return start + count;
};

protected clearValue = (value: string) => {
for (const pattern of this.props.patterns) {
if (pattern instanceof Pattern) {
continue;
}

for (const char of pattern) {
const patternIndex = value.indexOf(char);
if (patternIndex === -1) {
break;
}
value = value.substr(0, patternIndex) + value.substr(patternIndex + 1);
}
}
return value;
};

protected handleFocus = async () => {
if (
this.props.patterns[0] instanceof Pattern
|| !!this.context.value
) {
return;
}

await this.context.onChange(this.props.patterns[0].toString());
};

protected handleBlur = async () => {
if (
this.props.patterns[0] instanceof Pattern
|| this.context.value !== this.props.patterns[0]
) {
return;
}

await this.context.onChange("");
};
}
14 changes: 14 additions & 0 deletions src/MultiplePatternInput/Pattern.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
export interface PatternInterface {
regex: RegExp;
length: number;
}

export class Pattern implements PatternInterface {
public regex: RegExp;
public length: number;

constructor(regex, length) {
this.regex = regex;
this.length = length;
}
}
4 changes: 4 additions & 0 deletions src/MultiplePatternInput/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export * from "./MultlplePatternInput";
export * from "./MultiplePatternInputContext";
export * from "./MultiplePatternInputProps";
export * from "./Pattern";
46 changes: 46 additions & 0 deletions src/PatternInput/PatternInput.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import * as React from "react";
import * as PropTypes from "prop-types";
import {Input} from "../Input/Input";
import {PatternInputProps, PatternInputPropTypes} from "./PatternInputProps";
import {PatternInputContext, PatternInputContextTypes} from "./PatternInputContext";
import {InputContext, InputContextTypes} from "../Input/InputContext";

export class PatternInput extends React.Component<PatternInputProps> {
public static propTypes = PatternInputPropTypes;

public static childContextTypes = PatternInputContextTypes;
public static contextTypes = InputContextTypes;
public context: InputContext;

public getChildContext(): PatternInputContext {
return {
onChange: this.handleChange,
};
}

protected get regex(): RegExp {
let {regex} = this.props;

if (regex.source[0] !== "^") {
regex = new RegExp("^" + regex.source);
}
if (regex.source[regex.source.length] !== "$") {
regex = new RegExp(regex.source + "$");
}

return regex;
}

public render(): JSX.Element {
const {regex, ...childProps} = this.props;
return <Input {...childProps as any}/>;
}

protected handleChange = (value: string) => {
if (value && !value.match(this.regex)) {
return;
}

this.context.onChange(value);
};
}
9 changes: 9 additions & 0 deletions src/PatternInput/PatternInputContext.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import * as PropTypes from "prop-types";

export interface PatternInputContext {
onChange: (event: string) => void
}

export const PatternInputContextTypes = {
onChange: PropTypes.func.isRequired,
};
10 changes: 10 additions & 0 deletions src/PatternInput/PatternInputProps.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import * as React from "react";
import * as PropTypes from "prop-types";

export interface PatternInputProps extends React.HTMLProps<HTMLElement> {
regex: RegExp;
}

export const PatternInputPropTypes = {
regex: PropTypes.instanceOf(RegExp),
};
2 changes: 2 additions & 0 deletions src/PatternInput/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from "./PatternInput";
export * from "./PatternInputProps";
6 changes: 3 additions & 3 deletions tests/bootstrap.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import * as chai from 'chai';
import * as chaiEnzyme from 'chai-enzyme';
import * as chai from "chai";
import * as chaiEnzyme from "chai-enzyme";

chai.use(chaiEnzyme());
chai.use(chaiEnzyme());
Loading