fix lib
This commit is contained in:
parent
c35c4c41f8
commit
cf2b1c02e5
|
|
@ -18,7 +18,7 @@ export function AutoHeightTextarea({
|
||||||
paddingBottom,
|
paddingBottom,
|
||||||
paddingTop,
|
paddingTop,
|
||||||
} = window.getComputedStyle(ref.current);
|
} = window.getComputedStyle(ref.current);
|
||||||
ref.current.style.height = lineHeight; // set height temporarily to a single row to obtain scrollHeight, disregarding empty space after text (otherwise, scrollHeight would be equal to the height of the element) - this solves auto-shrinking of the textarea (it's not needed for auto-growing it)
|
ref.current.style.minHeight = lineHeight; // set height temporarily to a single row to obtain scrollHeight, disregarding empty space after text (otherwise, scrollHeight would be equal to the height of the element) - this solves auto-shrinking of the textarea (it's not needed for auto-growing it)
|
||||||
const { scrollHeight } = ref.current; // scrollHeight = content height + padding top + padding bottom
|
const { scrollHeight } = ref.current; // scrollHeight = content height + padding top + padding bottom
|
||||||
|
|
||||||
if (boxSizing === "border-box") {
|
if (boxSizing === "border-box") {
|
||||||
|
|
@ -32,12 +32,12 @@ export function AutoHeightTextarea({
|
||||||
scrollHeight +
|
scrollHeight +
|
||||||
parseFloat(borderTopWidth) +
|
parseFloat(borderTopWidth) +
|
||||||
parseFloat(borderBottomWidth);
|
parseFloat(borderBottomWidth);
|
||||||
ref.current.style.height = `${Math.max(minHeight, allTextHeight)}px`;
|
ref.current.style.minHeight = `${Math.max(minHeight, allTextHeight)}px`;
|
||||||
} else if (boxSizing === "content-box") {
|
} else if (boxSizing === "content-box") {
|
||||||
const minHeight = parseFloat(lineHeight) * minRows;
|
const minHeight = parseFloat(lineHeight) * minRows;
|
||||||
const allTextHeight =
|
const allTextHeight =
|
||||||
scrollHeight - parseFloat(paddingTop) - parseFloat(paddingBottom);
|
scrollHeight - parseFloat(paddingTop) - parseFloat(paddingBottom);
|
||||||
ref.current.style.height = `${Math.max(minHeight, allTextHeight)}px`;
|
ref.current.style.minHeight = `${Math.max(minHeight, allTextHeight)}px`;
|
||||||
} else {
|
} else {
|
||||||
console.error("Unknown box-sizing value.");
|
console.error("Unknown box-sizing value.");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,7 @@ export const Breadcrumb: FC<BreadcrumbProps> = (_arg) => {
|
||||||
|
|
||||||
if (local.status === "init") {
|
if (local.status === "init") {
|
||||||
let should_load = true;
|
let should_load = true;
|
||||||
|
local.status = "loading";
|
||||||
|
|
||||||
if (isEditor && item && breadcrumbData[item.id]) {
|
if (isEditor && item && breadcrumbData[item.id]) {
|
||||||
local.list = breadcrumbData[item.id];
|
local.list = breadcrumbData[item.id];
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,13 @@
|
||||||
import { useLocal } from "@/utils/use-local";
|
import { useLocal } from "@/utils/use-local";
|
||||||
import get from "lodash.get";
|
import get from "lodash.get";
|
||||||
import { FC, useRef } from "react";
|
import { FC, useEffect, useRef } from "react";
|
||||||
import { createPortal } from "react-dom";
|
import { createPortal } from "react-dom";
|
||||||
import { Toaster } from "sonner";
|
import { Toaster } from "sonner";
|
||||||
import { FMInternal, FMProps } from "./typings";
|
import { FMInternal, FMProps } from "./typings";
|
||||||
import { editorFormData } from "./utils/ed-data";
|
import { editorFormData } from "./utils/ed-data";
|
||||||
import { formInit } from "./utils/init";
|
import { formInit } from "./utils/init";
|
||||||
import { formReload } from "./utils/reload";
|
import { formReload } from "./utils/reload";
|
||||||
|
import { getPathname } from "../../..";
|
||||||
|
|
||||||
const editorFormWidth = {} as Record<string, { w: number; f: any }>;
|
const editorFormWidth = {} as Record<string, { w: number; f: any }>;
|
||||||
|
|
||||||
|
|
@ -83,6 +84,13 @@ export const Form: FC<FMProps> = (props) => {
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (fm.status === "ready") {
|
||||||
|
fm.status = "init";
|
||||||
|
fm.render();
|
||||||
|
}
|
||||||
|
}, [getPathname()]);
|
||||||
|
|
||||||
if (fm.status === "init") {
|
if (fm.status === "init") {
|
||||||
formInit(fm, props);
|
formInit(fm, props);
|
||||||
fm.reload();
|
fm.reload();
|
||||||
|
|
@ -95,6 +103,14 @@ export const Form: FC<FMProps> = (props) => {
|
||||||
}
|
}
|
||||||
const toaster_el = document.getElementsByClassName("prasi-toaster")[0];
|
const toaster_el = document.getElementsByClassName("prasi-toaster")[0];
|
||||||
|
|
||||||
|
if (fm.status === "resizing") {
|
||||||
|
setTimeout(() => {
|
||||||
|
fm.status = "ready";
|
||||||
|
fm.render();
|
||||||
|
}, 100);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form
|
<form
|
||||||
onSubmit={(e) => {
|
onSubmit={(e) => {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,58 @@
|
||||||
|
import { FMLocal } from "../typings";
|
||||||
|
import { BaseLabel } from "./BaseLabel";
|
||||||
|
import { BaseFieldProps } from "./utils/type/field";
|
||||||
|
import { useField } from "./utils/use-field";
|
||||||
|
|
||||||
|
export const BaseField = <T extends Record<string, any>>(
|
||||||
|
arg: BaseFieldProps<T> & { fm: FMLocal }
|
||||||
|
) => {
|
||||||
|
const field = useField<T>(arg);
|
||||||
|
const fm = arg.fm;
|
||||||
|
const mode = fm.props.label_mode || "vertical";
|
||||||
|
const props = arg.props;
|
||||||
|
const w = field.width;
|
||||||
|
const errors = fm.error.get(field.name);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<label
|
||||||
|
className={cx(
|
||||||
|
"field",
|
||||||
|
"c-flex",
|
||||||
|
css`
|
||||||
|
padding: 5px 0px 0px 10px;
|
||||||
|
`,
|
||||||
|
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-3/4",
|
||||||
|
w === "½" && "c-w-1/2",
|
||||||
|
w === "⅓" && "c-w-1/3",
|
||||||
|
w === "¼" && "c-w-1/4",
|
||||||
|
mode === "horizontal" && "c-flex-row c-items-center",
|
||||||
|
mode === "vertical" && "c-flex-col c-space-y-1"
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{mode !== "hidden" && <BaseLabel field={field} fm={fm} />}
|
||||||
|
<div className="field-inner c-flex c-flex-1 c-flex-col">
|
||||||
|
{field.desc && (
|
||||||
|
<div className={cx("c-p-2 c-text-xs", errors.length > 0 && "c-pb-1")}>
|
||||||
|
{field.desc}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{errors.length > 0 && (
|
||||||
|
<div
|
||||||
|
className={cx(
|
||||||
|
"c-p-2 c-text-xs c-text-red-600",
|
||||||
|
field.desc && "c-pt-0"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{errors.map((err) => {
|
||||||
|
return <div>{err}</div>;
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,51 @@
|
||||||
|
import { useLocal } from "@/utils/use-local";
|
||||||
|
import { FC } from "react";
|
||||||
|
import { BaseField } from "./BaseField";
|
||||||
|
import { initSimpleForm as initBaseForm } from "./utils/init";
|
||||||
|
import { BaseFieldProps, BaseFormProps } from "./utils/type/field";
|
||||||
|
|
||||||
|
type Children<T extends Record<string, any>> = Exclude<
|
||||||
|
BaseFormProps<T>["children"],
|
||||||
|
undefined
|
||||||
|
>;
|
||||||
|
|
||||||
|
export const BaseForm = <T extends Record<string, any>>(
|
||||||
|
arg: BaseFormProps<T>
|
||||||
|
) => {
|
||||||
|
const fm = useLocal<FMInternal>({ status: "init" } as any);
|
||||||
|
const local = useLocal({
|
||||||
|
Field: null as null | FC<BaseFieldProps<T>>,
|
||||||
|
children: null as unknown as Children<T>,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (fm.status === "init") {
|
||||||
|
local.Field = (props) => {
|
||||||
|
return <BaseField fm={fm} {...props} />;
|
||||||
|
};
|
||||||
|
if (arg.children) local.children = arg.children;
|
||||||
|
else {
|
||||||
|
local.children = ({ Field }) => {
|
||||||
|
const data = Object.entries(fm.data);
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{data.map(([key, value]) => {
|
||||||
|
return <Field name={key} />;
|
||||||
|
})}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
initBaseForm(fm, arg);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<form
|
||||||
|
onSubmit={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
fm.submit();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{local.Field && local.children({ Field: local.Field })}
|
||||||
|
</form>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,49 @@
|
||||||
|
import { FC } from "react";
|
||||||
|
import { BaseFieldLocal } from "./utils/type/field";
|
||||||
|
import { FMLocal } from "../typings";
|
||||||
|
|
||||||
|
export const BaseLabel = <T extends Record<string, any>>({
|
||||||
|
field,
|
||||||
|
fm,
|
||||||
|
}: {
|
||||||
|
field: BaseFieldLocal<T>;
|
||||||
|
fm: FMLocal;
|
||||||
|
}) => {
|
||||||
|
const errors = fm.error.get(field.name);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={cx(
|
||||||
|
"label c-text-sm c-flex c-items-center",
|
||||||
|
fm.props.label_mode === "horizontal" &&
|
||||||
|
css`
|
||||||
|
width: ${fm.props.label_width}px;
|
||||||
|
`,
|
||||||
|
fm.props.label_mode === "vertical" && "c-mt-3"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<span className={cx(errors.length > 0 && `c-text-red-600`)}>
|
||||||
|
{field.label}
|
||||||
|
</span>
|
||||||
|
{field.required && (
|
||||||
|
<span className="c-text-red-600 c-mb-2 c-ml-1">
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="12"
|
||||||
|
height="12"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeWidth="2"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
>
|
||||||
|
<path d="M12 6v12" />
|
||||||
|
<path d="M17.196 9 6.804 15" />
|
||||||
|
<path d="m6.804 9 10.392 6" />
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,35 @@
|
||||||
|
import { FMLocal } from "../../typings";
|
||||||
|
import { formError } from "../../utils/error";
|
||||||
|
import { reloadBaseForm } from "./reload";
|
||||||
|
import { submitBaseForm } from "./submit";
|
||||||
|
import { BaseFormProps } from "./type/field";
|
||||||
|
|
||||||
|
export const initSimpleForm = <T extends Record<string, any>>(
|
||||||
|
fm: FMLocal,
|
||||||
|
arg: BaseFormProps<T>
|
||||||
|
) => {
|
||||||
|
fm.data = {};
|
||||||
|
fm.error = formError(fm);
|
||||||
|
fm.events = {
|
||||||
|
on_change(name, new_value) {},
|
||||||
|
};
|
||||||
|
fm.field_def = {};
|
||||||
|
fm.fields = {};
|
||||||
|
fm.internal = {
|
||||||
|
submit: { done: [], promises: [], timeout: null as any },
|
||||||
|
reload: { done: [], promises: [], timeout: null as any },
|
||||||
|
};
|
||||||
|
fm.props = {} as any;
|
||||||
|
fm.reload = async () => {
|
||||||
|
await reloadBaseForm(fm, arg);
|
||||||
|
};
|
||||||
|
fm.size = {
|
||||||
|
width: 0,
|
||||||
|
height: 0,
|
||||||
|
field: "full",
|
||||||
|
};
|
||||||
|
fm.submit = () => {
|
||||||
|
return submitBaseForm(fm, arg);
|
||||||
|
};
|
||||||
|
fm.status = "ready";
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
import { FMLocal } from "../../typings";
|
||||||
|
import { BaseFormProps } from "./type/field";
|
||||||
|
|
||||||
|
export const reloadBaseForm = async <T extends Record<string, any>>(
|
||||||
|
fm: FMLocal,
|
||||||
|
arg: BaseFormProps<T>
|
||||||
|
) => {};
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
import { FMLocal } from "../../typings";
|
||||||
|
import { BaseFormProps } from "./type/field";
|
||||||
|
|
||||||
|
export const submitBaseForm = async <T extends Record<string, any>>(
|
||||||
|
fm: FMLocal,
|
||||||
|
arg: BaseFormProps<T>
|
||||||
|
) => {
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,57 @@
|
||||||
|
import { FC, ReactNode } from "react";
|
||||||
|
|
||||||
|
|
||||||
|
export type BaseFieldWidth =
|
||||||
|
| "auto"
|
||||||
|
| "full"
|
||||||
|
| "¾"
|
||||||
|
| "½"
|
||||||
|
| "⅓"
|
||||||
|
| "¼"
|
||||||
|
| "1/2"
|
||||||
|
| "1/3"
|
||||||
|
| "1/4"
|
||||||
|
| "3/4";
|
||||||
|
|
||||||
|
export type BaseFieldType = "text" | "relation";
|
||||||
|
export type BaseFieldProps<T extends Record<string, any>> = {
|
||||||
|
name: keyof T;
|
||||||
|
label?: string;
|
||||||
|
type?: BaseFieldType;
|
||||||
|
props?: any;
|
||||||
|
desc?: string;
|
||||||
|
on_change?: (arg: { value: any }) => void | Promise<void>;
|
||||||
|
|
||||||
|
prefix?: any;
|
||||||
|
suffix?: any;
|
||||||
|
|
||||||
|
required?: boolean;
|
||||||
|
required_msg?: (name: string) => string;
|
||||||
|
disabled?: boolean;
|
||||||
|
|
||||||
|
width?: BaseFieldWidth;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type BaseFieldInternal<T extends Record<string, any>> = {
|
||||||
|
name: keyof T;
|
||||||
|
label: ReactNode;
|
||||||
|
type: BaseFieldType;
|
||||||
|
desc: string;
|
||||||
|
on_change: (arg: { value: any }) => void | Promise<void>;
|
||||||
|
|
||||||
|
prefix: any;
|
||||||
|
suffix: any;
|
||||||
|
|
||||||
|
required: boolean;
|
||||||
|
required_msg: (name: string) => string;
|
||||||
|
disabled: boolean;
|
||||||
|
|
||||||
|
width: BaseFieldWidth;
|
||||||
|
status: "init" | "loading" | "ready";
|
||||||
|
|
||||||
|
PassProp: any;
|
||||||
|
child: any;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type BaseFieldLocal<T extends Record<string, any>> =
|
||||||
|
BaseFieldInternal<T>;
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
import { FC, ReactNode } from "react";
|
||||||
|
import { BaseFieldProps } from "./field";
|
||||||
|
|
||||||
|
export type BaseFormProps<T extends Record<string, any>> = {
|
||||||
|
onLoad: () => Promise<T | null>;
|
||||||
|
onSubmit: (arg: { data: T | null }) => Promise<boolean>;
|
||||||
|
children?: (arg: { Field: FC<BaseFieldProps<T>> }) => ReactNode;
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
import { useLocal } from "@/utils/use-local";
|
||||||
|
import { FMLocal } from "../../typings";
|
||||||
|
import {
|
||||||
|
BaseFieldInternal,
|
||||||
|
BaseFieldLocal,
|
||||||
|
BaseFieldProps,
|
||||||
|
} from "./type/field";
|
||||||
|
import { formatName } from "@/gen/utils";
|
||||||
|
|
||||||
|
export const useField = <T extends Record<string, any>>(
|
||||||
|
arg: BaseFieldProps<T> & { fm: FMLocal }
|
||||||
|
) => {
|
||||||
|
const fm = arg.fm;
|
||||||
|
const local = useLocal<BaseFieldInternal<T>>({
|
||||||
|
status: "init",
|
||||||
|
} as any) as BaseFieldLocal<T>;
|
||||||
|
|
||||||
|
if (local.status === "init") {
|
||||||
|
local.name = arg.name;
|
||||||
|
local.label = arg.label || formatName(arg.name as string);
|
||||||
|
local.type = arg.type || "text";
|
||||||
|
local.desc = arg.desc || "";
|
||||||
|
local.status = "ready";
|
||||||
|
local.width = arg.width || fm.size.field === "full" ? "full" : "1/2";
|
||||||
|
}
|
||||||
|
|
||||||
|
return local as BaseFieldLocal<T>;
|
||||||
|
};
|
||||||
|
|
@ -2,9 +2,10 @@ import { FC } from "react";
|
||||||
import { FMLocal, FieldLocal } from "../../typings";
|
import { FMLocal, FieldLocal } from "../../typings";
|
||||||
import { useLocal } from "@/utils/use-local";
|
import { useLocal } from "@/utils/use-local";
|
||||||
import parser from "any-date-parser";
|
import parser from "any-date-parser";
|
||||||
|
import { AutoHeightTextarea } from "@/comps/custom/AutoHeightTextarea";
|
||||||
|
|
||||||
export type PropTypeText = {
|
export type PropTypeText = {
|
||||||
type: "text" | "password" | "number" | "date" | "datetime";
|
type: "text" | "password" | "number" | "date" | "datetime" | "textarea";
|
||||||
};
|
};
|
||||||
|
|
||||||
const parse = parser.exportAsFunctionAny("en-US");
|
const parse = parser.exportAsFunctionAny("en-US");
|
||||||
|
|
@ -31,6 +32,26 @@ export const FieldTypeText: FC<{
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
{prop.type === "textarea" ? (
|
||||||
|
<AutoHeightTextarea
|
||||||
|
onChange={(ev) => {
|
||||||
|
fm.data[field.name] = ev.currentTarget.value;
|
||||||
|
fm.render();
|
||||||
|
}}
|
||||||
|
value={value || ""}
|
||||||
|
disabled={field.disabled}
|
||||||
|
className="c-flex-1 c-bg-transparent c-outline-none c-p-2 c-text-sm c-w-full"
|
||||||
|
spellCheck={false}
|
||||||
|
onFocus={() => {
|
||||||
|
field.focused = true;
|
||||||
|
field.render();
|
||||||
|
}}
|
||||||
|
onBlur={() => {
|
||||||
|
field.focused = false;
|
||||||
|
field.render();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
<input
|
<input
|
||||||
type={prop.type}
|
type={prop.type}
|
||||||
onChange={(ev) => {
|
onChange={(ev) => {
|
||||||
|
|
@ -50,6 +71,7 @@ export const FieldTypeText: FC<{
|
||||||
field.render();
|
field.render();
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -73,9 +73,9 @@ export type FMInternal = {
|
||||||
error: {
|
error: {
|
||||||
readonly object: Record<string, string>;
|
readonly object: Record<string, string>;
|
||||||
readonly list: { name: string; error: string[] }[];
|
readonly list: { name: string; error: string[] }[];
|
||||||
set: (name: string, error: string[]) => void;
|
set: (name: string | number | symbol, error: string[]) => void;
|
||||||
get: (name: string) => string[];
|
get: (name: string | number | symbol) => string[];
|
||||||
clear: (name?: string) => void;
|
clear: (name?: string | number | symbol) => void;
|
||||||
};
|
};
|
||||||
internal: {
|
internal: {
|
||||||
reload: {
|
reload: {
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
export const getPathname = () => {
|
export const getPathname = (url?: string) => {
|
||||||
// if (["localhost", "prasi.avolut.com"].includes(location.hostname)) {
|
// if (["localhost", "prasi.avolut.com"].includes(location.hostname)) {
|
||||||
if (
|
if (
|
||||||
location.pathname.startsWith("/vi") ||
|
location.pathname.startsWith("/vi") ||
|
||||||
|
|
@ -6,6 +6,9 @@ export const getPathname = () => {
|
||||||
location.pathname.startsWith("/deploy")
|
location.pathname.startsWith("/deploy")
|
||||||
) {
|
) {
|
||||||
const hash = location.hash;
|
const hash = location.hash;
|
||||||
|
if (url?.startsWith("/prod")) {
|
||||||
|
return "/" + url.split("/").slice(3).join("/");
|
||||||
|
}
|
||||||
|
|
||||||
if (hash !== "") {
|
if (hash !== "") {
|
||||||
return "/" + location.pathname.split("/").slice(3).join("/") + hash;
|
return "/" + location.pathname.split("/").slice(3).join("/") + hash;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue