@abgov/jsonforms-components 2.36.2 → 2.38.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
@@ -12,6 +12,7 @@ import ___default, { isEqual, isEmpty as isEmpty$1, isObject as isObject$f } fro
12
12
  import merge from 'lodash/merge';
13
13
  import isEmpty from 'lodash/isEmpty';
14
14
  import range from 'lodash/range';
15
+ import pluralize from 'pluralize';
15
16
  import { evaluateSync, compileSync } from '@mdx-js/mdx';
16
17
  import { Parser } from 'expr-eval';
17
18
  import Ajv from 'ajv';
@@ -7084,10 +7085,10 @@ let _$6 = t => t,
7084
7085
  _t16$1,
7085
7086
  _t17$1,
7086
7087
  _t18$1,
7087
- _t19,
7088
- _t20,
7089
- _t21,
7090
- _t22,
7088
+ _t19$1,
7089
+ _t20$1,
7090
+ _t21$1,
7091
+ _t22$1,
7091
7092
  _t23,
7092
7093
  _t24,
7093
7094
  _t25,
@@ -7228,22 +7229,22 @@ styled.div(_t17$1 || (_t17$1 = _$6`
7228
7229
  const PageBorder = styled.div(_t18$1 || (_t18$1 = _$6`
7229
7230
  padding: var(--goa-space-m) var(--goa-space-3xl);
7230
7231
  `));
7231
- const TableReviewItemSection = styled.div(_t19 || (_t19 = _$6`
7232
+ const TableReviewItemSection = styled.div(_t19$1 || (_t19$1 = _$6`
7232
7233
  .element-style {
7233
7234
  max-width: 1600px;
7234
7235
  }
7235
7236
  `));
7236
- const TableReviewItem = styled.div(_t20 || (_t20 = _$6`
7237
+ const TableReviewItem = styled.div(_t20$1 || (_t20$1 = _$6`
7237
7238
  padding: var(--goa-space-xl) var(--goa-space-2xl);
7238
7239
  border: 1px solid var(--goa-color-greyscale-300);
7239
7240
  border-radius: var(--goa-border-radius-xl);
7240
7241
  `));
7241
- const TableReviewPageTitleRow = styled.div(_t21 || (_t21 = _$6`
7242
+ const TableReviewPageTitleRow = styled.div(_t21$1 || (_t21$1 = _$6`
7242
7243
  margin-top: var(--goa-space-xl);
7243
7244
  display: flex;
7244
7245
  justify-content: space-between;
7245
7246
  `));
7246
- const TableReviewCategoryLabel = styled.h3(_t22 || (_t22 = _$6`
7247
+ const TableReviewCategoryLabel = styled.h3(_t22$1 || (_t22$1 = _$6`
7247
7248
  color: var(--goa-color-text-secondary) !important;
7248
7249
  margin-bottom: var(--goa-space-l);
7249
7250
  `));
@@ -9922,97 +9923,117 @@ let _$3 = t => t,
9922
9923
  _t15,
9923
9924
  _t16,
9924
9925
  _t17,
9925
- _t18;
9926
+ _t18,
9927
+ _t19,
9928
+ _t20,
9929
+ _t21,
9930
+ _t22;
9926
9931
  const DeleteDialogContent = styled.div(_t$3 || (_t$3 = _$3`
9927
9932
  margin-bottom: var(--goa-space-m);
9928
9933
  `));
9929
- const NonEmptyCellStyle = styled.div(_t2$2 || (_t2$2 = _$3`
9934
+ const MarginTop = styled.div(_t2$2 || (_t2$2 = _$3`
9935
+ margin-top: var(--goa-space-l);
9936
+ `));
9937
+ const NonEmptyCellStyle = styled.div(_t3$1 || (_t3$1 = _$3`
9930
9938
  goa-table thead th {
9931
9939
  background-color: #000:
9932
9940
  }
9933
9941
  `));
9934
- const ToolBarHeader = styled.div(_t3$1 || (_t3$1 = _$3`
9935
- margin-bottom: var(--goa-space-l);
9942
+ const ToolBarHeader = styled.div(_t4$1 || (_t4$1 = _$3`
9943
+ margin-bottom: var(--goa-space-s);
9936
9944
  `));
9937
- const ObjectArrayTitle = styled.h3(_t4$1 || (_t4$1 = _$3`
9938
- margin-bottom: var(--goa-space-l);
9945
+ const ObjectArrayTitle = styled.h3(_t5$1 || (_t5$1 = _$3`
9946
+ margin-bottom: var(--goa-space-s);
9939
9947
 
9940
9948
  span {
9941
9949
  color: #666666;
9942
9950
  font-size: var(--goa-font-size-2);
9943
9951
  }
9944
9952
  `));
9945
- const RequiredSpan = styled.span(_t5$1 || (_t5$1 = _$3`
9953
+ const RequiredSpan = styled.span(_t6$1 || (_t6$1 = _$3`
9946
9954
  color: #666666;
9947
9955
  font-weight: var(--goa-font-weight-regular);
9948
9956
  font-size: var(--goa-font-size-2);
9949
9957
  `));
9950
- const TextCenter = styled.div(_t6$1 || (_t6$1 = _$3`
9958
+ const TextCenter = styled.div(_t7 || (_t7 = _$3`
9951
9959
  text-align: center;
9952
9960
  `));
9953
- const SideMenuItem = styled.div(_t7 || (_t7 = _$3`
9961
+ const SideMenuItem = styled.div(_t8 || (_t8 = _$3`
9954
9962
  &:hover {
9955
9963
  background: #f1f1f1;
9956
9964
  }
9957
9965
  `));
9958
- const RowFlex = styled.div(_t8 || (_t8 = _$3`
9966
+ const RowFlex = styled.div(_t9 || (_t9 = _$3`
9959
9967
  display: flex;
9960
9968
  align-items: stretch;
9961
9969
  `));
9962
- const RowFlexMenu = styled.div(_t9 || (_t9 = _$3`
9970
+ const RowFlexMenu = styled.div(_t0 || (_t0 = _$3`
9963
9971
  display: flex;
9964
9972
  flex-direction: row;
9965
- border-bottom: 1px solid #dcdcdc;
9973
+ border: 1px solid #dcdcdc;
9974
+ margin-top: 1rem;
9966
9975
  `));
9967
- const FlexTabs = styled.div(_t0 || (_t0 = _$3`
9976
+ const FlexTabs = styled.div(_t1 || (_t1 = _$3`
9968
9977
  flex-direction: column;
9969
9978
  flex: 1;
9970
9979
  overflow-y: auto !important;
9971
- margin-right: 1.5rem;
9972
9980
  `));
9973
- const FlexForm = styled.div(_t1 || (_t1 = _$3`
9981
+ const FlexForm = styled.div(_t10 || (_t10 = _$3`
9974
9982
  flex-direction: column;
9975
- margin: 1.5rem 0;
9976
9983
  flex: 3;
9977
9984
  `));
9978
- const TabName = styled.div(_t10 || (_t10 = _$3`
9985
+ const TabData = styled.div(_t11 || (_t11 = _$3`
9979
9986
  margin: 1rem 0 1rem 1rem;
9980
- font-weight: 700;
9987
+ font-weight: 400;
9988
+ white-space: nowrap;
9989
+ overflow: hidden;
9990
+ text-overflow: ellipsis;
9981
9991
  `));
9982
- const Trash = styled.div(_t11 || (_t11 = _$3`
9983
- margin: 0.75rem 1.25rem 0.75rem 0.75rem;
9992
+ const Trash = styled.div(_t12 || (_t12 = _$3`
9993
+ margin: 0.9rem 0.4rem 0.6rem 0.75rem;
9984
9994
  margin-left: auto;
9985
9995
  `));
9986
- const ListContainer = styled.div(_t12 || (_t12 = _$3`
9996
+ const ListContainer = styled.div(_t13 || (_t13 = _$3`
9997
+ padding: 0 0 0 0;
9998
+ `));
9999
+ const IconPadding = styled.div(_t14 || (_t14 = _$3`
10000
+ padding: 0.9rem 0.5rem 0 0;
10001
+ `));
10002
+ const UpdateListContainer = styled.div(_t15 || (_t15 = _$3`
10003
+ width: 100%;
10004
+ border: 1px solid #dcdcdc;
10005
+ padding: var(--goa-space-xl);
10006
+ `));
10007
+ styled.div(_t16 || (_t16 = _$3`
9987
10008
  padding: 0 1.5rem 0 0;
9988
10009
  border: 1px solid #dcdcdc;
9989
10010
  `));
9990
- const TableTHHeader = styled.th(_t13 || (_t13 = _$3`
10011
+ const TableTHHeader = styled.th(_t17 || (_t17 = _$3`
9991
10012
  background-color: var(--goa-color-greyscale-100) !important;
9992
10013
  vertical-align: top;
9993
10014
  `));
9994
- const ObjectArrayWarningIconDiv = styled.div(_t14 || (_t14 = _$3`
10015
+ const ObjectArrayWarningIconDiv = styled.div(_t18 || (_t18 = _$3`
9995
10016
  display: inline-flex;
9996
10017
  align-items: flex-start;
9997
10018
  gap: 0.25rem;
9998
10019
  font-size: var(--goa-font-size-2);
9999
10020
  color: var(--goa-color-interactive-error);
10000
10021
  `));
10001
- const ListWithDetailWarningIconDiv = styled.div(_t15 || (_t15 = _$3`
10022
+ const ListWithDetailWarningIconDiv = styled.div(_t19 || (_t19 = _$3`
10002
10023
  display: inline-flex;
10003
10024
  align-items: flex-start;
10004
10025
  gap: 0.25rem;
10005
10026
  font-size: var(--goa-font-size-3);
10006
10027
  color: var(--goa-color-interactive-error);
10007
10028
  `));
10008
- styled.label(_t16 || (_t16 = _$3`
10029
+ styled.label(_t20 || (_t20 = _$3`
10009
10030
  color: var(--goa-color-interactive-error);
10010
10031
  font-weight: var(--goa-font-weight-regular);
10011
10032
  font-size: var(--goa-font-size-3);
10012
10033
  line-height: var(--goa-line-height-1);
10013
10034
  font-style: normal;
10014
10035
  `));
10015
- styled.div(_t17 || (_t17 = _$3`
10036
+ styled.div(_t21 || (_t21 = _$3`
10016
10037
  margin-top: var(--goa-space-m);
10017
10038
  color: var(--goa-color-interactive-error);
10018
10039
  font-weight: var(--goa-font-weight-regular);
@@ -10020,7 +10041,7 @@ styled.div(_t17 || (_t17 = _$3`
10020
10041
  line-height: var(--goa-line-height-1);
10021
10042
  font-style: normal;
10022
10043
  `));
10023
- const HilightCellWarning = styled.div(_t18 || (_t18 = _$3`
10044
+ const HilightCellWarning = styled.div(_t22 || (_t22 = _$3`
10024
10045
  background-color: var(--goa-color-warning-default);
10025
10046
  `));
10026
10047
 
@@ -10072,15 +10093,18 @@ const DeleteDialog = /*#__PURE__*/React.memo(function DeleteDialog({
10072
10093
  });
10073
10094
 
10074
10095
  const ObjectArrayToolBar = /*#__PURE__*/React.memo(function TableToolbar({
10096
+ data,
10075
10097
  label,
10076
10098
  path,
10077
10099
  addItem,
10078
10100
  schema,
10079
10101
  enabled,
10080
10102
  rootSchema,
10081
- uischema
10103
+ uischema,
10104
+ setCurrentListPage,
10105
+ buttonType
10082
10106
  }) {
10083
- var _a, _b, _c, _d, _e;
10107
+ var _a, _b, _c, _d, _e, _f;
10084
10108
  const buttonPosition = ((_a = uischema === null || uischema === void 0 ? void 0 : uischema.options) === null || _a === void 0 ? void 0 : _a.addButtonPosition) || 'left';
10085
10109
  const buttonUIProps = ((_b = uischema === null || uischema === void 0 ? void 0 : uischema.options) === null || _b === void 0 ? void 0 : _b.addButtonUIProps) || {};
10086
10110
  const arrayLabel = getLabelText(uischema.scope, label);
@@ -10089,16 +10113,21 @@ const ObjectArrayToolBar = /*#__PURE__*/React.memo(function TableToolbar({
10089
10113
  style: {
10090
10114
  textAlign: buttonPosition
10091
10115
  },
10092
- children: jsx(GoAButton, Object.assign({
10093
- disabled: !enabled,
10094
- testId: `object-array-toolbar-${label}`,
10095
- leadingIcon: "add",
10096
- "aria-label": `Add to button to ${(label === null || label === void 0 ? void 0 : label.toLowerCase()) || ''}`,
10097
- onClick: addItem(path, createDefaultValue(schema, rootSchema)),
10098
- type: (_d = (_c = uischema.options) === null || _c === void 0 ? void 0 : _c.addButtonType) !== null && _d !== void 0 ? _d : 'secondary'
10099
- }, buttonUIProps, {
10100
- children: ((_e = uischema === null || uischema === void 0 ? void 0 : uischema.options) === null || _e === void 0 ? void 0 : _e.addButtonText) || capitalizeFirstLetter(`Add ${arrayLabel}`)
10101
- }))
10116
+ children: jsx(MarginTop, {
10117
+ children: jsx(GoAButton, Object.assign({
10118
+ disabled: !enabled,
10119
+ testId: `object-array-toolbar-${label}`,
10120
+ leadingIcon: "add",
10121
+ "aria-label": `Add to button to ${(label === null || label === void 0 ? void 0 : label.toLowerCase()) || ''}`,
10122
+ onClick: () => {
10123
+ addItem(path, createDefaultValue(schema, rootSchema))();
10124
+ setCurrentListPage === null || setCurrentListPage === void 0 ? void 0 : setCurrentListPage(data + 1);
10125
+ },
10126
+ type: (_e = (_d = (_c = uischema.options) === null || _c === void 0 ? void 0 : _c.addButtonType) !== null && _d !== void 0 ? _d : buttonType) !== null && _e !== void 0 ? _e : 'secondary'
10127
+ }, buttonUIProps, {
10128
+ children: ((_f = uischema === null || uischema === void 0 ? void 0 : uischema.options) === null || _f === void 0 ? void 0 : _f.addButtonText) || `Add ${pluralize.singular(arrayLabel.charAt(0).toLowerCase() + arrayLabel.slice(1))}`
10129
+ }))
10130
+ })
10102
10131
  })
10103
10132
  });
10104
10133
  });
@@ -11029,7 +11058,7 @@ const NonEmptyRowComponent = ({
11029
11058
  }) : null
11030
11059
  }, childPath);
11031
11060
  };
11032
- const LeftTab = ({
11061
+ const MainTab = ({
11033
11062
  childPath,
11034
11063
  rowIndex,
11035
11064
  openDeleteDialog,
@@ -11038,41 +11067,59 @@ const LeftTab = ({
11038
11067
  currentTab,
11039
11068
  name,
11040
11069
  current,
11041
- translations
11070
+ setCurrentListPage
11042
11071
  }) => {
11072
+ var _a;
11073
+ /* eslint-disable @typescript-eslint/no-explicit-any */
11074
+ const getDataAtPath = (data, path) => path.replace(/\[(\d+)\]/g, '.$1').split('.').reduce((acc, key) => acc ? acc[key] : undefined, data);
11075
+ const {
11076
+ core
11077
+ } = useJsonForms();
11078
+ const rowData = getDataAtPath(core === null || core === void 0 ? void 0 : core.data, childPath);
11079
+ const rowErrors = (_a = core === null || core === void 0 ? void 0 : core.errors) === null || _a === void 0 ? void 0 : _a.filter(e => {
11080
+ const base = `/${childPath.replace(/\./g, '/')}`;
11081
+ return e.instancePath === base || e.instancePath.startsWith(base + '/');
11082
+ }).map(error => error === null || error === void 0 ? void 0 : error.message);
11043
11083
  return jsx("div", {
11044
- children: jsx(SideMenuItem, {
11045
- style: currentTab === rowIndex ? {
11046
- background: '#EFF8FF'
11047
- } : {},
11048
- onClick: () => selectCurrentTab(rowIndex),
11049
- onKeyDown: e => {
11050
- if (e.key === 'ArrowRight') {
11051
- e.preventDefault();
11052
- if (current) {
11053
- const goa = current === null || current === void 0 ? void 0 : current.querySelector('goa-input, goa-button');
11054
- if (goa === null || goa === void 0 ? void 0 : goa.shadowRoot) {
11055
- const internal = goa.shadowRoot.querySelector('input, button');
11056
- internal === null || internal === void 0 ? void 0 : internal.focus();
11057
- selectCurrentTab(rowIndex);
11084
+ children: jsx(GoAFormItem, {
11085
+ error: (rowErrors === null || rowErrors === void 0 ? void 0 : rowErrors.length) ? rowErrors : undefined,
11086
+ children: jsx(SideMenuItem, {
11087
+ onClick: () => selectCurrentTab(rowIndex),
11088
+ onKeyDown: e => {
11089
+ if (e.key === 'ArrowRight') {
11090
+ e.preventDefault();
11091
+ if (current) {
11092
+ const goa = current === null || current === void 0 ? void 0 : current.querySelector('goa-input, goa-button');
11093
+ if (goa === null || goa === void 0 ? void 0 : goa.shadowRoot) {
11094
+ const internal = goa.shadowRoot.querySelector('input, button');
11095
+ internal === null || internal === void 0 ? void 0 : internal.focus();
11096
+ selectCurrentTab(rowIndex);
11097
+ }
11058
11098
  }
11059
11099
  }
11060
- }
11061
- },
11062
- children: jsxs(RowFlexMenu, {
11063
- tabIndex: 0,
11064
- children: [jsx(TabName, {
11065
- children: name
11066
- }), enabled ? jsx(Trash, {
11067
- role: "trash button",
11068
- children: jsx(GoAIconButton, {
11069
- disabled: !enabled,
11070
- icon: "trash",
11071
- title: 'trash button',
11072
- testId: "remove the details",
11073
- onClick: () => openDeleteDialog(childPath, rowIndex, name)
11074
- })
11075
- }) : null]
11100
+ },
11101
+ children: jsxs(RowFlexMenu, {
11102
+ tabIndex: 0,
11103
+ children: [jsxs(TabData, {
11104
+ children: [Object.keys(rowData !== null && rowData !== void 0 ? rowData : {}).length === 0 && 'No data', " ", Object.values(rowData).join(', ')]
11105
+ }), enabled ? jsx(Trash, {
11106
+ children: jsx(GoAIconButton, {
11107
+ disabled: !enabled,
11108
+ icon: "trash",
11109
+ title: 'remove',
11110
+ testId: "remove the details",
11111
+ onClick: () => openDeleteDialog(childPath, rowIndex, name)
11112
+ })
11113
+ }) : null, jsx(IconPadding, {
11114
+ children: jsx(GoAIconButton, {
11115
+ disabled: !enabled,
11116
+ icon: "create",
11117
+ title: 'edit',
11118
+ testId: "edit button",
11119
+ onClick: () => setCurrentListPage(currentTab + 1)
11120
+ })
11121
+ })]
11122
+ })
11076
11123
  })
11077
11124
  })
11078
11125
  }, childPath);
@@ -11089,13 +11136,17 @@ const ObjectArrayList = ({
11089
11136
  cells,
11090
11137
  translations,
11091
11138
  currentIndex,
11092
- setCurrentIndex
11139
+ setCurrentIndex,
11140
+ setCurrentListPage,
11141
+ currentListPage,
11142
+ listTitle,
11143
+ errors
11093
11144
  }) => {
11094
11145
  var _a, _b;
11095
11146
  const isEmptyList = data === 0;
11096
11147
  const rightRef = useRef(null);
11097
11148
  const current = rightRef.current;
11098
- const minHeight = 300;
11149
+ const minHeight = 100;
11099
11150
  const [rightHeight, setRightHeight] = useState(minHeight);
11100
11151
  useEffect(() => {
11101
11152
  const resizeObserver = new ResizeObserver(() => {
@@ -11123,18 +11174,20 @@ const ObjectArrayList = ({
11123
11174
  const selectCurrentTab = index => {
11124
11175
  setCurrentIndex(index);
11125
11176
  };
11126
- const paddedHeight = rightHeight && rightHeight + 48;
11127
- // const detailRef = useRef<HTMLDivElement>(null);
11177
+ (() => {
11178
+ const str = (appliedUiSchemaOptions === null || appliedUiSchemaOptions === void 0 ? void 0 : appliedUiSchemaOptions.itemLabel) ? `${appliedUiSchemaOptions.itemLabel}` : `${path}`;
11179
+ return str.charAt(0).toUpperCase() + str.slice(1);
11180
+ })();
11128
11181
  return jsx(ListContainer, {
11129
11182
  children: jsxs(RowFlex, {
11130
- children: [jsx(FlexTabs, {
11183
+ children: [currentListPage === 0 && jsx(FlexTabs, {
11131
11184
  style: {
11132
- height: paddedHeight
11185
+ height: '100%'
11133
11186
  },
11134
11187
  children: range(data).map(index => {
11135
11188
  const childPath = Paths.compose(path, `${index}`);
11136
- const name = (appliedUiSchemaOptions === null || appliedUiSchemaOptions === void 0 ? void 0 : appliedUiSchemaOptions.itemLabel) ? `${appliedUiSchemaOptions === null || appliedUiSchemaOptions === void 0 ? void 0 : appliedUiSchemaOptions.itemLabel} ${index + 1}` : `${path} ${index + 1}`;
11137
- return jsx(LeftTab, {
11189
+ const name = (appliedUiSchemaOptions === null || appliedUiSchemaOptions === void 0 ? void 0 : appliedUiSchemaOptions.itemLabel) ? `${pluralize.singular(appliedUiSchemaOptions === null || appliedUiSchemaOptions === void 0 ? void 0 : appliedUiSchemaOptions.itemLabel)} ${index + 1}` : `${pluralize.singular(path)} ${index + 1}`;
11190
+ return jsx(MainTab, {
11138
11191
  childPath: childPath,
11139
11192
  rowIndex: index,
11140
11193
  currentTab: currentIndex,
@@ -11143,24 +11196,35 @@ const ObjectArrayList = ({
11143
11196
  selectCurrentTab: selectCurrentTab,
11144
11197
  enabled: enabled,
11145
11198
  current: current,
11146
- translations: translations
11199
+ setCurrentListPage: index => setCurrentListPage(index)
11147
11200
  }, childPath);
11148
11201
  })
11149
- }), jsx(FlexForm, {
11150
- ref: rightRef,
11151
- tabIndex: -1,
11152
- children: jsx(NonEmptyList, {
11153
- childPath: Paths.compose(path, `${currentIndex}`),
11154
- rowIndex: currentIndex,
11155
- schema: schema,
11156
- openDeleteDialog: openDeleteDialog,
11157
- showSortButtons: appliedUiSchemaOptions.showSortButtons || appliedUiSchemaOptions.showArrayTableSortButtons,
11158
- enabled: enabled,
11159
- cells: cells,
11160
- path: path,
11161
- uischema: uischema,
11162
- translations: translations
11163
- }, Paths.compose(path, `${currentIndex}`))
11202
+ }), currentListPage > 0 && jsxs(UpdateListContainer, {
11203
+ children: [jsxs(FlexForm, {
11204
+ ref: rightRef,
11205
+ tabIndex: -1,
11206
+ children: [jsx(ObjectArrayTitle, {
11207
+ children: pluralize.singular(listTitle || '')
11208
+ }), jsx(NonEmptyList, {
11209
+ childPath: Paths.compose(path, `${currentIndex}`),
11210
+ rowIndex: currentIndex,
11211
+ schema: schema,
11212
+ openDeleteDialog: openDeleteDialog,
11213
+ showSortButtons: appliedUiSchemaOptions.showSortButtons || appliedUiSchemaOptions.showArrayTableSortButtons,
11214
+ enabled: enabled,
11215
+ cells: cells,
11216
+ path: path,
11217
+ uischema: uischema,
11218
+ translations: translations
11219
+ }, Paths.compose(path, `${currentIndex}`))]
11220
+ }), jsx(GoAButton, {
11221
+ type: 'primary',
11222
+ onClick: () => {
11223
+ setCurrentListPage(0);
11224
+ },
11225
+ testId: "next-list-button",
11226
+ children: "Continue"
11227
+ })]
11164
11228
  })]
11165
11229
  })
11166
11230
  });
@@ -11170,7 +11234,8 @@ class ListWithDetailControl extends React.Component {
11170
11234
  constructor() {
11171
11235
  super(...arguments);
11172
11236
  this.state = {
11173
- maxItemsError: ''
11237
+ maxItemsError: '',
11238
+ currentListPage: 0
11174
11239
  };
11175
11240
  // eslint-disable-next-line
11176
11241
  this.addItem = (path, value) => {
@@ -11228,17 +11293,20 @@ class ListWithDetailControl extends React.Component {
11228
11293
  visible: visible,
11229
11294
  "data-testid": "jsonforms-object-list-wrapper",
11230
11295
  children: [jsxs(ToolBarHeader, {
11231
- children: [listTitle && jsxs(ObjectArrayTitle, {
11232
- children: [listTitle, " ", jsx("span", {
11233
- children: additionalProps.required && '(required)'
11234
- }), this.state.maxItemsError && jsx("span", {
11235
- style: {
11236
- color: 'red',
11237
- marginLeft: '1rem'
11238
- },
11239
- children: this.state.maxItemsError
11240
- })]
11241
- }), jsx(ObjectArrayToolBar, {
11296
+ children: [listTitle && this.state.currentListPage === 0 && jsx(MarginTop, {
11297
+ children: jsxs(ObjectArrayTitle, {
11298
+ children: [listTitle, " ", jsx("span", {
11299
+ children: additionalProps.required && '(required)'
11300
+ }), this.state.maxItemsError && jsx("span", {
11301
+ style: {
11302
+ color: 'red',
11303
+ marginLeft: '1rem'
11304
+ },
11305
+ children: this.state.maxItemsError
11306
+ })]
11307
+ })
11308
+ }), this.state.currentListPage === 0 && data === 0 && jsx(ObjectArrayToolBar, {
11309
+ data: data,
11242
11310
  errors: errors,
11243
11311
  label: label,
11244
11312
  addItem: (path, value) => () => {
@@ -11249,7 +11317,14 @@ class ListWithDetailControl extends React.Component {
11249
11317
  uischema: controlElement,
11250
11318
  schema: schema,
11251
11319
  rootSchema: rootSchema,
11252
- enabled: enabled
11320
+ enabled: enabled,
11321
+ setCurrentListPage: listPage => {
11322
+ this.setState({
11323
+ currentListPage: listPage
11324
+ });
11325
+ },
11326
+ currentListPage: this.state.currentListPage,
11327
+ buttonType: "secondary"
11253
11328
  })]
11254
11329
  }), jsx("div", {
11255
11330
  children: jsx(ObjectArrayList, Object.assign({
@@ -11263,8 +11338,36 @@ class ListWithDetailControl extends React.Component {
11263
11338
  cells: cells,
11264
11339
  config: config,
11265
11340
  currentIndex: this.props.currentTab,
11266
- setCurrentIndex: this.props.setCurrentTab
11341
+ setCurrentIndex: this.props.setCurrentTab,
11342
+ setCurrentListPage: listPage => {
11343
+ this.setState({
11344
+ currentListPage: listPage
11345
+ });
11346
+ },
11347
+ errors: errors,
11348
+ currentListPage: this.state.currentListPage,
11349
+ listTitle: listTitle
11267
11350
  }, additionalProps))
11351
+ }), this.state.currentListPage === 0 && data > 0 && jsx(ObjectArrayToolBar, {
11352
+ data: data,
11353
+ errors: errors,
11354
+ label: label,
11355
+ addItem: (path, value) => () => {
11356
+ this.addItem(path, value);
11357
+ },
11358
+ numColumns: 0,
11359
+ path: path,
11360
+ uischema: controlElement,
11361
+ schema: schema,
11362
+ rootSchema: rootSchema,
11363
+ enabled: enabled,
11364
+ setCurrentListPage: listPage => {
11365
+ this.setState({
11366
+ currentListPage: listPage
11367
+ });
11368
+ },
11369
+ currentListPage: this.state.currentListPage,
11370
+ buttonType: "tertiary"
11268
11371
  })]
11269
11372
  });
11270
11373
  }
@@ -13022,71 +13125,191 @@ function resolveScope(scope, data) {
13022
13125
  const cleaned = scope.replace(/^#\/(properties\/)?/, '');
13023
13126
  const parts = cleaned.split('/').filter(Boolean);
13024
13127
  let cur = data;
13025
- for (const p of parts) {
13026
- if (cur == null || typeof cur !== 'object') return undefined;
13027
- cur = cur[p];
13128
+ for (const part of parts) {
13129
+ if (cur == null) return undefined;
13130
+ if (Array.isArray(cur) && /^\d+$/.test(part)) {
13131
+ cur = cur[Number(part)];
13132
+ } else if (typeof cur === 'object') {
13133
+ if (part === 'properties') continue;
13134
+ cur = cur[part];
13135
+ } else {
13136
+ return undefined;
13137
+ }
13028
13138
  }
13029
13139
  return cur;
13030
13140
  }
13031
- function sumColumn(scope, data) {
13032
- if (!scope || typeof scope !== 'string') return undefined;
13033
- // normalize scope: remove leading "#/" and optional "properties/"
13141
+ function evaluateSum(scope, data) {
13142
+ if (!scope || typeof scope !== 'string') {
13143
+ return {
13144
+ value: undefined,
13145
+ error: 'SUM() requires a JSON pointer argument.'
13146
+ };
13147
+ }
13034
13148
  const cleaned = scope.replace(/^#\/(properties\/)?/, '');
13035
13149
  const parts = cleaned.split('/').filter(Boolean);
13036
- if (parts.length < 2) return undefined;
13037
- const colKey = parts[parts.length - 1];
13150
+ if (parts.length < 2) {
13151
+ return {
13152
+ value: undefined,
13153
+ error: `SUM() pointer "${scope}" must include an array and field name.`
13154
+ };
13155
+ }
13156
+ const fieldKey = parts[parts.length - 1];
13038
13157
  const arrPath = parts.slice(0, -1);
13039
13158
  let cur = data;
13040
- for (const p of arrPath) {
13041
- if (cur == null) return undefined;
13042
- // handle numeric indices for arrays
13043
- if (Array.isArray(cur) && /^\d+$/.test(p)) {
13044
- cur = cur[Number(p)];
13159
+ for (const segment of arrPath) {
13160
+ if (cur == null) {
13161
+ cur = undefined;
13162
+ break;
13163
+ }
13164
+ if (Array.isArray(cur) && /^\d+$/.test(segment)) {
13165
+ cur = cur[Number(segment)];
13166
+ } else if (typeof cur === 'object') {
13167
+ if (segment === 'properties') continue;
13168
+ cur = cur[segment];
13045
13169
  } else {
13046
- cur = cur[p];
13170
+ cur = undefined;
13171
+ break;
13047
13172
  }
13048
13173
  }
13049
- if (!Array.isArray(cur)) return undefined;
13174
+ if (cur == null) {
13175
+ return {
13176
+ value: undefined,
13177
+ error: `Please provide values for: ${scope}`
13178
+ };
13179
+ }
13180
+ if (!Array.isArray(cur)) {
13181
+ return {
13182
+ value: undefined,
13183
+ error: `SUM() pointer "${scope}" does not resolve to an array.`
13184
+ };
13185
+ }
13186
+ const missingIndices = [];
13187
+ const invalidIndices = [];
13050
13188
  let sum = 0;
13051
- for (const row of cur) {
13052
- if (row == null) return undefined;
13053
- const v = row[colKey];
13054
- if (typeof v !== 'number') return undefined;
13055
- sum += v;
13189
+ for (let i = 0; i < cur.length; i++) {
13190
+ const row = cur[i];
13191
+ if (row == null || typeof row !== 'object') {
13192
+ missingIndices.push(i);
13193
+ continue;
13194
+ }
13195
+ const v = row[fieldKey];
13196
+ if (v === undefined || v === null || v === '') {
13197
+ missingIndices.push(i);
13198
+ } else if (typeof v !== 'number' || Number.isNaN(v)) {
13199
+ invalidIndices.push(i);
13200
+ } else {
13201
+ sum += v;
13202
+ }
13056
13203
  }
13057
- return sum;
13204
+ if (invalidIndices.length > 0) {
13205
+ return {
13206
+ value: undefined,
13207
+ error: `Expected numeric values for: ${scope} at rows [${invalidIndices.join(', ')}]`
13208
+ };
13209
+ }
13210
+ if (missingIndices.length === cur.length) {
13211
+ return {
13212
+ value: undefined,
13213
+ error: `Please provide values for: ${scope}`
13214
+ };
13215
+ }
13216
+ return {
13217
+ value: sum,
13218
+ error: missingIndices.length ? `Some rows are missing values for: ${scope}` : undefined
13219
+ };
13058
13220
  }
13221
+ /**
13222
+ * General expression evaluation.
13223
+ * - Supports SUM(#/properties/arr/c3)
13224
+ * - Supports arithmetic with JSON pointers:
13225
+ * "#/properties/x * #/properties/y + #/properties/z"
13226
+ *
13227
+ * Returns { value, error } and NEVER throws.
13228
+ */
13059
13229
  function evaluateExpression(expression, data) {
13060
- if (!expression || typeof expression !== 'string') return undefined;
13230
+ if (!expression || typeof expression !== 'string') {
13231
+ return {
13232
+ value: undefined,
13233
+ error: undefined
13234
+ };
13235
+ }
13061
13236
  const trimmed = expression.trim();
13237
+ if (!trimmed) return {
13238
+ value: undefined,
13239
+ error: undefined
13240
+ };
13062
13241
  const sumMatch = trimmed.match(/^SUM\(\s*['"]?(.+?)['"]?\s*\)$/i);
13063
13242
  if (sumMatch) {
13064
- return sumColumn(sumMatch[1], data);
13243
+ const pointer = sumMatch[1];
13244
+ return evaluateSum(pointer, data);
13065
13245
  }
13066
- // Find unique scope tokens like #/properties/x or #/arr/0/c3
13067
13246
  const scopeRegex = /#\/(?:properties\/)?[^\s"')]+/g;
13068
13247
  const matches = trimmed.match(scopeRegex) || [];
13069
13248
  const uniqueScopes = Array.from(new Set(matches));
13070
- // Prepare variable mapping for expr-eval
13249
+ let exprForParse = trimmed;
13250
+ const scopeToVar = new Map();
13251
+ uniqueScopes.forEach((scope, index) => {
13252
+ const varName = `v${index}`;
13253
+ scopeToVar.set(scope, varName);
13254
+ const pattern = new RegExp(`['"]?${escapeRegExp(scope)}['"]?`, 'g');
13255
+ exprForParse = exprForParse.replace(pattern, varName);
13256
+ });
13257
+ let parsed;
13258
+ try {
13259
+ const parser = new Parser();
13260
+ parsed = parser.parse(exprForParse);
13261
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
13262
+ } catch (e) {
13263
+ return {
13264
+ value: undefined,
13265
+ error: 'Invalid expression syntax'
13266
+ };
13267
+ }
13071
13268
  const vars = {};
13072
- let expr = trimmed;
13073
- for (let i = 0; i < uniqueScopes.length; i++) {
13074
- const scope = uniqueScopes[i];
13269
+ const missingScopes = [];
13270
+ const invalidTypeScopes = [];
13271
+ for (const scope of uniqueScopes) {
13075
13272
  const val = resolveScope(scope, data);
13076
- if (val === undefined || typeof val !== 'number') return undefined;
13077
- const varName = `v${i}`;
13273
+ if (val === undefined || val === null || val === '') {
13274
+ missingScopes.push(scope);
13275
+ continue;
13276
+ }
13277
+ if (typeof val !== 'number' || Number.isNaN(val)) {
13278
+ invalidTypeScopes.push(scope);
13279
+ continue;
13280
+ }
13281
+ const varName = scopeToVar.get(scope);
13078
13282
  vars[varName] = val;
13079
- const pattern = new RegExp(`['"]?${escapeRegExp(scope)}['"]?`, 'g');
13080
- expr = expr.replace(pattern, varName);
13081
13283
  }
13082
- // Evaluate with expr-eval parser
13284
+ if (invalidTypeScopes.length > 0) {
13285
+ return {
13286
+ value: undefined,
13287
+ error: `Expected numeric values for: ${invalidTypeScopes.join(', ')}`
13288
+ };
13289
+ }
13290
+ if (missingScopes.length > 0) {
13291
+ return {
13292
+ value: undefined,
13293
+ error: `Please provide values for: ${missingScopes.join(', ')}`
13294
+ };
13295
+ }
13083
13296
  try {
13084
- const parser = new Parser();
13085
- const parsed = parser.parse(expr);
13086
13297
  const result = parsed.evaluate(vars);
13087
- return typeof result === 'number' && Number.isFinite(result) ? result : undefined;
13298
+ if (typeof result === 'number' && Number.isFinite(result)) {
13299
+ return {
13300
+ value: result,
13301
+ error: undefined
13302
+ };
13303
+ }
13304
+ return {
13305
+ value: undefined,
13306
+ error: 'Expression did not produce a numeric result'
13307
+ };
13088
13308
  } catch (_a) {
13089
- return undefined;
13309
+ return {
13310
+ value: undefined,
13311
+ error: 'Error while evaluating expression'
13312
+ };
13090
13313
  }
13091
13314
  }
13092
13315
 
@@ -13094,29 +13317,44 @@ const GoACalculation = props => {
13094
13317
  var _a;
13095
13318
  const {
13096
13319
  uischema,
13097
- data,
13098
13320
  schema,
13099
13321
  path,
13100
13322
  id,
13101
13323
  visible,
13102
13324
  handleChange
13103
13325
  } = props;
13104
- const expression = schema === null || schema === void 0 ? void 0 : schema.description;
13105
13326
  const label = typeof (uischema === null || uischema === void 0 ? void 0 : uischema.label) === 'string' ? uischema.label : undefined;
13327
+ const expression = schema === null || schema === void 0 ? void 0 : schema.description;
13106
13328
  const {
13107
13329
  core
13108
13330
  } = useJsonForms();
13109
13331
  const rootData = (_a = core === null || core === void 0 ? void 0 : core.data) !== null && _a !== void 0 ? _a : {};
13110
- const computedValue = expression ? evaluateExpression(expression, rootData) : undefined;
13111
- React.useEffect(() => {
13332
+ const [hasInteracted, setHasInteracted] = useState(false);
13333
+ const prevDataRef = useRef(rootData);
13334
+ useEffect(() => {
13335
+ setHasInteracted(was => was ? true : true);
13336
+ }, [rootData]);
13337
+ useEffect(() => {
13338
+ if (prevDataRef.current !== rootData) {
13339
+ setHasInteracted(true);
13340
+ prevDataRef.current = rootData;
13341
+ }
13342
+ }, [rootData]);
13343
+ const {
13344
+ value: computedValue,
13345
+ error
13346
+ } = evaluateExpression(expression, rootData);
13347
+ useEffect(() => {
13112
13348
  if (computedValue !== undefined && typeof handleChange === 'function' && path) {
13113
13349
  handleChange(path, computedValue);
13114
13350
  }
13115
13351
  }, [computedValue, handleChange, path]);
13352
+ const showError = hasInteracted && !!error;
13116
13353
  return jsx(Visible, {
13117
13354
  visible: visible,
13118
13355
  children: jsx(GoAFormItem, {
13119
13356
  label: label,
13357
+ error: showError ? error : '',
13120
13358
  children: jsx(GoAInput, {
13121
13359
  name: `computed-input-${id}`,
13122
13360
  testId: `computed-input-${id}`,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@abgov/jsonforms-components",
3
- "version": "2.36.2",
3
+ "version": "2.38.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",
@@ -13,7 +13,8 @@
13
13
  "ajv-errors": "^3.0.0",
14
14
  "ajv-formats": "^3.0.1",
15
15
  "@mdx-js/mdx": "^3.1.0",
16
- "expr-eval": "^2.0.2"
16
+ "expr-eval": "^2.0.2",
17
+ "pluralize": "^8.0.0"
17
18
  },
18
19
  "dependencies": {
19
20
  "axios": "^1.6.7",
@@ -1,3 +1,15 @@
1
+ export interface EvaluationResult {
2
+ value?: number;
3
+ error?: string;
4
+ }
1
5
  export declare function resolveScope(scope: string, data: unknown): unknown;
2
- export declare function sumColumn(scope: string, data: unknown): number | undefined;
3
- export declare function evaluateExpression(expression: string, data: unknown): number | undefined;
6
+ export declare function evaluateSum(scope: string | undefined, data: unknown): EvaluationResult;
7
+ /**
8
+ * General expression evaluation.
9
+ * - Supports SUM(#/properties/arr/c3)
10
+ * - Supports arithmetic with JSON pointers:
11
+ * "#/properties/x * #/properties/y + #/properties/z"
12
+ *
13
+ * Returns { value, error } and NEVER throws.
14
+ */
15
+ export declare function evaluateExpression(expression: string | undefined, data: unknown): EvaluationResult;
@@ -31,6 +31,7 @@ interface NonEmptyRowProps {
31
31
  path: string;
32
32
  translations: ArrayTranslations;
33
33
  uischema: ControlElement;
34
+ listTitle?: string;
34
35
  }
35
36
  export declare const NonEmptyList: React.MemoExoticComponent<({ childPath, schema, enabled, cells, uischema, }: NonEmptyRowProps & WithDeleteDialogSupport) => import("react/jsx-runtime").JSX.Element>;
36
37
  interface ListWithDetailControlProps extends ObjectArrayControlProps {
@@ -40,6 +41,7 @@ interface ListWithDetailControlProps extends ObjectArrayControlProps {
40
41
  export declare class ListWithDetailControl extends React.Component<ListWithDetailControlProps, any> {
41
42
  state: {
42
43
  maxItemsError: string;
44
+ currentListPage: number;
43
45
  };
44
46
  addItem: (path: string, value: any) => void;
45
47
  render(): import("react/jsx-runtime").JSX.Element;
@@ -1,6 +1,7 @@
1
1
  import React from 'react';
2
2
  import { ControlElement, JsonSchema } from '@jsonforms/core';
3
3
  export interface ObjectArrayToolbarProps {
4
+ data?: any;
4
5
  numColumns: number;
5
6
  errors: string;
6
7
  label: string;
@@ -10,6 +11,9 @@ export interface ObjectArrayToolbarProps {
10
11
  rootSchema: JsonSchema;
11
12
  enabled: boolean;
12
13
  addItem(path: string, value: any): () => void;
14
+ setCurrentListPage?: (page: number) => void;
15
+ currentListPage?: number;
16
+ buttonType?: string;
13
17
  }
14
18
  declare const ObjectArrayToolBar: React.NamedExoticComponent<ObjectArrayToolbarProps>;
15
19
  export default ObjectArrayToolBar;
@@ -1,4 +1,5 @@
1
1
  export declare const DeleteDialogContent: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components").FastOmit<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLDivElement>, HTMLDivElement>, never>> & string;
2
+ export declare const MarginTop: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components").FastOmit<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLDivElement>, HTMLDivElement>, never>> & string;
2
3
  export declare const NonEmptyCellStyle: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components").FastOmit<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLDivElement>, HTMLDivElement>, never>> & string;
3
4
  export declare const ToolBarHeader: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components").FastOmit<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLDivElement>, HTMLDivElement>, never>> & string;
4
5
  export declare const ObjectArrayTitle: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components").FastOmit<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLHeadingElement>, HTMLHeadingElement>, never>> & string;
@@ -9,9 +10,12 @@ export declare const RowFlex: import("styled-components/dist/types").IStyledComp
9
10
  export declare const RowFlexMenu: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components").FastOmit<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLDivElement>, HTMLDivElement>, never>> & string;
10
11
  export declare const FlexTabs: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components").FastOmit<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLDivElement>, HTMLDivElement>, never>> & string;
11
12
  export declare const FlexForm: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components").FastOmit<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLDivElement>, HTMLDivElement>, never>> & string;
12
- export declare const TabName: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components").FastOmit<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLDivElement>, HTMLDivElement>, never>> & string;
13
+ export declare const TabData: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components").FastOmit<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLDivElement>, HTMLDivElement>, never>> & string;
13
14
  export declare const Trash: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components").FastOmit<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLDivElement>, HTMLDivElement>, never>> & string;
14
15
  export declare const ListContainer: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components").FastOmit<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLDivElement>, HTMLDivElement>, never>> & string;
16
+ export declare const IconPadding: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components").FastOmit<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLDivElement>, HTMLDivElement>, never>> & string;
17
+ export declare const UpdateListContainer: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components").FastOmit<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLDivElement>, HTMLDivElement>, never>> & string;
18
+ export declare const CompleteContainer: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components").FastOmit<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLDivElement>, HTMLDivElement>, never>> & string;
15
19
  export declare const TableTHHeader: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components").FastOmit<import("react").DetailedHTMLProps<import("react").ThHTMLAttributes<HTMLTableHeaderCellElement>, HTMLTableHeaderCellElement>, never>> & string;
16
20
  export declare const ObjectArrayWarningIconDiv: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components").FastOmit<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLDivElement>, HTMLDivElement>, never>> & string;
17
21
  export declare const ListWithDetailWarningIconDiv: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components").FastOmit<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLDivElement>, HTMLDivElement>, never>> & string;