@bpmn-io/form-js-viewer 0.7.1 → 0.8.0-alpha.1
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/assets/form-js.css +128 -27
- package/dist/index.cjs +624 -84
- package/dist/index.cjs.map +1 -1
- package/dist/index.es.js +621 -86
- package/dist/index.es.js.map +1 -1
- package/dist/types/Form.d.ts +3 -3
- package/dist/types/index.d.ts +1 -1
- package/dist/types/render/Renderer.d.ts +2 -2
- package/dist/types/render/components/Util.d.ts +2 -0
- package/dist/types/render/components/form-fields/Checkbox.d.ts +3 -0
- package/dist/types/render/components/form-fields/Checklist.d.ts +12 -0
- package/dist/types/render/components/form-fields/Number.d.ts +3 -0
- package/dist/types/render/components/form-fields/Radio.d.ts +3 -6
- package/dist/types/render/components/form-fields/Select.d.ts +3 -6
- package/dist/types/render/components/form-fields/Taglist.d.ts +12 -0
- package/dist/types/render/components/form-fields/Textfield.d.ts +3 -0
- package/dist/types/render/components/form-fields/parts/DropdownList.d.ts +1 -0
- package/dist/types/render/components/index.d.ts +4 -2
- package/dist/types/render/hooks/useKeyDownAction.d.ts +1 -0
- package/dist/types/render/hooks/useValuesAsync.d.ts +28 -0
- package/dist/types/util/index.d.ts +8 -0
- package/dist/types/util/injector.d.ts +2 -1
- package/package.json +5 -4
package/dist/index.es.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import Ids from 'ids';
|
|
2
|
-
import { isArray, isFunction, isNumber, bind, assign, isNil, get, isUndefined, set, isString } from 'min-dash';
|
|
2
|
+
import { isArray, isFunction, isNumber, bind, assign, isNil, get, isUndefined, isObject, 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
12
|
var FN_REF = '__fn';
|
|
@@ -588,45 +589,8 @@ class FormFieldRegistry {
|
|
|
588
589
|
FormFieldRegistry.$inject = ['eventBus'];
|
|
589
590
|
|
|
590
591
|
function createInjector(bootstrapModules) {
|
|
591
|
-
const
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
function hasModule(module) {
|
|
595
|
-
return modules.includes(module);
|
|
596
|
-
}
|
|
597
|
-
|
|
598
|
-
function addModule(module) {
|
|
599
|
-
modules.push(module);
|
|
600
|
-
}
|
|
601
|
-
|
|
602
|
-
function visit(module) {
|
|
603
|
-
if (hasModule(module)) {
|
|
604
|
-
return;
|
|
605
|
-
}
|
|
606
|
-
|
|
607
|
-
(module.__depends__ || []).forEach(visit);
|
|
608
|
-
|
|
609
|
-
if (hasModule(module)) {
|
|
610
|
-
return;
|
|
611
|
-
}
|
|
612
|
-
|
|
613
|
-
addModule(module);
|
|
614
|
-
(module.__init__ || []).forEach(function (component) {
|
|
615
|
-
components.push(component);
|
|
616
|
-
});
|
|
617
|
-
}
|
|
618
|
-
|
|
619
|
-
bootstrapModules.forEach(visit);
|
|
620
|
-
const injector = new Injector(modules);
|
|
621
|
-
components.forEach(function (component) {
|
|
622
|
-
try {
|
|
623
|
-
injector[typeof component === 'string' ? 'get' : 'invoke'](component);
|
|
624
|
-
} catch (err) {
|
|
625
|
-
console.error('Failed to instantiate component');
|
|
626
|
-
console.error(err.stack);
|
|
627
|
-
throw err;
|
|
628
|
-
}
|
|
629
|
-
});
|
|
592
|
+
const injector = new Injector(bootstrapModules);
|
|
593
|
+
injector.init();
|
|
630
594
|
return injector;
|
|
631
595
|
}
|
|
632
596
|
|
|
@@ -689,6 +653,41 @@ function generateIdForType(type) {
|
|
|
689
653
|
function clone(data, replacer) {
|
|
690
654
|
return JSON.parse(JSON.stringify(data, replacer));
|
|
691
655
|
}
|
|
656
|
+
/**
|
|
657
|
+
* Parse the schema for input variables a form might make use of
|
|
658
|
+
*
|
|
659
|
+
* @param {any} schema
|
|
660
|
+
*
|
|
661
|
+
* @return {string[]}
|
|
662
|
+
*/
|
|
663
|
+
|
|
664
|
+
function getSchemaVariables(schema) {
|
|
665
|
+
if (!schema.components) {
|
|
666
|
+
return [];
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
return schema.components.reduce((variables, component) => {
|
|
670
|
+
const {
|
|
671
|
+
key,
|
|
672
|
+
valuesKey,
|
|
673
|
+
type
|
|
674
|
+
} = component;
|
|
675
|
+
|
|
676
|
+
if (['text', 'button'].includes(type)) {
|
|
677
|
+
return variables;
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
if (key) {
|
|
681
|
+
variables = [...variables, key];
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
if (valuesKey && !variables.includes(valuesKey)) {
|
|
685
|
+
variables = [...variables, valuesKey];
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
return variables;
|
|
689
|
+
}, []);
|
|
690
|
+
}
|
|
692
691
|
|
|
693
692
|
class Importer {
|
|
694
693
|
/**
|
|
@@ -713,7 +712,7 @@ class Importer {
|
|
|
713
712
|
|
|
714
713
|
|
|
715
714
|
importSchema(schema, data = {}) {
|
|
716
|
-
// TODO: Add warnings
|
|
715
|
+
// TODO: Add warnings - https://github.com/bpmn-io/form-js/issues/289
|
|
717
716
|
const warnings = [];
|
|
718
717
|
|
|
719
718
|
try {
|
|
@@ -807,19 +806,39 @@ class Importer {
|
|
|
807
806
|
const {
|
|
808
807
|
defaultValue,
|
|
809
808
|
_path,
|
|
810
|
-
type
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
809
|
+
type,
|
|
810
|
+
valuesKey
|
|
811
|
+
} = formField; // get values defined via valuesKey
|
|
812
|
+
|
|
813
|
+
if (valuesKey) {
|
|
814
|
+
importedData = { ...importedData,
|
|
815
|
+
[valuesKey]: get(data, [valuesKey])
|
|
816
|
+
};
|
|
817
|
+
} // try to get value from data
|
|
818
|
+
// if unavailable - try to get default value from form field
|
|
819
|
+
// if unavailable - get empty value from form field
|
|
820
|
+
|
|
821
|
+
|
|
822
|
+
if (_path) {
|
|
823
|
+
const fieldImplementation = this._formFields.get(type);
|
|
824
|
+
|
|
825
|
+
let valueData = get(data, _path);
|
|
826
|
+
|
|
827
|
+
if (!isUndefined(valueData) && fieldImplementation.sanitizeValue) {
|
|
828
|
+
valueData = fieldImplementation.sanitizeValue({
|
|
829
|
+
formField,
|
|
830
|
+
data,
|
|
831
|
+
value: valueData
|
|
832
|
+
});
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
const initialFieldValue = !isUndefined(valueData) ? valueData : !isUndefined(defaultValue) ? defaultValue : fieldImplementation.emptyValue;
|
|
836
|
+
importedData = { ...importedData,
|
|
837
|
+
[_path[0]]: initialFieldValue
|
|
838
|
+
};
|
|
839
|
+
}
|
|
819
840
|
|
|
820
|
-
return
|
|
821
|
-
[_path[0]]: get(data, _path, isUndefined(defaultValue) ? this._formFields.get(type).emptyValue : defaultValue)
|
|
822
|
-
};
|
|
841
|
+
return importedData;
|
|
823
842
|
}, {});
|
|
824
843
|
}
|
|
825
844
|
|
|
@@ -979,8 +998,48 @@ function safeMarkdown(markdown) {
|
|
|
979
998
|
const html = markdownToHTML(markdown);
|
|
980
999
|
return sanitizeHTML(html);
|
|
981
1000
|
}
|
|
1001
|
+
function sanitizeSingleSelectValue(options) {
|
|
1002
|
+
const {
|
|
1003
|
+
formField,
|
|
1004
|
+
data,
|
|
1005
|
+
value
|
|
1006
|
+
} = options;
|
|
1007
|
+
const {
|
|
1008
|
+
valuesKey,
|
|
1009
|
+
values
|
|
1010
|
+
} = formField;
|
|
1011
|
+
|
|
1012
|
+
try {
|
|
1013
|
+
const validValues = (valuesKey ? get(data, [valuesKey]) : values).map(v => v.value) || [];
|
|
1014
|
+
return validValues.includes(value) ? value : null;
|
|
1015
|
+
} catch (error) {
|
|
1016
|
+
// use default value in case of formatting error
|
|
1017
|
+
// TODO(@Skaiir): log a warning when this happens - https://github.com/bpmn-io/form-js/issues/289
|
|
1018
|
+
return null;
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
1021
|
+
function sanitizeMultiSelectValue(options) {
|
|
1022
|
+
const {
|
|
1023
|
+
formField,
|
|
1024
|
+
data,
|
|
1025
|
+
value
|
|
1026
|
+
} = options;
|
|
1027
|
+
const {
|
|
1028
|
+
valuesKey,
|
|
1029
|
+
values
|
|
1030
|
+
} = formField;
|
|
1031
|
+
|
|
1032
|
+
try {
|
|
1033
|
+
const validValues = (valuesKey ? get(data, [valuesKey]) : values).map(v => v.value) || [];
|
|
1034
|
+
return value.filter(v => validValues.includes(v));
|
|
1035
|
+
} catch (error) {
|
|
1036
|
+
// use default value in case of formatting error
|
|
1037
|
+
// TODO(@Skaiir): log a warning when this happens - https://github.com/bpmn-io/form-js/issues/289
|
|
1038
|
+
return [];
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
982
1041
|
|
|
983
|
-
const type$
|
|
1042
|
+
const type$8 = 'button';
|
|
984
1043
|
function Button(props) {
|
|
985
1044
|
const {
|
|
986
1045
|
disabled,
|
|
@@ -990,7 +1049,7 @@ function Button(props) {
|
|
|
990
1049
|
action = 'submit'
|
|
991
1050
|
} = field;
|
|
992
1051
|
return jsx("div", {
|
|
993
|
-
class: formFieldClasses(type$
|
|
1052
|
+
class: formFieldClasses(type$8),
|
|
994
1053
|
children: jsx("button", {
|
|
995
1054
|
class: "fjs-button",
|
|
996
1055
|
type: action,
|
|
@@ -1007,7 +1066,7 @@ Button.create = function (options = {}) {
|
|
|
1007
1066
|
};
|
|
1008
1067
|
};
|
|
1009
1068
|
|
|
1010
|
-
Button.type = type$
|
|
1069
|
+
Button.type = type$8;
|
|
1011
1070
|
Button.label = 'Button';
|
|
1012
1071
|
Button.keyed = true;
|
|
1013
1072
|
|
|
@@ -1089,7 +1148,7 @@ function Label(props) {
|
|
|
1089
1148
|
});
|
|
1090
1149
|
}
|
|
1091
1150
|
|
|
1092
|
-
const type$
|
|
1151
|
+
const type$7 = 'checkbox';
|
|
1093
1152
|
function Checkbox(props) {
|
|
1094
1153
|
const {
|
|
1095
1154
|
disabled,
|
|
@@ -1116,7 +1175,7 @@ function Checkbox(props) {
|
|
|
1116
1175
|
formId
|
|
1117
1176
|
} = useContext(FormContext);
|
|
1118
1177
|
return jsxs("div", {
|
|
1119
|
-
class: formFieldClasses(type$
|
|
1178
|
+
class: formFieldClasses(type$7, errors),
|
|
1120
1179
|
children: [jsx(Label, {
|
|
1121
1180
|
id: prefixId(id, formId),
|
|
1122
1181
|
label: label,
|
|
@@ -1142,11 +1201,15 @@ Checkbox.create = function (options = {}) {
|
|
|
1142
1201
|
};
|
|
1143
1202
|
};
|
|
1144
1203
|
|
|
1145
|
-
Checkbox.type = type$
|
|
1204
|
+
Checkbox.type = type$7;
|
|
1146
1205
|
Checkbox.label = 'Checkbox';
|
|
1147
1206
|
Checkbox.keyed = true;
|
|
1148
1207
|
Checkbox.emptyValue = false;
|
|
1149
1208
|
|
|
1209
|
+
Checkbox.sanitizeValue = ({
|
|
1210
|
+
value
|
|
1211
|
+
}) => value === true;
|
|
1212
|
+
|
|
1150
1213
|
function useService (type, strict) {
|
|
1151
1214
|
const {
|
|
1152
1215
|
getService
|
|
@@ -1154,6 +1217,153 @@ function useService (type, strict) {
|
|
|
1154
1217
|
return getService(type, strict);
|
|
1155
1218
|
}
|
|
1156
1219
|
|
|
1220
|
+
/**
|
|
1221
|
+
* @enum { String }
|
|
1222
|
+
*/
|
|
1223
|
+
|
|
1224
|
+
const LOAD_STATES = {
|
|
1225
|
+
LOADING: 'loading',
|
|
1226
|
+
LOADED: 'loaded',
|
|
1227
|
+
ERROR: 'error'
|
|
1228
|
+
};
|
|
1229
|
+
/**
|
|
1230
|
+
* @typedef {Object} ValuesGetter
|
|
1231
|
+
* @property {Object[]} values - The values data
|
|
1232
|
+
* @property {(LOAD_STATES)} state - The values data's loading state, to use for conditional rendering
|
|
1233
|
+
*/
|
|
1234
|
+
|
|
1235
|
+
/**
|
|
1236
|
+
* A hook to load values for single and multiselect components.
|
|
1237
|
+
*
|
|
1238
|
+
* @param {Object} field - The form field to handle values for
|
|
1239
|
+
* @return {ValuesGetter} valuesGetter - A values getter object providing loading state and values
|
|
1240
|
+
*/
|
|
1241
|
+
|
|
1242
|
+
function useValuesAsync (field) {
|
|
1243
|
+
const {
|
|
1244
|
+
valuesKey,
|
|
1245
|
+
values: staticValues
|
|
1246
|
+
} = field;
|
|
1247
|
+
const [valuesGetter, setValuesGetter] = useState({
|
|
1248
|
+
values: [],
|
|
1249
|
+
error: undefined,
|
|
1250
|
+
state: LOAD_STATES.LOADING
|
|
1251
|
+
});
|
|
1252
|
+
|
|
1253
|
+
const initialData = useService('form')._getState().initialData;
|
|
1254
|
+
|
|
1255
|
+
useEffect(() => {
|
|
1256
|
+
let values = [];
|
|
1257
|
+
|
|
1258
|
+
if (valuesKey !== undefined) {
|
|
1259
|
+
const keyedValues = (initialData || {})[valuesKey];
|
|
1260
|
+
|
|
1261
|
+
if (keyedValues && Array.isArray(keyedValues)) {
|
|
1262
|
+
values = keyedValues;
|
|
1263
|
+
}
|
|
1264
|
+
} else if (staticValues !== undefined) {
|
|
1265
|
+
values = Array.isArray(staticValues) ? staticValues : [];
|
|
1266
|
+
} else {
|
|
1267
|
+
setValuesGetter(getErrorState('No values source defined in the form definition'));
|
|
1268
|
+
return;
|
|
1269
|
+
}
|
|
1270
|
+
|
|
1271
|
+
setValuesGetter(buildLoadedState(values));
|
|
1272
|
+
}, [valuesKey, staticValues, initialData]);
|
|
1273
|
+
return valuesGetter;
|
|
1274
|
+
}
|
|
1275
|
+
|
|
1276
|
+
const getErrorState = error => ({
|
|
1277
|
+
values: [],
|
|
1278
|
+
error,
|
|
1279
|
+
state: LOAD_STATES.ERROR
|
|
1280
|
+
});
|
|
1281
|
+
|
|
1282
|
+
const buildLoadedState = values => ({
|
|
1283
|
+
values,
|
|
1284
|
+
error: undefined,
|
|
1285
|
+
state: LOAD_STATES.LOADED
|
|
1286
|
+
});
|
|
1287
|
+
|
|
1288
|
+
const type$6 = 'checklist';
|
|
1289
|
+
function Checklist(props) {
|
|
1290
|
+
const {
|
|
1291
|
+
disabled,
|
|
1292
|
+
errors = [],
|
|
1293
|
+
field,
|
|
1294
|
+
value = []
|
|
1295
|
+
} = props;
|
|
1296
|
+
const {
|
|
1297
|
+
description,
|
|
1298
|
+
id,
|
|
1299
|
+
label
|
|
1300
|
+
} = field;
|
|
1301
|
+
|
|
1302
|
+
const toggleCheckbox = v => {
|
|
1303
|
+
let newValue = [...value];
|
|
1304
|
+
|
|
1305
|
+
if (!newValue.includes(v)) {
|
|
1306
|
+
newValue.push(v);
|
|
1307
|
+
} else {
|
|
1308
|
+
newValue = newValue.filter(x => x != v);
|
|
1309
|
+
}
|
|
1310
|
+
|
|
1311
|
+
props.onChange({
|
|
1312
|
+
field,
|
|
1313
|
+
value: newValue
|
|
1314
|
+
});
|
|
1315
|
+
};
|
|
1316
|
+
|
|
1317
|
+
const {
|
|
1318
|
+
state: loadState,
|
|
1319
|
+
values: options
|
|
1320
|
+
} = useValuesAsync(field);
|
|
1321
|
+
const {
|
|
1322
|
+
formId
|
|
1323
|
+
} = useContext(FormContext);
|
|
1324
|
+
return jsxs("div", {
|
|
1325
|
+
class: formFieldClasses(type$6, errors),
|
|
1326
|
+
children: [jsx(Label, {
|
|
1327
|
+
label: label
|
|
1328
|
+
}), loadState == LOAD_STATES.LOADED && options.map((v, index) => {
|
|
1329
|
+
return jsx(Label, {
|
|
1330
|
+
id: prefixId(`${id}-${index}`, formId),
|
|
1331
|
+
label: v.label,
|
|
1332
|
+
required: false,
|
|
1333
|
+
children: jsx("input", {
|
|
1334
|
+
checked: value.includes(v.value),
|
|
1335
|
+
class: "fjs-input",
|
|
1336
|
+
disabled: disabled,
|
|
1337
|
+
id: prefixId(`${id}-${index}`, formId),
|
|
1338
|
+
type: "checkbox",
|
|
1339
|
+
onClick: () => toggleCheckbox(v.value)
|
|
1340
|
+
})
|
|
1341
|
+
}, `${id}-${index}`);
|
|
1342
|
+
}), jsx(Description, {
|
|
1343
|
+
description: description
|
|
1344
|
+
}), jsx(Errors, {
|
|
1345
|
+
errors: errors
|
|
1346
|
+
})]
|
|
1347
|
+
});
|
|
1348
|
+
}
|
|
1349
|
+
|
|
1350
|
+
Checklist.create = function (options = {}) {
|
|
1351
|
+
if (options.valuesKey) return options;
|
|
1352
|
+
return {
|
|
1353
|
+
values: [{
|
|
1354
|
+
label: 'Value',
|
|
1355
|
+
value: 'value'
|
|
1356
|
+
}],
|
|
1357
|
+
...options
|
|
1358
|
+
};
|
|
1359
|
+
};
|
|
1360
|
+
|
|
1361
|
+
Checklist.type = type$6;
|
|
1362
|
+
Checklist.label = 'Checklist';
|
|
1363
|
+
Checklist.keyed = true;
|
|
1364
|
+
Checklist.emptyValue = [];
|
|
1365
|
+
Checklist.sanitizeValue = sanitizeMultiSelectValue;
|
|
1366
|
+
|
|
1157
1367
|
const noop$1 = () => false;
|
|
1158
1368
|
|
|
1159
1369
|
function FormField(props) {
|
|
@@ -1362,7 +1572,7 @@ function FormComponent(props) {
|
|
|
1362
1572
|
});
|
|
1363
1573
|
}
|
|
1364
1574
|
|
|
1365
|
-
const type$
|
|
1575
|
+
const type$5 = 'number';
|
|
1366
1576
|
function Number(props) {
|
|
1367
1577
|
const {
|
|
1368
1578
|
disabled,
|
|
@@ -1383,10 +1593,11 @@ function Number(props) {
|
|
|
1383
1593
|
const onChange = ({
|
|
1384
1594
|
target
|
|
1385
1595
|
}) => {
|
|
1386
|
-
const parsedValue = parseInt(target.value, 10);
|
|
1387
1596
|
props.onChange({
|
|
1388
1597
|
field,
|
|
1389
|
-
value:
|
|
1598
|
+
value: Number.sanitizeValue({
|
|
1599
|
+
value: target.value
|
|
1600
|
+
})
|
|
1390
1601
|
});
|
|
1391
1602
|
};
|
|
1392
1603
|
|
|
@@ -1394,7 +1605,7 @@ function Number(props) {
|
|
|
1394
1605
|
formId
|
|
1395
1606
|
} = useContext(FormContext);
|
|
1396
1607
|
return jsxs("div", {
|
|
1397
|
-
class: formFieldClasses(type$
|
|
1608
|
+
class: formFieldClasses(type$5, errors),
|
|
1398
1609
|
children: [jsx(Label, {
|
|
1399
1610
|
id: prefixId(id, formId),
|
|
1400
1611
|
label: label,
|
|
@@ -1419,12 +1630,19 @@ Number.create = function (options = {}) {
|
|
|
1419
1630
|
};
|
|
1420
1631
|
};
|
|
1421
1632
|
|
|
1422
|
-
Number.
|
|
1633
|
+
Number.sanitizeValue = ({
|
|
1634
|
+
value
|
|
1635
|
+
}) => {
|
|
1636
|
+
const parsedValue = parseInt(value, 10);
|
|
1637
|
+
return isNaN(parsedValue) ? null : parsedValue;
|
|
1638
|
+
};
|
|
1639
|
+
|
|
1640
|
+
Number.type = type$5;
|
|
1423
1641
|
Number.keyed = true;
|
|
1424
1642
|
Number.label = 'Number';
|
|
1425
1643
|
Number.emptyValue = null;
|
|
1426
1644
|
|
|
1427
|
-
const type$
|
|
1645
|
+
const type$4 = 'radio';
|
|
1428
1646
|
function Radio(props) {
|
|
1429
1647
|
const {
|
|
1430
1648
|
disabled,
|
|
@@ -1436,8 +1654,7 @@ function Radio(props) {
|
|
|
1436
1654
|
description,
|
|
1437
1655
|
id,
|
|
1438
1656
|
label,
|
|
1439
|
-
validate = {}
|
|
1440
|
-
values
|
|
1657
|
+
validate = {}
|
|
1441
1658
|
} = field;
|
|
1442
1659
|
const {
|
|
1443
1660
|
required
|
|
@@ -1450,26 +1667,30 @@ function Radio(props) {
|
|
|
1450
1667
|
});
|
|
1451
1668
|
};
|
|
1452
1669
|
|
|
1670
|
+
const {
|
|
1671
|
+
state: loadState,
|
|
1672
|
+
values: options
|
|
1673
|
+
} = useValuesAsync(field);
|
|
1453
1674
|
const {
|
|
1454
1675
|
formId
|
|
1455
1676
|
} = useContext(FormContext);
|
|
1456
1677
|
return jsxs("div", {
|
|
1457
|
-
class: formFieldClasses(type$
|
|
1678
|
+
class: formFieldClasses(type$4, errors),
|
|
1458
1679
|
children: [jsx(Label, {
|
|
1459
1680
|
label: label,
|
|
1460
1681
|
required: required
|
|
1461
|
-
}),
|
|
1682
|
+
}), loadState == LOAD_STATES.LOADED && options.map((option, index) => {
|
|
1462
1683
|
return jsx(Label, {
|
|
1463
1684
|
id: prefixId(`${id}-${index}`, formId),
|
|
1464
|
-
label:
|
|
1685
|
+
label: option.label,
|
|
1465
1686
|
required: false,
|
|
1466
1687
|
children: jsx("input", {
|
|
1467
|
-
checked:
|
|
1688
|
+
checked: option.value === value,
|
|
1468
1689
|
class: "fjs-input",
|
|
1469
1690
|
disabled: disabled,
|
|
1470
1691
|
id: prefixId(`${id}-${index}`, formId),
|
|
1471
1692
|
type: "radio",
|
|
1472
|
-
onClick: () => onChange(
|
|
1693
|
+
onClick: () => onChange(option.value)
|
|
1473
1694
|
})
|
|
1474
1695
|
}, `${id}-${index}`);
|
|
1475
1696
|
}), jsx(Description, {
|
|
@@ -1481,6 +1702,7 @@ function Radio(props) {
|
|
|
1481
1702
|
}
|
|
1482
1703
|
|
|
1483
1704
|
Radio.create = function (options = {}) {
|
|
1705
|
+
if (options.valuesKey) return options;
|
|
1484
1706
|
return {
|
|
1485
1707
|
values: [{
|
|
1486
1708
|
label: 'Value',
|
|
@@ -1490,12 +1712,13 @@ Radio.create = function (options = {}) {
|
|
|
1490
1712
|
};
|
|
1491
1713
|
};
|
|
1492
1714
|
|
|
1493
|
-
Radio.type = type$
|
|
1715
|
+
Radio.type = type$4;
|
|
1494
1716
|
Radio.label = 'Radio';
|
|
1495
1717
|
Radio.keyed = true;
|
|
1496
1718
|
Radio.emptyValue = null;
|
|
1719
|
+
Radio.sanitizeValue = sanitizeSingleSelectValue;
|
|
1497
1720
|
|
|
1498
|
-
const type$
|
|
1721
|
+
const type$3 = 'select';
|
|
1499
1722
|
function Select(props) {
|
|
1500
1723
|
const {
|
|
1501
1724
|
disabled,
|
|
@@ -1507,8 +1730,7 @@ function Select(props) {
|
|
|
1507
1730
|
description,
|
|
1508
1731
|
id,
|
|
1509
1732
|
label,
|
|
1510
|
-
validate = {}
|
|
1511
|
-
values
|
|
1733
|
+
validate = {}
|
|
1512
1734
|
} = field;
|
|
1513
1735
|
const {
|
|
1514
1736
|
required
|
|
@@ -1523,11 +1745,15 @@ function Select(props) {
|
|
|
1523
1745
|
});
|
|
1524
1746
|
};
|
|
1525
1747
|
|
|
1748
|
+
const {
|
|
1749
|
+
state: loadState,
|
|
1750
|
+
values: options
|
|
1751
|
+
} = useValuesAsync(field);
|
|
1526
1752
|
const {
|
|
1527
1753
|
formId
|
|
1528
1754
|
} = useContext(FormContext);
|
|
1529
1755
|
return jsxs("div", {
|
|
1530
|
-
class: formFieldClasses(type$
|
|
1756
|
+
class: formFieldClasses(type$3, errors),
|
|
1531
1757
|
children: [jsx(Label, {
|
|
1532
1758
|
id: prefixId(id, formId),
|
|
1533
1759
|
label: label,
|
|
@@ -1540,10 +1766,10 @@ function Select(props) {
|
|
|
1540
1766
|
value: value || '',
|
|
1541
1767
|
children: [jsx("option", {
|
|
1542
1768
|
value: ""
|
|
1543
|
-
}),
|
|
1769
|
+
}), loadState == LOAD_STATES.LOADED && options.map((option, index) => {
|
|
1544
1770
|
return jsx("option", {
|
|
1545
|
-
value:
|
|
1546
|
-
children:
|
|
1771
|
+
value: option.value,
|
|
1772
|
+
children: option.label
|
|
1547
1773
|
}, `${id}-${index}`);
|
|
1548
1774
|
})]
|
|
1549
1775
|
}), jsx(Description, {
|
|
@@ -1555,6 +1781,7 @@ function Select(props) {
|
|
|
1555
1781
|
}
|
|
1556
1782
|
|
|
1557
1783
|
Select.create = function (options = {}) {
|
|
1784
|
+
if (options.valuesKey) return options;
|
|
1558
1785
|
return {
|
|
1559
1786
|
values: [{
|
|
1560
1787
|
label: 'Value',
|
|
@@ -1564,10 +1791,314 @@ Select.create = function (options = {}) {
|
|
|
1564
1791
|
};
|
|
1565
1792
|
};
|
|
1566
1793
|
|
|
1567
|
-
Select.type = type$
|
|
1794
|
+
Select.type = type$3;
|
|
1568
1795
|
Select.label = 'Select';
|
|
1569
1796
|
Select.keyed = true;
|
|
1570
1797
|
Select.emptyValue = null;
|
|
1798
|
+
Select.sanitizeValue = sanitizeSingleSelectValue;
|
|
1799
|
+
|
|
1800
|
+
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); }
|
|
1801
|
+
var CloseIcon = (({
|
|
1802
|
+
styles = {},
|
|
1803
|
+
...props
|
|
1804
|
+
}) => /*#__PURE__*/React.createElement("svg", _extends({
|
|
1805
|
+
width: "16",
|
|
1806
|
+
height: "16",
|
|
1807
|
+
fill: "none",
|
|
1808
|
+
xmlns: "http://www.w3.org/2000/svg"
|
|
1809
|
+
}, props), /*#__PURE__*/React.createElement("path", {
|
|
1810
|
+
fillRule: "evenodd",
|
|
1811
|
+
clipRule: "evenodd",
|
|
1812
|
+
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",
|
|
1813
|
+
fill: "#000"
|
|
1814
|
+
})));
|
|
1815
|
+
|
|
1816
|
+
function useKeyDownAction(targetKey, action, listenerElement = window) {
|
|
1817
|
+
function downHandler({
|
|
1818
|
+
key
|
|
1819
|
+
}) {
|
|
1820
|
+
if (key === targetKey) {
|
|
1821
|
+
action();
|
|
1822
|
+
}
|
|
1823
|
+
}
|
|
1824
|
+
|
|
1825
|
+
useEffect(() => {
|
|
1826
|
+
listenerElement.addEventListener('keydown', downHandler);
|
|
1827
|
+
return () => {
|
|
1828
|
+
listenerElement.removeEventListener('keydown', downHandler);
|
|
1829
|
+
};
|
|
1830
|
+
});
|
|
1831
|
+
}
|
|
1832
|
+
|
|
1833
|
+
const DEFAULT_LABEL_GETTER = value => value;
|
|
1834
|
+
|
|
1835
|
+
const NOOP = () => {};
|
|
1836
|
+
|
|
1837
|
+
function DropdownList(props) {
|
|
1838
|
+
const {
|
|
1839
|
+
keyEventsListener = window,
|
|
1840
|
+
values = [],
|
|
1841
|
+
getLabel = DEFAULT_LABEL_GETTER,
|
|
1842
|
+
onValueSelected = NOOP,
|
|
1843
|
+
height = 235,
|
|
1844
|
+
emptyListMessage = 'No results'
|
|
1845
|
+
} = props;
|
|
1846
|
+
const [mouseControl, setMouseControl] = useState(true);
|
|
1847
|
+
const [focusedValueIndex, setFocusedValueIndex] = useState(0);
|
|
1848
|
+
const dropdownContainer = useRef();
|
|
1849
|
+
const mouseScreenPos = useRef();
|
|
1850
|
+
const focusedItem = useMemo(() => values.length ? values[focusedValueIndex] : null, [focusedValueIndex, values]);
|
|
1851
|
+
const changeFocusedValueIndex = useCallback(delta => {
|
|
1852
|
+
setFocusedValueIndex(x => Math.min(Math.max(0, x + delta), values.length - 1));
|
|
1853
|
+
}, [values.length]);
|
|
1854
|
+
useEffect(() => {
|
|
1855
|
+
if (focusedValueIndex === 0) return;
|
|
1856
|
+
|
|
1857
|
+
if (!focusedValueIndex || !values.length) {
|
|
1858
|
+
setFocusedValueIndex(0);
|
|
1859
|
+
} else if (focusedValueIndex >= values.length) {
|
|
1860
|
+
setFocusedValueIndex(values.length - 1);
|
|
1861
|
+
}
|
|
1862
|
+
}, [focusedValueIndex, values.length]);
|
|
1863
|
+
useKeyDownAction('ArrowUp', () => {
|
|
1864
|
+
if (values.length) {
|
|
1865
|
+
changeFocusedValueIndex(-1);
|
|
1866
|
+
setMouseControl(false);
|
|
1867
|
+
}
|
|
1868
|
+
}, keyEventsListener);
|
|
1869
|
+
useKeyDownAction('ArrowDown', () => {
|
|
1870
|
+
if (values.length) {
|
|
1871
|
+
changeFocusedValueIndex(1);
|
|
1872
|
+
setMouseControl(false);
|
|
1873
|
+
}
|
|
1874
|
+
}, keyEventsListener);
|
|
1875
|
+
useKeyDownAction('Enter', () => {
|
|
1876
|
+
if (focusedItem) {
|
|
1877
|
+
onValueSelected(focusedItem);
|
|
1878
|
+
}
|
|
1879
|
+
}, keyEventsListener);
|
|
1880
|
+
useEffect(() => {
|
|
1881
|
+
const individualEntries = dropdownContainer.current.children;
|
|
1882
|
+
|
|
1883
|
+
if (individualEntries.length && !mouseControl) {
|
|
1884
|
+
individualEntries[focusedValueIndex].scrollIntoView({
|
|
1885
|
+
block: 'nearest',
|
|
1886
|
+
inline: 'nearest'
|
|
1887
|
+
});
|
|
1888
|
+
}
|
|
1889
|
+
}, [focusedValueIndex, mouseControl]);
|
|
1890
|
+
|
|
1891
|
+
const mouseMove = (e, i) => {
|
|
1892
|
+
const userMoved = !mouseScreenPos.current || mouseScreenPos.current.x !== e.screenX && mouseScreenPos.current.y !== e.screenY;
|
|
1893
|
+
|
|
1894
|
+
if (userMoved) {
|
|
1895
|
+
mouseScreenPos.current = {
|
|
1896
|
+
x: e.screenX,
|
|
1897
|
+
y: e.screenY
|
|
1898
|
+
};
|
|
1899
|
+
|
|
1900
|
+
if (!mouseControl) {
|
|
1901
|
+
setMouseControl(true);
|
|
1902
|
+
setFocusedValueIndex(i);
|
|
1903
|
+
}
|
|
1904
|
+
}
|
|
1905
|
+
};
|
|
1906
|
+
|
|
1907
|
+
return jsxs("div", {
|
|
1908
|
+
ref: dropdownContainer,
|
|
1909
|
+
tabIndex: -1,
|
|
1910
|
+
class: "fjs-dropdownlist",
|
|
1911
|
+
style: {
|
|
1912
|
+
maxHeight: height
|
|
1913
|
+
},
|
|
1914
|
+
children: [!!values.length && values.map((v, i) => {
|
|
1915
|
+
return jsx("div", {
|
|
1916
|
+
class: 'fjs-dropdownlist-item' + (focusedValueIndex === i ? ' focused' : ''),
|
|
1917
|
+
onMouseMove: e => mouseMove(e, i),
|
|
1918
|
+
onMouseEnter: mouseControl ? () => setFocusedValueIndex(i) : undefined,
|
|
1919
|
+
onMouseDown: e => {
|
|
1920
|
+
e.preventDefault();
|
|
1921
|
+
onValueSelected(v);
|
|
1922
|
+
},
|
|
1923
|
+
children: getLabel(v)
|
|
1924
|
+
});
|
|
1925
|
+
}), !values.length && jsx("div", {
|
|
1926
|
+
class: "fjs-dropdownlist-empty",
|
|
1927
|
+
children: emptyListMessage
|
|
1928
|
+
})]
|
|
1929
|
+
});
|
|
1930
|
+
}
|
|
1931
|
+
|
|
1932
|
+
const type$2 = 'taglist';
|
|
1933
|
+
function Taglist(props) {
|
|
1934
|
+
const {
|
|
1935
|
+
disabled,
|
|
1936
|
+
errors = [],
|
|
1937
|
+
field,
|
|
1938
|
+
value: values = []
|
|
1939
|
+
} = props;
|
|
1940
|
+
const {
|
|
1941
|
+
description,
|
|
1942
|
+
id,
|
|
1943
|
+
label
|
|
1944
|
+
} = field;
|
|
1945
|
+
const {
|
|
1946
|
+
formId
|
|
1947
|
+
} = useContext(FormContext);
|
|
1948
|
+
const [filter, setFilter] = useState('');
|
|
1949
|
+
const [selectedValues, setSelectedValues] = useState([]);
|
|
1950
|
+
const [filteredValues, setFilteredValues] = useState([]);
|
|
1951
|
+
const [isDropdownExpanded, setIsDropdownExpanded] = useState(false);
|
|
1952
|
+
const [hasValuesLeft, setHasValuesLeft] = useState(true);
|
|
1953
|
+
const [isEscapeClosed, setIsEscapeClose] = useState(false);
|
|
1954
|
+
const searchbarRef = useRef();
|
|
1955
|
+
const {
|
|
1956
|
+
state: loadState,
|
|
1957
|
+
values: options
|
|
1958
|
+
} = 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
|
|
1959
|
+
|
|
1960
|
+
useEffect(() => {
|
|
1961
|
+
if (loadState === LOAD_STATES.LOADED) {
|
|
1962
|
+
const selectedValues = values.map(v => options.find(o => o.value === v)).filter(v => v !== undefined);
|
|
1963
|
+
setSelectedValues(selectedValues);
|
|
1964
|
+
} else {
|
|
1965
|
+
setSelectedValues([]);
|
|
1966
|
+
}
|
|
1967
|
+
}, [JSON.stringify(values), options, loadState]);
|
|
1968
|
+
useEffect(() => {
|
|
1969
|
+
if (loadState === LOAD_STATES.LOADED) {
|
|
1970
|
+
setFilteredValues(options.filter(o => o.label && o.value && o.label.toLowerCase().includes(filter.toLowerCase()) && !values.includes(o.value)));
|
|
1971
|
+
} else {
|
|
1972
|
+
setFilteredValues([]);
|
|
1973
|
+
}
|
|
1974
|
+
}, [filter, JSON.stringify(values), options]);
|
|
1975
|
+
useEffect(() => {
|
|
1976
|
+
setHasValuesLeft(selectedValues.length < options.length);
|
|
1977
|
+
}, [selectedValues.length, options.length]);
|
|
1978
|
+
|
|
1979
|
+
const onFilterChange = ({
|
|
1980
|
+
target
|
|
1981
|
+
}) => {
|
|
1982
|
+
setIsEscapeClose(false);
|
|
1983
|
+
setFilter(target.value);
|
|
1984
|
+
};
|
|
1985
|
+
|
|
1986
|
+
const selectValue = option => {
|
|
1987
|
+
setFilter('');
|
|
1988
|
+
props.onChange({
|
|
1989
|
+
value: [...values, option.value],
|
|
1990
|
+
field
|
|
1991
|
+
});
|
|
1992
|
+
};
|
|
1993
|
+
|
|
1994
|
+
const deselectValue = option => {
|
|
1995
|
+
props.onChange({
|
|
1996
|
+
value: values.filter(v => v != option.value),
|
|
1997
|
+
field
|
|
1998
|
+
});
|
|
1999
|
+
};
|
|
2000
|
+
|
|
2001
|
+
const onInputKeyDown = e => {
|
|
2002
|
+
switch (e.key) {
|
|
2003
|
+
case 'ArrowUp':
|
|
2004
|
+
case 'ArrowDown':
|
|
2005
|
+
// We do not want the cursor to seek in the search field when we press up and down
|
|
2006
|
+
e.preventDefault();
|
|
2007
|
+
break;
|
|
2008
|
+
|
|
2009
|
+
case 'Backspace':
|
|
2010
|
+
if (!filter && selectedValues.length) {
|
|
2011
|
+
deselectValue(selectedValues[selectedValues.length - 1]);
|
|
2012
|
+
}
|
|
2013
|
+
|
|
2014
|
+
break;
|
|
2015
|
+
|
|
2016
|
+
case 'Escape':
|
|
2017
|
+
setIsEscapeClose(true);
|
|
2018
|
+
break;
|
|
2019
|
+
|
|
2020
|
+
case 'Enter':
|
|
2021
|
+
if (isEscapeClosed) {
|
|
2022
|
+
setIsEscapeClose(false);
|
|
2023
|
+
}
|
|
2024
|
+
|
|
2025
|
+
break;
|
|
2026
|
+
}
|
|
2027
|
+
};
|
|
2028
|
+
|
|
2029
|
+
return jsxs("div", {
|
|
2030
|
+
class: formFieldClasses(type$2, errors),
|
|
2031
|
+
children: [jsx(Label, {
|
|
2032
|
+
label: label,
|
|
2033
|
+
id: prefixId(id, formId)
|
|
2034
|
+
}), jsxs("div", {
|
|
2035
|
+
class: classNames('fjs-taglist', {
|
|
2036
|
+
'disabled': disabled
|
|
2037
|
+
}),
|
|
2038
|
+
children: [!disabled && loadState === LOAD_STATES.LOADED && selectedValues.map(sv => {
|
|
2039
|
+
return jsxs("div", {
|
|
2040
|
+
class: "fjs-taglist-tag",
|
|
2041
|
+
onMouseDown: e => e.preventDefault(),
|
|
2042
|
+
children: [jsx("span", {
|
|
2043
|
+
class: "fjs-taglist-tag-label",
|
|
2044
|
+
children: sv.label
|
|
2045
|
+
}), jsx("span", {
|
|
2046
|
+
class: "fjs-taglist-tag-remove",
|
|
2047
|
+
onMouseDown: () => deselectValue(sv),
|
|
2048
|
+
children: jsx(CloseIcon, {})
|
|
2049
|
+
})]
|
|
2050
|
+
});
|
|
2051
|
+
}), jsx("input", {
|
|
2052
|
+
disabled: disabled,
|
|
2053
|
+
class: "fjs-taglist-input",
|
|
2054
|
+
ref: searchbarRef,
|
|
2055
|
+
id: prefixId(`${id}-search`, formId),
|
|
2056
|
+
onChange: onFilterChange,
|
|
2057
|
+
type: "text",
|
|
2058
|
+
value: filter,
|
|
2059
|
+
placeholder: 'Search',
|
|
2060
|
+
autoComplete: "off",
|
|
2061
|
+
onKeyDown: e => onInputKeyDown(e),
|
|
2062
|
+
onMouseDown: () => setIsEscapeClose(false),
|
|
2063
|
+
onFocus: () => setIsDropdownExpanded(true),
|
|
2064
|
+
onBlur: () => {
|
|
2065
|
+
setIsDropdownExpanded(false);
|
|
2066
|
+
setFilter('');
|
|
2067
|
+
}
|
|
2068
|
+
})]
|
|
2069
|
+
}), jsx("div", {
|
|
2070
|
+
class: "fjs-taglist-anchor",
|
|
2071
|
+
children: !disabled && loadState === LOAD_STATES.LOADED && isDropdownExpanded && !isEscapeClosed && jsx(DropdownList, {
|
|
2072
|
+
values: filteredValues,
|
|
2073
|
+
getLabel: v => v.label,
|
|
2074
|
+
onValueSelected: v => selectValue(v),
|
|
2075
|
+
emptyListMessage: hasValuesLeft ? 'No results' : 'All values selected',
|
|
2076
|
+
listenerElement: searchbarRef.current
|
|
2077
|
+
})
|
|
2078
|
+
}), jsx(Description, {
|
|
2079
|
+
description: description
|
|
2080
|
+
}), jsx(Errors, {
|
|
2081
|
+
errors: errors
|
|
2082
|
+
})]
|
|
2083
|
+
});
|
|
2084
|
+
}
|
|
2085
|
+
|
|
2086
|
+
Taglist.create = function (options = {}) {
|
|
2087
|
+
if (options.valuesKey) return options;
|
|
2088
|
+
return {
|
|
2089
|
+
values: [{
|
|
2090
|
+
label: 'Value',
|
|
2091
|
+
value: 'value'
|
|
2092
|
+
}],
|
|
2093
|
+
...options
|
|
2094
|
+
};
|
|
2095
|
+
};
|
|
2096
|
+
|
|
2097
|
+
Taglist.type = type$2;
|
|
2098
|
+
Taglist.label = 'Taglist';
|
|
2099
|
+
Taglist.keyed = true;
|
|
2100
|
+
Taglist.emptyValue = [];
|
|
2101
|
+
Taglist.sanitizeValue = sanitizeMultiSelectValue;
|
|
1571
2102
|
|
|
1572
2103
|
const type$1 = 'text';
|
|
1573
2104
|
function Text(props) {
|
|
@@ -1657,7 +2188,11 @@ Textfield.label = 'Text Field';
|
|
|
1657
2188
|
Textfield.keyed = true;
|
|
1658
2189
|
Textfield.emptyValue = '';
|
|
1659
2190
|
|
|
1660
|
-
|
|
2191
|
+
Textfield.sanitizeValue = ({
|
|
2192
|
+
value
|
|
2193
|
+
}) => isArray(value) || isObject(value) ? null : String(value);
|
|
2194
|
+
|
|
2195
|
+
const formFields = [Button, Checkbox, Checklist, Default, Number, Radio, Select, Taglist, Text, Textfield];
|
|
1661
2196
|
|
|
1662
2197
|
class FormFields {
|
|
1663
2198
|
constructor() {
|
|
@@ -2144,7 +2679,7 @@ class Form {
|
|
|
2144
2679
|
|
|
2145
2680
|
}
|
|
2146
2681
|
|
|
2147
|
-
const schemaVersion =
|
|
2682
|
+
const schemaVersion = 5;
|
|
2148
2683
|
/**
|
|
2149
2684
|
* @typedef { import('./types').CreateFormOptions } CreateFormOptions
|
|
2150
2685
|
*/
|
|
@@ -2169,5 +2704,5 @@ function createForm(options) {
|
|
|
2169
2704
|
});
|
|
2170
2705
|
}
|
|
2171
2706
|
|
|
2172
|
-
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 };
|
|
2707
|
+
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, getSchemaVariables, isRequired, pathParse, pathStringify, pathsEqual, schemaVersion };
|
|
2173
2708
|
//# sourceMappingURL=index.es.js.map
|