UNPKG

@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
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 };