@openmrs/esm-form-engine-lib 2.1.0-pre.1575 → 2.1.0-pre.1581
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/openmrs-esm-form-engine-lib.js +1 -1
- package/package.json +1 -1
- package/src/adapters/obs-adapter.ts +1 -1
- package/src/components/renderer/field/fieldLogic.ts +3 -0
- package/src/components/renderer/field/form-field-renderer.component.tsx +0 -2
- package/src/components/renderer/form/form-renderer.component.tsx +20 -6
- package/src/components/renderer/page/page.renderer.component.tsx +12 -5
- package/src/components/sidebar/page-observer.ts +58 -0
- package/src/components/sidebar/sidebar.component.tsx +68 -94
- package/src/components/sidebar/sidebar.scss +56 -72
- package/src/components/sidebar/useCurrentActivePage.test.ts +222 -0
- package/src/components/sidebar/useCurrentActivePage.ts +137 -0
- package/src/components/sidebar/usePageObserver.ts +45 -0
- package/src/form-engine.component.tsx +34 -22
- package/src/hooks/useEvaluateFormFieldExpressions.ts +4 -1
- package/src/hooks/useFormStateHelpers.ts +1 -1
- package/src/hooks/useFormWorkspaceSize.test.ts +117 -0
- package/src/hooks/useFormWorkspaceSize.ts +52 -0
- package/src/lifecycle.ts +2 -0
- package/src/processors/encounter/encounter-form-processor.ts +1 -1
- package/src/provider/form-factory-provider.tsx +0 -4
- package/src/transformers/default-schema-transformer.test.ts +7 -0
- package/src/transformers/default-schema-transformer.ts +8 -5
- package/src/types/schema.ts +2 -0
- package/src/utils/common-utils.ts +10 -0
- package/src/utils/form-helper.test.ts +64 -1
- package/src/utils/form-helper.ts +20 -1
- package/src/hooks/useWorkspaceLayout.ts +0 -29
@@ -1 +1 @@
|
|
1
|
-
var _openmrs_esm_form_engine_lib;(()=>{"use strict";var e,r,t,n,o,i,a,l,s,u,f,p,d,c,h,m,v,g,b,y,w,_={8008:(e,r,t)=>{var n={"./start":()=>Promise.all([t.e(690),t.e(
|
1
|
+
var _openmrs_esm_form_engine_lib;(()=>{"use strict";var e,r,t,n,o,i,a,l,s,u,f,p,d,c,h,m,v,g,b,y,w,_={8008:(e,r,t)=>{var n={"./start":()=>Promise.all([t.e(690),t.e(585),t.e(72),t.e(385),t.e(539)]).then((()=>()=>t(5539)))},o=(e,r)=>(t.R=r,r=t.o(n,e)?n[e]():Promise.resolve().then((()=>{throw new Error('Module "'+e+'" does not exist in container.')})),t.R=void 0,r),i=(e,r)=>{if(t.S){var n="default",o=t.S[n];if(o&&o!==e)throw new Error("Container initialization failed as it has already been initialized with a different share scope");return t.S[n]=e,t.I(n,r)}};t.d(r,{get:()=>o,init:()=>i})}},P={};function S(e){var r=P[e];if(void 0!==r)return r.exports;var t=P[e]={id:e,loaded:!1,exports:{}};return _[e].call(t.exports,t,t.exports,S),t.loaded=!0,t.exports}S.m=_,S.c=P,S.n=e=>{var r=e&&e.__esModule?()=>e.default:()=>e;return S.d(r,{a:r}),r},r=Object.getPrototypeOf?e=>Object.getPrototypeOf(e):e=>e.__proto__,S.t=function(t,n){if(1&n&&(t=this(t)),8&n)return t;if("object"==typeof t&&t){if(4&n&&t.__esModule)return t;if(16&n&&"function"==typeof t.then)return t}var o=Object.create(null);S.r(o);var i={};e=e||[null,r({}),r([]),r(r)];for(var a=2&n&&t;"object"==typeof a&&!~e.indexOf(a);a=r(a))Object.getOwnPropertyNames(a).forEach((e=>i[e]=()=>t[e]));return i.default=()=>t,S.d(o,i),o},S.d=(e,r)=>{for(var t in r)S.o(r,t)&&!S.o(e,t)&&Object.defineProperty(e,t,{enumerable:!0,get:r[t]})},S.f={},S.e=e=>Promise.all(Object.keys(S.f).reduce(((r,t)=>(S.f[t](e,r),r)),[])),S.u=e=>e+".js",S.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),S.o=(e,r)=>Object.prototype.hasOwnProperty.call(e,r),t={},n="@openmrs/esm-form-engine-lib:",S.l=(e,r,o,i)=>{if(t[e])t[e].push(r);else{var a,l;if(void 0!==o)for(var s=document.getElementsByTagName("script"),u=0;u<s.length;u++){var f=s[u];if(f.getAttribute("src")==e||f.getAttribute("data-webpack")==n+o){a=f;break}}a||(l=!0,(a=document.createElement("script")).charset="utf-8",a.timeout=120,S.nc&&a.setAttribute("nonce",S.nc),a.setAttribute("data-webpack",n+o),a.src=e),t[e]=[r];var p=(r,n)=>{a.onerror=a.onload=null,clearTimeout(d);var o=t[e];if(delete t[e],a.parentNode&&a.parentNode.removeChild(a),o&&o.forEach((e=>e(n))),r)return r(n)},d=setTimeout(p.bind(null,void 0,{type:"timeout",target:a}),12e4);a.onerror=p.bind(null,a.onerror),a.onload=p.bind(null,a.onload),l&&document.head.appendChild(a)}},S.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},S.nmd=e=>(e.paths=[],e.children||(e.children=[]),e),(()=>{S.S={};var e={},r={};S.I=(t,n)=>{n||(n=[]);var o=r[t];if(o||(o=r[t]={}),!(n.indexOf(o)>=0)){if(n.push(o),e[t])return e[t];S.o(S.S,t)||(S.S[t]={});var i=S.S[t],a="@openmrs/esm-form-engine-lib",l=(e,r,t,n)=>{var o=i[e]=i[e]||{},l=o[r];(!l||!l.loaded&&(!n!=!l.eager?n:a>l.from))&&(o[r]={get:t,from:a,eager:!!n})},s=[];return"default"===t&&(l("@openmrs/esm-framework","5.8.2-pre.2396",(()=>Promise.all([S.e(151),S.e(72),S.e(766)]).then((()=>()=>S(5151))))),l("@openmrs/esm-patient-common-lib","8.2.1-pre.5761",(()=>Promise.all([S.e(254),S.e(690),S.e(72),S.e(465),S.e(385),S.e(70)]).then((()=>()=>S(3873))))),l("dayjs","1.11.13",(()=>S.e(353).then((()=>()=>S(4353))))),l("i18next","23.16.0",(()=>S.e(635).then((()=>()=>S(2635))))),l("react-i18next","11.18.6",(()=>Promise.all([S.e(979),S.e(72)]).then((()=>()=>S(2979))))),l("react","18.3.1",(()=>S.e(540).then((()=>()=>S(6540))))),l("swr/_internal","2.2.5",(()=>Promise.all([S.e(993),S.e(72)]).then((()=>()=>S(4993))))),l("swr/immutable","2.2.5",(()=>Promise.all([S.e(225),S.e(72),S.e(465)]).then((()=>()=>S(4225))))),l("swr/infinite","2.2.5",(()=>Promise.all([S.e(41),S.e(72),S.e(465)]).then((()=>()=>S(3041)))))),e[t]=s.length?Promise.all(s).then((()=>e[t]=1)):1}}})(),(()=>{var e;S.g.importScripts&&(e=S.g.location+"");var r=S.g.document;if(!e&&r&&(r.currentScript&&"SCRIPT"===r.currentScript.tagName.toUpperCase()&&(e=r.currentScript.src),!e)){var t=r.getElementsByTagName("script");if(t.length)for(var n=t.length-1;n>-1&&(!e||!/^http(s?):/.test(e));)e=t[n--].src}if(!e)throw new Error("Automatic publicPath is not supported in this browser");e=e.replace(/#.*$/,"").replace(/\?.*$/,"").replace(/\/[^\/]+$/,"/"),S.p=e})(),o=e=>{var r=e=>e.split(".").map((e=>+e==e?+e:e)),t=/^([^-+]+)?(?:-([^+]+))?(?:\+(.+))?$/.exec(e),n=t[1]?r(t[1]):[];return t[2]&&(n.length++,n.push.apply(n,r(t[2]))),t[3]&&(n.push([]),n.push.apply(n,r(t[3]))),n},i=(e,r)=>{e=o(e),r=o(r);for(var t=0;;){if(t>=e.length)return t<r.length&&"u"!=(typeof r[t])[0];var n=e[t],i=(typeof n)[0];if(t>=r.length)return"u"==i;var a=r[t],l=(typeof a)[0];if(i!=l)return"o"==i&&"n"==l||"s"==l||"u"==i;if("o"!=i&&"u"!=i&&n!=a)return n<a;t++}},a=e=>{var r=e[0],t="";if(1===e.length)return"*";if(r+.5){t+=0==r?">=":-1==r?"<":1==r?"^":2==r?"~":r>0?"=":"!=";for(var n=1,o=1;o<e.length;o++)n--,t+="u"==(typeof(l=e[o]))[0]?"-":(n>0?".":"")+(n=2,l);return t}var i=[];for(o=1;o<e.length;o++){var l=e[o];i.push(0===l?"not("+s()+")":1===l?"("+s()+" || "+s()+")":2===l?i.pop()+" "+i.pop():a(l))}return s();function s(){return i.pop().replace(/^\((.+)\)$/,"$1")}},l=(e,r)=>{if(0 in e){r=o(r);var t=e[0],n=t<0;n&&(t=-t-1);for(var i=0,a=1,s=!0;;a++,i++){var u,f,p=a<e.length?(typeof e[a])[0]:"";if(i>=r.length||"o"==(f=(typeof(u=r[i]))[0]))return!s||("u"==p?a>t&&!n:""==p!=n);if("u"==f){if(!s||"u"!=p)return!1}else if(s)if(p==f)if(a<=t){if(u!=e[a])return!1}else{if(n?u>e[a]:u<e[a])return!1;u!=e[a]&&(s=!1)}else if("s"!=p&&"n"!=p){if(n||a<=t)return!1;s=!1,a--}else{if(a<=t||f<p!=n)return!1;s=!1}else"s"!=p&&"n"!=p&&(s=!1,a--)}}var d=[],c=d.pop.bind(d);for(i=1;i<e.length;i++){var h=e[i];d.push(1==h?c()|c():2==h?c()&c():h?l(h,r):!c())}return!!c()},s=(e,r)=>e&&S.o(e,r),u=e=>(e.loaded=1,e.get()),f=e=>Object.keys(e).reduce(((r,t)=>(e[t].eager&&(r[t]=e[t]),r)),{}),p=(e,r,t)=>{var n=t?f(e[r]):e[r];return Object.keys(n).reduce(((e,r)=>!e||!n[e].loaded&&i(e,r)?r:e),0)},d=(e,r,t,n)=>"Unsatisfied version "+t+" from "+(t&&e[r][t].from)+" of shared singleton module "+r+" (required "+a(n)+")",c=e=>{throw new Error(e)},h=e=>{"undefined"!=typeof console&&console.warn&&console.warn(e)},m=(e,r,t)=>t?t():((e,r)=>c("Shared module "+r+" doesn't exist in shared scope "+e))(e,r),v=(e=>function(r,t,n,o,i){var a=S.I(r);return a&&a.then&&!n?a.then(e.bind(e,r,S.S[r],t,!1,o,i)):e(r,S.S[r],t,n,o,i)})(((e,r,t,n,o,i)=>{if(!s(r,t))return m(e,t,i);var a=p(r,t,n);return l(o,a)||h(d(r,t,a,o)),u(r[t][a])})),g={},b={6072:()=>v("default","react",!1,[1,18],(()=>S.e(540).then((()=>()=>S(6540))))),6766:()=>v("default","i18next",!1,[1,23],(()=>S.e(635).then((()=>()=>S(2635))))),8465:()=>v("default","swr/_internal",!1,[1,2],(()=>S.e(993).then((()=>()=>S(4993))))),3941:()=>v("default","react-i18next",!1,[1,11],(()=>S.e(979).then((()=>()=>S(2979))))),5972:()=>v("default","@openmrs/esm-framework",!1,[1,5],(()=>Promise.all([S.e(151),S.e(766)]).then((()=>()=>S(5151))))),6656:()=>v("default","@openmrs/esm-patient-common-lib",!1,[1,8],(()=>Promise.all([S.e(254),S.e(465)]).then((()=>()=>S(3873))))),4209:()=>v("default","swr/immutable",!1,[1,2],(()=>Promise.all([S.e(225),S.e(465)]).then((()=>()=>S(4225))))),231:()=>v("default","dayjs",!1,[1,1],(()=>S.e(353).then((()=>()=>S(4353))))),6339:()=>v("default","swr/infinite",!1,[1,2],(()=>Promise.all([S.e(41),S.e(465)]).then((()=>()=>S(3041)))))},y={70:[4209],72:[6072],385:[3941,5972,6656],465:[8465],539:[231,4209,6339],766:[6766]},w={},S.f.consumes=(e,r)=>{S.o(y,e)&&y[e].forEach((e=>{if(S.o(g,e))return r.push(g[e]);if(!w[e]){var t=r=>{g[e]=0,S.m[e]=t=>{delete S.c[e],t.exports=r()}};w[e]=!0;var n=r=>{delete g[e],S.m[e]=t=>{throw delete S.c[e],r}};try{var o=b[e]();o.then?r.push(g[e]=o.then(t).catch(n)):t(o)}catch(e){n(e)}}}))},(()=>{var e={719:0};S.f.j=(r,t)=>{var n=S.o(e,r)?e[r]:void 0;if(0!==n)if(n)t.push(n[2]);else if(/^(385|465|72|766)$/.test(r))e[r]=0;else{var o=new Promise(((t,o)=>n=e[r]=[t,o]));t.push(n[2]=o);var i=S.p+S.u(r),a=new Error;S.l(i,(t=>{if(S.o(e,r)&&(0!==(n=e[r])&&(e[r]=void 0),n)){var o=t&&("load"===t.type?"missing":t.type),i=t&&t.target&&t.target.src;a.message="Loading chunk "+r+" failed.\n("+o+": "+i+")",a.name="ChunkLoadError",a.type=o,a.request=i,n[1](a)}}),"chunk-"+r,r)}};var r=(r,t)=>{var n,o,[i,a,l]=t,s=0;if(i.some((r=>0!==e[r]))){for(n in a)S.o(a,n)&&(S.m[n]=a[n]);l&&l(S)}for(r&&r(t);s<i.length;s++)o=i[s],S.o(e,o)&&e[o]&&e[o][0](),e[o]=0},t=globalThis.webpackChunk_openmrs_esm_form_engine_lib=globalThis.webpackChunk_openmrs_esm_form_engine_lib||[];t.forEach(r.bind(null,0)),t.push=r.bind(null,t.push.bind(t))})(),S.nc=void 0;var j=S(8008);_openmrs_esm_form_engine_lib=j})();
|
package/package.json
CHANGED
@@ -7,6 +7,7 @@ import {
|
|
7
7
|
type AttachmentResponse,
|
8
8
|
type Attachment,
|
9
9
|
type ValueAndDisplay,
|
10
|
+
type FormFieldValueAdapter,
|
10
11
|
} from '../types';
|
11
12
|
import {
|
12
13
|
hasRendering,
|
@@ -17,7 +18,6 @@ import {
|
|
17
18
|
formatDateAsDisplayString,
|
18
19
|
} from '../utils/common-utils';
|
19
20
|
import { type FormContextProps } from '../provider/form-provider';
|
20
|
-
import { type FormFieldValueAdapter } from '../types';
|
21
21
|
import { isEmpty } from '../validators/form-validator';
|
22
22
|
import { getAttachmentByUuid } from '../api';
|
23
23
|
import { formatDate, restBaseUrl } from '@openmrs/esm-framework';
|
@@ -6,6 +6,7 @@ import { hasRendering } from '../../../utils/common-utils';
|
|
6
6
|
import { evaluateAsyncExpression, evaluateExpression } from '../../../utils/expression-runner';
|
7
7
|
import { evalConditionalRequired, evaluateDisabled, evaluateHide } from '../../../utils/form-helper';
|
8
8
|
import { isEmpty } from '../../../validators/form-validator';
|
9
|
+
import { reportError } from '../../../utils/error-utils';
|
9
10
|
|
10
11
|
export function handleFieldLogic(field: FormField, context: FormContextProps) {
|
11
12
|
const {
|
@@ -80,6 +81,8 @@ function evaluateFieldDependents(field: FormField, values: any, context: FormCon
|
|
80
81
|
context.formFieldAdapters[dependent.type].transformFieldValue(dependent, result, context);
|
81
82
|
}
|
82
83
|
updateFormField(dependent);
|
84
|
+
}).catch((error) => {
|
85
|
+
reportError(error, 'Error evaluating calculate expression');
|
83
86
|
});
|
84
87
|
}
|
85
88
|
// evaluate hide
|
@@ -2,10 +2,8 @@ import React, { useEffect, useMemo, useState } from 'react';
|
|
2
2
|
import {
|
3
3
|
type FormField,
|
4
4
|
type FormFieldInputProps,
|
5
|
-
type FormFieldValidator,
|
6
5
|
type FormFieldValueAdapter,
|
7
6
|
type RenderType,
|
8
|
-
type SessionMode,
|
9
7
|
type ValidationResult,
|
10
8
|
type ValueAndDisplay,
|
11
9
|
} from '../../../types';
|
@@ -9,6 +9,8 @@ import { FormProvider, type FormContextProps } from '../../../provider/form-prov
|
|
9
9
|
import { isTrue } from '../../../utils/boolean-utils';
|
10
10
|
import { type FormProcessorContextProps } from '../../../types';
|
11
11
|
import { useFormStateHelpers } from '../../../hooks/useFormStateHelpers';
|
12
|
+
import { pageObserver } from '../../sidebar/page-observer';
|
13
|
+
import { isPageContentVisible } from '../../../utils/form-helper';
|
12
14
|
|
13
15
|
export type FormRendererProps = {
|
14
16
|
processorContext: FormProcessorContextProps;
|
@@ -23,7 +25,10 @@ export const FormRenderer = ({
|
|
23
25
|
isSubForm,
|
24
26
|
setIsLoadingFormDependencies,
|
25
27
|
}: FormRendererProps) => {
|
26
|
-
const { evaluatedFields, evaluatedFormJson } = useEvaluateFormFieldExpressions(
|
28
|
+
const { evaluatedFields, evaluatedFormJson, evaluatedPagesVisibility } = useEvaluateFormFieldExpressions(
|
29
|
+
initialValues,
|
30
|
+
processorContext,
|
31
|
+
);
|
27
32
|
const { registerForm, setIsFormDirty, workspaceLayout, isFormExpanded } = useFormFactory();
|
28
33
|
const methods = useForm({
|
29
34
|
defaultValues: initialValues,
|
@@ -50,6 +55,19 @@ export const FormRenderer = ({
|
|
50
55
|
setForm,
|
51
56
|
} = useFormStateHelpers(dispatch, formFields);
|
52
57
|
|
58
|
+
useEffect(() => {
|
59
|
+
const scrollablePages = formJson.pages.filter((page) => !page.isSubform).map((page) => page);
|
60
|
+
pageObserver.updateScrollablePages(scrollablePages);
|
61
|
+
}, [formJson.pages]);
|
62
|
+
|
63
|
+
useEffect(() => {
|
64
|
+
pageObserver.setEvaluatedPagesVisibility(evaluatedPagesVisibility);
|
65
|
+
}, [evaluatedPagesVisibility]);
|
66
|
+
|
67
|
+
useEffect(() => {
|
68
|
+
pageObserver.updatePagesWithErrors(invalidFields.map((field) => field.meta.pageId));
|
69
|
+
}, [invalidFields]);
|
70
|
+
|
53
71
|
const context: FormContextProps = useMemo(() => {
|
54
72
|
return {
|
55
73
|
...processorContext,
|
@@ -80,11 +98,7 @@ export const FormRenderer = ({
|
|
80
98
|
return (
|
81
99
|
<FormProvider {...context}>
|
82
100
|
{formJson.pages.map((page) => {
|
83
|
-
|
84
|
-
page.sections?.every((section) => section.isHidden) ||
|
85
|
-
page.sections?.every((section) => section.questions?.every((question) => question.isHidden)) ||
|
86
|
-
isTrue(page.isHidden);
|
87
|
-
if (!page.isSubform && pageHasNoVisibleContent) {
|
101
|
+
if (!page.isSubform && !isPageContentVisible(page)) {
|
88
102
|
return null;
|
89
103
|
}
|
90
104
|
if (page.isSubform && page.subform?.form) {
|
@@ -6,9 +6,9 @@ import { SectionRenderer } from '../section/section-renderer.component';
|
|
6
6
|
import { Waypoint } from 'react-waypoint';
|
7
7
|
import styles from './page.renderer.scss';
|
8
8
|
import { Accordion, AccordionItem } from '@carbon/react';
|
9
|
-
import { useFormFactory } from '../../../provider/form-factory-provider';
|
10
9
|
import { ChevronDownIcon, ChevronUpIcon } from '@openmrs/esm-framework';
|
11
10
|
import classNames from 'classnames';
|
11
|
+
import { pageObserver } from '../../sidebar/page-observer';
|
12
12
|
|
13
13
|
interface PageRendererProps {
|
14
14
|
page: FormPage;
|
@@ -24,10 +24,8 @@ interface CollapsibleSectionContainerProps {
|
|
24
24
|
|
25
25
|
function PageRenderer({ page, isFormExpanded }: PageRendererProps) {
|
26
26
|
const { t } = useTranslation();
|
27
|
-
const pageId = useMemo(() => page.label.replace(/\s/g, ''), [page.label]);
|
28
27
|
const [isCollapsed, setIsCollapsed] = useState(false);
|
29
28
|
|
30
|
-
const { setCurrentPage } = useFormFactory();
|
31
29
|
const visibleSections = useMemo(
|
32
30
|
() =>
|
33
31
|
page.sections.filter((section) => {
|
@@ -41,12 +39,21 @@ function PageRenderer({ page, isFormExpanded }: PageRendererProps) {
|
|
41
39
|
|
42
40
|
useEffect(() => {
|
43
41
|
setIsCollapsed(!isFormExpanded);
|
42
|
+
|
43
|
+
return () => {
|
44
|
+
pageObserver.removeInactivePage(page.id);
|
45
|
+
};
|
44
46
|
}, [isFormExpanded]);
|
45
47
|
|
46
48
|
return (
|
47
49
|
<div>
|
48
|
-
<Waypoint
|
49
|
-
|
50
|
+
<Waypoint
|
51
|
+
key={page.id}
|
52
|
+
onEnter={() => pageObserver.addActivePage(page.id)}
|
53
|
+
onLeave={() => pageObserver.removeInactivePage(page.id)}
|
54
|
+
topOffset="40%"
|
55
|
+
bottomOffset="40%">
|
56
|
+
<div id={page.id} className={styles.pageContent}>
|
50
57
|
<div className={styles.pageHeader} onClick={toggleCollapse}>
|
51
58
|
<p className={styles.pageTitle}>
|
52
59
|
{t(page.label)}
|
@@ -0,0 +1,58 @@
|
|
1
|
+
import { BehaviorSubject } from 'rxjs';
|
2
|
+
import { type FormPage } from '../../types';
|
3
|
+
|
4
|
+
class PageObserver {
|
5
|
+
private scrollablePagesSubject = new BehaviorSubject<Array<FormPage>>([]);
|
6
|
+
private pagesWithErrorsSubject = new BehaviorSubject<Set<string>>(new Set());
|
7
|
+
private activePagesSubject = new BehaviorSubject<Set<string>>(new Set());
|
8
|
+
private evaluatedPagesVisibilitySubject = new BehaviorSubject<boolean>(null);
|
9
|
+
|
10
|
+
setEvaluatedPagesVisibility(evaluatedPagesVisibility: boolean) {
|
11
|
+
this.evaluatedPagesVisibilitySubject.next(evaluatedPagesVisibility);
|
12
|
+
}
|
13
|
+
|
14
|
+
updateScrollablePages(newPages: Array<FormPage>) {
|
15
|
+
this.scrollablePagesSubject.next(newPages);
|
16
|
+
}
|
17
|
+
|
18
|
+
updatePagesWithErrors(newErrors: string[]) {
|
19
|
+
this.pagesWithErrorsSubject.next(new Set(newErrors));
|
20
|
+
}
|
21
|
+
|
22
|
+
addActivePage(pageId: string) {
|
23
|
+
const currentActivePages = this.activePagesSubject.value;
|
24
|
+
currentActivePages.add(pageId);
|
25
|
+
this.activePagesSubject.next(currentActivePages);
|
26
|
+
}
|
27
|
+
|
28
|
+
removeInactivePage(pageId: string) {
|
29
|
+
const currentActivePages = this.activePagesSubject.value;
|
30
|
+
currentActivePages.delete(pageId);
|
31
|
+
this.activePagesSubject.next(currentActivePages);
|
32
|
+
}
|
33
|
+
|
34
|
+
getActivePagesObservable() {
|
35
|
+
return this.activePagesSubject.asObservable();
|
36
|
+
}
|
37
|
+
|
38
|
+
getScrollablePagesObservable() {
|
39
|
+
return this.scrollablePagesSubject.asObservable();
|
40
|
+
}
|
41
|
+
|
42
|
+
getPagesWithErrorsObservable() {
|
43
|
+
return this.pagesWithErrorsSubject.asObservable();
|
44
|
+
}
|
45
|
+
|
46
|
+
getEvaluatedPagesVisibilityObservable() {
|
47
|
+
return this.evaluatedPagesVisibilitySubject.asObservable();
|
48
|
+
}
|
49
|
+
|
50
|
+
clear() {
|
51
|
+
this.scrollablePagesSubject.next([]);
|
52
|
+
this.pagesWithErrorsSubject.next(new Set());
|
53
|
+
this.activePagesSubject.next(new Set());
|
54
|
+
this.evaluatedPagesVisibilitySubject.next(false);
|
55
|
+
}
|
56
|
+
}
|
57
|
+
|
58
|
+
export const pageObserver = new PageObserver();
|
@@ -1,118 +1,68 @@
|
|
1
|
-
import React
|
1
|
+
import React from 'react';
|
2
2
|
import classNames from 'classnames';
|
3
3
|
import { useTranslation } from 'react-i18next';
|
4
|
-
import { Button
|
5
|
-
import {
|
6
|
-
import { type FormPage } from '../../types';
|
4
|
+
import { Button } from '@carbon/react';
|
5
|
+
import { type FormPage, type SessionMode } from '../../types';
|
7
6
|
import styles from './sidebar.scss';
|
8
|
-
import {
|
7
|
+
import { usePageObserver } from './usePageObserver';
|
8
|
+
import { useCurrentActivePage } from './useCurrentActivePage';
|
9
|
+
import { isPageContentVisible } from '../../utils/form-helper';
|
10
|
+
import { InlineLoading } from '@carbon/react';
|
9
11
|
|
10
12
|
interface SidebarProps {
|
11
|
-
allowUnspecifiedAll: boolean;
|
12
13
|
defaultPage: string;
|
13
|
-
handleClose: () => void;
|
14
|
-
hideFormCollapseToggle: () => void;
|
15
14
|
isFormSubmitting: boolean;
|
16
|
-
|
15
|
+
sessionMode: SessionMode;
|
17
16
|
onCancel: () => void;
|
18
|
-
|
19
|
-
|
20
|
-
selectedPage: string;
|
21
|
-
setValues: (values: unknown) => void;
|
22
|
-
values: object;
|
17
|
+
handleClose: () => void;
|
18
|
+
hideFormCollapseToggle: () => void;
|
23
19
|
}
|
24
20
|
|
25
21
|
const Sidebar: React.FC<SidebarProps> = ({
|
26
|
-
allowUnspecifiedAll,
|
27
22
|
defaultPage,
|
28
|
-
handleClose,
|
29
|
-
hideFormCollapseToggle,
|
30
23
|
isFormSubmitting,
|
31
|
-
|
24
|
+
sessionMode,
|
32
25
|
onCancel,
|
33
|
-
|
34
|
-
|
35
|
-
selectedPage,
|
36
|
-
setValues,
|
37
|
-
values,
|
26
|
+
handleClose,
|
27
|
+
hideFormCollapseToggle,
|
38
28
|
}) => {
|
39
29
|
const { t } = useTranslation();
|
40
|
-
const pages
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
}
|
47
|
-
|
48
|
-
const unspecifiedFields = useMemo(
|
49
|
-
() =>
|
50
|
-
Object.keys(values).filter(
|
51
|
-
(key) => key.endsWith('-unspecified') && isEmpty(values[key.split('-unspecified')[0]]),
|
52
|
-
),
|
53
|
-
[values],
|
54
|
-
);
|
55
|
-
|
56
|
-
const handleClick = (selected) => {
|
57
|
-
const activeId = joinWord(selected);
|
58
|
-
scrollIntoView(activeId);
|
59
|
-
};
|
60
|
-
|
61
|
-
const markAllAsUnspecified = useCallback(
|
62
|
-
(toggled) => {
|
63
|
-
const updatedValues = { ...values };
|
64
|
-
unspecifiedFields.forEach((field) => {
|
65
|
-
updatedValues[field] = toggled;
|
66
|
-
});
|
67
|
-
setValues(updatedValues);
|
68
|
-
},
|
69
|
-
[unspecifiedFields, values, setValues],
|
70
|
-
);
|
30
|
+
const { pages, pagesWithErrors, activePages, evaluatedPagesVisibility } = usePageObserver();
|
31
|
+
const { currentActivePage, requestPage } = useCurrentActivePage({
|
32
|
+
pages,
|
33
|
+
defaultPage,
|
34
|
+
activePages,
|
35
|
+
evaluatedPagesVisibility,
|
36
|
+
});
|
71
37
|
|
72
38
|
return (
|
73
39
|
<div className={styles.sidebar}>
|
74
|
-
{pages
|
75
|
-
|
40
|
+
{pages
|
41
|
+
.filter((page) => isPageContentVisible(page))
|
42
|
+
.map((page) => (
|
43
|
+
<PageLink
|
44
|
+
key={page.id}
|
45
|
+
page={page}
|
46
|
+
currentActivePage={currentActivePage}
|
47
|
+
pagesWithErrors={pagesWithErrors}
|
48
|
+
requestPage={requestPage}
|
49
|
+
/>
|
50
|
+
))}
|
51
|
+
{sessionMode !== 'view' && <hr className={styles.divider} />}
|
76
52
|
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
return (
|
81
|
-
<div
|
82
|
-
aria-hidden="true"
|
83
|
-
className={classNames({
|
84
|
-
[styles.erroredSection]: isCurrentlySelected && hasError,
|
85
|
-
[styles.activeSection]: isCurrentlySelected && !hasError,
|
86
|
-
[styles.activeErroredSection]: !isCurrentlySelected && hasError,
|
87
|
-
[styles.section]: !isCurrentlySelected && !hasError,
|
88
|
-
})}
|
89
|
-
key={index}
|
90
|
-
onClick={() => handleClick(page.label)}>
|
91
|
-
<div className={styles.sectionLink}>{page.label}</div>
|
92
|
-
</div>
|
93
|
-
);
|
94
|
-
})}
|
95
|
-
{mode !== 'view' && <hr className={styles.divider} />}
|
96
|
-
<div className={styles.sidenavActions}>
|
97
|
-
{allowUnspecifiedAll && mode !== 'view' && (
|
98
|
-
<div className={styles.toggleContainer}>
|
99
|
-
<Toggle
|
100
|
-
id="auto-unspecifier"
|
101
|
-
labelA={t('unspecifyAll', 'Unspecify All')}
|
102
|
-
labelB={t('revert', 'Revert')}
|
103
|
-
labelText=""
|
104
|
-
onToggle={markAllAsUnspecified}
|
105
|
-
/>
|
106
|
-
</div>
|
107
|
-
)}
|
108
|
-
{mode !== 'view' && (
|
53
|
+
<div className={styles.sideNavActions}>
|
54
|
+
{sessionMode !== 'view' && (
|
109
55
|
<Button className={styles.saveButton} disabled={isFormSubmitting} type="submit">
|
110
|
-
{
|
56
|
+
{isFormSubmitting ? (
|
57
|
+
<InlineLoading description={t('submitting', 'Submitting') + '...'} />
|
58
|
+
) : (
|
59
|
+
<span>{`${t('save', 'Save')}`}</span>
|
60
|
+
)}
|
111
61
|
</Button>
|
112
62
|
)}
|
113
63
|
<Button
|
114
|
-
className={classNames(styles.
|
115
|
-
[styles.topMargin]:
|
64
|
+
className={classNames(styles.closeButton, {
|
65
|
+
[styles.topMargin]: sessionMode === 'view',
|
116
66
|
})}
|
117
67
|
kind="tertiary"
|
118
68
|
onClick={() => {
|
@@ -120,15 +70,39 @@ const Sidebar: React.FC<SidebarProps> = ({
|
|
120
70
|
handleClose?.();
|
121
71
|
hideFormCollapseToggle();
|
122
72
|
}}>
|
123
|
-
{
|
73
|
+
{sessionMode === 'view' ? t('close', 'Close') : t('cancel', 'Cancel')}
|
124
74
|
</Button>
|
125
75
|
</div>
|
126
76
|
</div>
|
127
77
|
);
|
128
78
|
};
|
129
79
|
|
130
|
-
|
131
|
-
|
80
|
+
interface PageLinkProps {
|
81
|
+
page: FormPage;
|
82
|
+
currentActivePage: string;
|
83
|
+
pagesWithErrors: string[];
|
84
|
+
requestPage: (page: string) => void;
|
85
|
+
}
|
86
|
+
|
87
|
+
function PageLink({ page, currentActivePage, pagesWithErrors, requestPage }: PageLinkProps) {
|
88
|
+
const isActive = page.id === currentActivePage;
|
89
|
+
const hasError = pagesWithErrors.includes(page.id);
|
90
|
+
return (
|
91
|
+
<div
|
92
|
+
className={classNames(styles.pageLink, {
|
93
|
+
[styles.activePage]: isActive && !hasError,
|
94
|
+
[styles.errorPage]: hasError && !isActive,
|
95
|
+
[styles.activeErrorPage]: hasError && isActive,
|
96
|
+
})}>
|
97
|
+
<button
|
98
|
+
onClick={(e) => {
|
99
|
+
e.preventDefault();
|
100
|
+
requestPage(page.id);
|
101
|
+
}}>
|
102
|
+
<span>{page.label}</span>
|
103
|
+
</button>
|
104
|
+
</div>
|
105
|
+
);
|
132
106
|
}
|
133
107
|
|
134
108
|
export default Sidebar;
|
@@ -1,102 +1,87 @@
|
|
1
1
|
@use '@carbon/colors';
|
2
2
|
@use '@carbon/type';
|
3
3
|
|
4
|
-
.
|
5
|
-
width: 12rem;
|
6
|
-
min-height: 8rem;
|
7
|
-
overscroll-behavior: contain;
|
8
|
-
margin-right: 1rem;
|
9
|
-
}
|
10
|
-
|
11
|
-
.sidebarList {
|
12
|
-
max-height: 100%;
|
13
|
-
}
|
14
|
-
|
15
|
-
.sidenavActions {
|
16
|
-
margin-left: 0.6rem;
|
17
|
-
}
|
18
|
-
|
19
|
-
@media all and (device-width: 600px) and (device-height: 1024px) and (orientation: portrait) {
|
20
|
-
.sidebar {
|
21
|
-
width: 11rem;
|
22
|
-
max-height: 500px;
|
23
|
-
margin-right: 20px;
|
24
|
-
position: fixed;
|
25
|
-
}
|
26
|
-
|
27
|
-
.sidebarList {
|
28
|
-
max-height: 200px;
|
29
|
-
overflow-y: scroll;
|
30
|
-
}
|
31
|
-
}
|
32
|
-
|
33
|
-
.link {
|
34
|
-
margin: 0.375rem 0 0.375rem 0.5rem;
|
35
|
-
font-size: 1rem;
|
36
|
-
line-height: 1.43;
|
37
|
-
letter-spacing: 0.16px;
|
38
|
-
color: colors.$gray-100;
|
39
|
-
cursor: pointer;
|
40
|
-
|
41
|
-
:hover {
|
42
|
-
outline: none;
|
43
|
-
}
|
44
|
-
}
|
45
|
-
|
46
|
-
.section {
|
4
|
+
.pageLink {
|
47
5
|
border-left: 0.5rem solid colors.$teal-20;
|
48
6
|
display: flex;
|
49
7
|
align-items: center;
|
50
|
-
height:
|
8
|
+
height: 3rem;
|
51
9
|
padding: 0.25rem 0.5rem;
|
52
10
|
background-color: colors.$white;
|
53
|
-
|
11
|
+
margin: 0 0 0.063rem;
|
54
12
|
}
|
55
13
|
|
56
|
-
|
57
|
-
:global(.omrs-breakpoint-lt-desktop) {
|
58
|
-
.section {
|
59
|
-
height: 3rem;
|
60
|
-
}
|
61
|
-
}
|
62
|
-
|
63
|
-
.sectionLink {
|
14
|
+
.pageLink button {
|
64
15
|
@include type.type-style('body-01');
|
16
|
+
font-family: inherit;
|
17
|
+
display: block;
|
18
|
+
background-color: inherit;
|
19
|
+
width: 100%;
|
20
|
+
border: none;
|
21
|
+
outline: none;
|
22
|
+
text-align: left;
|
23
|
+
min-height: 2rem;
|
24
|
+
white-space: normal;
|
25
|
+
word-wrap: break-word;
|
26
|
+
margin: 0 0 0.063rem;
|
65
27
|
color: colors.$gray-100;
|
28
|
+
:hover {
|
29
|
+
cursor: pointer;
|
30
|
+
}
|
66
31
|
}
|
67
32
|
|
68
|
-
.
|
69
|
-
@extend .section;
|
33
|
+
.activePage {
|
70
34
|
border-left: 0.5rem solid colors.$teal-50;
|
71
35
|
background-color: #ededed;
|
72
36
|
|
73
|
-
|
37
|
+
button {
|
74
38
|
font-weight: 600;
|
75
39
|
}
|
76
40
|
}
|
77
41
|
|
78
|
-
.
|
79
|
-
|
80
|
-
|
81
|
-
|
42
|
+
.errorPage {
|
43
|
+
border-left: 0.5rem solid colors.$red-40;
|
44
|
+
background-color: colors.$red-10;
|
45
|
+
|
46
|
+
button {
|
47
|
+
color: colors.$red-60 !important;
|
48
|
+
}
|
82
49
|
}
|
83
50
|
|
84
|
-
.
|
85
|
-
@extend .
|
51
|
+
.activeErrorPage {
|
52
|
+
@extend .errorPage;
|
53
|
+
background-color: colors.$red-30;
|
86
54
|
border-left: 0.5rem solid colors.$red-60;
|
87
|
-
|
88
|
-
|
55
|
+
|
56
|
+
button {
|
57
|
+
font-weight: 600;
|
58
|
+
}
|
89
59
|
}
|
90
60
|
|
91
|
-
.
|
92
|
-
|
93
|
-
|
61
|
+
.sidebar {
|
62
|
+
width: 12rem;
|
63
|
+
min-height: 8rem;
|
64
|
+
overscroll-behavior: contain;
|
65
|
+
margin-right: 1rem;
|
66
|
+
}
|
67
|
+
|
68
|
+
.sideNavActions {
|
69
|
+
margin-left: 0.6rem;
|
70
|
+
}
|
71
|
+
|
72
|
+
@media all and (device-width: 600px) and (device-height: 1024px) and (orientation: portrait) {
|
73
|
+
.sidebar {
|
74
|
+
width: 11rem;
|
75
|
+
max-height: 500px;
|
76
|
+
margin-right: 20px;
|
77
|
+
position: fixed;
|
78
|
+
}
|
94
79
|
}
|
95
80
|
|
96
81
|
.divider {
|
97
82
|
border: 0;
|
98
83
|
border-top: 1px solid colors.$gray-40;
|
99
|
-
margin:
|
84
|
+
margin: 1rem 0.5rem;
|
100
85
|
}
|
101
86
|
|
102
87
|
.button {
|
@@ -114,8 +99,7 @@
|
|
114
99
|
|
115
100
|
.closeButton {
|
116
101
|
@extend .button;
|
102
|
+
&:hover {
|
103
|
+
background-color: colors.$red-60 !important;
|
104
|
+
}
|
117
105
|
}
|
118
|
-
|
119
|
-
.toggleContainer {
|
120
|
-
margin-bottom: 0.5rem;
|
121
|
-
}
|