150 lines
3.7 KiB
TypeScript
Executable File
150 lines
3.7 KiB
TypeScript
Executable File
import { Toaster } from "lib/exports";
|
|
import { ReactNode, useEffect, useRef, useState } from "react";
|
|
import { createPortal } from "react-dom";
|
|
import { FMLocal } from "../typings";
|
|
import { createFm } from "./utils/create-fm";
|
|
import { DivForm } from "./utils/DivForm";
|
|
import { editorFormWidth } from "../Form";
|
|
|
|
export type BaseFormProps<T> = {
|
|
name: string;
|
|
data: T;
|
|
className?: string;
|
|
onField?: () => void;
|
|
onSubmit?: (arg: { fm: FMLocal }) => Promise<boolean> | boolean;
|
|
onChange?: (fm: FMLocal, name: string, new_value: any) => any;
|
|
children: ReactNode | ((arg: { fm: FMLocal }) => ReactNode);
|
|
tag?: "form" | "div" | "blank";
|
|
};
|
|
|
|
export const BaseForm = <T extends Record<string, any>>({
|
|
tag,
|
|
data,
|
|
children,
|
|
name,
|
|
onSubmit,
|
|
className,
|
|
}: BaseFormProps<T>) => {
|
|
const render = useState({})[1];
|
|
const local = useRef({
|
|
fm: null as null | FMLocal,
|
|
el: null as null | HTMLFormElement,
|
|
rob: new ResizeObserver(async ([e]) => {
|
|
measureSize(name, fm, e.target as HTMLFormElement);
|
|
}),
|
|
}).current;
|
|
|
|
if (!local.fm) {
|
|
local.fm = createFm({
|
|
data,
|
|
onSubmit,
|
|
render() {
|
|
render({});
|
|
},
|
|
});
|
|
}
|
|
|
|
useEffect(() => {
|
|
if (local.fm && local.fm.data !== data) {
|
|
for (const k of Object.keys(local.fm.data)) {
|
|
delete local.fm.data[k];
|
|
}
|
|
for (const [k, v] of Object.entries(data)) {
|
|
local.fm.data[k] = v;
|
|
}
|
|
local.fm.render();
|
|
}
|
|
return () => {
|
|
delete (local as any).fm;
|
|
};
|
|
}, [data]);
|
|
|
|
const fm = local.fm;
|
|
|
|
let child = null;
|
|
if (children) {
|
|
if (typeof children === "function") {
|
|
child = children({ fm: local.fm });
|
|
} else {
|
|
child = children;
|
|
}
|
|
}
|
|
|
|
if (document.getElementsByClassName("prasi-toaster").length === 0) {
|
|
const elemDiv = document.createElement("div");
|
|
elemDiv.className = "prasi-toaster";
|
|
document.body.appendChild(elemDiv);
|
|
}
|
|
const toaster_el = document.getElementsByClassName("prasi-toaster")[0];
|
|
|
|
if (tag === "blank") {
|
|
return child;
|
|
}
|
|
|
|
return (
|
|
<DivForm
|
|
tag={tag || "form"}
|
|
onSubmit={(e) => {
|
|
e.preventDefault();
|
|
}}
|
|
ref={(el) => {
|
|
if (el) {
|
|
if (!local.el && fm) {
|
|
local.el = el;
|
|
measureSize(name, fm, el);
|
|
local.rob.observe(el);
|
|
}
|
|
}
|
|
}}
|
|
className={cx(
|
|
"form c-flex-1 c-flex c-flex-col c-w-full c-h-full c-relative c-overflow-auto",
|
|
className
|
|
)}
|
|
>
|
|
{toaster_el && createPortal(<Toaster />, toaster_el)}
|
|
<div
|
|
className={cx(
|
|
"form-inner c-flex-1 c-flex c-flex-row c-flex-wrap c-items-start c-content-start c-absolute c-inset-0",
|
|
css`
|
|
padding-right: 10px;
|
|
`
|
|
)}
|
|
>
|
|
{fm.size && fm.size.width > 0 && <>{child}</>}
|
|
</div>
|
|
</DivForm>
|
|
);
|
|
};
|
|
|
|
const measureSize = (name: string, fm: FMLocal, e: HTMLFormElement) => {
|
|
if (!fm.size) {
|
|
fm.size = { field: "full", width: 0, height: 0 };
|
|
}
|
|
|
|
if (e.clientWidth > 0 && fm && fm.size) {
|
|
fm.size.height = e.clientHeight;
|
|
fm.size.width = e.clientWidth;
|
|
|
|
if (fm.props?.layout === "auto" || !fm.props?.layout) {
|
|
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";
|
|
}
|
|
|
|
if (isEditor) {
|
|
editorFormWidth[name] = {
|
|
w: fm.size.width,
|
|
f: fm.size.field,
|
|
};
|
|
}
|
|
fm.status = "ready";
|
|
|
|
fm.render();
|
|
}
|
|
};
|