@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.es.js CHANGED
@@ -2,115 +2,13 @@ import Ids from 'ids';
2
2
  import { isArray, isFunction, isNumber, bind, assign, isNil, get, isUndefined, set, isString } from 'min-dash';
3
3
  import snarkdown from '@bpmn-io/snarkdown';
4
4
  import { jsx, jsxs } from 'preact/jsx-runtime';
5
- import { useContext, useState, useCallback } from 'preact/hooks';
5
+ import { useContext, useState, useEffect, useRef, useMemo, useCallback } from 'preact/hooks';
6
6
  import { createContext, createElement, Fragment, render } from 'preact';
7
+ import React, { createPortal } from 'preact/compat';
8
+ import classNames from 'classnames';
7
9
  import Markup from 'preact-markup';
8
- import { createPortal } from 'preact/compat';
9
10
  import { Injector } from 'didi';
10
11
 
11
- function createInjector(bootstrapModules) {
12
- const modules = [],
13
- components = [];
14
-
15
- function hasModule(module) {
16
- return modules.includes(module);
17
- }
18
-
19
- function addModule(module) {
20
- modules.push(module);
21
- }
22
-
23
- function visit(module) {
24
- if (hasModule(module)) {
25
- return;
26
- }
27
-
28
- (module.__depends__ || []).forEach(visit);
29
-
30
- if (hasModule(module)) {
31
- return;
32
- }
33
-
34
- addModule(module);
35
- (module.__init__ || []).forEach(function (component) {
36
- components.push(component);
37
- });
38
- }
39
-
40
- bootstrapModules.forEach(visit);
41
- const injector = new Injector(modules);
42
- components.forEach(function (component) {
43
- try {
44
- injector[typeof component === 'string' ? 'get' : 'invoke'](component);
45
- } catch (err) {
46
- console.error('Failed to instantiate component');
47
- console.error(err.stack);
48
- throw err;
49
- }
50
- });
51
- return injector;
52
- }
53
-
54
- /**
55
- * @param {string?} prefix
56
- *
57
- * @returns Element
58
- */
59
- function createFormContainer(prefix = 'fjs') {
60
- const container = document.createElement('div');
61
- container.classList.add(`${prefix}-container`);
62
- return container;
63
- }
64
-
65
- function findErrors(errors, path) {
66
- return errors[pathStringify(path)];
67
- }
68
- function isRequired(field) {
69
- return field.required;
70
- }
71
- function pathParse(path) {
72
- if (!path) {
73
- return [];
74
- }
75
-
76
- return path.split('.').map(key => {
77
- return isNaN(parseInt(key)) ? key : parseInt(key);
78
- });
79
- }
80
- function pathsEqual(a, b) {
81
- return a && b && a.length === b.length && a.every((value, index) => value === b[index]);
82
- }
83
- function pathStringify(path) {
84
- if (!path) {
85
- return '';
86
- }
87
-
88
- return path.join('.');
89
- }
90
- const indices = {};
91
- function generateIndexForType(type) {
92
- if (type in indices) {
93
- indices[type]++;
94
- } else {
95
- indices[type] = 1;
96
- }
97
-
98
- return indices[type];
99
- }
100
- function generateIdForType(type) {
101
- return `${type}${generateIndexForType(type)}`;
102
- }
103
- /**
104
- * @template T
105
- * @param {T} data
106
- * @param {(this: any, key: string, value: any) => any} [replacer]
107
- * @return {T}
108
- */
109
-
110
- function clone(data, replacer) {
111
- return JSON.parse(JSON.stringify(data, replacer));
112
- }
113
-
114
12
  var FN_REF = '__fn';
115
13
  var DEFAULT_PRIORITY = 1000;
116
14
  var slice = Array.prototype.slice;
@@ -624,6 +522,7 @@ class Validator {
624
522
  }
625
523
 
626
524
  }
525
+ Validator.$inject = [];
627
526
 
628
527
  class FormFieldRegistry {
629
528
  constructor(eventBus) {
@@ -689,6 +588,109 @@ class FormFieldRegistry {
689
588
  }
690
589
  FormFieldRegistry.$inject = ['eventBus'];
691
590
 
591
+ function createInjector(bootstrapModules) {
592
+ const modules = [],
593
+ components = [];
594
+
595
+ function hasModule(module) {
596
+ return modules.includes(module);
597
+ }
598
+
599
+ function addModule(module) {
600
+ modules.push(module);
601
+ }
602
+
603
+ function visit(module) {
604
+ if (hasModule(module)) {
605
+ return;
606
+ }
607
+
608
+ (module.__depends__ || []).forEach(visit);
609
+
610
+ if (hasModule(module)) {
611
+ return;
612
+ }
613
+
614
+ addModule(module);
615
+ (module.__init__ || []).forEach(function (component) {
616
+ components.push(component);
617
+ });
618
+ }
619
+
620
+ bootstrapModules.forEach(visit);
621
+ const injector = new Injector(modules);
622
+ components.forEach(function (component) {
623
+ try {
624
+ injector[typeof component === 'string' ? 'get' : 'invoke'](component);
625
+ } catch (err) {
626
+ console.error('Failed to instantiate component');
627
+ console.error(err.stack);
628
+ throw err;
629
+ }
630
+ });
631
+ return injector;
632
+ }
633
+
634
+ /**
635
+ * @param {string?} prefix
636
+ *
637
+ * @returns Element
638
+ */
639
+ function createFormContainer(prefix = 'fjs') {
640
+ const container = document.createElement('div');
641
+ container.classList.add(`${prefix}-container`);
642
+ return container;
643
+ }
644
+
645
+ function findErrors(errors, path) {
646
+ return errors[pathStringify(path)];
647
+ }
648
+ function isRequired(field) {
649
+ return field.required;
650
+ }
651
+ function pathParse(path) {
652
+ if (!path) {
653
+ return [];
654
+ }
655
+
656
+ return path.split('.').map(key => {
657
+ return isNaN(parseInt(key)) ? key : parseInt(key);
658
+ });
659
+ }
660
+ function pathsEqual(a, b) {
661
+ return a && b && a.length === b.length && a.every((value, index) => value === b[index]);
662
+ }
663
+ function pathStringify(path) {
664
+ if (!path) {
665
+ return '';
666
+ }
667
+
668
+ return path.join('.');
669
+ }
670
+ const indices = {};
671
+ function generateIndexForType(type) {
672
+ if (type in indices) {
673
+ indices[type]++;
674
+ } else {
675
+ indices[type] = 1;
676
+ }
677
+
678
+ return indices[type];
679
+ }
680
+ function generateIdForType(type) {
681
+ return `${type}${generateIndexForType(type)}`;
682
+ }
683
+ /**
684
+ * @template T
685
+ * @param {T} data
686
+ * @param {(this: any, key: string, value: any) => any} [replacer]
687
+ * @return {T}
688
+ */
689
+
690
+ function clone(data, replacer) {
691
+ return JSON.parse(JSON.stringify(data, replacer));
692
+ }
693
+
692
694
  class Importer {
693
695
  /**
694
696
  * @constructor
@@ -806,19 +808,26 @@ class Importer {
806
808
  const {
807
809
  defaultValue,
808
810
  _path,
809
- type
810
- } = formField;
811
-
812
- if (!_path) {
813
- return importedData;
814
- } // (1) try to get value from data
815
- // (2) try to get default value from form field
816
- // (3) get empty value from form field
817
-
811
+ type,
812
+ valuesKey
813
+ } = formField; // get values defined via valuesKey
814
+
815
+ if (valuesKey) {
816
+ importedData = { ...importedData,
817
+ [valuesKey]: get(data, [valuesKey])
818
+ };
819
+ } // try to get value from data
820
+ // if unavailable - try to get default value from form field
821
+ // if unavailable - get empty value from form field
822
+
823
+
824
+ if (_path) {
825
+ importedData = { ...importedData,
826
+ [_path[0]]: get(data, _path, isUndefined(defaultValue) ? this._formFields.get(type).emptyValue : defaultValue)
827
+ };
828
+ }
818
829
 
819
- return { ...importedData,
820
- [_path[0]]: get(data, _path, isUndefined(defaultValue) ? this._formFields.get(type).emptyValue : defaultValue)
821
- };
830
+ return importedData;
822
831
  }, {});
823
832
  }
824
833
 
@@ -979,7 +988,7 @@ function safeMarkdown(markdown) {
979
988
  return sanitizeHTML(html);
980
989
  }
981
990
 
982
- const type$6 = 'button';
991
+ const type$8 = 'button';
983
992
  function Button(props) {
984
993
  const {
985
994
  disabled,
@@ -989,7 +998,7 @@ function Button(props) {
989
998
  action = 'submit'
990
999
  } = field;
991
1000
  return jsx("div", {
992
- class: formFieldClasses(type$6),
1001
+ class: formFieldClasses(type$8),
993
1002
  children: jsx("button", {
994
1003
  class: "fjs-button",
995
1004
  type: action,
@@ -1006,7 +1015,7 @@ Button.create = function (options = {}) {
1006
1015
  };
1007
1016
  };
1008
1017
 
1009
- Button.type = type$6;
1018
+ Button.type = type$8;
1010
1019
  Button.label = 'Button';
1011
1020
  Button.keyed = true;
1012
1021
 
@@ -1088,7 +1097,7 @@ function Label(props) {
1088
1097
  });
1089
1098
  }
1090
1099
 
1091
- const type$5 = 'checkbox';
1100
+ const type$7 = 'checkbox';
1092
1101
  function Checkbox(props) {
1093
1102
  const {
1094
1103
  disabled,
@@ -1115,7 +1124,7 @@ function Checkbox(props) {
1115
1124
  formId
1116
1125
  } = useContext(FormContext);
1117
1126
  return jsxs("div", {
1118
- class: formFieldClasses(type$5, errors),
1127
+ class: formFieldClasses(type$7, errors),
1119
1128
  children: [jsx(Label, {
1120
1129
  id: prefixId(id, formId),
1121
1130
  label: label,
@@ -1141,7 +1150,7 @@ Checkbox.create = function (options = {}) {
1141
1150
  };
1142
1151
  };
1143
1152
 
1144
- Checkbox.type = type$5;
1153
+ Checkbox.type = type$7;
1145
1154
  Checkbox.label = 'Checkbox';
1146
1155
  Checkbox.keyed = true;
1147
1156
  Checkbox.emptyValue = false;
@@ -1153,6 +1162,152 @@ function useService (type, strict) {
1153
1162
  return getService(type, strict);
1154
1163
  }
1155
1164
 
1165
+ /**
1166
+ * @enum { String }
1167
+ */
1168
+
1169
+ const LOAD_STATES = {
1170
+ LOADING: 'loading',
1171
+ LOADED: 'loaded',
1172
+ ERROR: 'error'
1173
+ };
1174
+ /**
1175
+ * @typedef {Object} ValuesGetter
1176
+ * @property {Object[]} values - The values data
1177
+ * @property {(LOAD_STATES)} state - The values data's loading state, to use for conditional rendering
1178
+ */
1179
+
1180
+ /**
1181
+ * A hook to load values for single and multiselect components.
1182
+ *
1183
+ * @param {Object} field - The form field to handle values for
1184
+ * @return {ValuesGetter} valuesGetter - A values getter object providing loading state and values
1185
+ */
1186
+
1187
+ function useValuesAsync (field) {
1188
+ const {
1189
+ valuesKey,
1190
+ values: staticValues
1191
+ } = field;
1192
+ const [valuesGetter, setValuesGetter] = useState({
1193
+ values: [],
1194
+ error: undefined,
1195
+ state: LOAD_STATES.LOADING
1196
+ });
1197
+
1198
+ const initialData = useService('form')._getState().initialData;
1199
+
1200
+ useEffect(() => {
1201
+ let values = [];
1202
+
1203
+ if (valuesKey !== undefined) {
1204
+ const keyedValues = (initialData || {})[valuesKey];
1205
+
1206
+ if (keyedValues && Array.isArray(keyedValues)) {
1207
+ values = keyedValues;
1208
+ }
1209
+ } else if (staticValues !== undefined) {
1210
+ values = Array.isArray(staticValues) ? staticValues : [];
1211
+ } else {
1212
+ setValuesGetter(getErrorState('No values source defined in the form definition'));
1213
+ return;
1214
+ }
1215
+
1216
+ setValuesGetter(buildLoadedState(values));
1217
+ }, [valuesKey, staticValues, initialData]);
1218
+ return valuesGetter;
1219
+ }
1220
+
1221
+ const getErrorState = error => ({
1222
+ values: [],
1223
+ error,
1224
+ state: LOAD_STATES.ERROR
1225
+ });
1226
+
1227
+ const buildLoadedState = values => ({
1228
+ values,
1229
+ error: undefined,
1230
+ state: LOAD_STATES.LOADED
1231
+ });
1232
+
1233
+ const type$6 = 'checklist';
1234
+ function Checklist(props) {
1235
+ const {
1236
+ disabled,
1237
+ errors = [],
1238
+ field,
1239
+ value = []
1240
+ } = props;
1241
+ const {
1242
+ description,
1243
+ id,
1244
+ label
1245
+ } = field;
1246
+
1247
+ const toggleCheckbox = v => {
1248
+ let newValue = [...value];
1249
+
1250
+ if (!newValue.includes(v)) {
1251
+ newValue.push(v);
1252
+ } else {
1253
+ newValue = newValue.filter(x => x != v);
1254
+ }
1255
+
1256
+ props.onChange({
1257
+ field,
1258
+ value: newValue
1259
+ });
1260
+ };
1261
+
1262
+ const {
1263
+ state: loadState,
1264
+ values: options
1265
+ } = useValuesAsync(field);
1266
+ const {
1267
+ formId
1268
+ } = useContext(FormContext);
1269
+ return jsxs("div", {
1270
+ class: formFieldClasses(type$6, errors),
1271
+ children: [jsx(Label, {
1272
+ label: label
1273
+ }), loadState == LOAD_STATES.LOADED && options.map((v, index) => {
1274
+ return jsx(Label, {
1275
+ id: prefixId(`${id}-${index}`, formId),
1276
+ label: v.label,
1277
+ required: false,
1278
+ children: jsx("input", {
1279
+ checked: value.includes(v.value),
1280
+ class: "fjs-input",
1281
+ disabled: disabled,
1282
+ id: prefixId(`${id}-${index}`, formId),
1283
+ type: "checkbox",
1284
+ onClick: () => toggleCheckbox(v.value)
1285
+ })
1286
+ }, `${id}-${index}`);
1287
+ }), jsx(Description, {
1288
+ description: description
1289
+ }), jsx(Errors, {
1290
+ errors: errors
1291
+ })]
1292
+ });
1293
+ }
1294
+
1295
+ Checklist.create = function (options = {}) {
1296
+ if (options.valuesKey) return options;
1297
+ return {
1298
+ values: [{
1299
+ label: 'Value',
1300
+ value: 'value'
1301
+ }],
1302
+ ...options
1303
+ };
1304
+ };
1305
+
1306
+ Checklist.type = type$6;
1307
+ Checklist.label = 'Checklist';
1308
+ Checklist.keyed = true;
1309
+ Checklist.emptyValue = [];
1310
+
1156
1311
  const noop$1 = () => false;
1157
1312
 
1158
1313
  function FormField(props) {
@@ -1361,7 +1516,7 @@ function FormComponent(props) {
1361
1516
  });
1362
1517
  }
1363
1518
 
1364
- const type$4 = 'number';
1519
+ const type$5 = 'number';
1365
1520
  function Number(props) {
1366
1521
  const {
1367
1522
  disabled,
@@ -1393,7 +1548,7 @@ function Number(props) {
1393
1548
  formId
1394
1549
  } = useContext(FormContext);
1395
1550
  return jsxs("div", {
1396
- class: formFieldClasses(type$4, errors),
1551
+ class: formFieldClasses(type$5, errors),
1397
1552
  children: [jsx(Label, {
1398
1553
  id: prefixId(id, formId),
1399
1554
  label: label,
@@ -1418,12 +1573,12 @@ Number.create = function (options = {}) {
1418
1573
  };
1419
1574
  };
1420
1575
 
1421
- Number.type = type$4;
1576
+ Number.type = type$5;
1422
1577
  Number.keyed = true;
1423
1578
  Number.label = 'Number';
1424
1579
  Number.emptyValue = null;
1425
1580
 
1426
- const type$3 = 'radio';
1581
+ const type$4 = 'radio';
1427
1582
  function Radio(props) {
1428
1583
  const {
1429
1584
  disabled,
@@ -1435,8 +1590,7 @@ function Radio(props) {
1435
1590
  description,
1436
1591
  id,
1437
1592
  label,
1438
- validate = {},
1439
- values
1593
+ validate = {}
1440
1594
  } = field;
1441
1595
  const {
1442
1596
  required
@@ -1449,26 +1603,30 @@ function Radio(props) {
1449
1603
  });
1450
1604
  };
1451
1605
 
1606
+ const {
1607
+ state: loadState,
1608
+ values: options
1609
+ } = useValuesAsync(field);
1452
1610
  const {
1453
1611
  formId
1454
1612
  } = useContext(FormContext);
1455
1613
  return jsxs("div", {
1456
- class: formFieldClasses(type$3, errors),
1614
+ class: formFieldClasses(type$4, errors),
1457
1615
  children: [jsx(Label, {
1458
1616
  label: label,
1459
1617
  required: required
1460
- }), values.map((v, index) => {
1618
+ }), loadState == LOAD_STATES.LOADED && options.map((option, index) => {
1461
1619
  return jsx(Label, {
1462
1620
  id: prefixId(`${id}-${index}`, formId),
1463
- label: v.label,
1621
+ label: option.label,
1464
1622
  required: false,
1465
1623
  children: jsx("input", {
1466
- checked: v.value === value,
1624
+ checked: option.value === value,
1467
1625
  class: "fjs-input",
1468
1626
  disabled: disabled,
1469
1627
  id: prefixId(`${id}-${index}`, formId),
1470
1628
  type: "radio",
1471
- onClick: () => onChange(v.value)
1629
+ onClick: () => onChange(option.value)
1472
1630
  })
1473
1631
  }, `${id}-${index}`);
1474
1632
  }), jsx(Description, {
@@ -1480,6 +1638,7 @@ function Radio(props) {
1480
1638
  }
1481
1639
 
1482
1640
  Radio.create = function (options = {}) {
1641
+ if (options.valuesKey) return options;
1483
1642
  return {
1484
1643
  values: [{
1485
1644
  label: 'Value',
@@ -1489,12 +1648,12 @@ Radio.create = function (options = {}) {
1489
1648
  };
1490
1649
  };
1491
1650
 
1492
- Radio.type = type$3;
1651
+ Radio.type = type$4;
1493
1652
  Radio.label = 'Radio';
1494
1653
  Radio.keyed = true;
1495
1654
  Radio.emptyValue = null;
1496
1655
 
1497
- const type$2 = 'select';
1656
+ const type$3 = 'select';
1498
1657
  function Select(props) {
1499
1658
  const {
1500
1659
  disabled,
@@ -1506,8 +1665,7 @@ function Select(props) {
1506
1665
  description,
1507
1666
  id,
1508
1667
  label,
1509
- validate = {},
1510
- values
1668
+ validate = {}
1511
1669
  } = field;
1512
1670
  const {
1513
1671
  required
@@ -1522,11 +1680,15 @@ function Select(props) {
1522
1680
  });
1523
1681
  };
1524
1682
 
1683
+ const {
1684
+ state: loadState,
1685
+ values: options
1686
+ } = useValuesAsync(field);
1525
1687
  const {
1526
1688
  formId
1527
1689
  } = useContext(FormContext);
1528
1690
  return jsxs("div", {
1529
- class: formFieldClasses(type$2, errors),
1691
+ class: formFieldClasses(type$3, errors),
1530
1692
  children: [jsx(Label, {
1531
1693
  id: prefixId(id, formId),
1532
1694
  label: label,
@@ -1539,10 +1701,10 @@ function Select(props) {
1539
1701
  value: value || '',
1540
1702
  children: [jsx("option", {
1541
1703
  value: ""
1542
- }), values.map((v, index) => {
1704
+ }), loadState == LOAD_STATES.LOADED && options.map((option, index) => {
1543
1705
  return jsx("option", {
1544
- value: v.value,
1545
- children: v.label
1706
+ value: option.value,
1707
+ children: option.label
1546
1708
  }, `${id}-${index}`);
1547
1709
  })]
1548
1710
  }), jsx(Description, {
@@ -1554,6 +1716,7 @@ function Select(props) {
1554
1716
  }
1555
1717
 
1556
1718
  Select.create = function (options = {}) {
1719
+ if (options.valuesKey) return options;
1557
1720
  return {
1558
1721
  values: [{
1559
1722
  label: 'Value',
@@ -1563,11 +1726,313 @@ Select.create = function (options = {}) {
1563
1726
  };
1564
1727
  };
1565
1728
 
1566
- Select.type = type$2;
1729
+ Select.type = type$3;
1567
1730
  Select.label = 'Select';
1568
1731
  Select.keyed = true;
1569
1732
  Select.emptyValue = null;
1570
1733
 
1734
+ 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); }
1735
+ var CloseIcon = (({
1736
+ styles = {},
1737
+ ...props
1738
+ }) => /*#__PURE__*/React.createElement("svg", _extends({
1739
+ width: "16",
1740
+ height: "16",
1741
+ fill: "none",
1742
+ xmlns: "http://www.w3.org/2000/svg"
1743
+ }, props), /*#__PURE__*/React.createElement("path", {
1744
+ fillRule: "evenodd",
1745
+ clipRule: "evenodd",
1746
+ 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",
1747
+ fill: "#000"
1748
+ })));
1749
+
1750
+ function useKeyDownAction(targetKey, action, listenerElement = window) {
1751
+ function downHandler({
1752
+ key
1753
+ }) {
1754
+ if (key === targetKey) {
1755
+ action();
1756
+ }
1757
+ }
1758
+
1759
+ useEffect(() => {
1760
+ listenerElement.addEventListener('keydown', downHandler);
1761
+ return () => {
1762
+ listenerElement.removeEventListener('keydown', downHandler);
1763
+ };
1764
+ });
1765
+ }
1766
+
1767
+ const DEFAULT_LABEL_GETTER = value => value;
1768
+
1769
+ const NOOP = () => {};
1770
+
1771
+ function DropdownList(props) {
1772
+ const {
1773
+ keyEventsListener = window,
1774
+ values = [],
1775
+ getLabel = DEFAULT_LABEL_GETTER,
1776
+ onValueSelected = NOOP,
1777
+ height = 235,
1778
+ emptyListMessage = 'No results'
1779
+ } = props;
1780
+ const [mouseControl, setMouseControl] = useState(true);
1781
+ const [focusedValueIndex, setFocusedValueIndex] = useState(0);
1782
+ const dropdownContainer = useRef();
1783
+ const mouseScreenPos = useRef();
1784
+ const focusedItem = useMemo(() => values.length ? values[focusedValueIndex] : null, [focusedValueIndex, values]);
1785
+ const changeFocusedValueIndex = useCallback(delta => {
1786
+ setFocusedValueIndex(x => Math.min(Math.max(0, x + delta), values.length - 1));
1787
+ }, [values.length]);
1788
+ useEffect(() => {
1789
+ if (focusedValueIndex === 0) return;
1790
+
1791
+ if (!focusedValueIndex || !values.length) {
1792
+ setFocusedValueIndex(0);
1793
+ } else if (focusedValueIndex >= values.length) {
1794
+ setFocusedValueIndex(values.length - 1);
1795
+ }
1796
+ }, [focusedValueIndex, values.length]);
1797
+ useKeyDownAction('ArrowUp', () => {
1798
+ if (values.length) {
1799
+ changeFocusedValueIndex(-1);
1800
+ setMouseControl(false);
1801
+ }
1802
+ }, keyEventsListener);
1803
+ useKeyDownAction('ArrowDown', () => {
1804
+ if (values.length) {
1805
+ changeFocusedValueIndex(1);
1806
+ setMouseControl(false);
1807
+ }
1808
+ }, keyEventsListener);
1809
+ useKeyDownAction('Enter', () => {
1810
+ if (focusedItem) {
1811
+ onValueSelected(focusedItem);
1812
+ }
1813
+ }, keyEventsListener);
1814
+ useEffect(() => {
1815
+ const individualEntries = dropdownContainer.current.children;
1816
+
1817
+ if (individualEntries.length && !mouseControl) {
1818
+ individualEntries[focusedValueIndex].scrollIntoView({
1819
+ block: 'nearest',
1820
+ inline: 'nearest'
1821
+ });
1822
+ }
1823
+ }, [focusedValueIndex, mouseControl]);
1824
+
1825
+ const mouseMove = (e, i) => {
1826
+ const userMoved = !mouseScreenPos.current || mouseScreenPos.current.x !== e.screenX && mouseScreenPos.current.y !== e.screenY;
1827
+
1828
+ if (userMoved) {
1829
+ mouseScreenPos.current = {
1830
+ x: e.screenX,
1831
+ y: e.screenY
1832
+ };
1833
+
1834
+ if (!mouseControl) {
1835
+ setMouseControl(true);
1836
+ setFocusedValueIndex(i);
1837
+ }
1838
+ }
1839
+ };
1840
+
1841
+ return jsxs("div", {
1842
+ ref: dropdownContainer,
1843
+ tabIndex: -1,
1844
+ class: "fjs-dropdownlist",
1845
+ style: {
1846
+ maxHeight: height
1847
+ },
1848
+ children: [!!values.length && values.map((v, i) => {
1849
+ return jsx("div", {
1850
+ class: 'fjs-dropdownlist-item' + (focusedValueIndex === i ? ' focused' : ''),
1851
+ onMouseMove: e => mouseMove(e, i),
1852
+ onMouseEnter: mouseControl ? () => setFocusedValueIndex(i) : undefined,
1853
+ onMouseDown: e => {
1854
+ e.preventDefault();
1855
+ onValueSelected(v);
1856
+ },
1857
+ children: getLabel(v)
1858
+ });
1859
+ }), !values.length && jsx("div", {
1860
+ class: "fjs-dropdownlist-empty",
1861
+ children: emptyListMessage
1862
+ })]
1863
+ });
1864
+ }
1865
+
1866
+ const type$2 = 'taglist';
1867
+ function Taglist(props) {
1868
+ const {
1869
+ disabled,
1870
+ errors = [],
1871
+ field,
1872
+ value: values = []
1873
+ } = props;
1874
+ const {
1875
+ description,
1876
+ id,
1877
+ label
1878
+ } = field;
1879
+ const {
1880
+ formId
1881
+ } = useContext(FormContext);
1882
+ const [filter, setFilter] = useState('');
1883
+ const [selectedValues, setSelectedValues] = useState([]);
1884
+ const [filteredValues, setFilteredValues] = useState([]);
1885
+ const [isDropdownExpanded, setIsDropdownExpanded] = useState(false);
1886
+ const [hasValuesLeft, setHasValuesLeft] = useState(true);
1887
+ const [isEscapeClosed, setIsEscapeClose] = useState(false);
1888
+ const searchbarRef = useRef();
1889
+ const {
1890
+ state: loadState,
1891
+ values: options
1892
+ } = 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
1893
+
1894
+ useEffect(() => {
1895
+ if (loadState === LOAD_STATES.LOADED) {
1896
+ const selectedValues = values.map(v => options.find(o => o.value === v)).filter(v => v !== undefined);
1897
+ setSelectedValues(selectedValues);
1898
+ } else {
1899
+ setSelectedValues([]);
1900
+ }
1901
+ }, [JSON.stringify(values), options, loadState]);
1902
+ useEffect(() => {
1903
+ if (loadState === LOAD_STATES.LOADED) {
1904
+ setFilteredValues(options.filter(o => o.label && o.label.toLowerCase().includes(filter.toLowerCase()) && !values.includes(o.value)));
1905
+ } else {
1906
+ setFilteredValues([]);
1907
+ }
1908
+ }, [filter, JSON.stringify(values), options]);
1909
+ useEffect(() => {
1910
+ setHasValuesLeft(selectedValues.length < options.length);
1911
+ }, [selectedValues.length, options.length]);
1912
+
1913
+ const onFilterChange = ({
1914
+ target
1915
+ }) => {
1916
+ setIsEscapeClose(false);
1917
+ setFilter(target.value);
1918
+ };
1919
+
1920
+ const selectValue = option => {
1921
+ setFilter('');
1922
+ props.onChange({
1923
+ value: [...values, option.value],
1924
+ field
1925
+ });
1926
+ };
1927
+
1928
+ const deselectValue = option => {
1929
+ props.onChange({
1930
+ value: values.filter(v => v != option.value),
1931
+ field
1932
+ });
1933
+ };
1934
+
1935
+ const onInputKeyDown = e => {
1936
+ switch (e.key) {
1937
+ case 'ArrowUp':
1938
+ case 'ArrowDown':
1939
+ // We do not want the cursor to seek in the search field when we press up and down
1940
+ e.preventDefault();
1941
+ break;
1942
+
1943
+ case 'Backspace':
1944
+ if (!filter && selectedValues.length) {
1945
+ deselectValue(selectedValues[selectedValues.length - 1]);
1946
+ }
1947
+
1948
+ break;
1949
+
1950
+ case 'Escape':
1951
+ setIsEscapeClose(true);
1952
+ break;
1953
+
1954
+ case 'Enter':
1955
+ if (isEscapeClosed) {
1956
+ setIsEscapeClose(false);
1957
+ }
1958
+
1959
+ break;
1960
+ }
1961
+ };
1962
+
1963
+ return jsxs("div", {
1964
+ class: formFieldClasses(type$2, errors),
1965
+ children: [jsx(Label, {
1966
+ label: label,
1967
+ id: prefixId(id, formId)
1968
+ }), jsxs("div", {
1969
+ class: classNames('fjs-taglist', {
1970
+ 'disabled': disabled
1971
+ }),
1972
+ children: [!disabled && loadState === LOAD_STATES.LOADED && selectedValues.map(sv => {
1973
+ return jsxs("div", {
1974
+ class: "fjs-taglist-tag",
1975
+ onMouseDown: e => e.preventDefault(),
1976
+ children: [jsx("span", {
1977
+ class: "fjs-taglist-tag-label",
1978
+ children: sv.label
1979
+ }), jsx("span", {
1980
+ class: "fjs-taglist-tag-remove",
1981
+ onMouseDown: () => deselectValue(sv),
1982
+ children: jsx(CloseIcon, {})
1983
+ })]
1984
+ });
1985
+ }), jsx("input", {
1986
+ disabled: disabled,
1987
+ class: "fjs-taglist-input",
1988
+ ref: searchbarRef,
1989
+ id: prefixId(`${id}-search`, formId),
1990
+ onChange: onFilterChange,
1991
+ type: "text",
1992
+ value: filter,
1993
+ placeholder: 'Search',
1994
+ autoComplete: "off",
1995
+ onKeyDown: e => onInputKeyDown(e),
1996
+ onMouseDown: () => setIsEscapeClose(false),
1997
+ onFocus: () => setIsDropdownExpanded(true),
1998
+ onBlur: () => {
1999
+ setIsDropdownExpanded(false);
2000
+ setFilter('');
2001
+ }
2002
+ })]
2003
+ }), jsx("div", {
2004
+ class: "fjs-taglist-anchor",
2005
+ children: !disabled && loadState === LOAD_STATES.LOADED && isDropdownExpanded && !isEscapeClosed && jsx(DropdownList, {
2006
+ values: filteredValues,
2007
+ getLabel: v => v.label,
2008
+ onValueSelected: v => selectValue(v),
2009
+ emptyListMessage: hasValuesLeft ? 'No results' : 'All values selected',
2010
+ listenerElement: searchbarRef.current
2011
+ })
2012
+ }), jsx(Description, {
2013
+ description: description
2014
+ }), jsx(Errors, {
2015
+ errors: errors
2016
+ })]
2017
+ });
2018
+ }
2019
+
2020
+ Taglist.create = function (options = {}) {
2021
+ if (options.valuesKey) return options;
2022
+ return {
2023
+ values: [{
2024
+ label: 'Value',
2025
+ value: 'value'
2026
+ }],
2027
+ ...options
2028
+ };
2029
+ };
2030
+
2031
+ Taglist.type = type$2;
2032
+ Taglist.label = 'Taglist';
2033
+ Taglist.keyed = true;
2034
+ Taglist.emptyValue = [];
2035
+
1571
2036
  const type$1 = 'text';
1572
2037
  function Text(props) {
1573
2038
  const {
@@ -1656,7 +2121,7 @@ Textfield.label = 'Text Field';
1656
2121
  Textfield.keyed = true;
1657
2122
  Textfield.emptyValue = '';
1658
2123
 
1659
- const formFields = [Button, Checkbox, Default, Number, Radio, Select, Text, Textfield];
2124
+ const formFields = [Button, Checkbox, Checklist, Default, Number, Radio, Select, Taglist, Text, Textfield];
1660
2125
 
1661
2126
  class FormFields {
1662
2127
  constructor() {
@@ -1765,6 +2230,10 @@ var core = {
1765
2230
  * properties: FormProperties,
1766
2231
  * schema: Schema
1767
2232
  * } } State
2233
+ *
2234
+ * @typedef { (type:FormEvent, priority:number, handler:Function) => void } OnEventWithPriority
2235
+ * @typedef { (type:FormEvent, handler:Function) => void } OnEventWithOutPriority
2236
+ * @typedef { OnEventWithPriority & OnEventWithOutPriority } OnEventType
1768
2237
  */
1769
2238
 
1770
2239
  const ids = new Ids([32, 36, 1]);
@@ -1778,10 +2247,16 @@ class Form {
1778
2247
  * @param {FormOptions} options
1779
2248
  */
1780
2249
  constructor(options = {}) {
2250
+ /**
2251
+ * @public
2252
+ * @type {OnEventType}
2253
+ */
2254
+ this.on = this._onEvent;
1781
2255
  /**
1782
2256
  * @public
1783
2257
  * @type {String}
1784
2258
  */
2259
+
1785
2260
  this._id = ids.next();
1786
2261
  /**
1787
2262
  * @private
@@ -2028,16 +2503,6 @@ class Form {
2028
2503
  properties
2029
2504
  });
2030
2505
  }
2031
- /**
2032
- * @param {FormEvent} type
2033
- * @param {number} priority
2034
- * @param {Function} handler
2035
- */
2036
-
2037
-
2038
- on(type, priority, handler) {
2039
- this.get('eventBus').on(type, priority, handler);
2040
- }
2041
2506
  /**
2042
2507
  * @param {FormEvent} type
2043
2508
  * @param {Function} handler
@@ -2132,10 +2597,18 @@ class Form {
2132
2597
 
2133
2598
  this._emit('changed', this._getState());
2134
2599
  }
2600
+ /**
2601
+ * @internal
2602
+ */
2603
+
2604
+
2605
+ _onEvent(type, priority, handler) {
2606
+ this.get('eventBus').on(type, priority, handler);
2607
+ }
2135
2608
 
2136
2609
  }
2137
2610
 
2138
- const schemaVersion = 4;
2611
+ const schemaVersion = 5;
2139
2612
  /**
2140
2613
  * @typedef { import('./types').CreateFormOptions } CreateFormOptions
2141
2614
  */
@@ -2160,5 +2633,5 @@ function createForm(options) {
2160
2633
  });
2161
2634
  }
2162
2635
 
2163
- export { Button, Checkbox, Default, Form, FormComponent, FormContext, FormFieldRegistry, FormFields, FormRenderContext, Number, Radio, Select, Text, Textfield, clone, createForm, createFormContainer, createInjector, findErrors, formFields, generateIdForType, generateIndexForType, isRequired, pathParse, pathStringify, pathsEqual, schemaVersion };
2636
+ export { Button, Checkbox, Checklist, Default, Form, FormComponent, FormContext, FormFieldRegistry, FormFields, FormRenderContext, Number, Radio, Select, Taglist, Text, Textfield, clone, createForm, createFormContainer, createInjector, findErrors, formFields, generateIdForType, generateIndexForType, isRequired, pathParse, pathStringify, pathsEqual, schemaVersion };
2164
2637
  //# sourceMappingURL=index.es.js.map