@abgov/jsonforms-components 1.21.0 → 1.22.0

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/index.esm.js CHANGED
@@ -4,7 +4,7 @@ import { GoAFormItem, GoAInput, GoATextArea, GoACallout, GoAInputDate, GoAInputD
4
4
  import styled from 'styled-components';
5
5
  import axios from 'axios';
6
6
  import get$1 from 'lodash/get';
7
- import { rankWith, isStringControl, and, optionIs, uiTypeIs, isDateControl, isNumberControl, isIntegerControl, isDateTimeControl, isTimeControl, isEnumControl, isBooleanControl, getAjv, isVisible, isEnabled, deriveLabelForUISchemaElement, schemaTypeIs, formatIs, createDefaultValue, Paths, or, isObjectArrayControl, isPrimitiveArrayControl, withIncreasedRank, hasType, isControl, isCategorization, isLayout } from '@jsonforms/core';
7
+ import { rankWith, isStringControl, and, optionIs, uiTypeIs, isDateControl, isNumberControl, isIntegerControl, isDateTimeControl, isTimeControl, isEnumControl, isBooleanControl, getAjv, isVisible, isEnabled, deriveLabelForUISchemaElement, schemaTypeIs, formatIs, createDefaultValue, Paths, or, isObjectArrayControl, isPrimitiveArrayControl, scopeEndsWith, withIncreasedRank, hasType, isControl as isControl$1, isCategorization, isLayout as isLayout$1 } from '@jsonforms/core';
8
8
  import { withJsonFormsControlProps, withJsonFormsRendererProps, withJsonFormsEnumProps, withTranslateProps, useJsonForms, JsonFormsDispatch, withJsonFormsLayoutProps, withJsonFormsArrayLayoutProps, withJsonFormsCellProps } from '@jsonforms/react';
9
9
  import merge from 'lodash/merge';
10
10
  import isEmpty$1 from 'lodash/isEmpty';
@@ -2871,6 +2871,20 @@ const fetchRegister = props => __awaiter(void 0, void 0, void 0, function* () {
2871
2871
  }
2872
2872
  return undefined;
2873
2873
  });
2874
+ const validateUrl = props => __awaiter(void 0, void 0, void 0, function* () {
2875
+ const {
2876
+ url
2877
+ } = props;
2878
+ if (url) {
2879
+ try {
2880
+ yield axios.get(url);
2881
+ return true;
2882
+ } catch (err) {
2883
+ console.warn(`Error in fetching data from remote: ${err}`);
2884
+ return false;
2885
+ }
2886
+ } else return false;
2887
+ });
2874
2888
 
2875
2889
  const JsonFormsRegisterContext = /*#__PURE__*/createContext(undefined);
2876
2890
  const JsonFormRegisterProvider = ({
@@ -4595,7 +4609,6 @@ const flatten = arr => {
4595
4609
  const flatter = flatten(val[1]);
4596
4610
  return acc.concat(flatter);
4597
4611
  }
4598
- // If the current value is a string, add it to the accumulator
4599
4612
  if (isNameValuePair(val)) {
4600
4613
  return acc.concat([[String(val[0]), getValue(val[1]) || '']]);
4601
4614
  }
@@ -4613,7 +4626,7 @@ const flatten = arr => {
4613
4626
  * However, we need to decide how to handle these sorts of nested data in the summary
4614
4627
  * page before messing with it.
4615
4628
  */
4616
- const getFormFieldValue = (scope, data) => {
4629
+ const getFormFieldValue = (schema, scope, data) => {
4617
4630
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
4618
4631
  let currentValue = data;
4619
4632
  if (scope) {
@@ -4660,7 +4673,7 @@ const renderFileLink = (fileUploaderElement, downloadFile) => {
4660
4673
  children: fileUploaderElement === null || fileUploaderElement === void 0 ? void 0 : fileUploaderElement.filename
4661
4674
  });
4662
4675
  };
4663
- const renderReviewControl = (data, element, requiredFields, index,
4676
+ const renderReviewControl = (schema, data, element, requiredFields, index,
4664
4677
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
4665
4678
  fileList, downloadFile) => {
4666
4679
  const fieldName = element.scope.split('/').pop() || '';
@@ -4668,21 +4681,21 @@ fileList, downloadFile) => {
4668
4681
  if (!fieldName || !label) return null;
4669
4682
  const isFileUploader = element.scope.includes('fileUploader');
4670
4683
  const fileUploaderElement = isFileUploader ? fileList && fileList[fieldName] : null;
4671
- const fieldValues = getFormFieldValue(element.scope, data ? data : {});
4684
+ const fieldValues = getFormFieldValue(schema, element.scope, data ? data : {});
4672
4685
  const isRequired = requiredFields.includes(fieldName);
4673
4686
  const asterisk = isRequired ? ' *' : '';
4674
4687
  const values = fieldValues.value;
4675
4688
  return jsxs(React.Fragment, {
4676
4689
  children: [fieldValues.type === 'primitive' && renderValue(`${label}${asterisk}: `, `${index}`, fieldValues.value, fileUploaderElement, downloadFile), fieldValues.type === 'object' && values && values.length > 0 && values.map((v, i) => {
4677
4690
  return renderValue(`${v[0]}: `, `${index}:${i}`, v[1]);
4678
- }), fieldValues.type === 'array' && values && values.length > 0 && renderListDetails(values)]
4691
+ }), fieldValues.type === 'array' && values && values.length > 0 && renderList(values)]
4679
4692
  }, index);
4680
4693
  };
4681
- const renderListDetails = items => {
4694
+ const renderList = items => {
4682
4695
  return jsx(React.Fragment, {
4683
4696
  children: items.map((item, itemIndex) => {
4684
4697
  const details = Array.isArray(item) ? item : [undefined, [undefined, undefined]];
4685
- return jsxs(Fragment, {
4698
+ return jsxs("div", {
4686
4699
  children: [jsx(Grid, {
4687
4700
  children: details[1].map((detail, detailIndex) => {
4688
4701
  const safeDetail = Array.isArray(detail) ? detail : [undefined, undefined];
@@ -4696,32 +4709,44 @@ const renderListDetails = items => {
4696
4709
  });
4697
4710
  };
4698
4711
 
4699
- const renderReviewListWithDetail = (element,
4712
+ const renderReviewListWithDetail = (schema, elements,
4700
4713
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
4701
4714
  data, field, index, requiredFields) => {
4702
4715
  const listData = data[field];
4716
+ const detailData = [];
4717
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
4718
+ listData.forEach((elementData, i) => {
4719
+ const itemData = [];
4720
+ elements.forEach((element, j) => {
4721
+ const fieldName = element.scope.split('/').pop() || '';
4722
+ const label = resolveLabelFromScope(element.scope);
4723
+ const value = String(elementData[fieldName]);
4724
+ itemData.push([label, value]);
4725
+ });
4726
+ detailData.push([`${i}`, itemData]);
4727
+ });
4703
4728
  return jsxs(ListWithDetail, {
4704
4729
  children: [jsxs(ListWithDetailHeading, {
4705
4730
  children: [field, listData.length > 1 && 's']
4706
- }), jsx(Grid, {
4707
- children: listData.map((childData, childIndex) => {
4708
- var _a, _b;
4709
- return jsx(React.Fragment, {
4710
- children: jsx(RenderFormReviewFields, {
4711
- elements: ((_b = (_a = element === null || element === void 0 ? void 0 : element.options) === null || _a === void 0 ? void 0 : _a.detail) === null || _b === void 0 ? void 0 : _b.elements) || [],
4712
- data: childData,
4713
- requiredFields: requiredFields
4714
- })
4715
- }, `${index}-${childIndex}`);
4716
- })
4717
- })]
4731
+ }), renderList(detailData)]
4718
4732
  }, `${index}-${field}`);
4719
4733
  };
4720
4734
 
4735
+ const isControl = type => {
4736
+ return type === 'Control';
4737
+ };
4738
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
4739
+ const isListWithDetail$1 = (type, data, fieldName) => {
4740
+ return type === 'ListWithDetail' && typeof data === 'object' && data !== null && fieldName in data && Array.isArray(data[fieldName]) && data[fieldName].length > 0;
4741
+ };
4742
+ const isLayout = schema => {
4743
+ return typeof schema === 'object' && schema !== null && 'elements' in schema;
4744
+ };
4721
4745
  const RenderFormReviewFields = ({
4722
4746
  elements,
4723
4747
  data,
4724
- requiredFields
4748
+ requiredFields,
4749
+ schema
4725
4750
  }) => {
4726
4751
  var _a, _b;
4727
4752
  const enumerators = useContext(JsonFormContext);
@@ -4736,25 +4761,38 @@ const RenderFormReviewFields = ({
4736
4761
  }
4737
4762
  };
4738
4763
  return elements.map((element, index) => {
4739
- var _a;
4764
+ var _a, _b, _c;
4740
4765
  const clonedElement = JSON.parse(JSON.stringify(element));
4741
4766
  const fieldName = (_a = clonedElement.scope) === null || _a === void 0 ? void 0 : _a.split('/').pop();
4742
- if (clonedElement.type === 'Control' && clonedElement.scope) {
4743
- return renderReviewControl(data, clonedElement, requiredFields, index, fileList, downloadFile);
4744
- } else if (clonedElement.type !== 'ListWithDetail' && (clonedElement === null || clonedElement === void 0 ? void 0 : clonedElement.elements)) {
4767
+ if (isControl(clonedElement.type)) {
4768
+ return renderReviewControl(schema, data, clonedElement, requiredFields, index, fileList, downloadFile);
4769
+ } else if (isListWithDetail$1(clonedElement.type, data, fieldName)) {
4770
+ const elements = removeLayouts((_c = (_b = clonedElement === null || clonedElement === void 0 ? void 0 : clonedElement.options) === null || _b === void 0 ? void 0 : _b.detail) === null || _c === void 0 ? void 0 : _c.elements) || [];
4771
+ return renderReviewListWithDetail(schema, elements, data, fieldName, index);
4772
+ } else if (isLayout(clonedElement)) {
4745
4773
  return jsx(React.Fragment, {
4746
4774
  children: jsx(RenderFormReviewFields, {
4747
4775
  elements: clonedElement.elements,
4748
4776
  data: data,
4749
- requiredFields: requiredFields
4777
+ requiredFields: requiredFields,
4778
+ schema: schema
4750
4779
  })
4751
4780
  }, index);
4752
- } else if (clonedElement.type === 'ListWithDetail' && data && data[fieldName] && data[fieldName].length > 0) {
4753
- return renderReviewListWithDetail(clonedElement, data, fieldName, index, requiredFields);
4754
4781
  }
4755
4782
  return null;
4756
4783
  });
4757
4784
  };
4785
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
4786
+ const removeLayouts = elements => {
4787
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
4788
+ return elements.reduce((acc, item) => {
4789
+ if (isLayout(item)) {
4790
+ return acc.concat(removeLayouts(item.elements));
4791
+ } else {
4792
+ return acc.concat(item);
4793
+ }
4794
+ }, []);
4795
+ };
4758
4796
 
4759
4797
  const RenderStepElements = props => {
4760
4798
  return (
@@ -5001,7 +5039,8 @@ const FormStepper = props => {
5001
5039
  children: jsx(RenderFormReviewFields, {
5002
5040
  elements: category === null || category === void 0 ? void 0 : category.elements,
5003
5041
  data: data,
5004
- requiredFields: requiredFields
5042
+ requiredFields: requiredFields,
5043
+ schema: schema
5005
5044
  })
5006
5045
  })]
5007
5046
  }, index);
@@ -5724,6 +5763,72 @@ const GoAArrayControlTester = rankWith(3, or(isObjectArrayControl, isPrimitiveAr
5724
5763
  const GoAArrayControlRenderer = withJsonFormsArrayLayoutProps(ArrayControl);
5725
5764
  const GoAListWithDetailsTester = rankWith(3, and(uiTypeIs('ListWithDetail')));
5726
5765
 
5766
+ const linkLength = 40;
5767
+ const invalidExtensions = ['exe'];
5768
+ const LinkSelect = props => {
5769
+ var _a, _b;
5770
+ const componentProps = (_b = (_a = props === null || props === void 0 ? void 0 : props.uischema) === null || _a === void 0 ? void 0 : _a.options) === null || _b === void 0 ? void 0 : _b.componentProps;
5771
+ const {
5772
+ link,
5773
+ label,
5774
+ heading,
5775
+ description
5776
+ } = componentProps;
5777
+ const [linkValid, setLinkValid] = useState(null);
5778
+ let error = undefined;
5779
+ let linkLabel = (link === null || link === void 0 ? void 0 : link.length) > linkLength ? `${link === null || link === void 0 ? void 0 : link.slice(0, linkLength)}...` : link;
5780
+ let linkUrl = link;
5781
+ if (label) {
5782
+ linkLabel = label;
5783
+ }
5784
+ const count = link === null || link === void 0 ? void 0 : link.split('.').length;
5785
+ const extension = link === null || link === void 0 ? void 0 : link.split('.')[count - 1];
5786
+ if (invalidExtensions.includes(extension)) {
5787
+ linkUrl = null;
5788
+ linkLabel = '';
5789
+ error = `Invalid extension: ${extension}`;
5790
+ }
5791
+ useEffect(() => {
5792
+ function validateLink(linkUrl) {
5793
+ return __awaiter(this, void 0, void 0, function* () {
5794
+ if (linkUrl) {
5795
+ const response = yield validateUrl({
5796
+ url: linkUrl
5797
+ });
5798
+ setLinkValid(response);
5799
+ }
5800
+ });
5801
+ }
5802
+ validateLink(linkUrl);
5803
+ }, [linkUrl]);
5804
+ if (!linkLabel && !error) {
5805
+ linkLabel = 'Link';
5806
+ }
5807
+ if (linkValid === false) {
5808
+ linkLabel = '';
5809
+ error = 'Invalid Link';
5810
+ }
5811
+ return jsx(GoAFormItem, {
5812
+ error: error,
5813
+ label: heading,
5814
+ children: jsxs("div", {
5815
+ "data-testid": "link-jsonform",
5816
+ children: [description && jsx("div", {
5817
+ children: description
5818
+ }), linkUrl && linkValid ? jsx("a", {
5819
+ href: link,
5820
+ target: "_blank",
5821
+ rel: "noreferrer",
5822
+ children: linkLabel
5823
+ }) : linkLabel]
5824
+ })
5825
+ });
5826
+ };
5827
+ const linkControl = props => {
5828
+ return jsx(LinkSelect, Object.assign({}, props));
5829
+ };
5830
+ const GoALinkControlTester = rankWith(2, scopeEndsWith('link'));
5831
+
5727
5832
  const GoATextCell = props => jsx(GoAInputText, Object.assign({}, props));
5728
5833
  const GoATextCellTester = rankWith(1, isStringControl);
5729
5834
  withJsonFormsCellProps(GoATextCell);
@@ -6046,7 +6151,7 @@ const getUISchemaErrors = (uiSchema, schema) => {
6046
6151
  return '';
6047
6152
  }
6048
6153
  // Check control elements
6049
- if (isControl(uiSchema) && hasType(uiSchema, 'Control')) {
6154
+ if (isControl$1(uiSchema) && hasType(uiSchema, 'Control')) {
6050
6155
  if (!isScopedPrefixed(uiSchema.scope)) {
6051
6156
  return errMalformedScope(uiSchema.scope);
6052
6157
  }
@@ -6080,7 +6185,7 @@ const getUISchemaErrors = (uiSchema, schema) => {
6080
6185
  }
6081
6186
  // ensure each element has type Category, and that each category
6082
6187
  // has elements
6083
- if (isLayout(uiSchema)) {
6188
+ if (isLayout$1(uiSchema)) {
6084
6189
  const invalidCategorizations = [];
6085
6190
  const invalidCategories = [];
6086
6191
  uiSchema.elements.forEach(e => {
@@ -6155,6 +6260,9 @@ const GoABaseRenderers = [
6155
6260
  {
6156
6261
  tester: GoAEnumControlTester,
6157
6262
  renderer: GoAEnumControl
6263
+ }, {
6264
+ tester: GoALinkControlTester,
6265
+ renderer: linkControl
6158
6266
  }, {
6159
6267
  tester: GoAIntegerControlTester,
6160
6268
  renderer: GoAInputIntegerControl
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@abgov/jsonforms-components",
3
- "version": "1.21.0",
3
+ "version": "1.22.0",
4
4
  "license": "Apache-2.0",
5
5
  "description": "Government of Alberta - React renderers for JSON Forms based on the design system.",
6
6
  "repository": "https://github.com/GovAlta/adsp-monorepo",
@@ -1,2 +1,7 @@
1
1
  import { RegisterConfig } from './actions';
2
2
  export declare const fetchRegister: (props: RegisterConfig) => Promise<any[] | undefined>;
3
+ interface Validate {
4
+ url?: string;
5
+ }
6
+ export declare const validateUrl: (props: Validate) => Promise<boolean>;
7
+ export {};
@@ -1,10 +1,11 @@
1
- import { LabelDescription } from '@jsonforms/core';
1
+ import { JsonSchema, LabelDescription } from '@jsonforms/core';
2
2
  type jsonformsLabel = string | boolean | LabelDescription | undefined;
3
3
  export declare const labelToString: (label: jsonformsLabel, scope: string) => string;
4
+ export declare const resolveLabelFromScope: (scope: string) => string;
4
5
  export interface InputValue {
5
6
  type: 'primitive' | 'object' | 'array';
6
7
  value?: string | NestedStringArray;
7
8
  }
8
9
  export type NestedStringArray = (string | undefined | NestedStringArray)[];
9
- export declare const getFormFieldValue: (scope: string, data: unknown) => InputValue;
10
+ export declare const getFormFieldValue: (schema: JsonSchema, scope: string, data: unknown) => InputValue;
10
11
  export {};
@@ -1,9 +1,10 @@
1
1
  import React from 'react';
2
- import { Categorization, Category, UISchemaElement } from '@jsonforms/core';
2
+ import { Categorization, Category, JsonSchema, UISchemaElement } from '@jsonforms/core';
3
3
  interface RenderFormReviewFieldsProps {
4
4
  elements: UISchemaElement[] | (Category | Categorization)[];
5
5
  data: any;
6
6
  requiredFields: string[];
7
+ schema: JsonSchema;
7
8
  }
8
9
  export declare const RenderFormReviewFields: React.FC<RenderFormReviewFieldsProps>;
9
10
  export {};
@@ -1,6 +1,8 @@
1
- import { ControlElement } from '@jsonforms/core';
1
+ import { ControlElement, JsonSchema } from '@jsonforms/core';
2
+ import { NestedStringArray } from './GenerateFormFields';
2
3
  export interface FileElement extends File {
3
4
  filename: string;
4
5
  propertyId: string;
5
6
  }
6
- export declare const renderReviewControl: (data: unknown, element: ControlElement, requiredFields: string[], index: number, fileList: Record<string, any>, downloadFile: (file: File, propertyId: string) => void) => JSX.Element | null;
7
+ export declare const renderReviewControl: (schema: JsonSchema, data: unknown, element: ControlElement, requiredFields: string[], index: number, fileList: Record<string, any>, downloadFile: (file: File, propertyId: string) => void) => JSX.Element | null;
8
+ export declare const renderList: (items: NestedStringArray) => JSX.Element;
@@ -1,2 +1,2 @@
1
- import { ControlElement } from '@jsonforms/core';
2
- export declare const renderReviewListWithDetail: (element: ControlElement, data: any, field: string, index: number, requiredFields: string[]) => JSX.Element;
1
+ import { ControlElement, JsonSchema } from '@jsonforms/core';
2
+ export declare const renderReviewListWithDetail: (schema: JsonSchema, elements: ControlElement[], data: any, field: string, index: number, requiredFields: string[]) => JSX.Element;
@@ -0,0 +1,9 @@
1
+ import { ControlProps, RankedTester } from '@jsonforms/core';
2
+ import { TranslateProps } from '@jsonforms/react';
3
+ import { WithInputProps } from '../Inputs/type';
4
+ import { WithOptionLabel } from '../../util';
5
+ import { WithClassname } from '@jsonforms/core';
6
+ export type LinkSelectProps = WithClassname & TranslateProps & WithInputProps & ControlProps;
7
+ export declare const LinkSelect: (props: LinkSelectProps) => JSX.Element;
8
+ export declare const linkControl: (props: ControlProps & WithOptionLabel & TranslateProps) => import("react/jsx-runtime").JSX.Element;
9
+ export declare const GoALinkControlTester: RankedTester;