@transact-open-ux/react
Version:
Library to integrate React with Transact Open UX
925 lines (892 loc) • 37.9 kB
JavaScript
/**
* 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.
*/
import { formNew, isSaveChallengeRequired, formLoad, formError, formFunction, formIneligible, formInit, formStart, formUpdate, userCancel, userSave, userSubmit, setDataFromPath, getDataFromPath, getUrlQueryParam, redirect } from '@transact-open-ux/core';
import { Formik, getIn, yupToFormErrors, setNestedObjectValues, FormikConsumer } from 'formik';
import React, { useReducer, useEffect, useContext, useRef, Suspense, Fragment } from '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.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: 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] = useReducer(reducer, initialState);
let formApi;
function init() {
return __awaiter(this, void 0, void 0, function* () {
try {
if (formNewEndpoint && (formCode || formNewTrackingCode)) {
yield formNew(formNewEndpoint, formCode, formVersion, formNewTrackingCode);
}
if (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 = () => getDataFromPath("SFMData.SystemProfile.Insights", state.formData);
const getNarrativeData = (path) => 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(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;
});
}
useEffect(() => {
init();
}, []);
const ctx = Object.assign({}, state, { formError: (stacktrace, context) => dispatchCall(formError(stacktrace, context)), formFunction, formIneligible: (data, ...args) => dispatchCall(formIneligible(data, ...args)), formInit: () => dispatchCall(formInit()), formLoad: handleFormLoad, formNew: (endpoint, ...args) => dispatchCall(formNew(endpoint, ...args)), formStart: (data, ...args) => dispatchCall(formStart(data, ...args)), formUpdate: (data, ...args) => dispatchCall(formUpdate(data, ...args)), getFormApi: () => formApi, getInsightsProfile,
getNarrativeData,
getValidationError,
resumeForm, setFormApi: (formApiObj) => {
formApi = formApiObj;
}, userCancel: (data, milestone) => handleTransactCall(userCancel, "CancelSuccess", data, milestone), userSave: (data, ...args) => handleTransactCall(userSave, "SaveSuccess", data, ...args), userSubmit: (data, ...args) => handleTransactCall(userSubmit, "SubmitSuccess", data, ...args) });
return (React.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 = () => 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 = 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 = !!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 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 = !!getIn(touched, path);
if (isTouched &&
hasValue(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
useEffect(() => {
validatedOnce = {};
}, []);
return formLoaded ? (React.createElement(Formik, Object.assign({ initialValues: Object.assign({ $SaveChallenge: {
TrackingCode: getUrlQueryParam("trackingCode")
} }, formData) }, rest, { validate: (validationSchema || validate) && handleValidation, enableReinitialize: true, onSubmit: handleSubmit, render: renderFormComponent, ref: formikRef }))) : LoadingComponent ? (React.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.createElement(Suspense, { fallback: loadingComponent }, children)) : (React.createElement(Fragment, null, children));
const PageContext = React.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] = 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;
}
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
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];
setDataFromPath("SFMData.NarrativeMeta.CurrentPage", formApi.values, id || index);
dispatch({
payload: index,
type: "SET_INDEX"
});
}
// perform formUpdate
return formUpdate(formApi.values);
});
}
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) {
redirect(RedirectUrl);
}
const ctx = Object.assign({}, state, { currentPageMeta: getCurrentPageMeta(), disableBack,
disableSubmit,
getCurrentPage,
getFormApi,
goToPage,
initPages,
navigate });
return React.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 = () => 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();
useEffect(() => {
const pages = React.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(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.createElement(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.createElement(TransactConsumer, null, renderProps => React.createElement(WrappedComponent, Object.assign({}, props, { transact: renderProps }))));
export { Page, PageConsumer, PageController, PageProvider, TransactConsumer, TransactForm, TransactProvider, Visibility, usePageContext, useTransactContext, withTransact };
//# sourceMappingURL=open-ux-react.es.js.map