@appbuckets/react-ui-smart-components
Version:
UI Extended Components that work with @appbuckets/react-client and @appbuckets/react-ui
464 lines (461 loc) • 15.8 kB
JavaScript
import {
__assign,
__read,
__awaiter,
__generator,
} from '../_virtual/_tslib.js';
import * as React from 'react';
import realyFastDeepClone from 'rfdc';
import { yupResolver } from '@hookform/resolvers/yup';
import Modal from '@appbuckets/react-ui/Modal';
import HookedForm from '@appbuckets/react-ui-forms/HookedForm';
import QuerySuspenseError from '../QuerySuspenseError/QuerySuspenseError.js';
import useActionBuilder from './lib/useActionBuilder.js';
import useActionNotification from './lib/useActionNotifications.js';
import assertUniqueComponentName from '../utils/assertUniqueComponentName.js';
import defaultValuesFromYupSchema from './utils/defaultValuesFromYupSchema.js';
import { FormBuiltProvider } from './FormBuiltProvider.js';
/* --------
* Create a data cloner
* -------- */
var dataCloner = realyFastDeepClone({ circles: false, proto: false });
/* --------
* Builder Function Definition
* -------- */
function buildFormAction(configuration) {
var _this = this;
// ----
// Deconstruct base configuration object
// ----
var // Strict
// Content Element
FormContent = configuration.Content,
defaultDefinedDisplayName = configuration.displayName,
// Buttons
defaultDefinedCancelButton = configuration.cancelButton,
defaultDefinedSubmitButton = configuration.submitButton,
// Action Notification
defaultDefinedToastSettings = configuration.toast,
// Local
// Props Builder
defaultPropsBuilder = configuration.defaultProps,
overridePropsBuilder = configuration.overrideProps,
// Schema and Form
extendValidation = configuration.extendValidation,
parseDataFn = configuration.parseData,
yupSchemaBuilder = configuration.schema,
stripUnknown = configuration.stripUnknown;
// ----
// Check multiple displayName in development mode only
// ----
assertUniqueComponentName(defaultDefinedDisplayName, 'form');
// ----
// Define the Built Component
// ----
var Form = function (userDefinedProps) {
// ----
// Extract useful props from userDefinedProps
// ----
var forcedUserDefinedEditingMode = userDefinedProps.isEditing;
// ----
// Computed the isEditing props
// ----
var couldBeEditing = React.useMemo(
function () {
return (
typeof userDefinedProps.defaultValues === 'object' &&
userDefinedProps.defaultValues !== null &&
!Array.isArray(userDefinedProps.defaultValues) &&
!!Object.keys(userDefinedProps.defaultValues).length
);
},
[userDefinedProps.defaultValues]
);
var isEditing =
typeof forcedUserDefinedEditingMode === 'boolean'
? forcedUserDefinedEditingMode
: couldBeEditing;
// ----
// Build Component Props
// ----
/** Compute default props */
var defaultProps =
typeof defaultPropsBuilder === 'function'
? defaultPropsBuilder(
__assign(__assign({}, userDefinedProps), { isEditing: isEditing })
)
: defaultPropsBuilder;
/** Compute override props */
var overrideProps =
typeof overridePropsBuilder === 'function'
? overridePropsBuilder(
__assign(__assign(__assign({}, defaultProps), userDefinedProps), {
isEditing: isEditing,
})
)
: overridePropsBuilder;
/** Merge all props into a single props object */
var props = __assign(
__assign(__assign({}, defaultProps), userDefinedProps),
overrideProps
);
// ----
// Deconstruct Props
// ----
var // Buttons
userDefinedCancelButton = props.cancelButton,
userDefinedSubmitButton = props.submitButton,
// Modal Props
renderAsModal = props.modal,
userDefinedModalProps = props.modalProps,
// Form Props
userDefinedDefaultValues = props.defaultValues;
// ----
// Hooks and State Definition
// ----
var _a =
// eslint-disable-next-line max-len
useActionBuilder(configuration, props),
actionHelpers = _a.actionHelpers,
couldRenderActionButton = _a.couldRenderActionButton,
open = _a.open,
handleModalOpen = _a.handleModalOpen,
handleModalClose = _a.handleModalClose,
trigger = _a.trigger,
onCancel = _a.onCancel,
onCompleted = _a.onCompleted,
onSubmit = _a.onSubmit,
onSubmitError = _a.onSubmitError;
var notify = useActionNotification(actionHelpers.toast, {
onCanceled:
defaultDefinedToastSettings === null ||
defaultDefinedToastSettings === void 0
? void 0
: defaultDefinedToastSettings.onCanceled,
onError:
defaultDefinedToastSettings === null ||
defaultDefinedToastSettings === void 0
? void 0
: defaultDefinedToastSettings.onError,
onSubmitted: isEditing
? defaultDefinedToastSettings === null ||
defaultDefinedToastSettings === void 0
? void 0
: defaultDefinedToastSettings.onEditingSubmit
: defaultDefinedToastSettings === null ||
defaultDefinedToastSettings === void 0
? void 0
: defaultDefinedToastSettings.onCreatingSubmit,
});
// ----
// Build Schema and initial defaultValues
// ----
var _b = __read(
React.useState(function () {
return typeof yupSchemaBuilder === 'function'
? yupSchemaBuilder(
__assign(__assign({}, props), { isEditing: isEditing })
)
: yupSchemaBuilder;
}),
1
),
schema = _b[0];
/** Default values are computed once only */
var defaultValues = React.useMemo(
function () {
/** Editing mode will clone default values to loose object reference while editing data */
if ((isEditing && couldBeEditing) || couldBeEditing) {
/** Clone data */
var clonedData = dataCloner(userDefinedDefaultValues);
/** Use the parse function if exists */
var parsedData =
typeof parseDataFn === 'function'
? parseDataFn(
clonedData,
__assign(__assign({}, props), { isEditing: isEditing })
)
: clonedData;
/** Return casted data using yup schema */
return stripUnknown
? schema.noUnknown().cast(parsedData)
: schema.cast(parsedData);
}
/** Else, if form is not in editing mode, build a default object starting from yup schema */
return defaultValuesFromYupSchema(schema);
},
// Heads Up
// Props always change, then, default values can't change every render
// component props are stripped from useMemo dependencies
// eslint-disable-next-line react-hooks/exhaustive-deps
[couldBeEditing, isEditing, schema, userDefinedDefaultValues]
);
// ----
// Utilities
// ----
var isFormValid = React.useCallback(
function (data, helpers) {
return __awaiter(_this, void 0, void 0, function () {
var error_1, _a, errorMessage, errorPath;
return __generator(this, function (_b) {
switch (_b.label) {
case 0:
_b.trys.push([0, 2, , 3]);
/** Await form validation */
return [4 /*yield*/, schema.validate(data)];
case 1:
/** Await form validation */
_b.sent();
/** Return form is valid */
return [2 /*return*/, true];
case 2:
error_1 = _b.sent();
(_a = error_1),
(errorMessage = _a.message),
(errorPath = _a.path);
/** Set error on form */
helpers.setError(
errorPath,
{ message: errorMessage },
{ shouldFocus: true }
);
/** Return form is invalid */
return [2 /*return*/, false];
case 3:
return [2 /*return*/];
}
});
});
},
[schema]
);
// ----
// Handlers
// ----
var handleSubmit = function (data, helpers) {
return __awaiter(_this, void 0, void 0, function () {
var formActionHelpers,
isValid,
dataToSend,
result,
error_2,
catchFunctionError_1;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
formActionHelpers = __assign(__assign({}, actionHelpers), {
form: helpers,
});
if (!extendValidation) return [3 /*break*/, 2];
return [4 /*yield*/, isFormValid(data, helpers)];
case 1:
isValid = _a.sent();
if (!isValid) {
return [2 /*return*/, undefined];
}
_a.label = 2;
case 2:
_a.trys.push([2, 7, , 12]);
dataToSend = stripUnknown
? schema.noUnknown().cast(data)
: schema.cast(data);
result = void 0;
if (!(typeof onSubmit === 'function')) return [3 /*break*/, 4];
return [
4 /*yield*/,
onSubmit(
dataToSend,
formActionHelpers,
__assign(__assign({}, props), { isEditing: isEditing })
),
];
case 3:
/** Await the result */
result = _a.sent();
_a.label = 4;
case 4:
if (!(typeof onCompleted === 'function')) return [3 /*break*/, 6];
/** Await function completion */
return [
4 /*yield*/,
onCompleted(
result,
data,
formActionHelpers,
__assign(__assign({}, props), { isEditing: isEditing })
),
];
case 5:
/** Await function completion */
_a.sent();
_a.label = 6;
case 6:
/** Raise the Submitted Notification */
notify.raiseOnSubmitted();
/** If has been rendered as modal, close it */
if (renderAsModal) {
handleModalClose(null, __assign({}, userDefinedModalProps));
}
return [2 /*return*/, result];
case 7:
error_2 = _a.sent();
/** Raise the onError notification */
notify.raiseOnError(error_2);
if (!(typeof onSubmitError === 'function'))
return [3 /*break*/, 11];
_a.label = 8;
case 8:
_a.trys.push([8, 10, , 11]);
/** Await catch error function */
return [
4 /*yield*/,
onSubmitError(
error_2,
data,
formActionHelpers,
__assign(__assign({}, props), { isEditing: isEditing })
),
];
case 9:
/** Await catch error function */
_a.sent();
return [3 /*break*/, 11];
case 10:
catchFunctionError_1 = _a.sent();
/** Log error in development mode only */
if (process.env.NODE_ENV === 'development') {
global.console.warn(
'[ @appbuckets/react-ui-smart-components ] : an error occurred on onSubmitError handler.',
catchFunctionError_1
);
}
return [3 /*break*/, 11];
case 11:
return [2 /*return*/, undefined];
case 12:
return [2 /*return*/];
}
});
});
};
var handleCancel = function (data, helpers) {
return __awaiter(_this, void 0, void 0, function () {
var formActionHelpers, error_3;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
formActionHelpers = __assign(__assign({}, actionHelpers), {
form: helpers,
});
_a.label = 1;
case 1:
_a.trys.push([1, 4, , 5]);
if (!(typeof onCancel === 'function')) return [3 /*break*/, 3];
/** Fire the onCancel Handler */
return [
4 /*yield*/,
onCancel(
formActionHelpers,
__assign(__assign({}, props), { isEditing: isEditing })
),
];
case 2:
/** Fire the onCancel Handler */
_a.sent();
_a.label = 3;
case 3:
/** Close the Modal is Confirm is rendered as it */
if (renderAsModal) {
handleModalClose(null, userDefinedModalProps || {});
}
/** Raise the onCanceled Notification */
notify.raiseOnCanceled();
return [3 /*break*/, 5];
case 4:
error_3 = _a.sent();
/** Log error in development mode only */
if (process.env.NODE_ENV === 'development') {
global.console.warn(
'[ @appbuckets/react-ui-smart-components ] : an error occurred on onCancel handler.',
error_3
);
}
return [3 /*break*/, 5];
case 5:
return [2 /*return*/];
}
});
});
};
// ----
// Build the Form Element
// ----
var formElement = React.createElement(
HookedForm,
{
resetOnCancel: true,
actionsWrapper: renderAsModal ? Modal.Actions : 'div',
contentWrapper: renderAsModal ? Modal.Content : 'div',
submitButton: couldRenderActionButton(
userDefinedSubmitButton,
defaultDefinedSubmitButton
)
? userDefinedSubmitButton || defaultDefinedSubmitButton
: null,
cancelButton: couldRenderActionButton(
userDefinedCancelButton,
defaultDefinedCancelButton
)
? userDefinedCancelButton || defaultDefinedCancelButton
: null,
defaultValues: defaultValues,
restoreDefaultValuesIfChanged: renderAsModal ? !open : false,
onSubmit: handleSubmit,
onCancel: handleCancel,
resolver: yupResolver(schema),
},
React.createElement(
FormBuiltProvider,
{ value: { isEditing: isEditing } },
actionHelpers.error &&
React.createElement(
QuerySuspenseError,
__assign({}, actionHelpers.error)
),
FormContent &&
React.createElement(
FormContent,
__assign({}, props, { isEditing: isEditing })
)
)
);
// ----
// Render Component as plain element
// ----
if (!renderAsModal) {
return formElement || null;
}
// ----
// Render Component as modal element
// ----
return React.createElement(
Modal,
__assign({}, userDefinedModalProps, {
open: open,
onOpen: handleModalOpen,
onClose: handleModalClose,
trigger: trigger,
}),
formElement
);
};
// ----
// Set the Display Name
// ----
Form.displayName = defaultDefinedDisplayName;
// ----
// Return the Component
// ----
return Form;
}
export { buildFormAction as default };