@bsol-oss/react-datatable5 12.0.0-beta.62 → 12.0.0-beta.64
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +11 -6
- package/dist/index.js +162 -47
- package/dist/index.mjs +164 -49
- package/dist/types/components/DataTable/DataTableServer.d.ts +4 -4
- package/dist/types/components/Form/SchemaFormContext.d.ts +5 -0
- package/dist/types/components/Form/components/core/DefaultForm.d.ts +1 -1
- package/dist/types/components/Form/components/core/FormRoot.d.ts +6 -1
- package/dist/types/components/Form/utils/validateData.d.ts +9 -0
- package/dist/types/components/TextArea/TextArea.d.ts +22 -0
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -253,7 +253,7 @@ interface DataResponse<T = unknown> extends Result<T> {
|
|
|
253
253
|
}
|
|
254
254
|
declare const useDataTableServer: <TData>({ url, default: { sorting: defaultSorting, pagination: defaultPagination, rowSelection: defaultRowSelection, columnFilters: defaultColumnFilters, columnOrder: defaultColumnOrder, columnVisibility: defaultColumnVisibility, globalFilter: defaultGlobalFilter, density: defaultDensity, }, keyPrefix, }: UseDataTableServerProps) => UseDataTableServerReturn<TData>;
|
|
255
255
|
|
|
256
|
-
interface DataTableServerProps<TData
|
|
256
|
+
interface DataTableServerProps<TData = unknown> {
|
|
257
257
|
children: ReactNode | ReactNode[];
|
|
258
258
|
/**
|
|
259
259
|
* Column definitions for the table.
|
|
@@ -282,10 +282,10 @@ interface DataTableServerProps<TData extends DataResponse = DataResponse<unknown
|
|
|
282
282
|
setColumnOrder: OnChangeFn<ColumnOrderState>;
|
|
283
283
|
setDensity: OnChangeFn<DensityState>;
|
|
284
284
|
setColumnVisibility: OnChangeFn<VisibilityState>;
|
|
285
|
-
query: UseQueryResult<TData
|
|
285
|
+
query: UseQueryResult<DataResponse<TData>>;
|
|
286
286
|
url: string;
|
|
287
287
|
translate: UseTranslationResponse<any, any>;
|
|
288
|
-
tableLabel
|
|
288
|
+
tableLabel?: DataTableLabel;
|
|
289
289
|
}
|
|
290
290
|
/**
|
|
291
291
|
* DataTableServer will create a context to hold all values to
|
|
@@ -298,7 +298,7 @@ interface DataTableServerProps<TData extends DataResponse = DataResponse<unknown
|
|
|
298
298
|
*
|
|
299
299
|
* @link https://tanstack.com/table/latest/docs/guide/column-defs
|
|
300
300
|
*/
|
|
301
|
-
declare function DataTableServer<TData
|
|
301
|
+
declare function DataTableServer<TData = unknown>({ columns, enableRowSelection, enableMultiRowSelection, enableSubRowSelection, columnOrder, columnFilters, columnVisibility, density, globalFilter, pagination, sorting, rowSelection, setPagination, setSorting, setColumnFilters, setRowSelection, setGlobalFilter, setColumnOrder, setDensity, setColumnVisibility, query, url, translate, children, tableLabel, }: DataTableServerProps<TData>): react_jsx_runtime.JSX.Element;
|
|
302
302
|
|
|
303
303
|
interface TableControlsProps {
|
|
304
304
|
totalText?: string;
|
|
@@ -547,6 +547,11 @@ interface FormRootProps<TData extends FieldValues> {
|
|
|
547
547
|
requestOptions?: AxiosRequestConfig;
|
|
548
548
|
getUpdatedData?: () => TData | Promise<TData> | void;
|
|
549
549
|
customErrorRenderer?: (error: unknown) => ReactNode;
|
|
550
|
+
displayConfig?: {
|
|
551
|
+
showSubmitButton?: boolean;
|
|
552
|
+
showResetButton?: boolean;
|
|
553
|
+
showTitle?: boolean;
|
|
554
|
+
};
|
|
550
555
|
}
|
|
551
556
|
interface CustomJSONSchema7Definition extends JSONSchema7 {
|
|
552
557
|
variant: string;
|
|
@@ -563,13 +568,13 @@ declare const idPickerSanityCheck: (column: string, foreign_key?: {
|
|
|
563
568
|
column?: string | undefined;
|
|
564
569
|
display_column?: string | undefined;
|
|
565
570
|
} | undefined) => void;
|
|
566
|
-
declare const FormRoot: <TData extends FieldValues>({ schema, idMap, setIdMap, form, serverUrl, translate, children, order, ignore, include, onSubmit, rowNumber, requestOptions, getUpdatedData, customErrorRenderer, }: FormRootProps<TData>) => react_jsx_runtime.JSX.Element;
|
|
571
|
+
declare const FormRoot: <TData extends FieldValues>({ schema, idMap, setIdMap, form, serverUrl, translate, children, order, ignore, include, onSubmit, rowNumber, requestOptions, getUpdatedData, customErrorRenderer, displayConfig, }: FormRootProps<TData>) => react_jsx_runtime.JSX.Element;
|
|
567
572
|
|
|
568
573
|
interface DefaultFormProps<TData extends FieldValues> {
|
|
569
574
|
formConfig: Omit<FormRootProps<TData>, "children">;
|
|
570
575
|
showTitle?: boolean;
|
|
571
576
|
}
|
|
572
|
-
declare const DefaultForm: <TData extends FieldValues>({ formConfig,
|
|
577
|
+
declare const DefaultForm: <TData extends FieldValues>({ formConfig, }: DefaultFormProps<TData>) => react_jsx_runtime.JSX.Element;
|
|
573
578
|
|
|
574
579
|
declare const FormTitle: () => react_jsx_runtime.JSX.Element;
|
|
575
580
|
|
package/dist/index.js
CHANGED
|
@@ -29,13 +29,13 @@ var gr = require('react-icons/gr');
|
|
|
29
29
|
var reactI18next = require('react-i18next');
|
|
30
30
|
var axios = require('axios');
|
|
31
31
|
var reactHookForm = require('react-hook-form');
|
|
32
|
+
var Ajv = require('ajv');
|
|
33
|
+
var addFormats = require('ajv-formats');
|
|
34
|
+
var addErrors = require('ajv-errors');
|
|
32
35
|
var dayjs = require('dayjs');
|
|
33
36
|
var utc = require('dayjs/plugin/utc');
|
|
34
37
|
var timezone = require('dayjs/plugin/timezone');
|
|
35
38
|
var ti = require('react-icons/ti');
|
|
36
|
-
var Ajv = require('ajv');
|
|
37
|
-
var addFormats = require('ajv-formats');
|
|
38
|
-
var addErrors = require('ajv-errors');
|
|
39
39
|
|
|
40
40
|
function _interopNamespaceDefault(e) {
|
|
41
41
|
var n = Object.create(null);
|
|
@@ -3693,6 +3693,11 @@ const SchemaFormContext = React.createContext({
|
|
|
3693
3693
|
rowNumber: 0,
|
|
3694
3694
|
requestOptions: {},
|
|
3695
3695
|
timezone: 'Asia/Hong_Kong',
|
|
3696
|
+
displayConfig: {
|
|
3697
|
+
showSubmitButton: true,
|
|
3698
|
+
showResetButton: true,
|
|
3699
|
+
showTitle: true,
|
|
3700
|
+
},
|
|
3696
3701
|
});
|
|
3697
3702
|
|
|
3698
3703
|
const useSchemaContext = () => {
|
|
@@ -3703,6 +3708,23 @@ const clearEmptyString = (object) => {
|
|
|
3703
3708
|
return Object.fromEntries(Object.entries(object).filter(([, value]) => value !== ""));
|
|
3704
3709
|
};
|
|
3705
3710
|
|
|
3711
|
+
const validateData = (data, schema) => {
|
|
3712
|
+
const ajv = new Ajv({
|
|
3713
|
+
strict: false,
|
|
3714
|
+
allErrors: true,
|
|
3715
|
+
});
|
|
3716
|
+
addFormats(ajv);
|
|
3717
|
+
addErrors(ajv);
|
|
3718
|
+
const validate = ajv.compile(schema);
|
|
3719
|
+
const validationResult = validate(data);
|
|
3720
|
+
const errors = validate.errors;
|
|
3721
|
+
return {
|
|
3722
|
+
isValid: validationResult,
|
|
3723
|
+
validate,
|
|
3724
|
+
errors,
|
|
3725
|
+
};
|
|
3726
|
+
};
|
|
3727
|
+
|
|
3706
3728
|
const idPickerSanityCheck = (column, foreign_key) => {
|
|
3707
3729
|
if (!!foreign_key == false) {
|
|
3708
3730
|
throw new Error(`The key foreign_key does not exist in properties of column ${column} when using id-picker.`);
|
|
@@ -3718,7 +3740,11 @@ const idPickerSanityCheck = (column, foreign_key) => {
|
|
|
3718
3740
|
throw new Error(`The key column does not exist in properties of column ${column} when using id-picker.`);
|
|
3719
3741
|
}
|
|
3720
3742
|
};
|
|
3721
|
-
const FormRoot = ({ schema, idMap, setIdMap, form, serverUrl, translate, children, order = [], ignore = [], include = [], onSubmit = undefined, rowNumber = undefined, requestOptions = {}, getUpdatedData = () => { }, customErrorRenderer,
|
|
3743
|
+
const FormRoot = ({ schema, idMap, setIdMap, form, serverUrl, translate, children, order = [], ignore = [], include = [], onSubmit = undefined, rowNumber = undefined, requestOptions = {}, getUpdatedData = () => { }, customErrorRenderer, displayConfig = {
|
|
3744
|
+
showSubmitButton: true,
|
|
3745
|
+
showResetButton: true,
|
|
3746
|
+
showTitle: true,
|
|
3747
|
+
}, }) => {
|
|
3722
3748
|
const [isSuccess, setIsSuccess] = React.useState(false);
|
|
3723
3749
|
const [isError, setIsError] = React.useState(false);
|
|
3724
3750
|
const [isSubmiting, setIsSubmiting] = React.useState(false);
|
|
@@ -3752,6 +3778,7 @@ const FormRoot = ({ schema, idMap, setIdMap, form, serverUrl, translate, childre
|
|
|
3752
3778
|
setError,
|
|
3753
3779
|
getUpdatedData,
|
|
3754
3780
|
customErrorRenderer,
|
|
3781
|
+
displayConfig,
|
|
3755
3782
|
}, children: jsxRuntime.jsx(reactHookForm.FormProvider, { ...form, children: children }) }));
|
|
3756
3783
|
};
|
|
3757
3784
|
|
|
@@ -4076,14 +4103,14 @@ const EnumPicker = ({ column, isMultiple = false, schema, prefix, showTotalAndLi
|
|
|
4076
4103
|
if (!!item === false) {
|
|
4077
4104
|
return jsxRuntime.jsx(jsxRuntime.Fragment, {});
|
|
4078
4105
|
}
|
|
4079
|
-
return (jsxRuntime.jsx(Tag, { closable: true, onClick: () => {
|
|
4106
|
+
return (jsxRuntime.jsx(Tag, { size: "lg", closable: true, onClick: () => {
|
|
4080
4107
|
setValue(column, watchEnums.filter((id) => id != item));
|
|
4081
4108
|
}, children: !!renderDisplay === true
|
|
4082
4109
|
? renderDisplay(item)
|
|
4083
|
-
: translate.t(removeIndex(`${colLabel}.${item}`)) }));
|
|
4084
|
-
}), jsxRuntime.jsx(Tag, { cursor: "pointer", onClick: () => {
|
|
4110
|
+
: translate.t(removeIndex(`${colLabel}.${item}`)) }, item));
|
|
4111
|
+
}), jsxRuntime.jsx(Tag, { size: "lg", cursor: "pointer", onClick: () => {
|
|
4085
4112
|
setOpenSearchResult(true);
|
|
4086
|
-
}, children: translate.t(removeIndex(`${colLabel}.add_more`)) })] })), !isMultiple && (jsxRuntime.jsx(Button, { variant: "outline", onClick: () => {
|
|
4113
|
+
}, children: translate.t(removeIndex(`${colLabel}.add_more`)) }, `${colLabel}-add-more-tag`)] })), !isMultiple && (jsxRuntime.jsx(Button, { variant: "outline", onClick: () => {
|
|
4087
4114
|
setOpenSearchResult(true);
|
|
4088
4115
|
}, justifyContent: "start", children: !!watchEnum === false
|
|
4089
4116
|
? ""
|
|
@@ -4904,13 +4931,101 @@ const TagPicker = ({ column, schema, prefix }) => {
|
|
|
4904
4931
|
}), errors[`${column}`] && (jsxRuntime.jsx(react.Text, { color: "red.400", children: (errors[`${column}`]?.message ?? "No error message") }))] }));
|
|
4905
4932
|
};
|
|
4906
4933
|
|
|
4934
|
+
const Textarea = React.forwardRef(({ value, defaultValue, placeholder, onChange, onFocus, onBlur, disabled = false, readOnly = false, className, rows = 4, maxLength, autoFocus = false, invalid = false, required = false, label, helperText, errorText, ...props }, ref) => {
|
|
4935
|
+
const contentEditableRef = React.useRef(null);
|
|
4936
|
+
const isControlled = value !== undefined;
|
|
4937
|
+
// Handle input changes
|
|
4938
|
+
const handleInput = (e) => {
|
|
4939
|
+
const text = e.currentTarget.textContent || "";
|
|
4940
|
+
// Check maxLength if specified
|
|
4941
|
+
if (maxLength && text.length > maxLength) {
|
|
4942
|
+
e.currentTarget.textContent = text.slice(0, maxLength);
|
|
4943
|
+
// Move cursor to end
|
|
4944
|
+
const selection = window.getSelection();
|
|
4945
|
+
if (selection) {
|
|
4946
|
+
selection.selectAllChildren(e.currentTarget);
|
|
4947
|
+
selection.collapseToEnd();
|
|
4948
|
+
}
|
|
4949
|
+
return;
|
|
4950
|
+
}
|
|
4951
|
+
onChange?.(text);
|
|
4952
|
+
};
|
|
4953
|
+
// Handle paste events to strip formatting and respect maxLength
|
|
4954
|
+
const handlePaste = (e) => {
|
|
4955
|
+
e.preventDefault();
|
|
4956
|
+
const text = e.clipboardData.getData('text/plain');
|
|
4957
|
+
const currentText = e.currentTarget.textContent || "";
|
|
4958
|
+
let pasteText = text;
|
|
4959
|
+
if (maxLength) {
|
|
4960
|
+
const remainingLength = maxLength - currentText.length;
|
|
4961
|
+
pasteText = text.slice(0, remainingLength);
|
|
4962
|
+
}
|
|
4963
|
+
document.execCommand('insertText', false, pasteText);
|
|
4964
|
+
};
|
|
4965
|
+
// Set initial content
|
|
4966
|
+
React.useEffect(() => {
|
|
4967
|
+
if (contentEditableRef.current && !isControlled) {
|
|
4968
|
+
const initialValue = defaultValue || "";
|
|
4969
|
+
if (contentEditableRef.current.textContent !== initialValue) {
|
|
4970
|
+
contentEditableRef.current.textContent = initialValue;
|
|
4971
|
+
}
|
|
4972
|
+
}
|
|
4973
|
+
}, [defaultValue, isControlled]);
|
|
4974
|
+
// Update content when value changes (controlled mode)
|
|
4975
|
+
React.useEffect(() => {
|
|
4976
|
+
if (contentEditableRef.current && isControlled && value !== undefined) {
|
|
4977
|
+
if (contentEditableRef.current.textContent !== value) {
|
|
4978
|
+
contentEditableRef.current.textContent = value;
|
|
4979
|
+
}
|
|
4980
|
+
}
|
|
4981
|
+
}, [value, isControlled]);
|
|
4982
|
+
// Auto focus
|
|
4983
|
+
React.useEffect(() => {
|
|
4984
|
+
if (autoFocus && contentEditableRef.current) {
|
|
4985
|
+
contentEditableRef.current.focus();
|
|
4986
|
+
}
|
|
4987
|
+
}, [autoFocus]);
|
|
4988
|
+
// Forward ref
|
|
4989
|
+
React.useEffect(() => {
|
|
4990
|
+
if (typeof ref === 'function') {
|
|
4991
|
+
ref(contentEditableRef.current);
|
|
4992
|
+
}
|
|
4993
|
+
else if (ref) {
|
|
4994
|
+
ref.current = contentEditableRef.current;
|
|
4995
|
+
}
|
|
4996
|
+
}, [ref]);
|
|
4997
|
+
const textareaElement = (jsxRuntime.jsx(react.Box, { ref: contentEditableRef, contentEditable: !disabled && !readOnly, onInput: handleInput, onPaste: handlePaste, onFocus: onFocus, onBlur: onBlur, className: className, minHeight: `${rows * 1.5}em`, padding: "2", border: "1px solid", borderColor: invalid ? "red.500" : "gray.200", borderRadius: "md", outline: "none", _focus: {
|
|
4998
|
+
borderColor: invalid ? "red.500" : "blue.500",
|
|
4999
|
+
boxShadow: `0 0 0 1px ${invalid ? "red.500" : "blue.500"}`,
|
|
5000
|
+
}, _disabled: {
|
|
5001
|
+
opacity: 0.6,
|
|
5002
|
+
cursor: "not-allowed",
|
|
5003
|
+
bg: "gray.50",
|
|
5004
|
+
}, _empty: {
|
|
5005
|
+
_before: {
|
|
5006
|
+
content: placeholder ? `"${placeholder}"` : undefined,
|
|
5007
|
+
color: "gray.400",
|
|
5008
|
+
pointerEvents: "none",
|
|
5009
|
+
}
|
|
5010
|
+
}, whiteSpace: "pre-wrap", overflowWrap: "break-word", overflow: "auto", maxHeight: `${rows * 4}em`, suppressContentEditableWarning: true, ...props }));
|
|
5011
|
+
// If we have additional field props, wrap in Field component
|
|
5012
|
+
if (label || helperText || errorText || required) {
|
|
5013
|
+
return (jsxRuntime.jsxs(react.Field.Root, { invalid: invalid, required: required, children: [label && (jsxRuntime.jsxs(react.Field.Label, { children: [label, required && jsxRuntime.jsx(react.Field.RequiredIndicator, {})] })), textareaElement, helperText && jsxRuntime.jsx(react.Field.HelperText, { children: helperText }), errorText && jsxRuntime.jsx(react.Field.ErrorText, { children: errorText })] }));
|
|
5014
|
+
}
|
|
5015
|
+
return textareaElement;
|
|
5016
|
+
});
|
|
5017
|
+
Textarea.displayName = "Textarea";
|
|
5018
|
+
|
|
4907
5019
|
const TextAreaInput = ({ column, schema, prefix, }) => {
|
|
4908
5020
|
const { register, formState: { errors }, } = reactHookForm.useFormContext();
|
|
4909
5021
|
const { translate } = useSchemaContext();
|
|
4910
5022
|
const { required, gridColumn = "span 4", gridRow = "span 1" } = schema;
|
|
4911
5023
|
const isRequired = required?.some((columnId) => columnId === column);
|
|
4912
5024
|
const colLabel = `${prefix}${column}`;
|
|
4913
|
-
|
|
5025
|
+
const form = reactHookForm.useFormContext();
|
|
5026
|
+
const { setValue, watch } = form;
|
|
5027
|
+
const watchValue = watch(colLabel);
|
|
5028
|
+
return (jsxRuntime.jsx(jsxRuntime.Fragment, { children: jsxRuntime.jsxs(Field, { label: `${translate.t(removeIndex(`${colLabel}.field_label`))}`, required: isRequired, gridColumn: gridColumn ?? "span 4", gridRow: gridRow ?? "span 1", display: "grid", children: [jsxRuntime.jsx(Textarea, { value: watchValue, onChange: (value) => setValue(colLabel, value) }), errors[colLabel] && (jsxRuntime.jsx(react.Text, { color: "red.400", children: translate.t(removeIndex(`${colLabel}.field_required`)) }))] }) }));
|
|
4914
5029
|
};
|
|
4915
5030
|
|
|
4916
5031
|
function TimePicker$1({ hour, setHour, minute, setMinute, meridiem, setMeridiem, meridiemLabel = {
|
|
@@ -5402,6 +5517,15 @@ const SchemaRenderer = ({ schema, prefix, column, }) => {
|
|
|
5402
5517
|
if (variant === "file-picker") {
|
|
5403
5518
|
return jsxRuntime.jsx(FilePicker, { schema: colSchema, prefix, column });
|
|
5404
5519
|
}
|
|
5520
|
+
if (variant === "enum-picker") {
|
|
5521
|
+
const { items } = colSchema;
|
|
5522
|
+
const { enum: enumItems } = items;
|
|
5523
|
+
const enumSchema = {
|
|
5524
|
+
type: "string",
|
|
5525
|
+
enum: enumItems,
|
|
5526
|
+
};
|
|
5527
|
+
return (jsxRuntime.jsx(EnumPicker, { isMultiple: true, schema: enumSchema, prefix, column }));
|
|
5528
|
+
}
|
|
5405
5529
|
if (items) {
|
|
5406
5530
|
return jsxRuntime.jsx(ArrayRenderer, { schema: colSchema, prefix, column });
|
|
5407
5531
|
}
|
|
@@ -5493,9 +5617,9 @@ const EnumViewer = ({ column, isMultiple = false, schema, prefix, }) => {
|
|
|
5493
5617
|
if (item === undefined) {
|
|
5494
5618
|
return jsxRuntime.jsx(jsxRuntime.Fragment, { children: "undefined" });
|
|
5495
5619
|
}
|
|
5496
|
-
return (jsxRuntime.jsx(Tag, { children: !!renderDisplay === true
|
|
5620
|
+
return (jsxRuntime.jsx(Tag, { size: "lg", children: !!renderDisplay === true
|
|
5497
5621
|
? renderDisplay(item)
|
|
5498
|
-
: customTranslate(item) }));
|
|
5622
|
+
: customTranslate(item) }, item));
|
|
5499
5623
|
}) })), !isMultiple && jsxRuntime.jsx(react.Text, { children: customTranslate(watchEnum) }), errors[`${column}`] && (jsxRuntime.jsx(react.Text, { color: "red.400", children: customTranslate(`field_required`) }))] }));
|
|
5500
5624
|
};
|
|
5501
5625
|
|
|
@@ -5809,6 +5933,15 @@ const SchemaViewer = ({ schema, prefix, column, }) => {
|
|
|
5809
5933
|
if (variant === "file-picker") {
|
|
5810
5934
|
return jsxRuntime.jsx(FileViewer, { schema: colSchema, prefix, column });
|
|
5811
5935
|
}
|
|
5936
|
+
if (variant === "enum-picker") {
|
|
5937
|
+
const { items } = schema;
|
|
5938
|
+
const { enum: enumItems } = items;
|
|
5939
|
+
const enumSchema = {
|
|
5940
|
+
type: "string",
|
|
5941
|
+
enum: enumItems,
|
|
5942
|
+
};
|
|
5943
|
+
return (jsxRuntime.jsx(EnumViewer, { isMultiple: true, schema: enumSchema, prefix, column }));
|
|
5944
|
+
}
|
|
5812
5945
|
if (items) {
|
|
5813
5946
|
return jsxRuntime.jsx(ArrayViewer, { schema: colSchema, prefix, column });
|
|
5814
5947
|
}
|
|
@@ -5836,16 +5969,12 @@ const SubmitButton = () => {
|
|
|
5836
5969
|
const methods = reactHookForm.useFormContext();
|
|
5837
5970
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
5838
5971
|
const onValid = (data) => {
|
|
5839
|
-
|
|
5840
|
-
|
|
5841
|
-
|
|
5842
|
-
|
|
5843
|
-
|
|
5844
|
-
|
|
5845
|
-
// @ts-expect-error TODO: find appropriate type
|
|
5846
|
-
const errors = validationResult.errors;
|
|
5847
|
-
if (errors && errors.length > 0) {
|
|
5848
|
-
setError(errors);
|
|
5972
|
+
const { isValid, errors } = validateData(data, schema);
|
|
5973
|
+
if (!isValid) {
|
|
5974
|
+
setError({
|
|
5975
|
+
type: "validation",
|
|
5976
|
+
errors,
|
|
5977
|
+
});
|
|
5849
5978
|
setIsError(true);
|
|
5850
5979
|
return;
|
|
5851
5980
|
}
|
|
@@ -5860,7 +5989,8 @@ const SubmitButton = () => {
|
|
|
5860
5989
|
};
|
|
5861
5990
|
|
|
5862
5991
|
const FormBody = () => {
|
|
5863
|
-
const { schema, requestUrl, order, ignore, include, onSubmit,
|
|
5992
|
+
const { schema, requestUrl, order, ignore, include, onSubmit, translate, requestOptions, isSuccess, setIsSuccess, isError, setIsError, isSubmiting, setIsSubmiting, isConfirming, setIsConfirming, validatedData, setValidatedData, error, setError, getUpdatedData, customErrorRenderer, displayConfig, } = useSchemaContext();
|
|
5993
|
+
const { showSubmitButton, showResetButton } = displayConfig;
|
|
5864
5994
|
const methods = reactHookForm.useFormContext();
|
|
5865
5995
|
const { properties } = schema;
|
|
5866
5996
|
const onBeforeSubmit = () => {
|
|
@@ -5876,24 +6006,11 @@ const FormBody = () => {
|
|
|
5876
6006
|
const onSubmitSuccess = () => {
|
|
5877
6007
|
setIsSuccess(true);
|
|
5878
6008
|
};
|
|
5879
|
-
// Enhanced validation function using AJV with i18n support
|
|
5880
6009
|
const validateFormData = (data) => {
|
|
5881
6010
|
try {
|
|
5882
|
-
const
|
|
5883
|
-
strict: false,
|
|
5884
|
-
allErrors: true,
|
|
5885
|
-
});
|
|
5886
|
-
addFormats(ajv);
|
|
5887
|
-
addErrors(ajv);
|
|
5888
|
-
const validate = ajv.compile(schema);
|
|
5889
|
-
const validationResult = validate(data);
|
|
5890
|
-
const errors = validate.errors;
|
|
5891
|
-
console.log({
|
|
5892
|
-
isValid: validationResult,
|
|
5893
|
-
errors,
|
|
5894
|
-
}, "plkdfs");
|
|
6011
|
+
const { isValid, errors } = validateData(data, schema);
|
|
5895
6012
|
return {
|
|
5896
|
-
isValid
|
|
6013
|
+
isValid,
|
|
5897
6014
|
errors,
|
|
5898
6015
|
};
|
|
5899
6016
|
}
|
|
@@ -5953,10 +6070,7 @@ const FormBody = () => {
|
|
|
5953
6070
|
};
|
|
5954
6071
|
// Custom error renderer for validation errors with i18n support
|
|
5955
6072
|
const renderValidationErrors = (validationErrors) => {
|
|
5956
|
-
return (jsxRuntime.jsx(
|
|
5957
|
-
base: "red.50",
|
|
5958
|
-
_dark: "red.950",
|
|
5959
|
-
}, p: "4", colorPalette: "red", collapsible: true, defaultValue: [], children: jsxRuntime.jsxs(AccordionItem, { value: "validation-errors", children: [jsxRuntime.jsx(AccordionItemTrigger, { children: translate.t("validation_error") }), jsxRuntime.jsx(AccordionItemContent, { display: "flex", flexFlow: "column", gap: "2", children: validationErrors.map((err, index) => (jsxRuntime.jsxs(react.AlertRoot, { status: "error", display: "flex", alignItems: "center", children: [jsxRuntime.jsx(react.AlertIndicator, {}), jsxRuntime.jsx(react.AlertContent, { children: jsxRuntime.jsx(react.AlertDescription, { children: err.message }) })] }))) })] }) }));
|
|
6073
|
+
return (jsxRuntime.jsx(react.Flex, { flexFlow: "column", gap: "2", children: validationErrors.map((err, index) => (jsxRuntime.jsxs(react.Alert.Root, { status: "error", display: "flex", alignItems: "center", children: [jsxRuntime.jsx(react.Alert.Indicator, {}), jsxRuntime.jsx(react.Alert.Content, { children: jsxRuntime.jsx(react.Alert.Description, { children: err.message }) })] }, index))) }));
|
|
5960
6074
|
};
|
|
5961
6075
|
const renderColumns = ({ order, keys, ignore, include, }) => {
|
|
5962
6076
|
const included = include.length > 0 ? include : keys;
|
|
@@ -5972,7 +6086,7 @@ const FormBody = () => {
|
|
|
5972
6086
|
include,
|
|
5973
6087
|
});
|
|
5974
6088
|
if (isSuccess) {
|
|
5975
|
-
return (jsxRuntime.jsxs(react.Flex, { flexFlow: "column", gap: "2", children: [jsxRuntime.jsxs(react.Alert.Root, { status: "success", children: [jsxRuntime.jsx(react.Alert.Indicator, {}), jsxRuntime.jsx(react.Alert.Title, { children: translate.t("submit_success") })] }), jsxRuntime.jsx(react.Flex, { justifyContent: "end", children: jsxRuntime.jsx(react.Button, { onClick: async () => {
|
|
6089
|
+
return (jsxRuntime.jsxs(react.Flex, { flexFlow: "column", gap: "2", children: [jsxRuntime.jsxs(react.Alert.Root, { status: "success", children: [jsxRuntime.jsx(react.Alert.Indicator, {}), jsxRuntime.jsx(react.Alert.Content, { children: jsxRuntime.jsx(react.Alert.Title, { children: translate.t("submit_success") }) })] }), jsxRuntime.jsx(react.Flex, { justifyContent: "end", children: jsxRuntime.jsx(react.Button, { onClick: async () => {
|
|
5976
6090
|
setIsError(false);
|
|
5977
6091
|
setIsSubmiting(false);
|
|
5978
6092
|
setIsSuccess(false);
|
|
@@ -5994,7 +6108,7 @@ const FormBody = () => {
|
|
|
5994
6108
|
}, variant: "subtle", children: translate.t("cancel") }), jsxRuntime.jsx(react.Button, { onClick: () => {
|
|
5995
6109
|
onFormSubmit(validatedData);
|
|
5996
6110
|
}, children: translate.t("confirm") })] }), isSubmiting && (jsxRuntime.jsx(react.Box, { pos: "absolute", inset: "0", bg: "bg/80", children: jsxRuntime.jsx(react.Center, { h: "full", children: jsxRuntime.jsx(react.Spinner, { color: "teal.500" }) }) })), isError && (jsxRuntime.jsx(jsxRuntime.Fragment, { children: customErrorRenderer ? (customErrorRenderer(error)) : (jsxRuntime.jsx(jsxRuntime.Fragment, { children: error?.type === "validation" &&
|
|
5997
|
-
error?.errors ? (renderValidationErrors(error.errors)) : (jsxRuntime.
|
|
6111
|
+
error?.errors ? (renderValidationErrors(error.errors)) : (jsxRuntime.jsxs(react.Alert.Root, { status: "error", children: [jsxRuntime.jsx(react.Alert.Indicator, {}), jsxRuntime.jsxs(react.Alert.Content, { children: [jsxRuntime.jsx(react.Alert.Title, { children: "Error" }), jsxRuntime.jsx(react.Alert.Description, { children: jsxRuntime.jsx(AccordionRoot, { collapsible: true, defaultValue: [], children: jsxRuntime.jsxs(AccordionItem, { value: "b", children: [jsxRuntime.jsx(AccordionItemTrigger, { children: `${error}` }), jsxRuntime.jsx(AccordionItemContent, { children: `${JSON.stringify(error)}` })] }) }) })] })] })) })) }))] }));
|
|
5998
6112
|
}
|
|
5999
6113
|
return (jsxRuntime.jsxs(react.Flex, { flexFlow: "column", gap: "2", children: [jsxRuntime.jsx(react.Grid, { gap: "4", gridTemplateColumns: "repeat(12, 1fr)", autoFlow: "row", children: ordered.map((column) => {
|
|
6000
6114
|
return (jsxRuntime.jsx(ColumnRenderer
|
|
@@ -6002,10 +6116,10 @@ const FormBody = () => {
|
|
|
6002
6116
|
, {
|
|
6003
6117
|
// @ts-expect-error find suitable types
|
|
6004
6118
|
properties: properties, prefix: ``, column }, `form-input-${column}`));
|
|
6005
|
-
}) }), jsxRuntime.jsxs(react.Flex, { justifyContent: "end", gap: "2", children: [jsxRuntime.jsx(react.Button, { onClick: () => {
|
|
6119
|
+
}) }), jsxRuntime.jsxs(react.Flex, { justifyContent: "end", gap: "2", children: [showResetButton && (jsxRuntime.jsx(react.Button, { onClick: () => {
|
|
6006
6120
|
methods.reset();
|
|
6007
|
-
}, variant: "subtle", children: translate.t("reset") }), jsxRuntime.jsx(SubmitButton, {})] }), isError &&
|
|
6008
|
-
|
|
6121
|
+
}, variant: "subtle", children: translate.t("reset") })), showSubmitButton && jsxRuntime.jsx(SubmitButton, {})] }), isError && (jsxRuntime.jsx(jsxRuntime.Fragment, { children: customErrorRenderer ? (customErrorRenderer(error)) : (jsxRuntime.jsx(jsxRuntime.Fragment, { children: error?.type === "validation" &&
|
|
6122
|
+
error?.errors ? (renderValidationErrors(error.errors)) : (jsxRuntime.jsxs(react.Alert.Root, { status: "error", children: [jsxRuntime.jsx(react.Alert.Indicator, {}), jsxRuntime.jsxs(react.Alert.Content, { children: [jsxRuntime.jsx(react.Alert.Title, { children: "Error" }), jsxRuntime.jsx(react.Alert.Description, { children: jsxRuntime.jsx(AccordionRoot, { collapsible: true, defaultValue: [], children: jsxRuntime.jsxs(AccordionItem, { value: "b", children: [jsxRuntime.jsx(AccordionItemTrigger, { children: `${error}` }), jsxRuntime.jsx(AccordionItemContent, { children: `${JSON.stringify(error)}` })] }) }) })] })] })) })) }))] }));
|
|
6009
6123
|
};
|
|
6010
6124
|
|
|
6011
6125
|
const FormTitle = () => {
|
|
@@ -6013,7 +6127,8 @@ const FormTitle = () => {
|
|
|
6013
6127
|
return jsxRuntime.jsx(react.Heading, { children: translate.t("title") });
|
|
6014
6128
|
};
|
|
6015
6129
|
|
|
6016
|
-
const DefaultForm = ({ formConfig,
|
|
6130
|
+
const DefaultForm = ({ formConfig, }) => {
|
|
6131
|
+
const { showTitle } = formConfig.displayConfig ?? {};
|
|
6017
6132
|
return (jsxRuntime.jsx(FormRoot, { ...formConfig, children: jsxRuntime.jsxs(react.Grid, { gap: "2", children: [showTitle && jsxRuntime.jsx(FormTitle, {}), jsxRuntime.jsx(FormBody, {})] }) }));
|
|
6018
6133
|
};
|
|
6019
6134
|
|
package/dist/index.mjs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
2
|
-
import { Button as Button$1, AbsoluteCenter, Spinner, Span, IconButton, Portal, Dialog, Flex, Text, useDisclosure, DialogBackdrop, RadioGroup as RadioGroup$1, Grid, Box, Slider as Slider$1, HStack, For, Tag as Tag$1, Input, Menu, createRecipeContext, createContext as createContext$1, Pagination as Pagination$1, usePaginationContext, CheckboxCard as CheckboxCard$1, Image, EmptyState as EmptyState$2, VStack, Alert, Card, Group, InputElement, Tooltip as Tooltip$1, Icon, List, Table as Table$1, Checkbox as Checkbox$1, MenuRoot as MenuRoot$1, MenuTrigger as MenuTrigger$1, Accordion, Field as Field$1, Popover, NumberInput, Show, RadioCard, CheckboxGroup,
|
|
2
|
+
import { Button as Button$1, AbsoluteCenter, Spinner, Span, IconButton, Portal, Dialog, Flex, Text, useDisclosure, DialogBackdrop, RadioGroup as RadioGroup$1, Grid, Box, Slider as Slider$1, HStack, For, Tag as Tag$1, Input, Menu, createRecipeContext, createContext as createContext$1, Pagination as Pagination$1, usePaginationContext, CheckboxCard as CheckboxCard$1, Image, EmptyState as EmptyState$2, VStack, Alert, Card, Group, InputElement, Tooltip as Tooltip$1, Icon, List, Table as Table$1, Checkbox as Checkbox$1, MenuRoot as MenuRoot$1, MenuTrigger as MenuTrigger$1, Accordion, Field as Field$1, Popover, NumberInput, Show, RadioCard, CheckboxGroup, Center, Heading } from '@chakra-ui/react';
|
|
3
3
|
import { AiOutlineColumnWidth } from 'react-icons/ai';
|
|
4
4
|
import * as React from 'react';
|
|
5
|
-
import React__default, { createContext, useContext, useState, useEffect, useRef } from 'react';
|
|
5
|
+
import React__default, { createContext, useContext, useState, useEffect, useRef, forwardRef } from 'react';
|
|
6
6
|
import { LuX, LuCheck, LuChevronRight, LuChevronDown } from 'react-icons/lu';
|
|
7
7
|
import { MdOutlineSort, MdFilterAlt, MdSearch, MdOutlineViewColumn, MdFilterListAlt, MdPushPin, MdCancel, MdClear, MdOutlineChecklist, MdDateRange } from 'react-icons/md';
|
|
8
8
|
import { FaUpDown, FaGripLinesVertical, FaTrash } from 'react-icons/fa6';
|
|
@@ -28,13 +28,13 @@ import { GrAscend, GrDescend } from 'react-icons/gr';
|
|
|
28
28
|
import { useTranslation } from 'react-i18next';
|
|
29
29
|
import axios from 'axios';
|
|
30
30
|
import { FormProvider, useFormContext, useForm as useForm$1 } from 'react-hook-form';
|
|
31
|
+
import Ajv from 'ajv';
|
|
32
|
+
import addFormats from 'ajv-formats';
|
|
33
|
+
import addErrors from 'ajv-errors';
|
|
31
34
|
import dayjs from 'dayjs';
|
|
32
35
|
import utc from 'dayjs/plugin/utc';
|
|
33
36
|
import timezone from 'dayjs/plugin/timezone';
|
|
34
37
|
import { TiDeleteOutline } from 'react-icons/ti';
|
|
35
|
-
import Ajv from 'ajv';
|
|
36
|
-
import addFormats from 'ajv-formats';
|
|
37
|
-
import addErrors from 'ajv-errors';
|
|
38
38
|
|
|
39
39
|
const DataTableContext = createContext({
|
|
40
40
|
table: {},
|
|
@@ -3673,6 +3673,11 @@ const SchemaFormContext = createContext({
|
|
|
3673
3673
|
rowNumber: 0,
|
|
3674
3674
|
requestOptions: {},
|
|
3675
3675
|
timezone: 'Asia/Hong_Kong',
|
|
3676
|
+
displayConfig: {
|
|
3677
|
+
showSubmitButton: true,
|
|
3678
|
+
showResetButton: true,
|
|
3679
|
+
showTitle: true,
|
|
3680
|
+
},
|
|
3676
3681
|
});
|
|
3677
3682
|
|
|
3678
3683
|
const useSchemaContext = () => {
|
|
@@ -3683,6 +3688,23 @@ const clearEmptyString = (object) => {
|
|
|
3683
3688
|
return Object.fromEntries(Object.entries(object).filter(([, value]) => value !== ""));
|
|
3684
3689
|
};
|
|
3685
3690
|
|
|
3691
|
+
const validateData = (data, schema) => {
|
|
3692
|
+
const ajv = new Ajv({
|
|
3693
|
+
strict: false,
|
|
3694
|
+
allErrors: true,
|
|
3695
|
+
});
|
|
3696
|
+
addFormats(ajv);
|
|
3697
|
+
addErrors(ajv);
|
|
3698
|
+
const validate = ajv.compile(schema);
|
|
3699
|
+
const validationResult = validate(data);
|
|
3700
|
+
const errors = validate.errors;
|
|
3701
|
+
return {
|
|
3702
|
+
isValid: validationResult,
|
|
3703
|
+
validate,
|
|
3704
|
+
errors,
|
|
3705
|
+
};
|
|
3706
|
+
};
|
|
3707
|
+
|
|
3686
3708
|
const idPickerSanityCheck = (column, foreign_key) => {
|
|
3687
3709
|
if (!!foreign_key == false) {
|
|
3688
3710
|
throw new Error(`The key foreign_key does not exist in properties of column ${column} when using id-picker.`);
|
|
@@ -3698,7 +3720,11 @@ const idPickerSanityCheck = (column, foreign_key) => {
|
|
|
3698
3720
|
throw new Error(`The key column does not exist in properties of column ${column} when using id-picker.`);
|
|
3699
3721
|
}
|
|
3700
3722
|
};
|
|
3701
|
-
const FormRoot = ({ schema, idMap, setIdMap, form, serverUrl, translate, children, order = [], ignore = [], include = [], onSubmit = undefined, rowNumber = undefined, requestOptions = {}, getUpdatedData = () => { }, customErrorRenderer,
|
|
3723
|
+
const FormRoot = ({ schema, idMap, setIdMap, form, serverUrl, translate, children, order = [], ignore = [], include = [], onSubmit = undefined, rowNumber = undefined, requestOptions = {}, getUpdatedData = () => { }, customErrorRenderer, displayConfig = {
|
|
3724
|
+
showSubmitButton: true,
|
|
3725
|
+
showResetButton: true,
|
|
3726
|
+
showTitle: true,
|
|
3727
|
+
}, }) => {
|
|
3702
3728
|
const [isSuccess, setIsSuccess] = useState(false);
|
|
3703
3729
|
const [isError, setIsError] = useState(false);
|
|
3704
3730
|
const [isSubmiting, setIsSubmiting] = useState(false);
|
|
@@ -3732,6 +3758,7 @@ const FormRoot = ({ schema, idMap, setIdMap, form, serverUrl, translate, childre
|
|
|
3732
3758
|
setError,
|
|
3733
3759
|
getUpdatedData,
|
|
3734
3760
|
customErrorRenderer,
|
|
3761
|
+
displayConfig,
|
|
3735
3762
|
}, children: jsx(FormProvider, { ...form, children: children }) }));
|
|
3736
3763
|
};
|
|
3737
3764
|
|
|
@@ -4056,14 +4083,14 @@ const EnumPicker = ({ column, isMultiple = false, schema, prefix, showTotalAndLi
|
|
|
4056
4083
|
if (!!item === false) {
|
|
4057
4084
|
return jsx(Fragment, {});
|
|
4058
4085
|
}
|
|
4059
|
-
return (jsx(Tag, { closable: true, onClick: () => {
|
|
4086
|
+
return (jsx(Tag, { size: "lg", closable: true, onClick: () => {
|
|
4060
4087
|
setValue(column, watchEnums.filter((id) => id != item));
|
|
4061
4088
|
}, children: !!renderDisplay === true
|
|
4062
4089
|
? renderDisplay(item)
|
|
4063
|
-
: translate.t(removeIndex(`${colLabel}.${item}`)) }));
|
|
4064
|
-
}), jsx(Tag, { cursor: "pointer", onClick: () => {
|
|
4090
|
+
: translate.t(removeIndex(`${colLabel}.${item}`)) }, item));
|
|
4091
|
+
}), jsx(Tag, { size: "lg", cursor: "pointer", onClick: () => {
|
|
4065
4092
|
setOpenSearchResult(true);
|
|
4066
|
-
}, children: translate.t(removeIndex(`${colLabel}.add_more`)) })] })), !isMultiple && (jsx(Button, { variant: "outline", onClick: () => {
|
|
4093
|
+
}, children: translate.t(removeIndex(`${colLabel}.add_more`)) }, `${colLabel}-add-more-tag`)] })), !isMultiple && (jsx(Button, { variant: "outline", onClick: () => {
|
|
4067
4094
|
setOpenSearchResult(true);
|
|
4068
4095
|
}, justifyContent: "start", children: !!watchEnum === false
|
|
4069
4096
|
? ""
|
|
@@ -4884,13 +4911,101 @@ const TagPicker = ({ column, schema, prefix }) => {
|
|
|
4884
4911
|
}), errors[`${column}`] && (jsx(Text, { color: "red.400", children: (errors[`${column}`]?.message ?? "No error message") }))] }));
|
|
4885
4912
|
};
|
|
4886
4913
|
|
|
4914
|
+
const Textarea = forwardRef(({ value, defaultValue, placeholder, onChange, onFocus, onBlur, disabled = false, readOnly = false, className, rows = 4, maxLength, autoFocus = false, invalid = false, required = false, label, helperText, errorText, ...props }, ref) => {
|
|
4915
|
+
const contentEditableRef = useRef(null);
|
|
4916
|
+
const isControlled = value !== undefined;
|
|
4917
|
+
// Handle input changes
|
|
4918
|
+
const handleInput = (e) => {
|
|
4919
|
+
const text = e.currentTarget.textContent || "";
|
|
4920
|
+
// Check maxLength if specified
|
|
4921
|
+
if (maxLength && text.length > maxLength) {
|
|
4922
|
+
e.currentTarget.textContent = text.slice(0, maxLength);
|
|
4923
|
+
// Move cursor to end
|
|
4924
|
+
const selection = window.getSelection();
|
|
4925
|
+
if (selection) {
|
|
4926
|
+
selection.selectAllChildren(e.currentTarget);
|
|
4927
|
+
selection.collapseToEnd();
|
|
4928
|
+
}
|
|
4929
|
+
return;
|
|
4930
|
+
}
|
|
4931
|
+
onChange?.(text);
|
|
4932
|
+
};
|
|
4933
|
+
// Handle paste events to strip formatting and respect maxLength
|
|
4934
|
+
const handlePaste = (e) => {
|
|
4935
|
+
e.preventDefault();
|
|
4936
|
+
const text = e.clipboardData.getData('text/plain');
|
|
4937
|
+
const currentText = e.currentTarget.textContent || "";
|
|
4938
|
+
let pasteText = text;
|
|
4939
|
+
if (maxLength) {
|
|
4940
|
+
const remainingLength = maxLength - currentText.length;
|
|
4941
|
+
pasteText = text.slice(0, remainingLength);
|
|
4942
|
+
}
|
|
4943
|
+
document.execCommand('insertText', false, pasteText);
|
|
4944
|
+
};
|
|
4945
|
+
// Set initial content
|
|
4946
|
+
useEffect(() => {
|
|
4947
|
+
if (contentEditableRef.current && !isControlled) {
|
|
4948
|
+
const initialValue = defaultValue || "";
|
|
4949
|
+
if (contentEditableRef.current.textContent !== initialValue) {
|
|
4950
|
+
contentEditableRef.current.textContent = initialValue;
|
|
4951
|
+
}
|
|
4952
|
+
}
|
|
4953
|
+
}, [defaultValue, isControlled]);
|
|
4954
|
+
// Update content when value changes (controlled mode)
|
|
4955
|
+
useEffect(() => {
|
|
4956
|
+
if (contentEditableRef.current && isControlled && value !== undefined) {
|
|
4957
|
+
if (contentEditableRef.current.textContent !== value) {
|
|
4958
|
+
contentEditableRef.current.textContent = value;
|
|
4959
|
+
}
|
|
4960
|
+
}
|
|
4961
|
+
}, [value, isControlled]);
|
|
4962
|
+
// Auto focus
|
|
4963
|
+
useEffect(() => {
|
|
4964
|
+
if (autoFocus && contentEditableRef.current) {
|
|
4965
|
+
contentEditableRef.current.focus();
|
|
4966
|
+
}
|
|
4967
|
+
}, [autoFocus]);
|
|
4968
|
+
// Forward ref
|
|
4969
|
+
useEffect(() => {
|
|
4970
|
+
if (typeof ref === 'function') {
|
|
4971
|
+
ref(contentEditableRef.current);
|
|
4972
|
+
}
|
|
4973
|
+
else if (ref) {
|
|
4974
|
+
ref.current = contentEditableRef.current;
|
|
4975
|
+
}
|
|
4976
|
+
}, [ref]);
|
|
4977
|
+
const textareaElement = (jsx(Box, { ref: contentEditableRef, contentEditable: !disabled && !readOnly, onInput: handleInput, onPaste: handlePaste, onFocus: onFocus, onBlur: onBlur, className: className, minHeight: `${rows * 1.5}em`, padding: "2", border: "1px solid", borderColor: invalid ? "red.500" : "gray.200", borderRadius: "md", outline: "none", _focus: {
|
|
4978
|
+
borderColor: invalid ? "red.500" : "blue.500",
|
|
4979
|
+
boxShadow: `0 0 0 1px ${invalid ? "red.500" : "blue.500"}`,
|
|
4980
|
+
}, _disabled: {
|
|
4981
|
+
opacity: 0.6,
|
|
4982
|
+
cursor: "not-allowed",
|
|
4983
|
+
bg: "gray.50",
|
|
4984
|
+
}, _empty: {
|
|
4985
|
+
_before: {
|
|
4986
|
+
content: placeholder ? `"${placeholder}"` : undefined,
|
|
4987
|
+
color: "gray.400",
|
|
4988
|
+
pointerEvents: "none",
|
|
4989
|
+
}
|
|
4990
|
+
}, whiteSpace: "pre-wrap", overflowWrap: "break-word", overflow: "auto", maxHeight: `${rows * 4}em`, suppressContentEditableWarning: true, ...props }));
|
|
4991
|
+
// If we have additional field props, wrap in Field component
|
|
4992
|
+
if (label || helperText || errorText || required) {
|
|
4993
|
+
return (jsxs(Field$1.Root, { invalid: invalid, required: required, children: [label && (jsxs(Field$1.Label, { children: [label, required && jsx(Field$1.RequiredIndicator, {})] })), textareaElement, helperText && jsx(Field$1.HelperText, { children: helperText }), errorText && jsx(Field$1.ErrorText, { children: errorText })] }));
|
|
4994
|
+
}
|
|
4995
|
+
return textareaElement;
|
|
4996
|
+
});
|
|
4997
|
+
Textarea.displayName = "Textarea";
|
|
4998
|
+
|
|
4887
4999
|
const TextAreaInput = ({ column, schema, prefix, }) => {
|
|
4888
5000
|
const { register, formState: { errors }, } = useFormContext();
|
|
4889
5001
|
const { translate } = useSchemaContext();
|
|
4890
5002
|
const { required, gridColumn = "span 4", gridRow = "span 1" } = schema;
|
|
4891
5003
|
const isRequired = required?.some((columnId) => columnId === column);
|
|
4892
5004
|
const colLabel = `${prefix}${column}`;
|
|
4893
|
-
|
|
5005
|
+
const form = useFormContext();
|
|
5006
|
+
const { setValue, watch } = form;
|
|
5007
|
+
const watchValue = watch(colLabel);
|
|
5008
|
+
return (jsx(Fragment, { children: jsxs(Field, { label: `${translate.t(removeIndex(`${colLabel}.field_label`))}`, required: isRequired, gridColumn: gridColumn ?? "span 4", gridRow: gridRow ?? "span 1", display: "grid", children: [jsx(Textarea, { value: watchValue, onChange: (value) => setValue(colLabel, value) }), errors[colLabel] && (jsx(Text, { color: "red.400", children: translate.t(removeIndex(`${colLabel}.field_required`)) }))] }) }));
|
|
4894
5009
|
};
|
|
4895
5010
|
|
|
4896
5011
|
function TimePicker$1({ hour, setHour, minute, setMinute, meridiem, setMeridiem, meridiemLabel = {
|
|
@@ -5382,6 +5497,15 @@ const SchemaRenderer = ({ schema, prefix, column, }) => {
|
|
|
5382
5497
|
if (variant === "file-picker") {
|
|
5383
5498
|
return jsx(FilePicker, { schema: colSchema, prefix, column });
|
|
5384
5499
|
}
|
|
5500
|
+
if (variant === "enum-picker") {
|
|
5501
|
+
const { items } = colSchema;
|
|
5502
|
+
const { enum: enumItems } = items;
|
|
5503
|
+
const enumSchema = {
|
|
5504
|
+
type: "string",
|
|
5505
|
+
enum: enumItems,
|
|
5506
|
+
};
|
|
5507
|
+
return (jsx(EnumPicker, { isMultiple: true, schema: enumSchema, prefix, column }));
|
|
5508
|
+
}
|
|
5385
5509
|
if (items) {
|
|
5386
5510
|
return jsx(ArrayRenderer, { schema: colSchema, prefix, column });
|
|
5387
5511
|
}
|
|
@@ -5473,9 +5597,9 @@ const EnumViewer = ({ column, isMultiple = false, schema, prefix, }) => {
|
|
|
5473
5597
|
if (item === undefined) {
|
|
5474
5598
|
return jsx(Fragment, { children: "undefined" });
|
|
5475
5599
|
}
|
|
5476
|
-
return (jsx(Tag, { children: !!renderDisplay === true
|
|
5600
|
+
return (jsx(Tag, { size: "lg", children: !!renderDisplay === true
|
|
5477
5601
|
? renderDisplay(item)
|
|
5478
|
-
: customTranslate(item) }));
|
|
5602
|
+
: customTranslate(item) }, item));
|
|
5479
5603
|
}) })), !isMultiple && jsx(Text, { children: customTranslate(watchEnum) }), errors[`${column}`] && (jsx(Text, { color: "red.400", children: customTranslate(`field_required`) }))] }));
|
|
5480
5604
|
};
|
|
5481
5605
|
|
|
@@ -5789,6 +5913,15 @@ const SchemaViewer = ({ schema, prefix, column, }) => {
|
|
|
5789
5913
|
if (variant === "file-picker") {
|
|
5790
5914
|
return jsx(FileViewer, { schema: colSchema, prefix, column });
|
|
5791
5915
|
}
|
|
5916
|
+
if (variant === "enum-picker") {
|
|
5917
|
+
const { items } = schema;
|
|
5918
|
+
const { enum: enumItems } = items;
|
|
5919
|
+
const enumSchema = {
|
|
5920
|
+
type: "string",
|
|
5921
|
+
enum: enumItems,
|
|
5922
|
+
};
|
|
5923
|
+
return (jsx(EnumViewer, { isMultiple: true, schema: enumSchema, prefix, column }));
|
|
5924
|
+
}
|
|
5792
5925
|
if (items) {
|
|
5793
5926
|
return jsx(ArrayViewer, { schema: colSchema, prefix, column });
|
|
5794
5927
|
}
|
|
@@ -5816,16 +5949,12 @@ const SubmitButton = () => {
|
|
|
5816
5949
|
const methods = useFormContext();
|
|
5817
5950
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
5818
5951
|
const onValid = (data) => {
|
|
5819
|
-
|
|
5820
|
-
|
|
5821
|
-
|
|
5822
|
-
|
|
5823
|
-
|
|
5824
|
-
|
|
5825
|
-
// @ts-expect-error TODO: find appropriate type
|
|
5826
|
-
const errors = validationResult.errors;
|
|
5827
|
-
if (errors && errors.length > 0) {
|
|
5828
|
-
setError(errors);
|
|
5952
|
+
const { isValid, errors } = validateData(data, schema);
|
|
5953
|
+
if (!isValid) {
|
|
5954
|
+
setError({
|
|
5955
|
+
type: "validation",
|
|
5956
|
+
errors,
|
|
5957
|
+
});
|
|
5829
5958
|
setIsError(true);
|
|
5830
5959
|
return;
|
|
5831
5960
|
}
|
|
@@ -5840,7 +5969,8 @@ const SubmitButton = () => {
|
|
|
5840
5969
|
};
|
|
5841
5970
|
|
|
5842
5971
|
const FormBody = () => {
|
|
5843
|
-
const { schema, requestUrl, order, ignore, include, onSubmit,
|
|
5972
|
+
const { schema, requestUrl, order, ignore, include, onSubmit, translate, requestOptions, isSuccess, setIsSuccess, isError, setIsError, isSubmiting, setIsSubmiting, isConfirming, setIsConfirming, validatedData, setValidatedData, error, setError, getUpdatedData, customErrorRenderer, displayConfig, } = useSchemaContext();
|
|
5973
|
+
const { showSubmitButton, showResetButton } = displayConfig;
|
|
5844
5974
|
const methods = useFormContext();
|
|
5845
5975
|
const { properties } = schema;
|
|
5846
5976
|
const onBeforeSubmit = () => {
|
|
@@ -5856,24 +5986,11 @@ const FormBody = () => {
|
|
|
5856
5986
|
const onSubmitSuccess = () => {
|
|
5857
5987
|
setIsSuccess(true);
|
|
5858
5988
|
};
|
|
5859
|
-
// Enhanced validation function using AJV with i18n support
|
|
5860
5989
|
const validateFormData = (data) => {
|
|
5861
5990
|
try {
|
|
5862
|
-
const
|
|
5863
|
-
strict: false,
|
|
5864
|
-
allErrors: true,
|
|
5865
|
-
});
|
|
5866
|
-
addFormats(ajv);
|
|
5867
|
-
addErrors(ajv);
|
|
5868
|
-
const validate = ajv.compile(schema);
|
|
5869
|
-
const validationResult = validate(data);
|
|
5870
|
-
const errors = validate.errors;
|
|
5871
|
-
console.log({
|
|
5872
|
-
isValid: validationResult,
|
|
5873
|
-
errors,
|
|
5874
|
-
}, "plkdfs");
|
|
5991
|
+
const { isValid, errors } = validateData(data, schema);
|
|
5875
5992
|
return {
|
|
5876
|
-
isValid
|
|
5993
|
+
isValid,
|
|
5877
5994
|
errors,
|
|
5878
5995
|
};
|
|
5879
5996
|
}
|
|
@@ -5933,10 +6050,7 @@ const FormBody = () => {
|
|
|
5933
6050
|
};
|
|
5934
6051
|
// Custom error renderer for validation errors with i18n support
|
|
5935
6052
|
const renderValidationErrors = (validationErrors) => {
|
|
5936
|
-
return (jsx(
|
|
5937
|
-
base: "red.50",
|
|
5938
|
-
_dark: "red.950",
|
|
5939
|
-
}, p: "4", colorPalette: "red", collapsible: true, defaultValue: [], children: jsxs(AccordionItem, { value: "validation-errors", children: [jsx(AccordionItemTrigger, { children: translate.t("validation_error") }), jsx(AccordionItemContent, { display: "flex", flexFlow: "column", gap: "2", children: validationErrors.map((err, index) => (jsxs(AlertRoot, { status: "error", display: "flex", alignItems: "center", children: [jsx(AlertIndicator, {}), jsx(AlertContent, { children: jsx(AlertDescription, { children: err.message }) })] }))) })] }) }));
|
|
6053
|
+
return (jsx(Flex, { flexFlow: "column", gap: "2", children: validationErrors.map((err, index) => (jsxs(Alert.Root, { status: "error", display: "flex", alignItems: "center", children: [jsx(Alert.Indicator, {}), jsx(Alert.Content, { children: jsx(Alert.Description, { children: err.message }) })] }, index))) }));
|
|
5940
6054
|
};
|
|
5941
6055
|
const renderColumns = ({ order, keys, ignore, include, }) => {
|
|
5942
6056
|
const included = include.length > 0 ? include : keys;
|
|
@@ -5952,7 +6066,7 @@ const FormBody = () => {
|
|
|
5952
6066
|
include,
|
|
5953
6067
|
});
|
|
5954
6068
|
if (isSuccess) {
|
|
5955
|
-
return (jsxs(Flex, { flexFlow: "column", gap: "2", children: [jsxs(Alert.Root, { status: "success", children: [jsx(Alert.Indicator, {}), jsx(Alert.Title, { children: translate.t("submit_success") })] }), jsx(Flex, { justifyContent: "end", children: jsx(Button$1, { onClick: async () => {
|
|
6069
|
+
return (jsxs(Flex, { flexFlow: "column", gap: "2", children: [jsxs(Alert.Root, { status: "success", children: [jsx(Alert.Indicator, {}), jsx(Alert.Content, { children: jsx(Alert.Title, { children: translate.t("submit_success") }) })] }), jsx(Flex, { justifyContent: "end", children: jsx(Button$1, { onClick: async () => {
|
|
5956
6070
|
setIsError(false);
|
|
5957
6071
|
setIsSubmiting(false);
|
|
5958
6072
|
setIsSuccess(false);
|
|
@@ -5974,7 +6088,7 @@ const FormBody = () => {
|
|
|
5974
6088
|
}, variant: "subtle", children: translate.t("cancel") }), jsx(Button$1, { onClick: () => {
|
|
5975
6089
|
onFormSubmit(validatedData);
|
|
5976
6090
|
}, children: translate.t("confirm") })] }), isSubmiting && (jsx(Box, { pos: "absolute", inset: "0", bg: "bg/80", children: jsx(Center, { h: "full", children: jsx(Spinner, { color: "teal.500" }) }) })), isError && (jsx(Fragment, { children: customErrorRenderer ? (customErrorRenderer(error)) : (jsx(Fragment, { children: error?.type === "validation" &&
|
|
5977
|
-
error?.errors ? (renderValidationErrors(error.errors)) : (
|
|
6091
|
+
error?.errors ? (renderValidationErrors(error.errors)) : (jsxs(Alert.Root, { status: "error", children: [jsx(Alert.Indicator, {}), jsxs(Alert.Content, { children: [jsx(Alert.Title, { children: "Error" }), jsx(Alert.Description, { children: jsx(AccordionRoot, { collapsible: true, defaultValue: [], children: jsxs(AccordionItem, { value: "b", children: [jsx(AccordionItemTrigger, { children: `${error}` }), jsx(AccordionItemContent, { children: `${JSON.stringify(error)}` })] }) }) })] })] })) })) }))] }));
|
|
5978
6092
|
}
|
|
5979
6093
|
return (jsxs(Flex, { flexFlow: "column", gap: "2", children: [jsx(Grid, { gap: "4", gridTemplateColumns: "repeat(12, 1fr)", autoFlow: "row", children: ordered.map((column) => {
|
|
5980
6094
|
return (jsx(ColumnRenderer
|
|
@@ -5982,10 +6096,10 @@ const FormBody = () => {
|
|
|
5982
6096
|
, {
|
|
5983
6097
|
// @ts-expect-error find suitable types
|
|
5984
6098
|
properties: properties, prefix: ``, column }, `form-input-${column}`));
|
|
5985
|
-
}) }), jsxs(Flex, { justifyContent: "end", gap: "2", children: [jsx(Button$1, { onClick: () => {
|
|
6099
|
+
}) }), jsxs(Flex, { justifyContent: "end", gap: "2", children: [showResetButton && (jsx(Button$1, { onClick: () => {
|
|
5986
6100
|
methods.reset();
|
|
5987
|
-
}, variant: "subtle", children: translate.t("reset") }), jsx(SubmitButton, {})] }), isError &&
|
|
5988
|
-
|
|
6101
|
+
}, variant: "subtle", children: translate.t("reset") })), showSubmitButton && jsx(SubmitButton, {})] }), isError && (jsx(Fragment, { children: customErrorRenderer ? (customErrorRenderer(error)) : (jsx(Fragment, { children: error?.type === "validation" &&
|
|
6102
|
+
error?.errors ? (renderValidationErrors(error.errors)) : (jsxs(Alert.Root, { status: "error", children: [jsx(Alert.Indicator, {}), jsxs(Alert.Content, { children: [jsx(Alert.Title, { children: "Error" }), jsx(Alert.Description, { children: jsx(AccordionRoot, { collapsible: true, defaultValue: [], children: jsxs(AccordionItem, { value: "b", children: [jsx(AccordionItemTrigger, { children: `${error}` }), jsx(AccordionItemContent, { children: `${JSON.stringify(error)}` })] }) }) })] })] })) })) }))] }));
|
|
5989
6103
|
};
|
|
5990
6104
|
|
|
5991
6105
|
const FormTitle = () => {
|
|
@@ -5993,7 +6107,8 @@ const FormTitle = () => {
|
|
|
5993
6107
|
return jsx(Heading, { children: translate.t("title") });
|
|
5994
6108
|
};
|
|
5995
6109
|
|
|
5996
|
-
const DefaultForm = ({ formConfig,
|
|
6110
|
+
const DefaultForm = ({ formConfig, }) => {
|
|
6111
|
+
const { showTitle } = formConfig.displayConfig ?? {};
|
|
5997
6112
|
return (jsx(FormRoot, { ...formConfig, children: jsxs(Grid, { gap: "2", children: [showTitle && jsx(FormTitle, {}), jsx(FormBody, {})] }) }));
|
|
5998
6113
|
};
|
|
5999
6114
|
|
|
@@ -5,7 +5,7 @@ import { DensityState } from "./controls/DensityFeature";
|
|
|
5
5
|
import { DataTableLabel } from "./context/DataTableContext";
|
|
6
6
|
import { DataResponse } from "./useDataTableServer";
|
|
7
7
|
import { UseTranslationResponse } from "react-i18next";
|
|
8
|
-
export interface DataTableServerProps<TData
|
|
8
|
+
export interface DataTableServerProps<TData = unknown> {
|
|
9
9
|
children: ReactNode | ReactNode[];
|
|
10
10
|
/**
|
|
11
11
|
* Column definitions for the table.
|
|
@@ -34,10 +34,10 @@ export interface DataTableServerProps<TData extends DataResponse = DataResponse<
|
|
|
34
34
|
setColumnOrder: OnChangeFn<ColumnOrderState>;
|
|
35
35
|
setDensity: OnChangeFn<DensityState>;
|
|
36
36
|
setColumnVisibility: OnChangeFn<VisibilityState>;
|
|
37
|
-
query: UseQueryResult<TData
|
|
37
|
+
query: UseQueryResult<DataResponse<TData>>;
|
|
38
38
|
url: string;
|
|
39
39
|
translate: UseTranslationResponse<any, any>;
|
|
40
|
-
tableLabel
|
|
40
|
+
tableLabel?: DataTableLabel;
|
|
41
41
|
}
|
|
42
42
|
/**
|
|
43
43
|
* DataTableServer will create a context to hold all values to
|
|
@@ -50,4 +50,4 @@ export interface DataTableServerProps<TData extends DataResponse = DataResponse<
|
|
|
50
50
|
*
|
|
51
51
|
* @link https://tanstack.com/table/latest/docs/guide/column-defs
|
|
52
52
|
*/
|
|
53
|
-
export declare function DataTableServer<TData
|
|
53
|
+
export declare function DataTableServer<TData = unknown>({ columns, enableRowSelection, enableMultiRowSelection, enableSubRowSelection, columnOrder, columnFilters, columnVisibility, density, globalFilter, pagination, sorting, rowSelection, setPagination, setSorting, setColumnFilters, setRowSelection, setGlobalFilter, setColumnOrder, setDensity, setColumnVisibility, query, url, translate, children, tableLabel, }: DataTableServerProps<TData>): import("react/jsx-runtime").JSX.Element;
|
|
@@ -31,5 +31,10 @@ export interface SchemaFormContext<TData extends FieldValues> {
|
|
|
31
31
|
getUpdatedData: () => TData | Promise<TData>;
|
|
32
32
|
customErrorRenderer?: (error: unknown) => ReactNode;
|
|
33
33
|
timezone?: string;
|
|
34
|
+
displayConfig: {
|
|
35
|
+
showSubmitButton?: boolean;
|
|
36
|
+
showResetButton?: boolean;
|
|
37
|
+
showTitle?: boolean;
|
|
38
|
+
};
|
|
34
39
|
}
|
|
35
40
|
export declare const SchemaFormContext: import("react").Context<SchemaFormContext<unknown>>;
|
|
@@ -4,4 +4,4 @@ export interface DefaultFormProps<TData extends FieldValues> {
|
|
|
4
4
|
formConfig: Omit<FormRootProps<TData>, "children">;
|
|
5
5
|
showTitle?: boolean;
|
|
6
6
|
}
|
|
7
|
-
export declare const DefaultForm: <TData extends FieldValues>({ formConfig,
|
|
7
|
+
export declare const DefaultForm: <TData extends FieldValues>({ formConfig, }: DefaultFormProps<TData>) => import("react/jsx-runtime").JSX.Element;
|
|
@@ -22,6 +22,11 @@ export interface FormRootProps<TData extends FieldValues> {
|
|
|
22
22
|
requestOptions?: AxiosRequestConfig;
|
|
23
23
|
getUpdatedData?: () => TData | Promise<TData> | void;
|
|
24
24
|
customErrorRenderer?: (error: unknown) => ReactNode;
|
|
25
|
+
displayConfig?: {
|
|
26
|
+
showSubmitButton?: boolean;
|
|
27
|
+
showResetButton?: boolean;
|
|
28
|
+
showTitle?: boolean;
|
|
29
|
+
};
|
|
25
30
|
}
|
|
26
31
|
export interface CustomJSONSchema7Definition extends JSONSchema7 {
|
|
27
32
|
variant: string;
|
|
@@ -38,4 +43,4 @@ export declare const idPickerSanityCheck: (column: string, foreign_key?: {
|
|
|
38
43
|
column?: string | undefined;
|
|
39
44
|
display_column?: string | undefined;
|
|
40
45
|
} | undefined) => void;
|
|
41
|
-
export declare const FormRoot: <TData extends FieldValues>({ schema, idMap, setIdMap, form, serverUrl, translate, children, order, ignore, include, onSubmit, rowNumber, requestOptions, getUpdatedData, customErrorRenderer, }: FormRootProps<TData>) => import("react/jsx-runtime").JSX.Element;
|
|
46
|
+
export declare const FormRoot: <TData extends FieldValues>({ schema, idMap, setIdMap, form, serverUrl, translate, children, order, ignore, include, onSubmit, rowNumber, requestOptions, getUpdatedData, customErrorRenderer, displayConfig, }: FormRootProps<TData>) => import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { ErrorObject, ValidateFunction } from "ajv";
|
|
2
|
+
import { JSONSchema7 } from "json-schema";
|
|
3
|
+
type ValidateDataResult = {
|
|
4
|
+
isValid: boolean;
|
|
5
|
+
validate: ValidateFunction;
|
|
6
|
+
errors: ErrorObject<string, Record<string, any>, unknown>[] | null | undefined;
|
|
7
|
+
};
|
|
8
|
+
export declare const validateData: (data: unknown, schema: JSONSchema7) => ValidateDataResult;
|
|
9
|
+
export {};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type React from "react";
|
|
2
|
+
interface CustomTextareaProps {
|
|
3
|
+
value?: string;
|
|
4
|
+
defaultValue?: string;
|
|
5
|
+
placeholder?: string;
|
|
6
|
+
onChange?: (value: string) => void;
|
|
7
|
+
onFocus?: () => void;
|
|
8
|
+
onBlur?: () => void;
|
|
9
|
+
disabled?: boolean;
|
|
10
|
+
readOnly?: boolean;
|
|
11
|
+
className?: string;
|
|
12
|
+
rows?: number;
|
|
13
|
+
maxLength?: number;
|
|
14
|
+
autoFocus?: boolean;
|
|
15
|
+
invalid?: boolean;
|
|
16
|
+
required?: boolean;
|
|
17
|
+
label?: string;
|
|
18
|
+
helperText?: string;
|
|
19
|
+
errorText?: string;
|
|
20
|
+
}
|
|
21
|
+
declare const Textarea: React.ForwardRefExoticComponent<CustomTextareaProps & React.RefAttributes<HTMLDivElement>>;
|
|
22
|
+
export { Textarea };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bsol-oss/react-datatable5",
|
|
3
|
-
"version": "12.0.0-beta.
|
|
3
|
+
"version": "12.0.0-beta.64",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.mjs",
|
|
@@ -75,8 +75,8 @@
|
|
|
75
75
|
"@typescript-eslint/parser": "^7.2.0",
|
|
76
76
|
"@vitejs/plugin-react": "^4.2.1",
|
|
77
77
|
"ajv": "^8.12.0",
|
|
78
|
-
"ajv-formats": "^3.0.1",
|
|
79
78
|
"ajv-errors": "^3.0.0",
|
|
79
|
+
"ajv-formats": "^3.0.1",
|
|
80
80
|
"eslint": "^8.57.0",
|
|
81
81
|
"eslint-plugin-react-hooks": "^4.6.0",
|
|
82
82
|
"eslint-plugin-react-refresh": "^0.4.6",
|