Skip to content

Commit

Permalink
feat: support input ref, fix: focus method
Browse files Browse the repository at this point in the history
support input ref, fix: focus method

fix: #529, fix: #250, fix: #524
  • Loading branch information
foxhound87 committed Mar 29, 2023
1 parent 389905d commit e0fd4c2
Show file tree
Hide file tree
Showing 9 changed files with 121 additions and 62 deletions.
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
# 5.6.0 (next)

- Introduced `ref` Field prop. (handle React Refs);
- `ref` is auto-binded with the input when using `bind()` or can be defined/changed with `set()`
- `autoFocus` bheavior changed, now can be defined in field definitions or can be assigned with `set()`.
- `focus()` method reimplemented using auto-ref (fixed).
- Introduced Field `blur()` method (using auto-ref).
- Fix: #529 #250 #524

# 5.5.2 (next)

- Fix: Empty Constructor (was requiring at least an empyt object if used with only class fileds definitions)
Expand Down
53 changes: 28 additions & 25 deletions src/Base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -250,34 +250,37 @@ export default class Base implements BaseInterface {
const initial = this.state.get("current", "props");
const struct = pathToStruct(path);
// try to get props from separated objects
const $try = (prop: string) => {
const _try = (prop: string) => {
const t = _.get(initial[prop], struct);
if ((prop === "input" || prop === "output") && typeof t !== "function")
return undefined;
const isIoProp: boolean = (prop === FieldPropsEnum.input || prop === FieldPropsEnum.output);
if (isIoProp && typeof t !== "function") return undefined;
return t;
};

const props = {
$value: _.get(initial["values"], path),
$label: $try("labels"),
$placeholder: $try("placeholders"),
$default: $try("defaults"),
$initial: $try("initials"),
$disabled: $try("disabled"),
$bindings: $try("bindings"),
$type: $try("types"),
$options: $try("options"),
$extra: $try("extra"),
$related: $try("related"),
$hooks: $try("hooks"),
$handlers: $try("handlers"),
$validatedWith: $try("validatedWith"),
$validators: $try("validators"),
$rules: $try("rules"),
$observers: $try("observers"),
$interceptors: $try("interceptors"),
$input: $try("input"),
$output: $try("output"),
$label: _try("labels"),
$placeholder: _try("placeholders"),
$default: _try("defaults"),
$initial: _try("initials"),
$disabled: _try("disabled"),
$deleted: _try("deleted"),
$type: _try("types"),
$related: _try("related"),
$rules: _try("rules"),
$options: _try("options"),
$bindings: _try("bindings"),
$extra: _try("extra"),
$hooks: _try("hooks"),
$handlers: _try("handlers"),
$validatedWith: _try("validatedWith"),
$validators: _try("validators"),
$observers: _try("observers"),
$interceptors: _try("interceptors"),
$input: _try("input"),
$output: _try("output"),
$autoFocus: _try("autoFocus"),
$ref: _try("ref"),
};

const field = this.state.form.makeField({
Expand Down Expand Up @@ -432,7 +435,7 @@ export default class Base implements BaseInterface {
$container.state.form.$changed ++;
$container.initField($key, $newFieldPath, field, true);
} else if (recursion) {
if (_.has(field, "fields") && !_.isNil(field.fields)) {
if (_.has(field, FieldPropsEnum.fields) && !_.isNil(field.fields)) {
// handle nested fields if defined
this.deepUpdate(field.fields, $path);
} else {
Expand Down Expand Up @@ -649,7 +652,7 @@ export default class Base implements BaseInterface {
container.state.form.$changed ++;

if (this.state.options.get(OptionsEnum.softDelete, this)) {
return this.select(fullpath).set("deleted", true);
return this.select(fullpath).set(FieldPropsEnum.deleted, true);
}

container.each((field) => field.debouncedValidation.cancel());
Expand Down Expand Up @@ -696,7 +699,7 @@ export default class Base implements BaseInterface {

_.merge(this.state.disposers[type], {
[$dkey]:
key === "fields"
key === FieldPropsEnum.fields
? ffn.apply((change: any) => $call(change))
: (fn as any)($instance, key, (change: any) => $call(change)),
});
Expand Down
52 changes: 35 additions & 17 deletions src/Field.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,17 +30,20 @@ const setupFieldProps = (instance: FieldInterface, props: any, data: any) =>
$label: props.$label || (data && data.label) || "",
$placeholder: props.$placeholder || (data && data.placeholder) || "",
$disabled: props.$disabled || (data && data.disabled) || false,
$bindings: props.$bindings || (data && data.bindings) || FieldPropsEnum.default,
$rules: props.$rules || (data && data.rules) || null,
$related: props.$related || (data && data.related) || [],
$deleted: props.$deleted || (data && data.deleted) || false,
$validators: toJS(props.$validators || (data && data.validators) || null),
$validatedWith: props.$validatedWith || (data && data.validatedWith) || FieldPropsEnum.value,
$rules: props.$rules || (data && data.rules) || null,
$bindings: props.$bindings || (data && data.bindings) || FieldPropsEnum.default,
$observers: props.$observers || (data && data.observers) || null,
$interceptors: props.$interceptors || (data && data.interceptors) || null,
$extra: props.$extra || (data && data.extra) || null,
$options: props.$options || (data && data.options) || {},
$hooks: props.$hooks || (data && data.hooks) || {},
$handlers: props.$handlers || (data && data.handlers) || {},
$autoFocus: props.$autoFocus || (data && data.autoFocus) || false,
$ref: props.$ref || (data && data.ref) || undefined,
});

const setupDefaultProp = (
Expand Down Expand Up @@ -91,21 +94,20 @@ export default class Field extends Base implements FieldInterface {
$extra: any;
$related: string[] | undefined;
$validatedWith: string | undefined;

$validators: any[] | undefined;
$rules: string[] | undefined;

$disabled: boolean = false;
$focused: boolean = false;
$blurred: boolean = false;
$deleted: boolean = false;
$autoFocus: boolean = false;
$ref: any = undefined

$clearing: boolean = false;
$resetting: boolean = false;

autoFocus: boolean = false;
showError: boolean = false;

errorSync: string | null = null;
errorAsync: string | null = null;

Expand Down Expand Up @@ -148,14 +150,14 @@ export default class Field extends Base implements FieldInterface {
$deleted: observable,
$clearing: observable,
$resetting: observable,
autoFocus: observable,
showError: observable,
errorSync: observable,
errorAsync: observable,
validationErrorStack: observable,
validationFunctionsData: observable,
validationAsyncData: observable,
files: observable,
autoFocus: computed,
checkValidationErrors: computed,
checked: computed,
value: computed,
Expand Down Expand Up @@ -194,6 +196,7 @@ export default class Field extends Base implements FieldInterface {
clear: action,
reset: action,
focus: action,
blur: action,
showErrors: action,
showAsyncErrors: action,
update: action
Expand All @@ -216,8 +219,8 @@ export default class Field extends Base implements FieldInterface {
this.observeValidationOnBlur();
this.observeValidationOnChange();

this.initMOBXEvent("observers");
this.initMOBXEvent("interceptors");
this.initMOBXEvent(FieldPropsEnum.observers);
this.initMOBXEvent(FieldPropsEnum.interceptors);

this.execHook(FieldPropsEnum.onInit);

Expand Down Expand Up @@ -300,6 +303,14 @@ export default class Field extends Base implements FieldInterface {
return this.$extra;
}

get ref() {
return this.$ref;
}

get autoFocus() {
return this.$autoFocus;
}

get type() {
return toJS(this.$type);
}
Expand Down Expand Up @@ -437,11 +448,8 @@ export default class Field extends Base implements FieldInterface {
FieldPropsEnum.onBlur,
args,
action(() => {
if (!this.$blurred) {
this.$blurred = true;
}

this.$focused = false;
this.$blurred = true;
})
);

Expand Down Expand Up @@ -697,8 +705,15 @@ export default class Field extends Base implements FieldInterface {
}

focus(): void {
this.state.form.each((field: any) => (field.autoFocus = false));
this.autoFocus = true;
if(this.ref && !this.focused) this.ref.focus();
this.$focused = true;
this.$touched = true;
}

blur(): void {
if(this.ref && this.focused) this.ref.blur();
this.$focused = false;
this.$blurred = true;
}

showErrors(show: boolean = true): void {
Expand Down Expand Up @@ -767,14 +782,17 @@ export default class Field extends Base implements FieldInterface {
if (!_.isArray(this[`$${type}`])) return;

let fn: any;
if (type === "observers") fn = this.observe;
if (type === "interceptors") fn = this.intercept;
if (type === FieldPropsEnum.observers) fn = this.observe;
if (type === FieldPropsEnum.interceptors) fn = this.intercept;
// @ts-ignore
this[`$${type}`].map((obj: any) => fn(_.omit(obj, FieldPropsEnum.path)));
}

bind(props = {}) {
return this.state.bindings.load(this, this.bindings, props);
return {
...this.state.bindings.load(this, this.bindings, props),
ref: ($ref) => (this.$ref = $ref),
}
}

update(fields: any): void {
Expand Down
1 change: 1 addition & 0 deletions src/models/FieldInterface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export default interface FieldInterface extends BaseInterface {
validationFunctionsData: any[];
debouncedValidation: any;
autoFocus: boolean;
ref: any;
showError: boolean;
checkValidationErrors: boolean;
checked: any;
Expand Down
11 changes: 10 additions & 1 deletion src/models/FieldProps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,29 @@ export enum FieldPropsEnum {
id = "id",
path = "path",
name = "name",
fields = "fields",
ref= "ref",
type = "type",
value = "value",
initial = "initial",
default = "default",
checked = "checked",
label = "label",
placeholder = "placeholder",
error = "error",
validatedWith = "validatedWith",
validators = "validators",
rules = "rules",
related = "related",
options = "options",
extra = "extra",
bindings = "bindings",
hooks = "hooks",
handlers = "handlers",
error = "error",
input="input",
output="output",
interceptors = "interceptors",
observers = "observers",
// computed
disabled = "disabled",
deleted = "deleted",
Expand Down
21 changes: 13 additions & 8 deletions src/parser.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import _ from "lodash";
import { FieldPropsEnum } from "./models/FieldProps";
import {
$try,
isArrayOfStrings,
Expand All @@ -13,11 +14,10 @@ const defaultClearValue =
({ value = undefined, type = undefined }
: { value: any, type?: string })
: false | any[] | 0 | "" | null | undefined => {
if (type === "date") return null;
if (_.isDate(value)) return null;
if (_.isDate(value) || type === "date") return null;
if (_.isNumber(value) || type === "number") return 0;
if (_.isArray(value)) return [];
if (_.isBoolean(value)) return false;
if (_.isNumber(value)) return 0;
if (_.isString(value)) return "";
return undefined;
};
Expand Down Expand Up @@ -63,7 +63,12 @@ const parseInput = (

const parseArrayProp = (val: any, prop: string, removeNullishValuesInArrays: boolean): any => {
const values = _.values(val);
if (removeNullishValuesInArrays && (prop === "value" || prop === "initial" || prop === "default")) {
const isValProp: boolean =
(prop === FieldPropsEnum.value
|| prop === FieldPropsEnum.initial
|| prop === FieldPropsEnum.default);

if (removeNullishValuesInArrays && isValProp) {
return _.without(values, ...[null, undefined, ""]);
}
return values;
Expand All @@ -73,8 +78,8 @@ const parseCheckArray = (field: any, value: any, prop: string, removeNullishValu
field.hasIncrementalKeys ? parseArrayProp(value, prop, removeNullishValuesInArrays) : value;

const parseCheckOutput = (field: any, prop: string) => {
if (prop === "value" || prop.startsWith("value.")) {
const base = field.$output ? field.$output(field["value"]) : field["value"]
if (prop === FieldPropsEnum.value || prop.startsWith("value.")) {
const base = field.$output ? field.$output(field[FieldPropsEnum.value]) : field[FieldPropsEnum.value]
return prop.startsWith("value.") ? _.get(base, prop.substring(6)) : base
}
return field[prop];
Expand Down Expand Up @@ -126,7 +131,7 @@ const handleFieldsArrayOfObjects = ($fields: any) => {
fields = _.transform(
fields,
($obj, field) => {
if (hasUnifiedProps({ fields: { field } }) && !_.has(field, "name")) return undefined;
if (hasUnifiedProps({ fields: { field } }) && !_.has(field, FieldPropsEnum.name)) return undefined;
return Object.assign($obj, { [field.name]: field });
},
{}
Expand Down Expand Up @@ -303,7 +308,7 @@ const pathToFieldsTree = (
const ss = s.split('.')
let t = fields[0]?.fields
for (let i = 0; i < ss.length; i++) {
t = t?.[ss[i]]?.['fields']
t = t?.[ss[i]]?.[FieldPropsEnum.fields]
if (!t) break;
}
if (t)
Expand Down
24 changes: 19 additions & 5 deletions src/props.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ export const props: PropsGroupsInterface = {
FieldPropsEnum.error,
FieldPropsEnum.deleted,
FieldPropsEnum.disabled,
FieldPropsEnum.autoFocus,
],
handlers: [
FieldPropsEnum.onChange,
Expand Down Expand Up @@ -66,19 +67,32 @@ export const props: PropsGroupsInterface = {
"labels",
"placeholders",
"disabled",
"deleted",
"related",
"options",
"extra",
"bindings",
"types",
"hooks",
"handlers",
"deleted",
"error",
"autoFocus",
"refs"
],
functions: [
FieldPropsEnum.observers,
FieldPropsEnum.interceptors,
FieldPropsEnum.input,
FieldPropsEnum.output,
],
validation: [
FieldPropsEnum.rules,
FieldPropsEnum.validators,
FieldPropsEnum.validatedWith,
],
exceptions: [
FieldPropsEnum.isDirty,
FieldPropsEnum.isPristine
],
functions: ["observers", "interceptors", "input", "output"],
validation: ["rules", "validators", "validatedWith"],
exceptions: ["isDirty", "isPristine"],
types: {
isDirty: "some",
isPristine: "every",
Expand Down
Loading

0 comments on commit e0fd4c2

Please sign in to comment.