UNPKG

@transact-open-ux/react

Version:

Library to integrate React with Transact Open UX

943 lines (907 loc) 42.3 kB
/** * Copyright (c) 2019 Avoka Technologies Pty Limited * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ (function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('@transact-open-ux/core'), require('formik'), require('react')) : typeof define === 'function' && define.amd ? define(['exports', '@transact-open-ux/core', 'formik', 'react'], factory) : (global = global || self, factory(global['transact-open-ux-react'] = {}, global['@transact-open-ux/core'], global.formik, global.React)); }(this, function (exports, core, formik, React) { 'use strict'; var React__default = 'default' in React ? React['default'] : React; /*! ***************************************************************************** Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABLITY OR NON-INFRINGEMENT. See the Apache Version 2.0 License for specific language governing permissions and limitations under the License. ***************************************************************************** */ /* global Reflect, Promise */ var extendStatics = function(d, b) { extendStatics = Object.setPrototypeOf || ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; return extendStatics(d, b); }; function __extends(d, b) { extendStatics(d, b); function __() { this.constructor = d; } d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); } var __assign = function() { __assign = Object.assign || function __assign(t) { for (var s, i = 1, n = arguments.length; i < n; i++) { s = arguments[i]; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; } return t; }; return __assign.apply(this, arguments); }; function __rest(s, e) { var t = {}; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) t[p] = s[p]; if (s != null && typeof Object.getOwnPropertySymbols === "function") for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) if (e.indexOf(p[i]) < 0) t[p[i]] = s[p[i]]; return t; } function __decorate(decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; } function __param(paramIndex, decorator) { return function (target, key) { decorator(target, key, paramIndex); } } function __metadata(metadataKey, metadataValue) { if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(metadataKey, metadataValue); } function __awaiter(thisArg, _arguments, P, generator) { return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); } function __generator(thisArg, body) { var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; function verb(n) { return function (v) { return step([n, v]); }; } function step(op) { if (f) throw new TypeError("Generator is already executing."); while (_) try { if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; if (y = 0, t) op = [op[0] & 2, t.value]; switch (op[0]) { case 0: case 1: t = op; break; case 4: _.label++; return { value: op[1], done: false }; case 5: _.label++; y = op[1]; op = [0]; continue; case 7: op = _.ops.pop(); _.trys.pop(); continue; default: if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } if (t[2]) _.ops.pop(); _.trys.pop(); continue; } op = body.call(thisArg, _); } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; } } function __exportStar(m, exports) { for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p]; } function __values(o) { var m = typeof Symbol === "function" && o[Symbol.iterator], i = 0; if (m) return m.call(o); return { next: function () { if (o && i >= o.length) o = void 0; return { value: o && o[i++], done: !o }; } }; } function __read(o, n) { var m = typeof Symbol === "function" && o[Symbol.iterator]; if (!m) return o; var i = m.call(o), r, ar = [], e; try { while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value); } catch (error) { e = { error: error }; } finally { try { if (r && !r.done && (m = i["return"])) m.call(i); } finally { if (e) throw e.error; } } return ar; } function __spread() { for (var ar = [], i = 0; i < arguments.length; i++) ar = ar.concat(__read(arguments[i])); return ar; } function __await(v) { return this instanceof __await ? (this.v = v, this) : new __await(v); } function __asyncGenerator(thisArg, _arguments, generator) { if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined."); var g = generator.apply(thisArg, _arguments || []), i, q = []; return i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i; function verb(n) { if (g[n]) i[n] = function (v) { return new Promise(function (a, b) { q.push([n, v, a, b]) > 1 || resume(n, v); }); }; } function resume(n, v) { try { step(g[n](v)); } catch (e) { settle(q[0][3], e); } } function step(r) { r.value instanceof __await ? Promise.resolve(r.value.v).then(fulfill, reject) : settle(q[0][2], r); } function fulfill(value) { resume("next", value); } function reject(value) { resume("throw", value); } function settle(f, v) { if (f(v), q.shift(), q.length) resume(q[0][0], q[0][1]); } } function __asyncDelegator(o) { var i, p; return i = {}, verb("next"), verb("throw", function (e) { throw e; }), verb("return"), i[Symbol.iterator] = function () { return this; }, i; function verb(n, f) { i[n] = o[n] ? function (v) { return (p = !p) ? { value: __await(o[n](v)), done: n === "return" } : f ? f(v) : v; } : f; } } function __asyncValues(o) { if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined."); var m = o[Symbol.asyncIterator], i; return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i); function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; } function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); } } function __makeTemplateObject(cooked, raw) { if (Object.defineProperty) { Object.defineProperty(cooked, "raw", { value: raw }); } else { cooked.raw = raw; } return cooked; } function __importStar(mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; result.default = mod; return result; } function __importDefault(mod) { return (mod && mod.__esModule) ? mod : { default: mod }; } var tslib_1 = /*#__PURE__*/Object.freeze({ __extends: __extends, get __assign () { return __assign; }, __rest: __rest, __decorate: __decorate, __param: __param, __metadata: __metadata, __awaiter: __awaiter, __generator: __generator, __exportStar: __exportStar, __values: __values, __read: __read, __spread: __spread, __await: __await, __asyncGenerator: __asyncGenerator, __asyncDelegator: __asyncDelegator, __asyncValues: __asyncValues, __makeTemplateObject: __makeTemplateObject, __importStar: __importStar, __importDefault: __importDefault }); const TransactContext = React__default.createContext({}); const initialState = { error: null, formData: {}, formError: null, formLoaded: false, isLoading: false, revisionNumber: 0, saveChallengeLoading: false, validationErrors: [] }; const reducer = (state, action) => { switch (action.type) { case "SET_LOADING": return Object.assign({}, state, { isLoading: action.payload }); case "SET_FORM_LOADED": return Object.assign({}, state, { formLoaded: true, saveChallengeLoading: false }); case "SET_NARRATIVE_DATA": return Object.assign({}, state, { formData: core.setDataFromPath(`SFMData.NarrativeMeta.${action.payload.path}`, state.formData, action.payload.value) }); case "SET_SAVE_CHALLENGE_LOADING": return Object.assign({}, state, { saveChallengeLoading: action.payload }); case "SET_VALIDATION_ERRORS": return Object.assign({}, state, { validationErrors: action.payload }); case "SET_RESPONSE_STATE": return Object.assign({}, state, action.payload, { isLoading: false }); case "SET_STATE": return Object.assign({}, state, action.payload); default: return state; } }; /** * A component that surrounds your entire app and handles all the transact API logic and state. * * ## Example * Wrapping your root App component * ```jsx * import { TransactProvider, PageProvider } from '@transact-open-ux/react'; * import App from './App'; * * ReactDOM.render( * <TransactProvider> * <PageProvider> * <App /> * </PageProvider> * </TransactProvider>, * document.getElementById('root') * ); * ``` */ const TransactProvider = ({ children, onSaveChallengeRequired, formCode, formVersion, endpoint: formNewEndpoint, trackingCode: formNewTrackingCode }) => { const [state, dispatch] = React.useReducer(reducer, initialState); let formApi; function init() { return __awaiter(this, void 0, void 0, function* () { try { if (formNewEndpoint && (formCode || formNewTrackingCode)) { yield core.formNew(formNewEndpoint, formCode, formVersion, formNewTrackingCode); } if (core.isSaveChallengeRequired()) { if (typeof onSaveChallengeRequired === "function") { onSaveChallengeRequired(); } // treat save challenge required as loaded form, to load children of TransactForm dispatch({ type: "SET_FORM_LOADED" }); } else { yield handleFormLoad(); } } catch (e) { dispatch({ payload: e, type: "SET_RESPONSE_STATE" }); } }); } /** * Sets the loading properties as well as updating the transact state * @param transactCall - A promise returned from a call */ function dispatchCall(transactCall) { return __awaiter(this, void 0, void 0, function* () { try { dispatch({ type: "SET_LOADING", payload: true }); const response = yield transactCall; dispatch({ payload: response, type: "SET_RESPONSE_STATE" }); return response; } catch (e) { dispatch({ payload: e, type: "SET_RESPONSE_STATE" }); return e; } }); } const getInsightsProfile = () => core.getDataFromPath("SFMData.SystemProfile.Insights", state.formData); const getNarrativeData = (path) => core.getDataFromPath(`SFMData.NarrativeMeta${path ? "." + path : ""}`, state.formData); const getValidationError = (errorKey) => { const { validationErrors } = state; const error = validationErrors.filter(({ errorKey: err }) => errorKey === err); return error.length ? error[0] : undefined; }; const updateNarrativeData = (path, value) => { dispatch({ payload: { path, value }, type: "SET_NARRATIVE_DATA" }); }; function handleFormLoad(...args) { return __awaiter(this, void 0, void 0, function* () { try { const response = yield dispatchCall(core.formLoad(...args)); dispatch({ type: "SET_FORM_LOADED" }); return response; } catch (e) { return e; } }); } function handleTransactCall(fn, successPage, ...args) { return __awaiter(this, void 0, void 0, function* () { const response = yield dispatchCall(fn(...args)); if (successPage) { updateNarrativeData("CurrentPage", successPage); } return response; }); } const removeValidationErrors = (errorKeys) => { const { validationErrors } = state; if (typeof errorKeys === "string") { errorKeys = [errorKeys]; } const newValidationErrors = validationErrors.filter(({ errorKey }) => errorKeys.indexOf(errorKey) === -1); if (newValidationErrors.length !== validationErrors.length) { dispatch({ type: "SET_VALIDATION_ERRORS", payload: newValidationErrors }); } }; function resumeForm(saveChallengeAnswer, trackingCode, parameters) { return __awaiter(this, void 0, void 0, function* () { dispatch({ type: "SET_SAVE_CHALLENGE_LOADING", payload: true }); const response = yield handleFormLoad(saveChallengeAnswer, trackingCode, parameters); const { validationErrors, formData: newFormData } = response; if (!validationErrors && typeof newFormData === "object") { removeValidationErrors([ "stdErrs.formSaveChallengeInvalidAnswer", "stdErrs.formTrackingCodeNotFound" ]); return Object.assign({}, response, { SaveChallengeSuccess: true }); } return response; }); } React.useEffect(() => { init(); }, []); const ctx = Object.assign({}, state, { formError: (stacktrace, context) => dispatchCall(core.formError(stacktrace, context)), formFunction: core.formFunction, formIneligible: (data, ...args) => dispatchCall(core.formIneligible(data, ...args)), formInit: () => dispatchCall(core.formInit()), formLoad: handleFormLoad, formNew: (endpoint, ...args) => dispatchCall(core.formNew(endpoint, ...args)), formStart: (data, ...args) => dispatchCall(core.formStart(data, ...args)), formUpdate: (data, ...args) => dispatchCall(core.formUpdate(data, ...args)), getFormApi: () => formApi, getInsightsProfile, getNarrativeData, getValidationError, resumeForm, setFormApi: (formApiObj) => { formApi = formApiObj; }, userCancel: (data, milestone) => handleTransactCall(core.userCancel, "CancelSuccess", data, milestone), userSave: (data, ...args) => handleTransactCall(core.userSave, "SaveSuccess", data, ...args), userSubmit: (data, ...args) => handleTransactCall(core.userSubmit, "SubmitSuccess", data, ...args) }); return (React__default.createElement(TransactContext.Provider, { value: ctx }, children)); }; /** * A component that can access the transact state and form api via render props from inside a `<TransactProvider/>` component. * * ## Example * * Creating a save button using render props. * * ```jsx * import { TransactConsumer } from '@transact-open-ux/react'; * * export default function SaveButton() { * return ( * <TransactConsumer> * {({ userSave, getFormApi }) => ( * <button onClick={() => userSave(getFormApi().values)}>Save</button> * )} * </TransactConsumer> * ) * } * ``` */ const TransactConsumer = TransactContext.Consumer; const useTransactContext = () => React.useContext(TransactContext); /** * @private * @param value */ const hasValue = (value) => { return typeof value === "number" || typeof value === "boolean" || !!value; }; // Used by component to determine whether a field has been validated at least once let validatedOnce = {}; /** * The `TransactForm` Component is a light wrapper on top of [Formik's](https://jaredpalmer.com/formik) `Formik` component. * <br> * It adds page level validation functionality with Yup and transact support to Formik, * while maintaining all of Formik's flexibility and functionality. * <br> * You can read more about the properties inherited from the `Formik` component [here](https://jaredpalmer.com/formik/docs/api/formik). */ const TransactForm = props => { const formikRef = React.useRef({ current: null }); const { render, children, validate, validationSchema, loadingComponent: LoadingComponent, sendEmailFormReceipt, submissionEmailAddress, submissionMilestone } = props, rest = __rest(props, ["render", "children", "validate", "validationSchema", "loadingComponent", "sendEmailFormReceipt", "submissionEmailAddress", "submissionMilestone"]); const { formData = {}, formLoaded, formStatus, userSubmit, setFormApi } = useTransactContext(); const handleSubmit = values => { if (formStatus !== "Completed") { userSubmit(values, sendEmailFormReceipt, submissionEmailAddress, submissionMilestone); } }; const renderFormComponent = formikProps => { if (setFormApi) { setFormApi(formikProps); } return typeof render === "function" ? render(Object.assign({}, props, formikProps)) : typeof children === "function" ? children(Object.assign({}, props, formikProps)) : null; }; const handleYupErrors = validationError => { const { inner } = validationError; const { fields: mountedFields = {}, state = {} } = formikRef.current; const { touched, isSubmitting } = state; const errorMessages = []; validationError.inner = inner.filter(innerError => { const { path, value, message } = innerError; const isTouched = !!formik.getIn(touched, path); let isMounted; if (Array.isArray(value)) { isMounted = mountedFields.hasOwnProperty(`${path}[0]`); } else if (value && typeof value === "object") { isMounted = Object.keys(value).every(key => { return mountedFields.hasOwnProperty(`${path}.${key}`); }); } else { isMounted = mountedFields.hasOwnProperty(path); } // submitting or touched and validated once or has a value const keepError = isMounted && (isSubmitting || (isTouched && (validatedOnce[path] || hasValue(value)))); if (keepError) { if (!validatedOnce[path]) { validatedOnce[path] = true; } errorMessages.push(message); } return keepError; }); if (errorMessages.length) { validationError.errors = errorMessages; throw formik.yupToFormErrors(validationError); } }; const handleValidation = values => { if (validationSchema) { const { fields: mountedFields = {}, state = {} } = formikRef.current; const { touched } = state; // loop mounted fields to mark which ones will be validated once Object.keys(mountedFields).forEach(path => { const isTouched = !!formik.getIn(touched, path); if (isTouched && hasValue(formik.getIn(values, path)) && !validatedOnce[path]) { validatedOnce[path] = true; } }); return validationSchema .validate(values, { abortEarly: false }) .then(() => ({})) .catch(handleYupErrors); } return validate(values); }; // ensures if the component is re-mounted the validation will be reset React.useEffect(() => { validatedOnce = {}; }, []); return formLoaded ? (React__default.createElement(formik.Formik, Object.assign({ initialValues: Object.assign({ $SaveChallenge: { TrackingCode: core.getUrlQueryParam("trackingCode") } }, formData) }, rest, { validate: (validationSchema || validate) && handleValidation, enableReinitialize: true, onSubmit: handleSubmit, render: renderFormComponent, ref: formikRef }))) : LoadingComponent ? (React__default.createElement(LoadingComponent, null)) : null; }; /** * This component provides page level configuration for the Transact Application Framework via the `id` prop. * <br> * It also adds meta data for each page via the `label` and `offMenu` props. * <br> * You can dynamically load pages using the [React.lazy](https://reactjs.org/docs/react-api.html#reactlazy) method * <br> * You can also provide a fallback component for whilst it is loading via the fallback prop, this uses [React.Suspense](https://reactjs.org/docs/react-api.html#reactsuspense) under the hood * * ## Example * This example has "two" Pages lazy loaded via React.lazy and "one" offMenu page for the confirmation page. * * This means that the "SubmitSuccess" Page will NOT be a member of the `navPages` array in the PageConsumer. * ```jsx * import React from 'react'; * import { Page, PageController } from '@transact-open-ux/react'; * * const Page1 = React.lazy(() => import('../Page1')); * const Page2 = React.lazy(() => import('../Page2')); * * export const Pages = () => ( * <PageController> * <Page id="Page1" loadingComponent={<div>loading...</div>} > * <Page1 /> * </Page> * <Page id="Page2" loadingComponent={<div>loading...</div>}> * <Page2 /> * </Page> * <Page id="SubmitSuccess" offMenu> * You have successfully submitted your application * </Page> * </PageController> * ); * ``` */ const Page = ({ children, loadingComponent }) => loadingComponent ? (React__default.createElement(React.Suspense, { fallback: loadingComponent }, children)) : (React__default.createElement(React.Fragment, null, children)); const PageContext = React__default.createContext({}); const initialState$1 = { currentPage: null, disableBack: false, disableSubmit: false, index: 0, navPages: [], pageMap: {}, pages: [], validatePage: null }; const reducer$1 = (state, action) => { switch (action.type) { case "SET_INDEX": return Object.assign({}, state, { index: action.payload }); case "SET_CURRENT_PAGE": return Object.assign({}, state, { currentPage: action.payload }); case "SET_STATE": return Object.assign({}, state, action.payload); default: return state; } }; /** * A component that surrounds your entire app and handles all the page logic. * <br> * This page logic includes Transact Application Framework support, page change logic, validation and meta data. * <br> * This component needs to be wrapped within the `TransactProvider` component. * * ## Example * Wrapping your root App component * ```jsx * import { TransactProvider, PageProvider } from '@transact-open-ux/react'; * import App from './App'; * * ReactDOM.render( * <TransactProvider> * <PageProvider> * <App /> * </PageProvider> * </TransactProvider>, * document.getElementById('root') * ); * ``` */ const PageProvider = ({ children, skipValidate }) => { const [state, dispatch] = React.useReducer(reducer$1, initialState$1); const { formUpdate, getFormApi, getNarrativeData, saveChallengeLoading } = useTransactContext(); const { navPages, pageMap, validatePage, tafEnabled } = state; const currentNarrativePage = getNarrativeData("CurrentPage"); const initPages = (pages, validatePageFn) => { const initialNavPages = []; const initialPageMap = {}; pages.forEach((page, index) => { const { props: { id, offMenu } } = page; const key = id || index; // use id (if available) otherwise use index as id initialPageMap[key] = { index, page }; if (!offMenu) { initialNavPages.push(page); } }); dispatch({ payload: { navPages: initialNavPages, pageMap: initialPageMap, pages, tafEnabled: Object.keys(getNarrativeData() || {}).length >= 2, validatePage: validatePageFn }, type: "SET_STATE" }); }; const getCurrentPage = () => { const { index, pages } = state; // Don't render if save challenge is loading or if there aren't any pages initialized // re-render page to ensure new data is re-initialized correctly if (saveChallengeLoading || (!pages || !pages.length)) { return null; } return pages[index]; }; const getCurrentPageMeta = () => { const page = getCurrentPage(); if (page) { const { props: { id, label, offMenu } } = page; const index = navPages.indexOf(page); const stepNumber = index >= 0 ? index + 1 : null; return { id, label, offMenu, stepNumber }; } return { offMenu: true }; }; function goToPage(pageIdOrNum, skipValidation, performFormUpdate) { return __awaiter(this, void 0, void 0, function* () { const formApi = (getFormApi && getFormApi()) || {}; const valid = skipValidation || (yield validatePage()); if (valid) { if (typeof pageIdOrNum === "number") { pageIdOrNum = pageIdOrNum - 1; } core.setDataFromPath("SFMData.NarrativeMeta.CurrentPage", formApi.values, pageIdOrNum); const { index } = pageMap[pageIdOrNum]; dispatch({ payload: index, type: "SET_INDEX" }); if (performFormUpdate) { formUpdate(formApi.values); } } }); } function navigate(action = "forward") { return __awaiter(this, void 0, void 0, function* () { const formApi = (getFormApi && getFormApi()) || {}; // Handle validation if (!skipValidate && action !== "back" && typeof validatePage === "function") { const valid = yield validatePage(); if (!valid) { return Promise.resolve(valid); } } if (tafEnabled) { // set narrative action type core.setDataFromPath("SFMData.NarrativeMeta.Action", formApi.values, action); } else { const { pages } = state; let { index } = state; index = action === "forward" ? Math.min(index + 1, pages.length) : Math.max(index - 1, 0); // update current page for save/resume capability const { props: { id } } = pages[index]; core.setDataFromPath("SFMData.NarrativeMeta.CurrentPage", formApi.values, id || index); dispatch({ payload: index, type: "SET_INDEX" }); } // perform formUpdate return formUpdate(formApi.values); }); } React.useEffect(() => { // This lifecycle method is used to update the index and currentPage // of the component based on changes coming from server or elsewhere if (currentNarrativePage && currentNarrativePage !== state.currentPage && pageMap.hasOwnProperty(currentNarrativePage)) { const { index } = pageMap[currentNarrativePage]; dispatch({ payload: index, type: "SET_INDEX" }); dispatch({ payload: currentNarrativePage, type: "SET_CURRENT_PAGE" }); } }, [currentNarrativePage]); const currentPageNode = getCurrentPage(); const narrativeMeta = getNarrativeData() || {}; const { DisableSubmit: disableSubmit = !!(currentPageNode && navPages.indexOf(currentPageNode) !== navPages.length - 1), DisableBack: disableBack = !!(currentPageNode && navPages.indexOf(currentPageNode) === 0), RedirectUrl } = narrativeMeta; if (RedirectUrl) { core.redirect(RedirectUrl); } const ctx = Object.assign({}, state, { currentPageMeta: getCurrentPageMeta(), disableBack, disableSubmit, getCurrentPage, getFormApi, goToPage, initPages, navigate }); return React__default.createElement(PageContext.Provider, { value: ctx }, children); }; /** * A component that can access the page state via render props from inside a `<PageProvider/>` component. * * ## Example * * Creating a navigation bar component * * ```jsx * import { PageConsumer } from '@transact-open-ux/react'; * * export default function NavBar() { * return ( * <PageConsumer> * {({ navigate, disableSubmit }) => ( * <div> * <button onClick={() => navigate("back")} type="button">Back</button> * {DisableSubmit ? ( * <button onClick={() => navigate("forward")} type="button">Next</button> * ) : ( * <button type="submit">Submit</button> * )} * </div> * )} * </PageConsumer>; * ) * } * ``` */ const PageConsumer = PageContext.Consumer; const usePageContext = () => React.useContext(PageContext); /** * Allows multiple pages to be added to an application. * <br> * This component initializes the pages and loads them into the page context state. * <br> * If you are not using the Transact Application Framework, ensure end of flow pages i.e. SaveSuccess * are at the end of the PageController children. * * ## Example * ```html * <PageController> * <Page id="LandingPage" offMenu> * <LandingPage /> * </Page> * <Page id="GettingStarted" label="Let's get started"> * <GettingStarted /> * </Page> * <Page id="AboutYou" label="All about you"> * <AboutYou /> * </Page> * <Page id="SubmitSuccess" offMenu> * <SubmitSuccess /> * </Page> * <Page id="SaveSuccess" offMenu> * <SaveSuccess /> * </Page> * <Page id="CancelSuccess" offMenu> * <CancelSuccess /> * </Page> * </PageController> * ``` */ const PageController = ({ children }) => { const { getCurrentPage, initPages, getFormApi } = usePageContext(); React.useEffect(() => { const pages = React__default.Children.toArray(children); function pageValidation() { return __awaiter(this, void 0, void 0, function* () { const formApi = getFormApi(); let valid = true; formApi.setSubmitting(true); // set touched so everything gets validated // validate try { valid = !Object.keys(yield formApi.validateForm()).length; // if valid reset touched for next page if (valid) { formApi.setTouched({}); } else { formApi.setTouched(formik.setNestedObjectValues(formApi.values, true)); } formApi.setSubmitting(false); return valid; } catch (e) { formApi.setSubmitting(false); return valid; } }); } initPages(pages, pageValidation); }, []); return getCurrentPage(); }; /** * @private * @param values * @param condition */ const isVisible = (values, condition) => { let visible = true; try { visible = !!condition(values); } catch (ex) { return visible; } return visible; }; /** * Allows conditional show/hide of elements based on a condition function passed as a prop. * <br> * This component needs to be within the `TransactForm` component to work as it uses the form state values. * * ## Example * Shows the Applicant.LastName field when the Applicant.FirstName field is 'Homer' * ```jsx * import { Field } from 'formik'; * import { Visibility } from '@transact-open-ux/react'; * * export const Page = () => ( * <Visibility condition={data => data.Applicant.FirstName === 'Homer'}> * <Field * name="Applicant.LastName" * required * label="Last Name" * /> * </Visibility> * ); * ``` */ const Visibility = ({ children, condition }) => (React__default.createElement(formik.FormikConsumer, null, ({ values }) => (isVisible(values, condition) ? children : null))); /** * `withTransact()` is a higher-order component (HoC) that allows you to hook into the TransactProvider components context. * * It is used internally to construct the `PageProvider` and `TransactForm` components, however you can use it to build out new components as your needs change. * * This component must be within the TransactProvider component. * ## Example * ```jsx * import React from 'react'; * import { withTransact } from '@transact-open-ux/react'; * * const SaveButton = props => { * // All TransactProps available on props.transact * return ( * <button type="button" onClick={() => props.transact.userSave(props.getFormApi().values)}> * Click To Save Application * </button> * ) * }; * * export default withTransact(SaveButton); * ``` */ const withTransact = (WrappedComponent) => (props) => (React__default.createElement(TransactConsumer, null, renderProps => React__default.createElement(WrappedComponent, Object.assign({}, props, { transact: renderProps })))); exports.Page = Page; exports.PageConsumer = PageConsumer; exports.PageController = PageController; exports.PageProvider = PageProvider; exports.TransactConsumer = TransactConsumer; exports.TransactForm = TransactForm; exports.TransactProvider = TransactProvider; exports.Visibility = Visibility; exports.usePageContext = usePageContext; exports.useTransactContext = useTransactContext; exports.withTransact = withTransact; Object.defineProperty(exports, '__esModule', { value: true }); })); //# sourceMappingURL=open-ux-react.js.map