@entryscape/rdforms 10.4.0 → 10.5.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/package.json CHANGED
@@ -8,7 +8,7 @@
8
8
  "application profile",
9
9
  "linked data"
10
10
  ],
11
- "version": "10.4.0",
11
+ "version": "10.5.0",
12
12
  "main": "dist/rdforms.node.js",
13
13
  "browser": "dist/rdforms.react.js",
14
14
  "module": "main.js",
@@ -18,7 +18,7 @@
18
18
  "dependencies": {
19
19
  "@emotion/react": "^11.4.1",
20
20
  "@emotion/styled": "^11.3.0",
21
- "@entryscape/rdfjson": "^2.5.3",
21
+ "@entryscape/rdfjson": "^2.5.4",
22
22
  "@mui/icons-material": "^5.0.3",
23
23
  "@mui/lab": "^5.0.0-alpha.51",
24
24
  "@mui/material": "^5.0.3",
@@ -30,14 +30,6 @@ export default class ChoiceBinding extends ValueBinding {
30
30
  return this._choice;
31
31
  }
32
32
 
33
- remove() {
34
- this.setValue(null);
35
- // Removed line below as it is also done in superclass
36
- // and therefore causes an error
37
- // this._parent.removeChildBinding(this);
38
- super.remove();
39
- }
40
-
41
33
  setValue(value, choice, silent) {
42
34
  super.setValue(value, choice, silent);
43
35
  const oldval = this.getValue();
@@ -30,6 +30,8 @@ export default class Item {
30
30
  'multiline',
31
31
  'horizontalRadioButtons',
32
32
  'verticalRadioButtons',
33
+ 'horizontalCheckBoxes',
34
+ 'verticalCheckBoxes',
33
35
  'nonEditable',
34
36
  'expandable',
35
37
  'compact',
package/src/utils.js CHANGED
@@ -3,7 +3,7 @@ import { cloneDeep } from 'lodash-es';
3
3
  import system from './model/system';
4
4
 
5
5
  const getLocalizedValue = (hash) => {
6
- const locale = moment.locale();
6
+ const locale = moment.locale('sv');
7
7
  if (hash == null) {
8
8
  return { precision: 'none' };
9
9
  } else if (typeof hash === 'string') {
@@ -1,14 +1,14 @@
1
1
  import renderingContext from './renderingContext';
2
2
  import Presenter from './Presenter';
3
3
  import * as engine from '../model/engine';
4
- import {bindingReport} from '../model/validate';
4
+ import { bindingReport } from '../model/validate';
5
5
 
6
6
  const showNow = (editor, item, bindings, includeLevel) => {
7
7
  // Invisible should be created as components and hidden using display: none
8
8
  // Otherwise certain extentions such as autoUUID does not work.
9
- /*if (item.hasStyle('invisible')) {
9
+ /* if (item.hasStyle('invisible')) {
10
10
  return false;
11
- }*/
11
+ } */
12
12
  if (item.hasStyle('presenterOnly')) {
13
13
  return false;
14
14
  }
@@ -135,6 +135,7 @@ export default class Editor extends Presenter {
135
135
  return showNow(this, item, bindings, this.includeLevel);
136
136
  }
137
137
 
138
+ // eslint-disable-next-line class-methods-use-this
138
139
  skipBinding(/* binding */) {
139
140
  return false;
140
141
  }
@@ -185,16 +186,16 @@ export default class Editor extends Presenter {
185
186
  if (firstBinding.getItem().hasStyle('nonEditable')) {
186
187
  return this.addComponent(newRow, firstBinding);
187
188
  }
188
- return renderingContext.addEditorTable(newRow, firstBinding, {view: this});
189
+ return renderingContext.addEditorTable(newRow, firstBinding, { view: this });
189
190
  }
190
191
 
191
192
  fillTable(table, bindings) {
192
- renderingContext.fillEditorTable(table, bindings, {view: this});
193
+ renderingContext.fillEditorTable(table, bindings, { view: this });
193
194
  }
194
195
 
195
196
  preRenderView() {
196
197
  renderingContext.preEditorViewRenderer(this.domNode, this.binding, {
197
- view: this, inEditor: true, topLevel: this.topLevel, hideAddress: this.hideAddress
198
+ view: this, inEditor: true, topLevel: this.topLevel, hideAddress: this.hideAddress,
198
199
  });
199
200
  }
200
201
 
@@ -209,7 +210,7 @@ export default class Editor extends Presenter {
209
210
 
210
211
  createRowNode(lastRowNode, binding, item) {
211
212
  if (binding == null && item.hasStyle('nonEditable')) {
212
- return;
213
+ return undefined;
213
214
  }
214
215
  const newNode = super.createRowNode(lastRowNode, binding, item);
215
216
  if (item.getType() === 'choice' && typeof item.getProperty() === 'undefined') {
@@ -255,4 +256,10 @@ export default class Editor extends Presenter {
255
256
  }
256
257
  return newNode;
257
258
  }
258
- };
259
+
260
+ // eslint-disable-next-line class-methods-use-this
261
+ isMultiValued(item) {
262
+ return renderingContext.multiValueSupport &&
263
+ (item.hasStyle('horizontalCheckBoxes') || item.hasStyle('verticalCheckBoxes'));
264
+ }
265
+ }
package/src/view/View.js CHANGED
@@ -180,11 +180,16 @@ export default class View {
180
180
 
181
181
  // Non table case
182
182
  } else if (bindings.length > 0) {
183
- for (let i = 0; i < bindings.length; i++) {
184
- // Add row with label if first row of same item or the binding is a group.
183
+ if (this.isMultiValued(item)) {
185
184
  this.context = { view: this };
186
- lastRow = this.addRow(lastRow, bindings[i], i === 0 ||
187
- bindings[i] instanceof GroupBinding);
185
+ lastRow = this.addRow(lastRow, bindings[0], true);
186
+ } else {
187
+ for (let i = 0; i < bindings.length; i++) {
188
+ // Add row with label if first row of same item or the binding is a group.
189
+ this.context = { view: this };
190
+ lastRow = this.addRow(lastRow, bindings[i], i === 0 ||
191
+ bindings[i] instanceof GroupBinding);
192
+ }
188
193
  }
189
194
  } else {
190
195
  lastRow = this.createRowNode(lastRow, null, item);
@@ -346,4 +351,8 @@ export default class View {
346
351
  this._labelIndex[binding.getHash()] = idx;
347
352
  return idx;
348
353
  }
354
+
355
+ isMultiValued(item) {
356
+ return false;
357
+ }
349
358
  }
@@ -1,10 +1,11 @@
1
1
  import moment from 'moment';
2
2
  import { escape } from 'lodash-es';
3
- import { fromDuration } from './util';
3
+ import { getDatePresentation, fromDuration } from './util';
4
4
  import renderingContext from '../renderingContext';
5
5
  import utils from '../../utils';
6
6
  import system from '../../model/system';
7
7
 
8
+
8
9
  const presenters = renderingContext.presenterRegistry;
9
10
 
10
11
  // Presenter for URIs.
@@ -117,33 +118,29 @@ presenters.itemtype('text').register((fieldDiv, binding, context) => {
117
118
  // Presenter for duration
118
119
  presenters.itemtype('text').datatype('xsd:duration').register((fieldDiv, binding, context) => {
119
120
  const data = fromDuration(binding.getValue());
120
- const bundle = context.view.messages
121
+ const bundle = context.view.messages;
121
122
  const node = jquery('<div>').appendTo(fieldDiv)[0];
122
123
  ['years', 'months', 'days', 'hours', 'minutes'].forEach((key) => {
123
124
  if (data.hasOwnProperty(key)) {
124
- jquery(`<span class="durationlabel">${bundle['duration_'+key]}:</span><span class="durationValue">${data[key]}</span>`)
125
+ jquery(`<span class="durationlabel">${bundle[`duration_${key}`]}:</span><span class="durationValue">${data[key]}</span>`)
125
126
  .appendTo(node);
126
127
  }
127
128
  });
128
129
  });
129
130
 
130
- const datePresenter = (fieldDiv, binding, context) => {
131
- const data = binding.getValue();
132
- if (data != null && data !== '') {
133
- try {
134
- let str;
135
- if (data.indexOf('T') > 0) {
136
- str = moment(data).format('lll');
137
- } else if (data.length > 4) {
138
- str = moment(data).format('LL');
139
- } else {
140
- str = moment(data).format('YYYY');
141
- }
142
- jquery('<div>').html(str).appendTo(fieldDiv);
143
- } catch (e) {
144
- console.warn(`Could not present date, expected ISO8601 format in the form 2001-01-01 (potentially with time given after a 'T' character as well) but found '${data}' instead.`);
145
- }
131
+ const datePresenter = (fieldDiv, binding) => {
132
+ try {
133
+ const pres = getDatePresentation(binding);
134
+ jquery('<div>').html(pres).appendTo(fieldDiv);
135
+ } catch (e) {
136
+ console.warn(`Could not present date, expected ISO8601 format in the form 2001-01-01
137
+ (potentially with time given after a 'T' character as well) but found '${binding.getValue()}' instead.`);
146
138
  }
147
139
  };
140
+ presenters.itemtype('text').datatype('xsd:dateTime').register(datePresenter);
148
141
  presenters.itemtype('text').datatype('xsd:date').register(datePresenter);
149
- presenters.itemtype('text').datatype('dcterms:W3CDTF').register(datePresenter);
142
+ presenters.itemtype('text').datatype('xsd:time').register(datePresenter);
143
+ presenters.itemtype('text').datatype('xsd:gYear').register(datePresenter);
144
+ presenters.itemtype('text').datatype('xsd:gYearMonth').register(datePresenter);
145
+ presenters.itemtype('text').datatype('xsd:gMonthDay').register(datePresenter);
146
+ presenters.itemtype('text').datatype('dcterms:W3CDTF').register(datePresenter);
@@ -1,3 +1,5 @@
1
+ import moment from 'moment';
2
+
1
3
  export const fromDuration = (value) => {
2
4
  const f = val => (val && val.length > 1 ? parseInt(val[0], 10) : 0);
3
5
  const years = f(value.match(/([0-9])*Y/));
@@ -29,3 +31,171 @@ export const toDuration = (data) => {
29
31
  }
30
32
  return null;
31
33
  };
34
+
35
+ export const getDate = (value) => {
36
+ try {
37
+ // xsd:time
38
+ if (value.includes(':') && !value.includes('T')) {
39
+ const datePart = new Date().toISOString().substr(0, 11);
40
+ const timeDate = new Date(`${datePart}${value}`);
41
+ timeDate.setMinutes(timeDate.getMinutes() + timeDate.getTimezoneOffset());
42
+ return timeDate;
43
+ }
44
+ // all other cases
45
+ return new Date(value);
46
+ } catch (e) {
47
+ return null;
48
+ }
49
+ };
50
+
51
+ export const getDatatype = (datatype) => {
52
+ switch (datatype) {
53
+ case 'http://www.w3.org/2001/XMLSchema#dateTime':
54
+ case 'http://purl.org/dc/terms/W3CDTF':
55
+ return 'DateTime';
56
+ case 'http://www.w3.org/2001/XMLSchema#date':
57
+ return 'Date';
58
+ case 'http://www.w3.org/2001/XMLSchema#gYear':
59
+ return 'Year';
60
+ case 'http://www.w3.org/2001/XMLSchema#time':
61
+ return 'Time';
62
+ case 'http://www.w3.org/2001/XMLSchema#gYearMonth':
63
+ return 'YearMonth';
64
+ case 'http://www.w3.org/2001/XMLSchema#gMonthDay':
65
+ return 'MonthDay';
66
+ default:
67
+ return undefined;
68
+ }
69
+ };
70
+
71
+ export const getDatatypeFromItem = (item) => {
72
+ const dt = item.getDatatype();
73
+ if (Array.isArray(dt)) {
74
+ return getDatatype(dt[0]);
75
+ }
76
+ return getDatatype(dt);
77
+ };
78
+
79
+ export const getDatatypeFromValue = (data) => {
80
+ if (data.indexOf('T') > 0) {
81
+ return 'DateTime';
82
+ } else if (data.indexOf(':') > 0) {
83
+ return 'Time';
84
+ } else if (/^\d\d\d\d-\d\d$/.test(data)) {
85
+ return 'YearMonth';
86
+ } else if (/^\d\d-\d\d$/.test(data)) {
87
+ return 'MonthDay';
88
+ } else if (data.length > 4) {
89
+ return 'Date';
90
+ }
91
+ return 'Year';
92
+ };
93
+
94
+ export const getDatatypeURI = (datatype) => {
95
+ switch (datatype) {
96
+ case 'DateTime':
97
+ return 'http://www.w3.org/2001/XMLSchema#dateTime';
98
+ case 'Date':
99
+ return 'http://www.w3.org/2001/XMLSchema#date';
100
+ case 'Time':
101
+ return 'http://www.w3.org/2001/XMLSchema#time';
102
+ case 'Year':
103
+ return 'http://www.w3.org/2001/XMLSchema#gYear';
104
+ case 'YearMonth':
105
+ return 'http://www.w3.org/2001/XMLSchema#gYearMonth';
106
+ case 'MonthDay':
107
+ return 'http://www.w3.org/2001/XMLSchema#gMonthDay';
108
+ default:
109
+ return '';
110
+ }
111
+ };
112
+
113
+ export const getDateValue = (value, datatype) => {
114
+ if (value) {
115
+ switch (datatype) {
116
+ case 'DateTime':
117
+ return value.toISOString();
118
+ case 'Date':
119
+ // Since we cut of the timezone section at the end we need to compensate for it
120
+ value.setMinutes(value.getMinutes() - value.getTimezoneOffset());
121
+ return value.toISOString().substr(0, 10);
122
+ case 'YearMonth':
123
+ // Since we cut of the timezone section at the end we need to compensate for it
124
+ value.setMinutes(value.getMinutes() - value.getTimezoneOffset());
125
+ return value.toISOString().substr(0, 7);
126
+ case 'MonthDay':
127
+ // Since we cut of the timezone section at the end we need to compensate for it
128
+ value.setMinutes(value.getMinutes() - value.getTimezoneOffset());
129
+ return value.toISOString().substr(5, 5);
130
+ case 'Time':
131
+ value.setMinutes(value.getMinutes() - value.getTimezoneOffset());
132
+ return value.toISOString().substr(11);
133
+ case 'Year':
134
+ // Since we cut of the timezone section at the end we need to compensate for it
135
+ value.setMinutes(value.getMinutes() - value.getTimezoneOffset());
136
+ return value.toISOString().substr(0, 4);
137
+ default:
138
+ return '';
139
+ }
140
+ }
141
+ return '';
142
+ };
143
+
144
+ export const getDatePresentationFromDatatype = (datatype, date) => {
145
+ switch (datatype) {
146
+ case 'DateTime':
147
+ return moment(date).format('lll');
148
+ case 'Date':
149
+ return moment(date).format('LL');
150
+ case 'Time':
151
+ return moment(date).format('LT');
152
+ case 'Year':
153
+ return moment(date).format('YYYY');
154
+ case 'YearMonth':
155
+ return moment(date).format('MMMM YYYY');
156
+ case 'MonthDay':
157
+ return moment(date).format('D MMMM');
158
+ default:
159
+ return '';
160
+ }
161
+ };
162
+
163
+ export const getAllowedDateAlternatives = (item) => {
164
+ const dateAllowedDataAlternatives = {};
165
+ const dt = item.getDatatype();
166
+ const alts = Array.isArray(dt) ? dt : [dt];
167
+ alts.forEach((datatype) => {
168
+ const alt = getDatatype(datatype);
169
+ if (alt) {
170
+ dateAllowedDataAlternatives[alt] = true;
171
+ }
172
+ });
173
+ return dateAllowedDataAlternatives;
174
+ };
175
+
176
+ export const getDatePresentation = (binding) => {
177
+ const data = binding.getValue();
178
+ if (data != null && data !== '') {
179
+ const item = binding.getItem();
180
+ const date = getDate(data);
181
+ if (date) {
182
+ const valueDatatype = getDatatypeFromValue(data);
183
+ const datatypeDatatype = getDatatype(binding.getDatatype());
184
+ const itemSuggestedDatatype = getDatatypeFromItem(item);
185
+ const allowed = getAllowedDateAlternatives(item);
186
+ let datatype;
187
+ if (allowed[datatypeDatatype]) {
188
+ // This is always the case unless "relaxedDatatypeMatch" is specified
189
+ datatype = datatypeDatatype;
190
+ } else if (allowed[valueDatatype]) {
191
+ // If the detected datatype from the value is supported
192
+ datatype = valueDatatype;
193
+ } else {
194
+ // Rely on the datatype from the item if everything else fails
195
+ datatype = itemSuggestedDatatype;
196
+ }
197
+ return getDatePresentationFromDatatype(datatype, date);
198
+ }
199
+ }
200
+ return undefined;
201
+ };
@@ -14,9 +14,13 @@ renderingContext.addExpandButton = (rowDiv, labelDiv, item, context) => {
14
14
 
15
15
  const isClearButton = (binding, context) => {
16
16
  const item = binding.getItem();
17
+ const notDeprecated = !item.hasStyle('deprecated');
18
+ if (context.view.isMultiValued(item) && notDeprecated) {
19
+ return true;
20
+ }
17
21
  const cardTr = binding.getCardinalityTracker();
18
22
  const showOne = context.view.showNow(binding.getItem(), []);
19
- return !item.hasStyle('deprecated') &&
23
+ return notDeprecated &&
20
24
  (cardTr.isMin() || (cardTr.getCardinality() === 1 && showOne && cardTr.isDepsOk()));
21
25
  };
22
26
  renderingContext.addRemoveButton = (rowDiv, binding, context) => () => {
@@ -61,6 +65,13 @@ renderingContext.addRemoveButton = (rowDiv, binding, context) => () => {
61
65
  binding.setLanguage('');
62
66
  }
63
67
  }
68
+ } else if (context.view.isMultiValued(item)) {
69
+ // This case corresponds to multivalued and deprecated.
70
+ // Slice so we have a copy of the array... since it will change behind the scenes otherwise
71
+ binding.getParent().getChildBindingsFor(item).slice(0).forEach((b) => {
72
+ b.remove();
73
+ });
74
+ rowDiv.parent.destroy();
64
75
  } else {
65
76
  binding.remove();
66
77
  rowDiv.destroy();
@@ -77,8 +88,8 @@ renderingContext.addRemoveButton = (rowDiv, binding, context) => () => {
77
88
  };
78
89
 
79
90
  renderingContext.addCreateChildButton = (rowDiv, labelDiv, binding, context) => () => {
80
- const cardTr = binding.getCardinalityTracker();
81
91
  const item = binding.getItem();
92
+ const cardTr = binding.getCardinalityTracker();
82
93
  const [disabled, setDisabled] = useState(cardTr.isMax() || !cardTr.isDepsOk() || item.hasStyle('deprecated'));
83
94
  useEffect(() => {
84
95
  context.rememberParent = binding.getParent(); // If the current binding is removed and the label is not redrawn
@@ -0,0 +1,87 @@
1
+ /* eslint-disable no-unused-vars */
2
+ import React, { useState, useEffect, useMemo } from 'react';
3
+ import FormControlLabel from '@mui/material/FormControlLabel';
4
+ import Checkbox from '@mui/material/Checkbox';
5
+ import { FormGroup } from '@mui/material';
6
+ import { useLocalizedSortedChoices, useName } from '../hooks';
7
+ import * as engine from '../../../model/engine';
8
+
9
+ const CheckOption = (props) => {
10
+ const { choice, binding, onChoiceChange } = props;
11
+ const [checked, setChecked] = React.useState(choice.value === binding.getValue());
12
+
13
+ const handleChange = (evt) => {
14
+ binding.setChoice(evt.target.checked ? choice.original : null);
15
+ setChecked(evt.target.checked);
16
+ onChoiceChange();
17
+ };
18
+ return <FormControlLabel
19
+ label={choice.label} control={<Checkbox checked={checked} onChange={handleChange}/>}
20
+ {...(choice.mismatch ? { className: 'mismatch' } : {})}
21
+ title={choice.description || choice.seeAlso || choice.value}/>;
22
+ };
23
+
24
+ export default function CheckBoxesEditor(props) {
25
+ const [resetCount, setResetCount] = React.useState(0);
26
+ const binding = props.binding;
27
+ const item = binding.getItem();
28
+ const choices = useLocalizedSortedChoices(binding);
29
+ const choiceBindingPairs = useMemo(() => {
30
+ const parentBinding = binding.getParent();
31
+ const val2binding = {};
32
+ // eslint-disable-next-line no-return-assign
33
+ parentBinding.getChildBindingsFor(item).forEach(b => (val2binding[b.getValue()] = b));
34
+ const pairs = choices.map((c) => {
35
+ const existingbinding = val2binding[c.value];
36
+ if (existingbinding) {
37
+ delete val2binding[c.value];
38
+ return [c, existingbinding];
39
+ }
40
+ return [c, engine.create(parentBinding, item)];
41
+ });
42
+ // Add checks for values that are non-conforming (should have mismatch on their choices).
43
+ Object.values(val2binding).forEach((b) => {
44
+ const choice = b.getChoice();
45
+ if (choice) {
46
+ pairs.push([choice, b]);
47
+ }
48
+ });
49
+ return pairs;
50
+ });
51
+
52
+ const [error, setError] = useState(binding.getChoice()?.mismatch === true);
53
+ const row = item.hasStyle('verticalCheckboxes') ? {} : { row: true };
54
+
55
+ const onChoiceChange = () => {
56
+ const newError = choiceBindingPairs.find(pair => pair[1].getChoice()
57
+ && pair[1].getChoice().mismatch) !== undefined;
58
+ if (newError !== error) {
59
+ setError(newError);
60
+ }
61
+ };
62
+
63
+ useEffect(() => {
64
+ props.field.toggleClass('mismatchReport', error);
65
+ }, [error]);
66
+
67
+ props.context.clear = () => {
68
+ choiceBindingPairs.forEach(pair => pair[1].setChoice(null));
69
+ setError(false);
70
+ setResetCount(resetCount + 1);
71
+ };
72
+
73
+ return (
74
+ <><FormGroup {...row}>
75
+ {choiceBindingPairs.map(pair => (
76
+ <CheckOption key={`${resetCount}-${pair[1].getHash()}`}
77
+ choice={pair[0]}
78
+ binding={pair[1]}
79
+ onChoiceChange={onChoiceChange} />
80
+ ))}
81
+ </FormGroup>{error && (
82
+ <div key="warning" className="rdformsWarning">
83
+ {props.context.view.messages.wrongValueField}
84
+ </div>
85
+ )}</>
86
+ );
87
+ }
@@ -2,6 +2,7 @@
2
2
  import React from 'react';
3
3
  import renderingContext from '../../renderingContext';
4
4
  import RadioButtonsEditor from './RadioButtonsEditor';
5
+ import CheckBoxesEditor from './CheckBoxesEditor';
5
6
  import ChoiceSelector from './ChoiceSelector';
6
7
  import ChoiceLookup from './ChoiceLookup';
7
8
  import ChoiceLookupAndInlineSearch from './ChoiceLookupAndInlineSearch';
@@ -27,6 +28,14 @@ editors.itemtype('choice').choices().check(radioCheck).register((fieldDiv, bindi
27
28
  field={fieldDiv}/>);
28
29
  });
29
30
 
31
+ const checkBoxComponent = (fieldDiv, binding, context) => {
32
+ // eslint-disable-next-line no-new
33
+ fieldDiv.appendChild(<CheckBoxesEditor key={binding.getHash()} binding={binding} context={context}
34
+ field={fieldDiv}/>);
35
+ };
36
+ editors.itemtype('choice').choices().style('verticalCheckBoxes').register(checkBoxComponent);
37
+ editors.itemtype('choice').choices().style('horizontalCheckBoxes').register(checkBoxComponent);
38
+
30
39
  editors.itemtype('choice').choices().register((fieldDiv, binding, context) => {
31
40
  fieldDiv.appendChild(<ChoiceSelector key={binding.getHash()} binding={binding} context={context} field={fieldDiv}/>);
32
41
  });
@@ -95,11 +95,11 @@ const newStruct = (Tag, parent) => {
95
95
  // -- START: Initial methods used before react kicks in.
96
96
  toggleClass: (clsStr, addOrNot) => toggleClass(firstClsSet, clsStr, addOrNot),
97
97
  domQuery: selector => (selectorInClasses(selector, firstClsSet) ? ext : findStruct(selector, firstChildArr)),
98
- appendChild: struct => {
98
+ appendChild: (struct) => {
99
99
  firstChildArr.push(struct);
100
100
  },
101
101
  appendAfter: (struct, sibling) => {
102
- firstChildArr.splice(firstChildArr.indexOf(sibling) + 1, 0, struct)
102
+ firstChildArr.splice(firstChildArr.indexOf(sibling) + 1, 0, struct);
103
103
  },
104
104
  text: (str) => {
105
105
  firstTextStr = str;
@@ -248,3 +248,5 @@ renderingContext.renderValidationMessage = (fieldDiv, type, message) => {
248
248
  fieldDiv.appendChild(<div className="rdformsValidationMessageWrapper" key={ `rdforms_valcount_${validationCounter}`}
249
249
  ><ValidationIcon/><span className="rdformsValidationMessage">{message}</span></div>);
250
250
  };
251
+
252
+ renderingContext.multiValueSupport = true;