@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 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 extends DataResponse = DataResponse<unknown>> {
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: DataTableLabel;
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 extends DataResponse = DataResponse<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;
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, showTitle, }: DefaultFormProps<TData>) => react_jsx_runtime.JSX.Element;
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
- 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", children: [jsxRuntime.jsx(react.Textarea, { ...register(`${colLabel}`, { required: isRequired }), autoComplete: "off" }), errors[colLabel] && (jsxRuntime.jsx(react.Text, { color: "red.400", children: translate.t(removeIndex(`${colLabel}.field_required`)) }))] }) }));
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
- // Validate data using AJV before proceeding to confirmation
5840
- const validate = new Ajv({
5841
- strict: false,
5842
- allErrors: true,
5843
- }).compile(schema);
5844
- const validationResult = validate(data);
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, rowNumber, translate, requestOptions, isSuccess, setIsSuccess, isError, setIsError, isSubmiting, setIsSubmiting, isConfirming, setIsConfirming, validatedData, setValidatedData, error, setError, getUpdatedData, customErrorRenderer, } = useSchemaContext();
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 ajv = new Ajv({
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: validationResult,
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(AccordionRoot, { backgroundColor: {
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.jsx(react.Alert.Root, { status: "error", children: jsxRuntime.jsx(react.Alert.Title, { children: jsxRuntime.jsx(AccordionRoot, { collapsible: true, defaultValue: [], children: jsxRuntime.jsxs(AccordionItem, { value: "b", children: [jsxRuntime.jsxs(AccordionItemTrigger, { children: [jsxRuntime.jsx(react.Alert.Indicator, {}), `${error}`] }), jsxRuntime.jsx(AccordionItemContent, { children: `${JSON.stringify(error)}` })] }) }) }) })) })) }))] }));
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 && error?.type === "validation" && (jsxRuntime.jsx(react.Box, { mt: 4, children: error?.errors &&
6008
- renderValidationErrors(error.errors) }))] }));
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, showTitle = true, }) => {
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, Textarea, Center, AlertRoot, AlertIndicator, AlertContent, AlertDescription, Heading } from '@chakra-ui/react';
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
- return (jsx(Fragment, { children: jsxs(Field, { label: `${translate.t(removeIndex(`${colLabel}.field_label`))}`, required: isRequired, gridColumn: gridColumn ?? "span 4", gridRow: gridRow ?? "span 1", children: [jsx(Textarea, { ...register(`${colLabel}`, { required: isRequired }), autoComplete: "off" }), errors[colLabel] && (jsx(Text, { color: "red.400", children: translate.t(removeIndex(`${colLabel}.field_required`)) }))] }) }));
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
- // Validate data using AJV before proceeding to confirmation
5820
- const validate = new Ajv({
5821
- strict: false,
5822
- allErrors: true,
5823
- }).compile(schema);
5824
- const validationResult = validate(data);
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, rowNumber, translate, requestOptions, isSuccess, setIsSuccess, isError, setIsError, isSubmiting, setIsSubmiting, isConfirming, setIsConfirming, validatedData, setValidatedData, error, setError, getUpdatedData, customErrorRenderer, } = useSchemaContext();
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 ajv = new Ajv({
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: validationResult,
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(AccordionRoot, { backgroundColor: {
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)) : (jsx(Alert.Root, { status: "error", children: jsx(Alert.Title, { children: jsx(AccordionRoot, { collapsible: true, defaultValue: [], children: jsxs(AccordionItem, { value: "b", children: [jsxs(AccordionItemTrigger, { children: [jsx(Alert.Indicator, {}), `${error}`] }), jsx(AccordionItemContent, { children: `${JSON.stringify(error)}` })] }) }) }) })) })) }))] }));
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 && error?.type === "validation" && (jsx(Box, { mt: 4, children: error?.errors &&
5988
- renderValidationErrors(error.errors) }))] }));
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, showTitle = true, }) => {
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 extends DataResponse = DataResponse<unknown>> {
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: DataTableLabel;
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 extends DataResponse = DataResponse<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;
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, showTitle, }: DefaultFormProps<TData>) => import("react/jsx-runtime").JSX.Element;
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.62",
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",