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