@bpmn-io/form-js-viewer 0.13.1 → 0.14.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/dist/index.es.js CHANGED
@@ -18,25 +18,25 @@ class FeelExpressionLanguage {
18
18
  this._eventBus = eventBus;
19
19
  }
20
20
 
21
- /**
22
- * Determines if the given string is a FEEL expression.
23
- *
24
- * @param {string} value
25
- * @returns {boolean}
26
- *
21
+ /**
22
+ * Determines if the given string is a FEEL expression.
23
+ *
24
+ * @param {string} value
25
+ * @returns {boolean}
26
+ *
27
27
  */
28
28
  isExpression(value) {
29
29
  return isString(value) && value.startsWith('=');
30
30
  }
31
31
 
32
- /**
33
- * Retrieve variable names from a given FEEL expression.
34
- *
35
- * @param {string} expression
36
- * @param {object} [options]
37
- * @param {string} [options.type]
38
- *
39
- * @returns {string[]}
32
+ /**
33
+ * Retrieve variable names from a given FEEL expression.
34
+ *
35
+ * @param {string} expression
36
+ * @param {object} [options]
37
+ * @param {string} [options.type]
38
+ *
39
+ * @returns {string[]}
40
40
  */
41
41
  getVariableNames(expression, options = {}) {
42
42
  const {
@@ -53,13 +53,13 @@ class FeelExpressionLanguage {
53
53
  throw new Error('Unknown expression type: ' + options.type);
54
54
  }
55
55
 
56
- /**
57
- * Evaluate an expression.
58
- *
59
- * @param {string} expression
60
- * @param {import('../../types').Data} [data]
61
- *
62
- * @returns {any}
56
+ /**
57
+ * Evaluate an expression.
58
+ *
59
+ * @param {string} expression
60
+ * @param {import('../../types').Data} [data]
61
+ *
62
+ * @returns {any}
63
63
  */
64
64
  evaluate(expression, data = {}) {
65
65
  if (!expression) {
@@ -111,17 +111,17 @@ class FeelersTemplating {
111
111
  return isString(value) && (value.startsWith('=') || /{{/.test(value));
112
112
  }
113
113
 
114
- /**
115
- * Evaluate a template.
116
- *
117
- * @param {string} template
118
- * @param {Object<string, any>} context
119
- * @param {Object} options
120
- * @param {boolean} [options.debug = false]
121
- * @param {boolean} [options.strict = false]
122
- * @param {Function} [options.buildDebugString]
123
- *
124
- * @returns
114
+ /**
115
+ * Evaluate a template.
116
+ *
117
+ * @param {string} template
118
+ * @param {Object<string, any>} context
119
+ * @param {Object} options
120
+ * @param {boolean} [options.debug = false]
121
+ * @param {boolean} [options.strict = false]
122
+ * @param {Function} [options.buildDebugString]
123
+ *
124
+ * @returns
125
125
  */
126
126
  evaluate(template, context = {}, options = {}) {
127
127
  const {
@@ -138,9 +138,9 @@ class FeelersTemplating {
138
138
  }
139
139
  FeelersTemplating.$inject = [];
140
140
 
141
- /**
142
- * @typedef {object} Condition
143
- * @property {string} [hide]
141
+ /**
142
+ * @typedef {object} Condition
143
+ * @property {string} [hide]
144
144
  */
145
145
 
146
146
  class ConditionChecker {
@@ -149,11 +149,11 @@ class ConditionChecker {
149
149
  this._eventBus = eventBus;
150
150
  }
151
151
 
152
- /**
153
- * For given data, remove properties based on condition.
154
- *
155
- * @param {Object<string, any>} properties
156
- * @param {Object<string, any>} data
152
+ /**
153
+ * For given data, remove properties based on condition.
154
+ *
155
+ * @param {Object<string, any>} properties
156
+ * @param {Object<string, any>} data
157
157
  */
158
158
  applyConditions(properties, data = {}) {
159
159
  const conditions = this._getConditions();
@@ -172,13 +172,13 @@ class ConditionChecker {
172
172
  return newProperties;
173
173
  }
174
174
 
175
- /**
176
- * Check if given condition is met. Returns null for invalid/missing conditions.
177
- *
178
- * @param {string} condition
179
- * @param {import('../../types').Data} [data]
180
- *
181
- * @returns {boolean|null}
175
+ /**
176
+ * Check if given condition is met. Returns null for invalid/missing conditions.
177
+ *
178
+ * @param {string} condition
179
+ * @param {import('../../types').Data} [data]
180
+ *
181
+ * @returns {boolean|null}
182
182
  */
183
183
  check(condition, data = {}) {
184
184
  if (!condition) {
@@ -199,12 +199,12 @@ class ConditionChecker {
199
199
  }
200
200
  }
201
201
 
202
- /**
203
- * Check if hide condition is met.
204
- *
205
- * @param {Condition} condition
206
- * @param {Object<string, any>} data
207
- * @returns {boolean}
202
+ /**
203
+ * Check if hide condition is met.
204
+ *
205
+ * @param {Condition} condition
206
+ * @param {Object<string, any>} data
207
+ * @returns {boolean}
208
208
  */
209
209
  _checkHideCondition(condition, data) {
210
210
  if (!condition.hide) {
@@ -246,12 +246,12 @@ class MarkdownRenderer {
246
246
  this._converter = new showdown.Converter();
247
247
  }
248
248
 
249
- /**
250
- * Render markdown to HTML.
251
- *
252
- * @param {string} markdown - The markdown to render
253
- *
254
- * @returns {string} HTML
249
+ /**
250
+ * Render markdown to HTML.
251
+ *
252
+ * @param {string} markdown - The markdown to render
253
+ *
254
+ * @returns {string} HTML
255
255
  */
256
256
  render(markdown) {
257
257
  return this._converter.makeHtml(markdown);
@@ -760,8 +760,13 @@ class Validator {
760
760
  if (validate.pattern && value && !new RegExp(validate.pattern).test(value)) {
761
761
  errors = [...errors, `Field must match pattern ${validate.pattern}.`];
762
762
  }
763
- if (validate.required && (isNil(value) || value === '')) {
764
- errors = [...errors, 'Field is required.'];
763
+ if (validate.required) {
764
+ const isUncheckedCheckbox = type === 'checkbox' && value === false;
765
+ const isUnsetValue = isNil(value) || value === '';
766
+ const isEmptyMultiselect = Array.isArray(value) && value.length === 0;
767
+ if (isUncheckedCheckbox || isUnsetValue || isEmptyMultiselect) {
768
+ errors = [...errors, 'Field is required.'];
769
+ }
765
770
  }
766
771
  if ('min' in validate && (value || value === 0) && value < validate.min) {
767
772
  errors = [...errors, `Field must have minimum value of ${validate.min}.`];
@@ -835,23 +840,23 @@ class FormFieldRegistry {
835
840
  }
836
841
  FormFieldRegistry.$inject = ['eventBus'];
837
842
 
838
- /**
839
- * @typedef { { id: String, components: Array<String> } } FormRow
840
- * @typedef { { formFieldId: String, rows: Array<FormRow> } } FormRows
843
+ /**
844
+ * @typedef { { id: String, components: Array<String> } } FormRow
845
+ * @typedef { { formFieldId: String, rows: Array<FormRow> } } FormRows
841
846
  */
842
847
 
843
- /**
844
- * Maintains the Form layout in a given structure, for example
845
- *
846
- * [
847
- * {
848
- * formFieldId: 'FormField_1',
849
- * rows: [
850
- * { id: 'Row_1', components: [ 'Text_1', 'Textdield_1', ... ] }
851
- * ]
852
- * }
853
- * ]
854
- *
848
+ /**
849
+ * Maintains the Form layout in a given structure, for example
850
+ *
851
+ * [
852
+ * {
853
+ * formFieldId: 'FormField_1',
854
+ * rows: [
855
+ * { id: 'Row_1', components: [ 'Text_1', 'Textdield_1', ... ] }
856
+ * ]
857
+ * }
858
+ * ]
859
+ *
855
860
  */
856
861
  class FormLayouter {
857
862
  constructor(eventBus) {
@@ -861,8 +866,8 @@ class FormLayouter {
861
866
  this._eventBus = eventBus;
862
867
  }
863
868
 
864
- /**
865
- * @param {FormRow} row
869
+ /**
870
+ * @param {FormRow} row
866
871
  */
867
872
  addRow(formFieldId, row) {
868
873
  let rowsPerComponent = this._rows.find(r => r.formFieldId === formFieldId);
@@ -876,18 +881,18 @@ class FormLayouter {
876
881
  rowsPerComponent.rows.push(row);
877
882
  }
878
883
 
879
- /**
880
- * @param {String} id
881
- * @returns {FormRow}
884
+ /**
885
+ * @param {String} id
886
+ * @returns {FormRow}
882
887
  */
883
888
  getRow(id) {
884
889
  const rows = allRows(this._rows);
885
890
  return rows.find(r => r.id === id);
886
891
  }
887
892
 
888
- /**
889
- * @param {any} formField
890
- * @returns {FormRow}
893
+ /**
894
+ * @param {any} formField
895
+ * @returns {FormRow}
891
896
  */
892
897
  getRowForField(formField) {
893
898
  return allRows(this._rows).find(r => {
@@ -898,9 +903,9 @@ class FormLayouter {
898
903
  });
899
904
  }
900
905
 
901
- /**
902
- * @param {String} formFieldId
903
- * @returns { Array<FormRow> }
906
+ /**
907
+ * @param {String} formFieldId
908
+ * @returns { Array<FormRow> }
904
909
  */
905
910
  getRows(formFieldId) {
906
911
  const rowsForField = this._rows.find(r => formFieldId === r.formFieldId);
@@ -910,15 +915,15 @@ class FormLayouter {
910
915
  return rowsForField.rows;
911
916
  }
912
917
 
913
- /**
914
- * @returns {string}
918
+ /**
919
+ * @returns {string}
915
920
  */
916
921
  nextRowId() {
917
922
  return this._ids.nextPrefixed('Row_');
918
923
  }
919
924
 
920
- /**
921
- * @param {any} formField
925
+ /**
926
+ * @param {any} formField
922
927
  */
923
928
  calculateLayout(formField) {
924
929
  const {
@@ -972,9 +977,9 @@ function groupByRow(components, ids) {
972
977
  });
973
978
  }
974
979
 
975
- /**
976
- * @param {Array<FormRows>} formRows
977
- * @returns {Array<FormRow>}
980
+ /**
981
+ * @param {Array<FormRows>} formRows
982
+ * @returns {Array<FormRow>}
978
983
  */
979
984
  function allRows(formRows) {
980
985
  return flatten(formRows.map(c => c.rows));
@@ -1051,10 +1056,10 @@ function createInjector(bootstrapModules) {
1051
1056
  return injector;
1052
1057
  }
1053
1058
 
1054
- /**
1055
- * @param {string?} prefix
1056
- *
1057
- * @returns Element
1059
+ /**
1060
+ * @param {string?} prefix
1061
+ *
1062
+ * @returns Element
1058
1063
  */
1059
1064
  function createFormContainer(prefix = 'fjs') {
1060
1065
  const container = document.createElement('div');
@@ -1099,22 +1104,22 @@ function generateIdForType(type) {
1099
1104
  return `${type}${generateIndexForType(type)}`;
1100
1105
  }
1101
1106
 
1102
- /**
1103
- * @template T
1104
- * @param {T} data
1105
- * @param {(this: any, key: string, value: any) => any} [replacer]
1106
- * @return {T}
1107
+ /**
1108
+ * @template T
1109
+ * @param {T} data
1110
+ * @param {(this: any, key: string, value: any) => any} [replacer]
1111
+ * @return {T}
1107
1112
  */
1108
1113
  function clone(data, replacer) {
1109
1114
  return JSON.parse(JSON.stringify(data, replacer));
1110
1115
  }
1111
1116
 
1112
- /**
1113
- * Parse the schema for input variables a form might make use of
1114
- *
1115
- * @param {any} schema
1116
- *
1117
- * @return {string[]}
1117
+ /**
1118
+ * Parse the schema for input variables a form might make use of
1119
+ *
1120
+ * @param {any} schema
1121
+ *
1122
+ * @return {string[]}
1118
1123
  */
1119
1124
  function getSchemaVariables(schema, expressionLanguage = new FeelExpressionLanguage(null)) {
1120
1125
  if (!schema.components) {
@@ -1159,11 +1164,11 @@ function getSchemaVariables(schema, expressionLanguage = new FeelExpressionLangu
1159
1164
  }
1160
1165
 
1161
1166
  class Importer {
1162
- /**
1163
- * @constructor
1164
- * @param { import('../core').FormFieldRegistry } formFieldRegistry
1165
- * @param { import('../render/FormFields').default } formFields
1166
- * @param { import('../core').FormLayouter } formLayouter
1167
+ /**
1168
+ * @constructor
1169
+ * @param { import('../core').FormFieldRegistry } formFieldRegistry
1170
+ * @param { import('../render/FormFields').default } formFields
1171
+ * @param { import('../core').FormLayouter } formLayouter
1167
1172
  */
1168
1173
  constructor(formFieldRegistry, formFields, formLayouter) {
1169
1174
  this._formFieldRegistry = formFieldRegistry;
@@ -1171,15 +1176,15 @@ class Importer {
1171
1176
  this._formLayouter = formLayouter;
1172
1177
  }
1173
1178
 
1174
- /**
1175
- * Import schema adding `id`, `_parent` and `_path`
1176
- * information to each field and adding it to the
1177
- * form field registry.
1178
- *
1179
- * @param {any} schema
1180
- * @param {any} [data]
1181
- *
1182
- * @return { { warnings: Array<any>, schema: any, data: any } }
1179
+ /**
1180
+ * Import schema adding `id`, `_parent` and `_path`
1181
+ * information to each field and adding it to the
1182
+ * form field registry.
1183
+ *
1184
+ * @param {any} schema
1185
+ * @param {any} [data]
1186
+ *
1187
+ * @return { { warnings: Array<any>, schema: any, data: any } }
1183
1188
  */
1184
1189
  importSchema(schema, data = {}) {
1185
1190
  // TODO: Add warnings - https://github.com/bpmn-io/form-js/issues/289
@@ -1200,11 +1205,11 @@ class Importer {
1200
1205
  }
1201
1206
  }
1202
1207
 
1203
- /**
1204
- * @param {any} formField
1205
- * @param {string} [parentId]
1206
- *
1207
- * @return {any} importedField
1208
+ /**
1209
+ * @param {any} formField
1210
+ * @param {string} [parentId]
1211
+ *
1212
+ * @return {any} importedField
1208
1213
  */
1209
1214
  importFormField(formField, parentId) {
1210
1215
  const {
@@ -1255,10 +1260,10 @@ class Importer {
1255
1260
  });
1256
1261
  }
1257
1262
 
1258
- /**
1259
- * @param {Object} data
1260
- *
1261
- * @return {Object} initializedData
1263
+ /**
1264
+ * @param {Object} data
1265
+ *
1266
+ * @return {Object} initializedData
1262
1267
  */
1263
1268
  initializeFieldValues(data) {
1264
1269
  return this._formFieldRegistry.getAll().reduce((initializedData, formField) => {
@@ -1390,11 +1395,11 @@ const FormRenderContext = createContext({
1390
1395
  });
1391
1396
  var FormRenderContext$1 = FormRenderContext;
1392
1397
 
1393
- /**
1394
- * @param {string} type
1395
- * @param {boolean} [strict]
1396
- *
1397
- * @returns {any}
1398
+ /**
1399
+ * @param {string} type
1400
+ * @param {boolean} [strict]
1401
+ *
1402
+ * @returns {any}
1398
1403
  */
1399
1404
  function getService(type, strict) {}
1400
1405
  const FormContext = createContext({
@@ -1465,8 +1470,12 @@ function Checkbox(props) {
1465
1470
  const {
1466
1471
  description,
1467
1472
  id,
1468
- label
1473
+ label,
1474
+ validate = {}
1469
1475
  } = field;
1476
+ const {
1477
+ required
1478
+ } = validate;
1470
1479
  const onChange = ({
1471
1480
  target
1472
1481
  }) => {
@@ -1488,7 +1497,7 @@ function Checkbox(props) {
1488
1497
  children: [jsx(Label, {
1489
1498
  id: prefixId(id, formId),
1490
1499
  label: label,
1491
- required: false,
1500
+ required: required,
1492
1501
  children: jsx("input", {
1493
1502
  checked: value,
1494
1503
  class: "fjs-input",
@@ -1569,8 +1578,8 @@ function useService(type, strict) {
1569
1578
  return getService(type, strict);
1570
1579
  }
1571
1580
 
1572
- /**
1573
- * @enum { String }
1581
+ /**
1582
+ * @enum { String }
1574
1583
  */
1575
1584
  const LOAD_STATES = {
1576
1585
  LOADING: 'loading',
@@ -1578,17 +1587,17 @@ const LOAD_STATES = {
1578
1587
  ERROR: 'error'
1579
1588
  };
1580
1589
 
1581
- /**
1582
- * @typedef {Object} ValuesGetter
1583
- * @property {Object[]} values - The values data
1584
- * @property {(LOAD_STATES)} state - The values data's loading state, to use for conditional rendering
1590
+ /**
1591
+ * @typedef {Object} ValuesGetter
1592
+ * @property {Object[]} values - The values data
1593
+ * @property {(LOAD_STATES)} state - The values data's loading state, to use for conditional rendering
1585
1594
  */
1586
1595
 
1587
- /**
1588
- * A hook to load values for single and multiselect components.
1589
- *
1590
- * @param {Object} field - The form field to handle values for
1591
- * @return {ValuesGetter} valuesGetter - A values getter object providing loading state and values
1596
+ /**
1597
+ * A hook to load values for single and multiselect components.
1598
+ *
1599
+ * @param {Object} field - The form field to handle values for
1600
+ * @return {ValuesGetter} valuesGetter - A values getter object providing loading state and values
1592
1601
  */
1593
1602
  function useValuesAsync (field) {
1594
1603
  const {
@@ -1855,8 +1864,12 @@ function Checklist(props) {
1855
1864
  const {
1856
1865
  description,
1857
1866
  id,
1858
- label
1867
+ label,
1868
+ validate = {}
1859
1869
  } = field;
1870
+ const {
1871
+ required
1872
+ } = validate;
1860
1873
  const toggleCheckbox = v => {
1861
1874
  let newValue = [...value];
1862
1875
  if (!newValue.includes(v)) {
@@ -1882,7 +1895,8 @@ function Checklist(props) {
1882
1895
  disabled
1883
1896
  })),
1884
1897
  children: [jsx(Label, {
1885
- label: label
1898
+ label: label,
1899
+ required: required
1886
1900
  }), loadState == LOAD_STATES.LOADED && options.map((v, index) => {
1887
1901
  return jsx(Label, {
1888
1902
  id: prefixId(`${id}-${index}`, formId),
@@ -1929,10 +1943,10 @@ Checklist.emptyValue = [];
1929
1943
  Checklist.sanitizeValue = sanitizeMultiSelectValue;
1930
1944
  Checklist.group = 'selection';
1931
1945
 
1932
- /**
1933
- * Returns the conditionally filtered data of a form reactively.
1934
- * Memoised to minimize re-renders
1935
- *
1946
+ /**
1947
+ * Returns the conditionally filtered data of a form reactively.
1948
+ * Memoised to minimize re-renders
1949
+ *
1936
1950
  */
1937
1951
  function useFilteredFormData() {
1938
1952
  const {
@@ -1949,12 +1963,12 @@ function useFilteredFormData() {
1949
1963
  }, [conditionChecker, data, initialData]);
1950
1964
  }
1951
1965
 
1952
- /**
1953
- * Evaluate if condition is met reactively based on the conditionChecker and form data.
1954
- *
1955
- * @param {string | undefined} condition
1956
- *
1957
- * @returns {boolean} true if condition is met or no condition or condition checker exists
1966
+ /**
1967
+ * Evaluate if condition is met reactively based on the conditionChecker and form data.
1968
+ *
1969
+ * @param {string | undefined} condition
1970
+ *
1971
+ * @returns {boolean} true if condition is met or no condition or condition checker exists
1958
1972
  */
1959
1973
  function useCondition(condition) {
1960
1974
  const conditionChecker = useService('conditionChecker', false);
@@ -1964,13 +1978,13 @@ function useCondition(condition) {
1964
1978
  }, [conditionChecker, condition, filteredData]);
1965
1979
  }
1966
1980
 
1967
- /**
1968
- * Evaluate a string reactively based on the expressionLanguage and form data.
1969
- * If the string is not an expression, it is returned as is.
1970
- * Memoised to minimize re-renders.
1971
- *
1972
- * @param {string} value
1973
- *
1981
+ /**
1982
+ * Evaluate a string reactively based on the expressionLanguage and form data.
1983
+ * If the string is not an expression, it is returned as is.
1984
+ * Memoised to minimize re-renders.
1985
+ *
1986
+ * @param {string} value
1987
+ *
1974
1988
  */
1975
1989
  function useExpressionEvaluation(value) {
1976
1990
  const formData = useFilteredFormData();
@@ -1999,16 +2013,16 @@ function useKeyDownAction(targetKey, action, listenerElement = window) {
1999
2013
  });
2000
2014
  }
2001
2015
 
2002
- /**
2003
- * Template a string reactively based on form data. If the string is not a template, it is returned as is.
2004
- * Memoised to minimize re-renders
2005
- *
2006
- * @param {string} value
2007
- * @param {Object} options
2008
- * @param {boolean} [options.debug = false]
2009
- * @param {boolean} [options.strict = false]
2010
- * @param {Function} [options.buildDebugString]
2011
- *
2016
+ /**
2017
+ * Template a string reactively based on form data. If the string is not a template, it is returned as is.
2018
+ * Memoised to minimize re-renders
2019
+ *
2020
+ * @param {string} value
2021
+ * @param {Object} options
2022
+ * @param {boolean} [options.debug = false]
2023
+ * @param {boolean} [options.strict = false]
2024
+ * @param {Function} [options.buildDebugString]
2025
+ *
2012
2026
  */
2013
2027
  function useTemplateEvaluation(value, options) {
2014
2028
  const filteredData = useFilteredFormData();
@@ -2759,10 +2773,10 @@ Datetime.sanitizeValue = sanitizeDateTimePickerValue;
2759
2773
  Datetime.label = 'Date time';
2760
2774
  Datetime.group = 'basic-input';
2761
2775
 
2762
- /**
2763
- * This file must not be changed or exchanged.
2764
- *
2765
- * @see http://bpmn.io/license for more information.
2776
+ /**
2777
+ * This file must not be changed or exchanged.
2778
+ *
2779
+ * @see http://bpmn.io/license for more information.
2766
2780
  */
2767
2781
  function Logo() {
2768
2782
  return jsxs("svg", {
@@ -2889,11 +2903,11 @@ const ATTR_WHITESPACE_PATTERN = /[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u
2889
2903
 
2890
2904
  const FORM_ELEMENT = document.createElement('form');
2891
2905
 
2892
- /**
2893
- * Sanitize a HTML string and return the cleaned, safe version.
2894
- *
2895
- * @param {string} html
2896
- * @return {string}
2906
+ /**
2907
+ * Sanitize a HTML string and return the cleaned, safe version.
2908
+ *
2909
+ * @param {string} html
2910
+ * @return {string}
2897
2911
  */
2898
2912
 
2899
2913
  // see https://github.com/developit/snarkdown/issues/70
@@ -2911,29 +2925,29 @@ function sanitizeHTML(html) {
2911
2925
  }
2912
2926
  }
2913
2927
 
2914
- /**
2915
- * Sanitizes an image source to ensure we only allow for data URI and links
2916
- * that start with http(s).
2917
- *
2918
- * Note: Most browsers anyway do not support script execution in <img> elements.
2919
- *
2920
- * @param {string} src
2921
- * @returns {string}
2928
+ /**
2929
+ * Sanitizes an image source to ensure we only allow for data URI and links
2930
+ * that start with http(s).
2931
+ *
2932
+ * Note: Most browsers anyway do not support script execution in <img> elements.
2933
+ *
2934
+ * @param {string} src
2935
+ * @returns {string}
2922
2936
  */
2923
2937
  function sanitizeImageSource(src) {
2924
2938
  const valid = ALLOWED_IMAGE_SRC_PATTERN.test(src);
2925
2939
  return valid ? src : '';
2926
2940
  }
2927
2941
 
2928
- /**
2929
- * Recursively sanitize a HTML node, potentially
2930
- * removing it, its children or attributes.
2931
- *
2932
- * Inspired by https://github.com/developit/snarkdown/issues/70
2933
- * and https://github.com/cure53/DOMPurify. Simplified
2934
- * for our use-case.
2935
- *
2936
- * @param {Element} node
2942
+ /**
2943
+ * Recursively sanitize a HTML node, potentially
2944
+ * removing it, its children or attributes.
2945
+ *
2946
+ * Inspired by https://github.com/developit/snarkdown/issues/70
2947
+ * and https://github.com/cure53/DOMPurify. Simplified
2948
+ * for our use-case.
2949
+ *
2950
+ * @param {Element} node
2937
2951
  */
2938
2952
  function sanitizeNode(node) {
2939
2953
  // allow text nodes
@@ -2977,13 +2991,13 @@ function sanitizeNode(node) {
2977
2991
  }
2978
2992
  }
2979
2993
 
2980
- /**
2981
- * Validates attributes for validity.
2982
- *
2983
- * @param {string} lcTag
2984
- * @param {string} lcName
2985
- * @param {string} value
2986
- * @return {boolean}
2994
+ /**
2995
+ * Validates attributes for validity.
2996
+ *
2997
+ * @param {string} lcTag
2998
+ * @param {string} lcName
2999
+ * @param {string} value
3000
+ * @return {boolean}
2987
3001
  */
2988
3002
  function isValidAttribute(lcTag, lcName, value) {
2989
3003
  // disallow most attributes based on whitelist
@@ -3752,8 +3766,12 @@ function Taglist(props) {
3752
3766
  const {
3753
3767
  description,
3754
3768
  id,
3755
- label
3769
+ label,
3770
+ validate = {}
3756
3771
  } = field;
3772
+ const {
3773
+ required
3774
+ } = validate;
3757
3775
  const {
3758
3776
  formId
3759
3777
  } = useContext(FormContext$1);
@@ -3852,6 +3870,7 @@ function Taglist(props) {
3852
3870
  }),
3853
3871
  children: [jsx(Label, {
3854
3872
  label: label,
3873
+ required: required,
3855
3874
  id: prefixId(`${id}-search`, formId)
3856
3875
  }), jsxs("div", {
3857
3876
  class: classNames('fjs-taglist', {