fix
This commit is contained in:
parent
70a4422600
commit
7ac64362da
|
|
@ -1,10 +1,11 @@
|
|||
import { useLocal } from "@/utils/use-local";
|
||||
import { FC, useEffect } from "react";
|
||||
import { FC, useEffect, useRef } from "react";
|
||||
import { FMInternal, FMProps } from "./typings";
|
||||
import { formReload } from "./utils/reload";
|
||||
import { formInit } from "./utils/init";
|
||||
import { createPortal } from "react-dom";
|
||||
import { Toaster } from "sonner";
|
||||
import get from "lodash.get";
|
||||
|
||||
export const Form: FC<FMProps> = (props) => {
|
||||
const { PassProp, body } = props;
|
||||
|
|
@ -15,6 +16,9 @@ export const Form: FC<FMProps> = (props) => {
|
|||
formReload(fm);
|
||||
},
|
||||
fields: {},
|
||||
events: {
|
||||
on_change(name: string, new_value: any) {},
|
||||
},
|
||||
submit: null as any,
|
||||
error: {} as any,
|
||||
internal: {
|
||||
|
|
@ -25,6 +29,33 @@ export const Form: FC<FMProps> = (props) => {
|
|||
},
|
||||
},
|
||||
props: {} as any,
|
||||
size: {
|
||||
width: 0,
|
||||
height: 0,
|
||||
field: "full",
|
||||
},
|
||||
});
|
||||
|
||||
const ref = useRef({
|
||||
el: null as null | HTMLFormElement,
|
||||
rob: new ResizeObserver(([e]) => {
|
||||
fm.size.height = e.contentRect.height;
|
||||
fm.size.width = e.contentRect.width;
|
||||
if (fm.status === "ready") fm.status = "resizing";
|
||||
|
||||
if (fm.props.layout === "auto") {
|
||||
if (fm.size.width > 650) {
|
||||
fm.size.field = "half";
|
||||
} else {
|
||||
fm.size.field = "full";
|
||||
}
|
||||
} else {
|
||||
if (fm.props.layout === "1-col") fm.size.field = "full";
|
||||
if (fm.props.layout === "2-col") fm.size.field = "half";
|
||||
}
|
||||
|
||||
fm.render();
|
||||
}),
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
|
|
@ -41,10 +72,45 @@ export const Form: FC<FMProps> = (props) => {
|
|||
}
|
||||
const toaster_el = document.getElementsByClassName("prasi-toaster")[0];
|
||||
|
||||
const childs = get(
|
||||
body,
|
||||
"props.meta.item.component.props.body.content.childs"
|
||||
) as any[];
|
||||
|
||||
if (fm.status === "resizing") return null;
|
||||
|
||||
return (
|
||||
<>
|
||||
<form
|
||||
onSubmit={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
fm.submit();
|
||||
}}
|
||||
ref={(el) => {
|
||||
if (!ref.current.el && el) {
|
||||
ref.current.el = el;
|
||||
ref.current.rob.observe(el);
|
||||
}
|
||||
}}
|
||||
className={cx(
|
||||
"form c-flex-1 c-w-full c-h-full c-relative c-overflow-auto"
|
||||
)}
|
||||
>
|
||||
{toaster_el && createPortal(<Toaster cn={cx} />, toaster_el)}
|
||||
{fm.status !== "init" && <PassProp fm={fm}>{body}</PassProp>}
|
||||
</>
|
||||
<div
|
||||
className={cx(
|
||||
"form-inner c-flex c-flex-1 c-flex-wrap c-items-start c-content-start c-absolute c-inset-0"
|
||||
)}
|
||||
>
|
||||
{fm.status !== "init" &&
|
||||
childs.map((child, idx) => {
|
||||
return (
|
||||
<PassProp fm={fm} key={idx}>
|
||||
{child}
|
||||
</PassProp>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,23 +1,49 @@
|
|||
import { FC } from "react";
|
||||
import { FC, useEffect } from "react";
|
||||
import { FieldProp } from "../typings";
|
||||
import { createField } from "../utils/create-field";
|
||||
import { useField } from "../utils/use-field";
|
||||
import { Label } from "./Label";
|
||||
|
||||
export const Field: FC<FieldProp> = (arg) => {
|
||||
const field = createField(arg);
|
||||
const { fm } = arg;
|
||||
const field = useField(arg);
|
||||
|
||||
const mode = fm.props.label_mode;
|
||||
const w = field.width;
|
||||
|
||||
useEffect(() => {
|
||||
if (field.required && typeof field.required_msg === "function") {
|
||||
const error_msg = field.required_msg(field.name);
|
||||
const error_list = fm.error
|
||||
.get(field.name)
|
||||
.filter((e) => e !== error_msg);
|
||||
if (fm.data[field.name]) {
|
||||
fm.error.set(field.name, [error_msg, ...error_list]);
|
||||
} else {
|
||||
fm.error.set(field.name, error_list);
|
||||
}
|
||||
}
|
||||
|
||||
fm.events.on_change(field.name, fm.data[field.name]);
|
||||
}, [fm.data[field.name]]);
|
||||
|
||||
if (field.status === "init") return null;
|
||||
|
||||
const mode = field.label_mode;
|
||||
return (
|
||||
<div
|
||||
<label
|
||||
className={cx(
|
||||
"field",
|
||||
mode === "horizontal" && "",
|
||||
mode === "vertical" && ""
|
||||
"c-flex",
|
||||
w === "auto" && fm.size.field === "full" && "c-w-full",
|
||||
w === "auto" && fm.size.field === "half" && "c-w-1/2",
|
||||
w === "full" && "c-w-full",
|
||||
w === "½" && "c-w-1/2",
|
||||
w === "⅓" && "c-w-1/3",
|
||||
w === "¼" && "c-w-1/4",
|
||||
mode === "horizontal" && "c-flex-row",
|
||||
mode === "vertical" && "c-flex-col"
|
||||
)}
|
||||
>
|
||||
{mode !== "hidden" && <Label field={field} />}
|
||||
</div>
|
||||
{mode !== "hidden" && <Label field={field} fm={fm} />}
|
||||
</label>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { FC } from "react";
|
||||
import { FMLocal, FieldLocal } from "../typings";
|
||||
|
||||
export const FieldInput: FC<{ field: FieldLocal; fm: FMLocal }> = ({
|
||||
|
|
|
|||
|
|
@ -1,6 +1,21 @@
|
|||
import { FC } from "react";
|
||||
import { FieldLocal } from "../typings";
|
||||
import { FMLocal, FieldLocal } from "../typings";
|
||||
|
||||
export const Label: FC<{ field: FieldLocal }> = ({ field }) => {
|
||||
return <div className={cx("label")}>{field.label}</div>;
|
||||
export const Label: FC<{ field: FieldLocal; fm: FMLocal }> = ({
|
||||
field,
|
||||
fm,
|
||||
}) => {
|
||||
return (
|
||||
<div
|
||||
className={cx(
|
||||
"label",
|
||||
fm.props.label_mode === "horizontal" &&
|
||||
css`
|
||||
width: ${fm.props.label_width}px;
|
||||
`
|
||||
)}
|
||||
>
|
||||
{field.label}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -16,13 +16,14 @@ export type FMProps = {
|
|||
layout: "auto" | "1-col" | "2-col";
|
||||
meta: any;
|
||||
item: any;
|
||||
label_mode: "vertical" | "horizontal" | "hidden";
|
||||
label_width: number;
|
||||
};
|
||||
|
||||
export type FieldProp = {
|
||||
name: string;
|
||||
label: string;
|
||||
desc?: string;
|
||||
label_mode: "vertical" | "horizontal" | "hidden";
|
||||
fm: FMLocal;
|
||||
type:
|
||||
| "text"
|
||||
|
|
@ -39,6 +40,7 @@ export type FieldProp = {
|
|||
| "master-link"
|
||||
| "custom";
|
||||
required: "y" | "n";
|
||||
required_msg: (name: string) => string;
|
||||
options: FieldOptions;
|
||||
on_change: (arg: { value: any }) => void | Promise<void>;
|
||||
PassProp: any;
|
||||
|
|
@ -51,19 +53,23 @@ export type FieldProp = {
|
|||
rel_table: string;
|
||||
rel_fields: string[];
|
||||
rel_query: () => any;
|
||||
width: "auto" | "full" | "½" | "⅓" | "¼";
|
||||
};
|
||||
|
||||
export type FMInternal = {
|
||||
status: "init" | "loading" | "saving" | "ready";
|
||||
status: "init" | "resizing" | "loading" | "saving" | "ready";
|
||||
data: any;
|
||||
reload: () => Promise<void>;
|
||||
submit: () => Promise<void>;
|
||||
events: {
|
||||
on_change: (name: string, new_value: any) => void;
|
||||
};
|
||||
fields: Record<string, FieldLocal>;
|
||||
error: {
|
||||
list: { name: string; error: string }[];
|
||||
set: (name: string, error: string) => void;
|
||||
get: (name: string, error: string) => void;
|
||||
clear: () => void;
|
||||
readonly list: { name: string; error: string[] }[];
|
||||
set: (name: string, error: string[]) => void;
|
||||
get: (name: string) => string[];
|
||||
clear: (name?: string) => void;
|
||||
};
|
||||
internal: {
|
||||
reload: {
|
||||
|
|
@ -73,6 +79,11 @@ export type FMInternal = {
|
|||
};
|
||||
};
|
||||
props: Exclude<FMProps, "body" | "PassProp">;
|
||||
size: {
|
||||
width: number;
|
||||
height: number;
|
||||
field: "full" | "half";
|
||||
};
|
||||
};
|
||||
export type FMLocal = FMInternal & { render: () => void };
|
||||
|
||||
|
|
@ -84,7 +95,9 @@ export type FieldInternal = {
|
|||
desc: FieldProp["desc"];
|
||||
prefix: FieldProp["prefix"];
|
||||
suffix: FieldProp["suffix"];
|
||||
label_mode: FieldProp["label_mode"];
|
||||
width: FieldProp["width"];
|
||||
required: boolean;
|
||||
required_msg: FieldProp["required_msg"];
|
||||
Child: () => ReactNode;
|
||||
};
|
||||
export type FieldLocal = FieldInternal & { render: () => void };
|
||||
|
|
|
|||
|
|
@ -1,10 +1,27 @@
|
|||
import { FMLocal } from "../typings";
|
||||
|
||||
export const formError = (fm: FMLocal) => {
|
||||
const error = {} as FMLocal["error"];
|
||||
error.list = [];
|
||||
error.clear = () => {};
|
||||
error.set = () => {};
|
||||
error.get = () => {};
|
||||
const error = {
|
||||
_internal: {},
|
||||
get list() {
|
||||
const res = Object.entries(this._internal).map(([name, error]) => {
|
||||
return { name, error };
|
||||
});
|
||||
|
||||
return res;
|
||||
},
|
||||
clear(name) {
|
||||
if (name) delete this._internal[name];
|
||||
else this._internal = {};
|
||||
},
|
||||
get(name) {
|
||||
return this._internal[name];
|
||||
},
|
||||
set(name, error) {
|
||||
this._internal[name] = error;
|
||||
},
|
||||
} as FMLocal["error"] & {
|
||||
_internal: Record<string, string[]>;
|
||||
};
|
||||
return error;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { useLocal } from "@/utils/use-local";
|
|||
import { FMLocal, FieldInternal, FieldProp } from "../typings";
|
||||
import { useEffect } from "react";
|
||||
|
||||
export const createField = (arg: FieldProp) => {
|
||||
export const useField = (arg: FieldProp) => {
|
||||
const field = useLocal<FieldInternal>({
|
||||
status: "init",
|
||||
name: arg.name,
|
||||
|
|
@ -11,7 +11,9 @@ export const createField = (arg: FieldProp) => {
|
|||
desc: arg.desc,
|
||||
prefix: arg.prefix,
|
||||
suffix: arg.suffix,
|
||||
label_mode: arg.label_mode,
|
||||
width: arg.width,
|
||||
required: arg.required === "y",
|
||||
required_msg: arg.required_msg,
|
||||
Child: () => {
|
||||
return <arg.PassProp>{arg.child}</arg.PassProp>;
|
||||
},
|
||||
Loading…
Reference in New Issue