@bpmn-io/form-js-viewer 0.7.0 → 0.8.0-alpha.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.cjs CHANGED
@@ -8,119 +8,19 @@ var snarkdown = require('@bpmn-io/snarkdown');
8
8
  var jsxRuntime = require('preact/jsx-runtime');
9
9
  var hooks = require('preact/hooks');
10
10
  var preact = require('preact');
11
+ var React = require('preact/compat');
12
+ var classNames = require('classnames');
11
13
  var Markup = require('preact-markup');
12
- var compat = require('preact/compat');
13
14
  var didi = require('didi');
14
15
 
15
16
  function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
16
17
 
17
18
  var Ids__default = /*#__PURE__*/_interopDefaultLegacy(Ids);
18
19
  var snarkdown__default = /*#__PURE__*/_interopDefaultLegacy(snarkdown);
20
+ var React__default = /*#__PURE__*/_interopDefaultLegacy(React);
21
+ var classNames__default = /*#__PURE__*/_interopDefaultLegacy(classNames);
19
22
  var Markup__default = /*#__PURE__*/_interopDefaultLegacy(Markup);
20
23
 
21
- function createInjector(bootstrapModules) {
22
- const modules = [],
23
- components = [];
24
-
25
- function hasModule(module) {
26
- return modules.includes(module);
27
- }
28
-
29
- function addModule(module) {
30
- modules.push(module);
31
- }
32
-
33
- function visit(module) {
34
- if (hasModule(module)) {
35
- return;
36
- }
37
-
38
- (module.__depends__ || []).forEach(visit);
39
-
40
- if (hasModule(module)) {
41
- return;
42
- }
43
-
44
- addModule(module);
45
- (module.__init__ || []).forEach(function (component) {
46
- components.push(component);
47
- });
48
- }
49
-
50
- bootstrapModules.forEach(visit);
51
- const injector = new didi.Injector(modules);
52
- components.forEach(function (component) {
53
- try {
54
- injector[typeof component === 'string' ? 'get' : 'invoke'](component);
55
- } catch (err) {
56
- console.error('Failed to instantiate component');
57
- console.error(err.stack);
58
- throw err;
59
- }
60
- });
61
- return injector;
62
- }
63
-
64
- /**
65
- * @param {string?} prefix
66
- *
67
- * @returns Element
68
- */
69
- function createFormContainer(prefix = 'fjs') {
70
- const container = document.createElement('div');
71
- container.classList.add(`${prefix}-container`);
72
- return container;
73
- }
74
-
75
- function findErrors(errors, path) {
76
- return errors[pathStringify(path)];
77
- }
78
- function isRequired(field) {
79
- return field.required;
80
- }
81
- function pathParse(path) {
82
- if (!path) {
83
- return [];
84
- }
85
-
86
- return path.split('.').map(key => {
87
- return isNaN(parseInt(key)) ? key : parseInt(key);
88
- });
89
- }
90
- function pathsEqual(a, b) {
91
- return a && b && a.length === b.length && a.every((value, index) => value === b[index]);
92
- }
93
- function pathStringify(path) {
94
- if (!path) {
95
- return '';
96
- }
97
-
98
- return path.join('.');
99
- }
100
- const indices = {};
101
- function generateIndexForType(type) {
102
- if (type in indices) {
103
- indices[type]++;
104
- } else {
105
- indices[type] = 1;
106
- }
107
-
108
- return indices[type];
109
- }
110
- function generateIdForType(type) {
111
- return `${type}${generateIndexForType(type)}`;
112
- }
113
- /**
114
- * @template T
115
- * @param {T} data
116
- * @param {(this: any, key: string, value: any) => any} [replacer]
117
- * @return {T}
118
- */
119
-
120
- function clone(data, replacer) {
121
- return JSON.parse(JSON.stringify(data, replacer));
122
- }
123
-
124
24
  var FN_REF = '__fn';
125
25
  var DEFAULT_PRIORITY = 1000;
126
26
  var slice = Array.prototype.slice;
@@ -634,6 +534,7 @@ class Validator {
634
534
  }
635
535
 
636
536
  }
537
+ Validator.$inject = [];
637
538
 
638
539
  class FormFieldRegistry {
639
540
  constructor(eventBus) {
@@ -699,6 +600,109 @@ class FormFieldRegistry {
699
600
  }
700
601
  FormFieldRegistry.$inject = ['eventBus'];
701
602
 
603
+ function createInjector(bootstrapModules) {
604
+ const modules = [],
605
+ components = [];
606
+
607
+ function hasModule(module) {
608
+ return modules.includes(module);
609
+ }
610
+
611
+ function addModule(module) {
612
+ modules.push(module);
613
+ }
614
+
615
+ function visit(module) {
616
+ if (hasModule(module)) {
617
+ return;
618
+ }
619
+
620
+ (module.__depends__ || []).forEach(visit);
621
+
622
+ if (hasModule(module)) {
623
+ return;
624
+ }
625
+
626
+ addModule(module);
627
+ (module.__init__ || []).forEach(function (component) {
628
+ components.push(component);
629
+ });
630
+ }
631
+
632
+ bootstrapModules.forEach(visit);
633
+ const injector = new didi.Injector(modules);
634
+ components.forEach(function (component) {
635
+ try {
636
+ injector[typeof component === 'string' ? 'get' : 'invoke'](component);
637
+ } catch (err) {
638
+ console.error('Failed to instantiate component');
639
+ console.error(err.stack);
640
+ throw err;
641
+ }
642
+ });
643
+ return injector;
644
+ }
645
+
646
+ /**
647
+ * @param {string?} prefix
648
+ *
649
+ * @returns Element
650
+ */
651
+ function createFormContainer(prefix = 'fjs') {
652
+ const container = document.createElement('div');
653
+ container.classList.add(`${prefix}-container`);
654
+ return container;
655
+ }
656
+
657
+ function findErrors(errors, path) {
658
+ return errors[pathStringify(path)];
659
+ }
660
+ function isRequired(field) {
661
+ return field.required;
662
+ }
663
+ function pathParse(path) {
664
+ if (!path) {
665
+ return [];
666
+ }
667
+
668
+ return path.split('.').map(key => {
669
+ return isNaN(parseInt(key)) ? key : parseInt(key);
670
+ });
671
+ }
672
+ function pathsEqual(a, b) {
673
+ return a && b && a.length === b.length && a.every((value, index) => value === b[index]);
674
+ }
675
+ function pathStringify(path) {
676
+ if (!path) {
677
+ return '';
678
+ }
679
+
680
+ return path.join('.');
681
+ }
682
+ const indices = {};
683
+ function generateIndexForType(type) {
684
+ if (type in indices) {
685
+ indices[type]++;
686
+ } else {
687
+ indices[type] = 1;
688
+ }
689
+
690
+ return indices[type];
691
+ }
692
+ function generateIdForType(type) {
693
+ return `${type}${generateIndexForType(type)}`;
694
+ }
695
+ /**
696
+ * @template T
697
+ * @param {T} data
698
+ * @param {(this: any, key: string, value: any) => any} [replacer]
699
+ * @return {T}
700
+ */
701
+
702
+ function clone(data, replacer) {
703
+ return JSON.parse(JSON.stringify(data, replacer));
704
+ }
705
+
702
706
  class Importer {
703
707
  /**
704
708
  * @constructor
@@ -816,19 +820,26 @@ class Importer {
816
820
  const {
817
821
  defaultValue,
818
822
  _path,
819
- type
820
- } = formField;
821
-
822
- if (!_path) {
823
- return importedData;
824
- } // (1) try to get value from data
825
- // (2) try to get default value from form field
826
- // (3) get empty value from form field
827
-
823
+ type,
824
+ valuesKey
825
+ } = formField; // get values defined via valuesKey
826
+
827
+ if (valuesKey) {
828
+ importedData = { ...importedData,
829
+ [valuesKey]: minDash.get(data, [valuesKey])
830
+ };
831
+ } // try to get value from data
832
+ // if unavailable - try to get default value from form field
833
+ // if unavailable - get empty value from form field
834
+
835
+
836
+ if (_path) {
837
+ importedData = { ...importedData,
838
+ [_path[0]]: minDash.get(data, _path, minDash.isUndefined(defaultValue) ? this._formFields.get(type).emptyValue : defaultValue)
839
+ };
840
+ }
828
841
 
829
- return { ...importedData,
830
- [_path[0]]: minDash.get(data, _path, minDash.isUndefined(defaultValue) ? this._formFields.get(type).emptyValue : defaultValue)
831
- };
842
+ return importedData;
832
843
  }, {});
833
844
  }
834
845
 
@@ -989,7 +1000,7 @@ function safeMarkdown(markdown) {
989
1000
  return sanitizeHTML(html);
990
1001
  }
991
1002
 
992
- const type$6 = 'button';
1003
+ const type$8 = 'button';
993
1004
  function Button(props) {
994
1005
  const {
995
1006
  disabled,
@@ -999,7 +1010,7 @@ function Button(props) {
999
1010
  action = 'submit'
1000
1011
  } = field;
1001
1012
  return jsxRuntime.jsx("div", {
1002
- class: formFieldClasses(type$6),
1013
+ class: formFieldClasses(type$8),
1003
1014
  children: jsxRuntime.jsx("button", {
1004
1015
  class: "fjs-button",
1005
1016
  type: action,
@@ -1016,7 +1027,7 @@ Button.create = function (options = {}) {
1016
1027
  };
1017
1028
  };
1018
1029
 
1019
- Button.type = type$6;
1030
+ Button.type = type$8;
1020
1031
  Button.label = 'Button';
1021
1032
  Button.keyed = true;
1022
1033
 
@@ -1098,7 +1109,7 @@ function Label(props) {
1098
1109
  });
1099
1110
  }
1100
1111
 
1101
- const type$5 = 'checkbox';
1112
+ const type$7 = 'checkbox';
1102
1113
  function Checkbox(props) {
1103
1114
  const {
1104
1115
  disabled,
@@ -1125,7 +1136,7 @@ function Checkbox(props) {
1125
1136
  formId
1126
1137
  } = hooks.useContext(FormContext);
1127
1138
  return jsxRuntime.jsxs("div", {
1128
- class: formFieldClasses(type$5, errors),
1139
+ class: formFieldClasses(type$7, errors),
1129
1140
  children: [jsxRuntime.jsx(Label, {
1130
1141
  id: prefixId(id, formId),
1131
1142
  label: label,
@@ -1151,7 +1162,7 @@ Checkbox.create = function (options = {}) {
1151
1162
  };
1152
1163
  };
1153
1164
 
1154
- Checkbox.type = type$5;
1165
+ Checkbox.type = type$7;
1155
1166
  Checkbox.label = 'Checkbox';
1156
1167
  Checkbox.keyed = true;
1157
1168
  Checkbox.emptyValue = false;
@@ -1163,6 +1174,152 @@ function useService (type, strict) {
1163
1174
  return getService(type, strict);
1164
1175
  }
1165
1176
 
1177
+ /**
1178
+ * @enum { String }
1179
+ */
1180
+
1181
+ const LOAD_STATES = {
1182
+ LOADING: 'loading',
1183
+ LOADED: 'loaded',
1184
+ ERROR: 'error'
1185
+ };
1186
+ /**
1187
+ * @typedef {Object} ValuesGetter
1188
+ * @property {Object[]} values - The values data
1189
+ * @property {(LOAD_STATES)} state - The values data's loading state, to use for conditional rendering
1190
+ */
1191
+
1192
+ /**
1193
+ * A hook to load values for single and multiselect components.
1194
+ *
1195
+ * @param {Object} field - The form field to handle values for
1196
+ * @return {ValuesGetter} valuesGetter - A values getter object providing loading state and values
1197
+ */
1198
+
1199
+ function useValuesAsync (field) {
1200
+ const {
1201
+ valuesKey,
1202
+ values: staticValues
1203
+ } = field;
1204
+ const [valuesGetter, setValuesGetter] = hooks.useState({
1205
+ values: [],
1206
+ error: undefined,
1207
+ state: LOAD_STATES.LOADING
1208
+ });
1209
+
1210
+ const initialData = useService('form')._getState().initialData;
1211
+
1212
+ hooks.useEffect(() => {
1213
+ let values = [];
1214
+
1215
+ if (valuesKey !== undefined) {
1216
+ const keyedValues = (initialData || {})[valuesKey];
1217
+
1218
+ if (keyedValues && Array.isArray(keyedValues)) {
1219
+ values = keyedValues;
1220
+ }
1221
+ } else if (staticValues !== undefined) {
1222
+ values = Array.isArray(staticValues) ? staticValues : [];
1223
+ } else {
1224
+ setValuesGetter(getErrorState('No values source defined in the form definition'));
1225
+ return;
1226
+ }
1227
+
1228
+ setValuesGetter(buildLoadedState(values));
1229
+ }, [valuesKey, staticValues, initialData]);
1230
+ return valuesGetter;
1231
+ }
1232
+
1233
+ const getErrorState = error => ({
1234
+ values: [],
1235
+ error,
1236
+ state: LOAD_STATES.ERROR
1237
+ });
1238
+
1239
+ const buildLoadedState = values => ({
1240
+ values,
1241
+ error: undefined,
1242
+ state: LOAD_STATES.LOADED
1243
+ });
1244
+
1245
+ const type$6 = 'checklist';
1246
+ function Checklist(props) {
1247
+ const {
1248
+ disabled,
1249
+ errors = [],
1250
+ field,
1251
+ value = []
1252
+ } = props;
1253
+ const {
1254
+ description,
1255
+ id,
1256
+ label
1257
+ } = field;
1258
+
1259
+ const toggleCheckbox = v => {
1260
+ let newValue = [...value];
1261
+
1262
+ if (!newValue.includes(v)) {
1263
+ newValue.push(v);
1264
+ } else {
1265
+ newValue = newValue.filter(x => x != v);
1266
+ }
1267
+
1268
+ props.onChange({
1269
+ field,
1270
+ value: newValue
1271
+ });
1272
+ };
1273
+
1274
+ const {
1275
+ state: loadState,
1276
+ values: options
1277
+ } = useValuesAsync(field);
1278
+ const {
1279
+ formId
1280
+ } = hooks.useContext(FormContext);
1281
+ return jsxRuntime.jsxs("div", {
1282
+ class: formFieldClasses(type$6, errors),
1283
+ children: [jsxRuntime.jsx(Label, {
1284
+ label: label
1285
+ }), loadState == LOAD_STATES.LOADED && options.map((v, index) => {
1286
+ return jsxRuntime.jsx(Label, {
1287
+ id: prefixId(`${id}-${index}`, formId),
1288
+ label: v.label,
1289
+ required: false,
1290
+ children: jsxRuntime.jsx("input", {
1291
+ checked: value.includes(v.value),
1292
+ class: "fjs-input",
1293
+ disabled: disabled,
1294
+ id: prefixId(`${id}-${index}`, formId),
1295
+ type: "checkbox",
1296
+ onClick: () => toggleCheckbox(v.value)
1297
+ })
1298
+ }, `${id}-${index}`);
1299
+ }), jsxRuntime.jsx(Description, {
1300
+ description: description
1301
+ }), jsxRuntime.jsx(Errors, {
1302
+ errors: errors
1303
+ })]
1304
+ });
1305
+ }
1306
+
1307
+ Checklist.create = function (options = {}) {
1308
+ if (options.valuesKey) return options;
1309
+ return {
1310
+ values: [{
1311
+ label: 'Value',
1312
+ value: 'value'
1313
+ }],
1314
+ ...options
1315
+ };
1316
+ };
1317
+
1318
+ Checklist.type = type$6;
1319
+ Checklist.label = 'Checklist';
1320
+ Checklist.keyed = true;
1321
+ Checklist.emptyValue = [];
1322
+
1166
1323
  const noop$1 = () => false;
1167
1324
 
1168
1325
  function FormField(props) {
@@ -1326,7 +1483,7 @@ function PoweredBy(props) {
1326
1483
  }
1327
1484
 
1328
1485
  return jsxRuntime.jsxs(preact.Fragment, {
1329
- children: [compat.createPortal(jsxRuntime.jsx(Lightbox, {
1486
+ children: [React.createPortal(jsxRuntime.jsx(Lightbox, {
1330
1487
  open: open,
1331
1488
  onBackdropClick: toggleOpen(false)
1332
1489
  }), document.body), jsxRuntime.jsx(Link, {
@@ -1371,7 +1528,7 @@ function FormComponent(props) {
1371
1528
  });
1372
1529
  }
1373
1530
 
1374
- const type$4 = 'number';
1531
+ const type$5 = 'number';
1375
1532
  function Number(props) {
1376
1533
  const {
1377
1534
  disabled,
@@ -1403,7 +1560,7 @@ function Number(props) {
1403
1560
  formId
1404
1561
  } = hooks.useContext(FormContext);
1405
1562
  return jsxRuntime.jsxs("div", {
1406
- class: formFieldClasses(type$4, errors),
1563
+ class: formFieldClasses(type$5, errors),
1407
1564
  children: [jsxRuntime.jsx(Label, {
1408
1565
  id: prefixId(id, formId),
1409
1566
  label: label,
@@ -1428,12 +1585,12 @@ Number.create = function (options = {}) {
1428
1585
  };
1429
1586
  };
1430
1587
 
1431
- Number.type = type$4;
1588
+ Number.type = type$5;
1432
1589
  Number.keyed = true;
1433
1590
  Number.label = 'Number';
1434
1591
  Number.emptyValue = null;
1435
1592
 
1436
- const type$3 = 'radio';
1593
+ const type$4 = 'radio';
1437
1594
  function Radio(props) {
1438
1595
  const {
1439
1596
  disabled,
@@ -1445,8 +1602,7 @@ function Radio(props) {
1445
1602
  description,
1446
1603
  id,
1447
1604
  label,
1448
- validate = {},
1449
- values
1605
+ validate = {}
1450
1606
  } = field;
1451
1607
  const {
1452
1608
  required
@@ -1459,26 +1615,30 @@ function Radio(props) {
1459
1615
  });
1460
1616
  };
1461
1617
 
1618
+ const {
1619
+ state: loadState,
1620
+ values: options
1621
+ } = useValuesAsync(field);
1462
1622
  const {
1463
1623
  formId
1464
1624
  } = hooks.useContext(FormContext);
1465
1625
  return jsxRuntime.jsxs("div", {
1466
- class: formFieldClasses(type$3, errors),
1626
+ class: formFieldClasses(type$4, errors),
1467
1627
  children: [jsxRuntime.jsx(Label, {
1468
1628
  label: label,
1469
1629
  required: required
1470
- }), values.map((v, index) => {
1630
+ }), loadState == LOAD_STATES.LOADED && options.map((option, index) => {
1471
1631
  return jsxRuntime.jsx(Label, {
1472
1632
  id: prefixId(`${id}-${index}`, formId),
1473
- label: v.label,
1633
+ label: option.label,
1474
1634
  required: false,
1475
1635
  children: jsxRuntime.jsx("input", {
1476
- checked: v.value === value,
1636
+ checked: option.value === value,
1477
1637
  class: "fjs-input",
1478
1638
  disabled: disabled,
1479
1639
  id: prefixId(`${id}-${index}`, formId),
1480
1640
  type: "radio",
1481
- onClick: () => onChange(v.value)
1641
+ onClick: () => onChange(option.value)
1482
1642
  })
1483
1643
  }, `${id}-${index}`);
1484
1644
  }), jsxRuntime.jsx(Description, {
@@ -1490,6 +1650,7 @@ function Radio(props) {
1490
1650
  }
1491
1651
 
1492
1652
  Radio.create = function (options = {}) {
1653
+ if (options.valuesKey) return options;
1493
1654
  return {
1494
1655
  values: [{
1495
1656
  label: 'Value',
@@ -1499,12 +1660,12 @@ Radio.create = function (options = {}) {
1499
1660
  };
1500
1661
  };
1501
1662
 
1502
- Radio.type = type$3;
1663
+ Radio.type = type$4;
1503
1664
  Radio.label = 'Radio';
1504
1665
  Radio.keyed = true;
1505
1666
  Radio.emptyValue = null;
1506
1667
 
1507
- const type$2 = 'select';
1668
+ const type$3 = 'select';
1508
1669
  function Select(props) {
1509
1670
  const {
1510
1671
  disabled,
@@ -1516,8 +1677,7 @@ function Select(props) {
1516
1677
  description,
1517
1678
  id,
1518
1679
  label,
1519
- validate = {},
1520
- values
1680
+ validate = {}
1521
1681
  } = field;
1522
1682
  const {
1523
1683
  required
@@ -1532,11 +1692,15 @@ function Select(props) {
1532
1692
  });
1533
1693
  };
1534
1694
 
1695
+ const {
1696
+ state: loadState,
1697
+ values: options
1698
+ } = useValuesAsync(field);
1535
1699
  const {
1536
1700
  formId
1537
1701
  } = hooks.useContext(FormContext);
1538
1702
  return jsxRuntime.jsxs("div", {
1539
- class: formFieldClasses(type$2, errors),
1703
+ class: formFieldClasses(type$3, errors),
1540
1704
  children: [jsxRuntime.jsx(Label, {
1541
1705
  id: prefixId(id, formId),
1542
1706
  label: label,
@@ -1549,10 +1713,10 @@ function Select(props) {
1549
1713
  value: value || '',
1550
1714
  children: [jsxRuntime.jsx("option", {
1551
1715
  value: ""
1552
- }), values.map((v, index) => {
1716
+ }), loadState == LOAD_STATES.LOADED && options.map((option, index) => {
1553
1717
  return jsxRuntime.jsx("option", {
1554
- value: v.value,
1555
- children: v.label
1718
+ value: option.value,
1719
+ children: option.label
1556
1720
  }, `${id}-${index}`);
1557
1721
  })]
1558
1722
  }), jsxRuntime.jsx(Description, {
@@ -1564,6 +1728,7 @@ function Select(props) {
1564
1728
  }
1565
1729
 
1566
1730
  Select.create = function (options = {}) {
1731
+ if (options.valuesKey) return options;
1567
1732
  return {
1568
1733
  values: [{
1569
1734
  label: 'Value',
@@ -1573,11 +1738,313 @@ Select.create = function (options = {}) {
1573
1738
  };
1574
1739
  };
1575
1740
 
1576
- Select.type = type$2;
1741
+ Select.type = type$3;
1577
1742
  Select.label = 'Select';
1578
1743
  Select.keyed = true;
1579
1744
  Select.emptyValue = null;
1580
1745
 
1746
+ function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
1747
+ var CloseIcon = (({
1748
+ styles = {},
1749
+ ...props
1750
+ }) => /*#__PURE__*/React__default['default'].createElement("svg", _extends({
1751
+ width: "16",
1752
+ height: "16",
1753
+ fill: "none",
1754
+ xmlns: "http://www.w3.org/2000/svg"
1755
+ }, props), /*#__PURE__*/React__default['default'].createElement("path", {
1756
+ fillRule: "evenodd",
1757
+ clipRule: "evenodd",
1758
+ d: "M12 4.7l-.7-.7L8 7.3 4.7 4l-.7.7L7.3 8 4 11.3l.7.7L8 8.7l3.3 3.3.7-.7L8.7 8 12 4.7z",
1759
+ fill: "#000"
1760
+ })));
1761
+
1762
+ function useKeyDownAction(targetKey, action, listenerElement = window) {
1763
+ function downHandler({
1764
+ key
1765
+ }) {
1766
+ if (key === targetKey) {
1767
+ action();
1768
+ }
1769
+ }
1770
+
1771
+ hooks.useEffect(() => {
1772
+ listenerElement.addEventListener('keydown', downHandler);
1773
+ return () => {
1774
+ listenerElement.removeEventListener('keydown', downHandler);
1775
+ };
1776
+ });
1777
+ }
1778
+
1779
+ const DEFAULT_LABEL_GETTER = value => value;
1780
+
1781
+ const NOOP = () => {};
1782
+
1783
+ function DropdownList(props) {
1784
+ const {
1785
+ keyEventsListener = window,
1786
+ values = [],
1787
+ getLabel = DEFAULT_LABEL_GETTER,
1788
+ onValueSelected = NOOP,
1789
+ height = 235,
1790
+ emptyListMessage = 'No results'
1791
+ } = props;
1792
+ const [mouseControl, setMouseControl] = hooks.useState(true);
1793
+ const [focusedValueIndex, setFocusedValueIndex] = hooks.useState(0);
1794
+ const dropdownContainer = hooks.useRef();
1795
+ const mouseScreenPos = hooks.useRef();
1796
+ const focusedItem = hooks.useMemo(() => values.length ? values[focusedValueIndex] : null, [focusedValueIndex, values]);
1797
+ const changeFocusedValueIndex = hooks.useCallback(delta => {
1798
+ setFocusedValueIndex(x => Math.min(Math.max(0, x + delta), values.length - 1));
1799
+ }, [values.length]);
1800
+ hooks.useEffect(() => {
1801
+ if (focusedValueIndex === 0) return;
1802
+
1803
+ if (!focusedValueIndex || !values.length) {
1804
+ setFocusedValueIndex(0);
1805
+ } else if (focusedValueIndex >= values.length) {
1806
+ setFocusedValueIndex(values.length - 1);
1807
+ }
1808
+ }, [focusedValueIndex, values.length]);
1809
+ useKeyDownAction('ArrowUp', () => {
1810
+ if (values.length) {
1811
+ changeFocusedValueIndex(-1);
1812
+ setMouseControl(false);
1813
+ }
1814
+ }, keyEventsListener);
1815
+ useKeyDownAction('ArrowDown', () => {
1816
+ if (values.length) {
1817
+ changeFocusedValueIndex(1);
1818
+ setMouseControl(false);
1819
+ }
1820
+ }, keyEventsListener);
1821
+ useKeyDownAction('Enter', () => {
1822
+ if (focusedItem) {
1823
+ onValueSelected(focusedItem);
1824
+ }
1825
+ }, keyEventsListener);
1826
+ hooks.useEffect(() => {
1827
+ const individualEntries = dropdownContainer.current.children;
1828
+
1829
+ if (individualEntries.length && !mouseControl) {
1830
+ individualEntries[focusedValueIndex].scrollIntoView({
1831
+ block: 'nearest',
1832
+ inline: 'nearest'
1833
+ });
1834
+ }
1835
+ }, [focusedValueIndex, mouseControl]);
1836
+
1837
+ const mouseMove = (e, i) => {
1838
+ const userMoved = !mouseScreenPos.current || mouseScreenPos.current.x !== e.screenX && mouseScreenPos.current.y !== e.screenY;
1839
+
1840
+ if (userMoved) {
1841
+ mouseScreenPos.current = {
1842
+ x: e.screenX,
1843
+ y: e.screenY
1844
+ };
1845
+
1846
+ if (!mouseControl) {
1847
+ setMouseControl(true);
1848
+ setFocusedValueIndex(i);
1849
+ }
1850
+ }
1851
+ };
1852
+
1853
+ return jsxRuntime.jsxs("div", {
1854
+ ref: dropdownContainer,
1855
+ tabIndex: -1,
1856
+ class: "fjs-dropdownlist",
1857
+ style: {
1858
+ maxHeight: height
1859
+ },
1860
+ children: [!!values.length && values.map((v, i) => {
1861
+ return jsxRuntime.jsx("div", {
1862
+ class: 'fjs-dropdownlist-item' + (focusedValueIndex === i ? ' focused' : ''),
1863
+ onMouseMove: e => mouseMove(e, i),
1864
+ onMouseEnter: mouseControl ? () => setFocusedValueIndex(i) : undefined,
1865
+ onMouseDown: e => {
1866
+ e.preventDefault();
1867
+ onValueSelected(v);
1868
+ },
1869
+ children: getLabel(v)
1870
+ });
1871
+ }), !values.length && jsxRuntime.jsx("div", {
1872
+ class: "fjs-dropdownlist-empty",
1873
+ children: emptyListMessage
1874
+ })]
1875
+ });
1876
+ }
1877
+
1878
+ const type$2 = 'taglist';
1879
+ function Taglist(props) {
1880
+ const {
1881
+ disabled,
1882
+ errors = [],
1883
+ field,
1884
+ value: values = []
1885
+ } = props;
1886
+ const {
1887
+ description,
1888
+ id,
1889
+ label
1890
+ } = field;
1891
+ const {
1892
+ formId
1893
+ } = hooks.useContext(FormContext);
1894
+ const [filter, setFilter] = hooks.useState('');
1895
+ const [selectedValues, setSelectedValues] = hooks.useState([]);
1896
+ const [filteredValues, setFilteredValues] = hooks.useState([]);
1897
+ const [isDropdownExpanded, setIsDropdownExpanded] = hooks.useState(false);
1898
+ const [hasValuesLeft, setHasValuesLeft] = hooks.useState(true);
1899
+ const [isEscapeClosed, setIsEscapeClose] = hooks.useState(false);
1900
+ const searchbarRef = hooks.useRef();
1901
+ const {
1902
+ state: loadState,
1903
+ values: options
1904
+ } = useValuesAsync(field); // Usage of stringify is necessary here because we want this effect to only trigger when there is a value change to the array
1905
+
1906
+ hooks.useEffect(() => {
1907
+ if (loadState === LOAD_STATES.LOADED) {
1908
+ const selectedValues = values.map(v => options.find(o => o.value === v)).filter(v => v !== undefined);
1909
+ setSelectedValues(selectedValues);
1910
+ } else {
1911
+ setSelectedValues([]);
1912
+ }
1913
+ }, [JSON.stringify(values), options, loadState]);
1914
+ hooks.useEffect(() => {
1915
+ if (loadState === LOAD_STATES.LOADED) {
1916
+ setFilteredValues(options.filter(o => o.label && o.label.toLowerCase().includes(filter.toLowerCase()) && !values.includes(o.value)));
1917
+ } else {
1918
+ setFilteredValues([]);
1919
+ }
1920
+ }, [filter, JSON.stringify(values), options]);
1921
+ hooks.useEffect(() => {
1922
+ setHasValuesLeft(selectedValues.length < options.length);
1923
+ }, [selectedValues.length, options.length]);
1924
+
1925
+ const onFilterChange = ({
1926
+ target
1927
+ }) => {
1928
+ setIsEscapeClose(false);
1929
+ setFilter(target.value);
1930
+ };
1931
+
1932
+ const selectValue = option => {
1933
+ setFilter('');
1934
+ props.onChange({
1935
+ value: [...values, option.value],
1936
+ field
1937
+ });
1938
+ };
1939
+
1940
+ const deselectValue = option => {
1941
+ props.onChange({
1942
+ value: values.filter(v => v != option.value),
1943
+ field
1944
+ });
1945
+ };
1946
+
1947
+ const onInputKeyDown = e => {
1948
+ switch (e.key) {
1949
+ case 'ArrowUp':
1950
+ case 'ArrowDown':
1951
+ // We do not want the cursor to seek in the search field when we press up and down
1952
+ e.preventDefault();
1953
+ break;
1954
+
1955
+ case 'Backspace':
1956
+ if (!filter && selectedValues.length) {
1957
+ deselectValue(selectedValues[selectedValues.length - 1]);
1958
+ }
1959
+
1960
+ break;
1961
+
1962
+ case 'Escape':
1963
+ setIsEscapeClose(true);
1964
+ break;
1965
+
1966
+ case 'Enter':
1967
+ if (isEscapeClosed) {
1968
+ setIsEscapeClose(false);
1969
+ }
1970
+
1971
+ break;
1972
+ }
1973
+ };
1974
+
1975
+ return jsxRuntime.jsxs("div", {
1976
+ class: formFieldClasses(type$2, errors),
1977
+ children: [jsxRuntime.jsx(Label, {
1978
+ label: label,
1979
+ id: prefixId(id, formId)
1980
+ }), jsxRuntime.jsxs("div", {
1981
+ class: classNames__default['default']('fjs-taglist', {
1982
+ 'disabled': disabled
1983
+ }),
1984
+ children: [!disabled && loadState === LOAD_STATES.LOADED && selectedValues.map(sv => {
1985
+ return jsxRuntime.jsxs("div", {
1986
+ class: "fjs-taglist-tag",
1987
+ onMouseDown: e => e.preventDefault(),
1988
+ children: [jsxRuntime.jsx("span", {
1989
+ class: "fjs-taglist-tag-label",
1990
+ children: sv.label
1991
+ }), jsxRuntime.jsx("span", {
1992
+ class: "fjs-taglist-tag-remove",
1993
+ onMouseDown: () => deselectValue(sv),
1994
+ children: jsxRuntime.jsx(CloseIcon, {})
1995
+ })]
1996
+ });
1997
+ }), jsxRuntime.jsx("input", {
1998
+ disabled: disabled,
1999
+ class: "fjs-taglist-input",
2000
+ ref: searchbarRef,
2001
+ id: prefixId(`${id}-search`, formId),
2002
+ onChange: onFilterChange,
2003
+ type: "text",
2004
+ value: filter,
2005
+ placeholder: 'Search',
2006
+ autoComplete: "off",
2007
+ onKeyDown: e => onInputKeyDown(e),
2008
+ onMouseDown: () => setIsEscapeClose(false),
2009
+ onFocus: () => setIsDropdownExpanded(true),
2010
+ onBlur: () => {
2011
+ setIsDropdownExpanded(false);
2012
+ setFilter('');
2013
+ }
2014
+ })]
2015
+ }), jsxRuntime.jsx("div", {
2016
+ class: "fjs-taglist-anchor",
2017
+ children: !disabled && loadState === LOAD_STATES.LOADED && isDropdownExpanded && !isEscapeClosed && jsxRuntime.jsx(DropdownList, {
2018
+ values: filteredValues,
2019
+ getLabel: v => v.label,
2020
+ onValueSelected: v => selectValue(v),
2021
+ emptyListMessage: hasValuesLeft ? 'No results' : 'All values selected',
2022
+ listenerElement: searchbarRef.current
2023
+ })
2024
+ }), jsxRuntime.jsx(Description, {
2025
+ description: description
2026
+ }), jsxRuntime.jsx(Errors, {
2027
+ errors: errors
2028
+ })]
2029
+ });
2030
+ }
2031
+
2032
+ Taglist.create = function (options = {}) {
2033
+ if (options.valuesKey) return options;
2034
+ return {
2035
+ values: [{
2036
+ label: 'Value',
2037
+ value: 'value'
2038
+ }],
2039
+ ...options
2040
+ };
2041
+ };
2042
+
2043
+ Taglist.type = type$2;
2044
+ Taglist.label = 'Taglist';
2045
+ Taglist.keyed = true;
2046
+ Taglist.emptyValue = [];
2047
+
1581
2048
  const type$1 = 'text';
1582
2049
  function Text(props) {
1583
2050
  const {
@@ -1666,7 +2133,7 @@ Textfield.label = 'Text Field';
1666
2133
  Textfield.keyed = true;
1667
2134
  Textfield.emptyValue = '';
1668
2135
 
1669
- const formFields = [Button, Checkbox, Default, Number, Radio, Select, Text, Textfield];
2136
+ const formFields = [Button, Checkbox, Checklist, Default, Number, Radio, Select, Taglist, Text, Textfield];
1670
2137
 
1671
2138
  class FormFields {
1672
2139
  constructor() {
@@ -1775,6 +2242,10 @@ var core = {
1775
2242
  * properties: FormProperties,
1776
2243
  * schema: Schema
1777
2244
  * } } State
2245
+ *
2246
+ * @typedef { (type:FormEvent, priority:number, handler:Function) => void } OnEventWithPriority
2247
+ * @typedef { (type:FormEvent, handler:Function) => void } OnEventWithOutPriority
2248
+ * @typedef { OnEventWithPriority & OnEventWithOutPriority } OnEventType
1778
2249
  */
1779
2250
 
1780
2251
  const ids = new Ids__default['default']([32, 36, 1]);
@@ -1788,10 +2259,16 @@ class Form {
1788
2259
  * @param {FormOptions} options
1789
2260
  */
1790
2261
  constructor(options = {}) {
2262
+ /**
2263
+ * @public
2264
+ * @type {OnEventType}
2265
+ */
2266
+ this.on = this._onEvent;
1791
2267
  /**
1792
2268
  * @public
1793
2269
  * @type {String}
1794
2270
  */
2271
+
1795
2272
  this._id = ids.next();
1796
2273
  /**
1797
2274
  * @private
@@ -2038,16 +2515,6 @@ class Form {
2038
2515
  properties
2039
2516
  });
2040
2517
  }
2041
- /**
2042
- * @param {FormEvent} type
2043
- * @param {number} priority
2044
- * @param {Function} handler
2045
- */
2046
-
2047
-
2048
- on(type, priority, handler) {
2049
- this.get('eventBus').on(type, priority, handler);
2050
- }
2051
2518
  /**
2052
2519
  * @param {FormEvent} type
2053
2520
  * @param {Function} handler
@@ -2142,10 +2609,18 @@ class Form {
2142
2609
 
2143
2610
  this._emit('changed', this._getState());
2144
2611
  }
2612
+ /**
2613
+ * @internal
2614
+ */
2615
+
2616
+
2617
+ _onEvent(type, priority, handler) {
2618
+ this.get('eventBus').on(type, priority, handler);
2619
+ }
2145
2620
 
2146
2621
  }
2147
2622
 
2148
- const schemaVersion = 4;
2623
+ const schemaVersion = 5;
2149
2624
  /**
2150
2625
  * @typedef { import('./types').CreateFormOptions } CreateFormOptions
2151
2626
  */
@@ -2172,6 +2647,7 @@ function createForm(options) {
2172
2647
 
2173
2648
  exports.Button = Button;
2174
2649
  exports.Checkbox = Checkbox;
2650
+ exports.Checklist = Checklist;
2175
2651
  exports.Default = Default;
2176
2652
  exports.Form = Form;
2177
2653
  exports.FormComponent = FormComponent;
@@ -2182,6 +2658,7 @@ exports.FormRenderContext = FormRenderContext;
2182
2658
  exports.Number = Number;
2183
2659
  exports.Radio = Radio;
2184
2660
  exports.Select = Select;
2661
+ exports.Taglist = Taglist;
2185
2662
  exports.Text = Text;
2186
2663
  exports.Textfield = Textfield;
2187
2664
  exports.clone = clone;