@bpmn-io/form-js-viewer 1.1.0 → 1.3.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.
Files changed (32) hide show
  1. package/LICENSE +22 -22
  2. package/README.md +164 -164
  3. package/dist/assets/form-js-base.css +985 -932
  4. package/dist/assets/form-js.css +54 -1
  5. package/dist/index.cjs +1151 -745
  6. package/dist/index.cjs.map +1 -1
  7. package/dist/index.es.js +1147 -744
  8. package/dist/index.es.js.map +1 -1
  9. package/dist/types/Form.d.ts +4 -0
  10. package/dist/types/core/FieldFactory.d.ts +19 -0
  11. package/dist/types/core/FormFieldRegistry.d.ts +0 -1
  12. package/dist/types/core/Importer.d.ts +56 -0
  13. package/dist/types/core/PathRegistry.d.ts +71 -0
  14. package/dist/types/core/index.d.ts +9 -5
  15. package/dist/types/features/expression-language/ConditionChecker.d.ts +3 -2
  16. package/dist/types/index.d.ts +2 -2
  17. package/dist/types/render/components/form-fields/Checklist.d.ts +2 -6
  18. package/dist/types/render/components/form-fields/Default.d.ts +3 -3
  19. package/dist/types/render/components/form-fields/Group.d.ts +14 -0
  20. package/dist/types/render/components/form-fields/Radio.d.ts +2 -6
  21. package/dist/types/render/components/form-fields/Select.d.ts +2 -6
  22. package/dist/types/render/components/form-fields/Taglist.d.ts +2 -6
  23. package/dist/types/render/components/form-fields/parts/Grid.d.ts +1 -0
  24. package/dist/types/render/components/index.d.ts +3 -2
  25. package/dist/types/render/components/util/localisationUtil.d.ts +24 -0
  26. package/dist/types/render/components/util/valuesUtil.d.ts +6 -0
  27. package/dist/types/render/context/FormRenderContext.d.ts +3 -0
  28. package/dist/types/types.d.ts +35 -35
  29. package/dist/types/util/index.d.ts +1 -2
  30. package/package.json +3 -2
  31. package/dist/types/import/Importer.d.ts +0 -45
  32. package/dist/types/import/index.d.ts +0 -5
package/dist/index.es.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import Ids from 'ids';
2
- import { isString, uniqueBy, isArray, get, set, isFunction, isNumber, bind, assign, isNil, groupBy, flatten, isUndefined, findIndex, isObject } from 'min-dash';
2
+ import { isString, get, set, isObject, values, uniqueBy, isArray, isFunction, isNumber, bind, assign, isNil, groupBy, flatten, findIndex, isUndefined } from 'min-dash';
3
3
  import Big from 'big.js';
4
4
  import { parseExpression, parseUnaryTests, evaluate, unaryTest } from 'feelin';
5
5
  import { evaluate as evaluate$1, parser, buildSimpleTree } from 'feelers';
@@ -47,26 +47,26 @@ const getFlavouredFeelVariableNames = (feelString, feelFlavour = 'expression', o
47
47
  return [...new Set(variables)];
48
48
  };
49
49
 
50
- /**
51
- * Get the variable name at the specified index in a given path expression.
52
- *
53
- * @param {Object} root - The root node of the path expression tree.
54
- * @param {number} index - The index of the variable name to retrieve.
55
- * @returns {string|null} The variable name at the specified index or null if index is out of bounds.
50
+ /**
51
+ * Get the variable name at the specified index in a given path expression.
52
+ *
53
+ * @param {Object} root - The root node of the path expression tree.
54
+ * @param {number} index - The index of the variable name to retrieve.
55
+ * @returns {string|null} The variable name at the specified index or null if index is out of bounds.
56
56
  */
57
57
  const _getVariableNameAtPathIndex = (root, index) => {
58
58
  const accessors = _deconstructPathExpression(root);
59
59
  return accessors[index] || null;
60
60
  };
61
61
 
62
- /**
63
- * Extracts the variables which are required of the external context for a given path expression.
64
- * This is done by traversing the path expression tree and keeping track of the current depth relative to the external context.
65
- *
66
- * @param {Object} node - The root node of the path expression tree.
67
- * @param {number} initialDepth - The depth at which the root node is located in the outer context.
68
- * @param {Object} specialDepthAccessors - Definitions of special keywords which represent more complex accesses of the outer context.
69
- * @returns {Set} - A set containing the extracted variable names.
62
+ /**
63
+ * Extracts the variables which are required of the external context for a given path expression.
64
+ * This is done by traversing the path expression tree and keeping track of the current depth relative to the external context.
65
+ *
66
+ * @param {Object} node - The root node of the path expression tree.
67
+ * @param {number} initialDepth - The depth at which the root node is located in the outer context.
68
+ * @param {Object} specialDepthAccessors - Definitions of special keywords which represent more complex accesses of the outer context.
69
+ * @returns {Set} - A set containing the extracted variable names.
70
70
  */
71
71
  const _smartExtractVariableNames = (node, initialDepth, specialDepthAccessors) => {
72
72
  // depth info represents the previous (initialised as null) and current depth of the current accessor in the path expression
@@ -112,11 +112,11 @@ const _smartExtractVariableNames = (node, initialDepth, specialDepthAccessors) =
112
112
  return new Set(extractedVariables);
113
113
  };
114
114
 
115
- /**
116
- * Deconstructs a path expression tree into an array of components.
117
- *
118
- * @param {Object} root - The root node of the path expression tree.
119
- * @returns {Array<string>} An array of components in the path expression, in the correct order.
115
+ /**
116
+ * Deconstructs a path expression tree into an array of components.
117
+ *
118
+ * @param {Object} root - The root node of the path expression tree.
119
+ * @returns {Array<string>} An array of components in the path expression, in the correct order.
120
120
  */
121
121
  const _deconstructPathExpression = root => {
122
122
  let node = root;
@@ -135,13 +135,13 @@ const _deconstructPathExpression = root => {
135
135
  return parts.reverse();
136
136
  };
137
137
 
138
- /**
139
- * Builds a simplified feel structure tree from the given parse tree and feel string.
140
- * The nodes follow this structure: `{ name: string, children: Array, variableName?: string }`
141
- *
142
- * @param {Object} parseTree - The parse tree generated by a parser.
143
- * @param {string} feelString - The feel string used for parsing.
144
- * @returns {Object} The simplified feel structure tree.
138
+ /**
139
+ * Builds a simplified feel structure tree from the given parse tree and feel string.
140
+ * The nodes follow this structure: `{ name: string, children: Array, variableName?: string }`
141
+ *
142
+ * @param {Object} parseTree - The parse tree generated by a parser.
143
+ * @param {string} feelString - The feel string used for parsing.
144
+ * @returns {Object} The simplified feel structure tree.
145
145
  */
146
146
  const _buildSimpleFeelStructureTree = (parseTree, feelString) => {
147
147
  const stack = [{
@@ -167,9 +167,9 @@ const _buildSimpleFeelStructureTree = (parseTree, feelString) => {
167
167
  return _extractFilterExpressions(stack[0].children[0]);
168
168
  };
169
169
 
170
- /**
171
- * Restructure the tree in such a way to bring filters (which create new contexts) to the root of the tree.
172
- * This is done to simplify the extraction of variables and match the context hierarchy.
170
+ /**
171
+ * Restructure the tree in such a way to bring filters (which create new contexts) to the root of the tree.
172
+ * This is done to simplify the extraction of variables and match the context hierarchy.
173
173
  */
174
174
  const _extractFilterExpressions = tree => {
175
175
  const flattenedExpressionTree = {
@@ -210,25 +210,25 @@ class FeelExpressionLanguage {
210
210
  this._eventBus = eventBus;
211
211
  }
212
212
 
213
- /**
214
- * Determines if the given value is a FEEL expression.
215
- *
216
- * @param {any} value
217
- * @returns {boolean}
218
- *
213
+ /**
214
+ * Determines if the given value is a FEEL expression.
215
+ *
216
+ * @param {any} value
217
+ * @returns {boolean}
218
+ *
219
219
  */
220
220
  isExpression(value) {
221
221
  return isString(value) && value.startsWith('=');
222
222
  }
223
223
 
224
- /**
225
- * Retrieve variable names from a given FEEL expression.
226
- *
227
- * @param {string} expression
228
- * @param {object} [options]
229
- * @param {string} [options.type]
230
- *
231
- * @returns {string[]}
224
+ /**
225
+ * Retrieve variable names from a given FEEL expression.
226
+ *
227
+ * @param {string} expression
228
+ * @param {object} [options]
229
+ * @param {string} [options.type]
230
+ *
231
+ * @returns {string[]}
232
232
  */
233
233
  getVariableNames(expression, options = {}) {
234
234
  const {
@@ -243,13 +243,13 @@ class FeelExpressionLanguage {
243
243
  return getFlavouredFeelVariableNames(expression, type);
244
244
  }
245
245
 
246
- /**
247
- * Evaluate an expression.
248
- *
249
- * @param {string} expression
250
- * @param {import('../../types').Data} [data]
251
- *
252
- * @returns {any}
246
+ /**
247
+ * Evaluate an expression.
248
+ *
249
+ * @param {string} expression
250
+ * @param {import('../../types').Data} [data]
251
+ *
252
+ * @returns {any}
253
253
  */
254
254
  evaluate(expression, data = {}) {
255
255
  if (!expression) {
@@ -274,23 +274,23 @@ FeelExpressionLanguage.$inject = ['eventBus'];
274
274
  class FeelersTemplating {
275
275
  constructor() {}
276
276
 
277
- /**
278
- * Determines if the given value is a feelers template.
279
- *
280
- * @param {any} value
281
- * @returns {boolean}
282
- *
277
+ /**
278
+ * Determines if the given value is a feelers template.
279
+ *
280
+ * @param {any} value
281
+ * @returns {boolean}
282
+ *
283
283
  */
284
284
  isTemplate(value) {
285
285
  return isString(value) && (value.startsWith('=') || /{{.*?}}/.test(value));
286
286
  }
287
287
 
288
- /**
289
- * Retrieve variable names from a given feelers template.
290
- *
291
- * @param {string} template
292
- *
293
- * @returns {string[]}
288
+ /**
289
+ * Retrieve variable names from a given feelers template.
290
+ *
291
+ * @param {string} template
292
+ *
293
+ * @returns {string[]}
294
294
  */
295
295
  getVariableNames(template) {
296
296
  if (!this.isTemplate(template)) {
@@ -316,17 +316,17 @@ class FeelersTemplating {
316
316
  }, []);
317
317
  }
318
318
 
319
- /**
320
- * Evaluate a template.
321
- *
322
- * @param {string} template
323
- * @param {Object<string, any>} context
324
- * @param {Object} options
325
- * @param {boolean} [options.debug = false]
326
- * @param {boolean} [options.strict = false]
327
- * @param {Function} [options.buildDebugString]
328
- *
329
- * @returns
319
+ /**
320
+ * Evaluate a template.
321
+ *
322
+ * @param {string} template
323
+ * @param {Object<string, any>} context
324
+ * @param {Object} options
325
+ * @param {boolean} [options.debug = false]
326
+ * @param {boolean} [options.strict = false]
327
+ * @param {Function} [options.buildDebugString]
328
+ *
329
+ * @returns
330
330
  */
331
331
  evaluate(template, context = {}, options = {}) {
332
332
  const {
@@ -341,22 +341,22 @@ class FeelersTemplating {
341
341
  });
342
342
  }
343
343
 
344
- /**
345
- * @typedef {Object} ExpressionWithDepth
346
- * @property {number} depth - The depth of the expression in the syntax tree.
347
- * @property {string} expression - The extracted expression
344
+ /**
345
+ * @typedef {Object} ExpressionWithDepth
346
+ * @property {number} depth - The depth of the expression in the syntax tree.
347
+ * @property {string} expression - The extracted expression
348
348
  */
349
349
 
350
- /**
351
- * Extracts all feel expressions in the template along with their depth in the syntax tree.
352
- * The depth is incremented for child expressions of loops to account for context drilling.
353
- * @name extractExpressionsWithDepth
354
- * @param {string} template - A feelers template string.
355
- * @returns {Array<ExpressionWithDepth>} An array of objects, each containing the depth and the extracted expression.
356
- *
357
- * @example
358
- * const template = "Hello {{user}}, you have:{{#loop items}}\n- {{amount}} {{name}}{{/loop}}.";
359
- * const extractedExpressions = _extractExpressionsWithDepth(template);
350
+ /**
351
+ * Extracts all feel expressions in the template along with their depth in the syntax tree.
352
+ * The depth is incremented for child expressions of loops to account for context drilling.
353
+ * @name extractExpressionsWithDepth
354
+ * @param {string} template - A feelers template string.
355
+ * @returns {Array<ExpressionWithDepth>} An array of objects, each containing the depth and the extracted expression.
356
+ *
357
+ * @example
358
+ * const template = "Hello {{user}}, you have:{{#loop items}}\n- {{amount}} {{name}}{{/loop}}.";
359
+ * const extractedExpressions = _extractExpressionsWithDepth(template);
360
360
  */
361
361
  _extractExpressionsWithDepth(template) {
362
362
  // build simplified feelers syntax tree
@@ -387,47 +387,271 @@ class FeelersTemplating {
387
387
  }
388
388
  FeelersTemplating.$inject = [];
389
389
 
390
- /**
391
- * @typedef {object} Condition
392
- * @property {string} [hide]
390
+ // config ///////////////////
391
+
392
+ const MINUTES_IN_DAY = 60 * 24;
393
+ const DATETIME_SUBTYPES = {
394
+ DATE: 'date',
395
+ TIME: 'time',
396
+ DATETIME: 'datetime'
397
+ };
398
+ const TIME_SERIALISING_FORMATS = {
399
+ UTC_OFFSET: 'utc_offset',
400
+ UTC_NORMALIZED: 'utc_normalized',
401
+ NO_TIMEZONE: 'no_timezone'
402
+ };
403
+ const DATETIME_SUBTYPES_LABELS = {
404
+ [DATETIME_SUBTYPES.DATE]: 'Date',
405
+ [DATETIME_SUBTYPES.TIME]: 'Time',
406
+ [DATETIME_SUBTYPES.DATETIME]: 'Date & Time'
407
+ };
408
+ const TIME_SERIALISINGFORMAT_LABELS = {
409
+ [TIME_SERIALISING_FORMATS.UTC_OFFSET]: 'UTC offset',
410
+ [TIME_SERIALISING_FORMATS.UTC_NORMALIZED]: 'UTC normalized',
411
+ [TIME_SERIALISING_FORMATS.NO_TIMEZONE]: 'No timezone'
412
+ };
413
+ const DATETIME_SUBTYPE_PATH = ['subtype'];
414
+ const DATE_LABEL_PATH = ['dateLabel'];
415
+ const DATE_DISALLOW_PAST_PATH = ['disallowPassedDates'];
416
+ const TIME_LABEL_PATH = ['timeLabel'];
417
+ const TIME_USE24H_PATH = ['use24h'];
418
+ const TIME_INTERVAL_PATH = ['timeInterval'];
419
+ const TIME_SERIALISING_FORMAT_PATH = ['timeSerializingFormat'];
420
+
421
+ // config ///////////////////
422
+
423
+ const VALUES_SOURCES = {
424
+ STATIC: 'static',
425
+ INPUT: 'input',
426
+ EXPRESSION: 'expression'
427
+ };
428
+ const VALUES_SOURCE_DEFAULT = VALUES_SOURCES.STATIC;
429
+ const VALUES_SOURCES_LABELS = {
430
+ [VALUES_SOURCES.STATIC]: 'Static',
431
+ [VALUES_SOURCES.INPUT]: 'Input data',
432
+ [VALUES_SOURCES.EXPRESSION]: 'Expression'
433
+ };
434
+ const VALUES_SOURCES_PATHS = {
435
+ [VALUES_SOURCES.STATIC]: ['values'],
436
+ [VALUES_SOURCES.INPUT]: ['valuesKey'],
437
+ [VALUES_SOURCES.EXPRESSION]: ['valuesExpression']
438
+ };
439
+ const VALUES_SOURCES_DEFAULTS = {
440
+ [VALUES_SOURCES.STATIC]: [{
441
+ label: 'Value',
442
+ value: 'value'
443
+ }],
444
+ [VALUES_SOURCES.INPUT]: '',
445
+ [VALUES_SOURCES.EXPRESSION]: '='
446
+ };
447
+
448
+ // helpers ///////////////////
449
+
450
+ function getValuesSource(field) {
451
+ for (const source of Object.values(VALUES_SOURCES)) {
452
+ if (get(field, VALUES_SOURCES_PATHS[source]) !== undefined) {
453
+ return source;
454
+ }
455
+ }
456
+ return VALUES_SOURCE_DEFAULT;
457
+ }
458
+
459
+ function createInjector(bootstrapModules) {
460
+ const injector = new Injector(bootstrapModules);
461
+ injector.init();
462
+ return injector;
463
+ }
464
+
465
+ /**
466
+ * @param {string?} prefix
467
+ *
468
+ * @returns Element
469
+ */
470
+ function createFormContainer(prefix = 'fjs') {
471
+ const container = document.createElement('div');
472
+ container.classList.add(`${prefix}-container`);
473
+ return container;
474
+ }
475
+
476
+ const EXPRESSION_PROPERTIES = ['alt', 'appearance.prefixAdorner', 'appearance.suffixAdorner', 'conditional.hide', 'description', 'label', 'source', 'readonly', 'text', 'validate.min', 'validate.max', 'validate.minLength', 'validate.maxLength', 'valuesExpression'];
477
+ const TEMPLATE_PROPERTIES = ['alt', 'appearance.prefixAdorner', 'appearance.suffixAdorner', 'description', 'label', 'source', 'text'];
478
+ function isRequired(field) {
479
+ return field.required;
480
+ }
481
+ function pathParse(path) {
482
+ if (!path) {
483
+ return [];
484
+ }
485
+ return path.split('.').map(key => {
486
+ return isNaN(parseInt(key)) ? key : parseInt(key);
487
+ });
488
+ }
489
+ function pathsEqual(a, b) {
490
+ return a && b && a.length === b.length && a.every((value, index) => value === b[index]);
491
+ }
492
+ const indices = {};
493
+ function generateIndexForType(type) {
494
+ if (type in indices) {
495
+ indices[type]++;
496
+ } else {
497
+ indices[type] = 1;
498
+ }
499
+ return indices[type];
500
+ }
501
+ function generateIdForType(type) {
502
+ return `${type}${generateIndexForType(type)}`;
503
+ }
504
+
505
+ /**
506
+ * @template T
507
+ * @param {T} data
508
+ * @param {(this: any, key: string, value: any) => any} [replacer]
509
+ * @return {T}
510
+ */
511
+ function clone(data, replacer) {
512
+ return JSON.parse(JSON.stringify(data, replacer));
513
+ }
514
+
515
+ /**
516
+ * Parse the schema for input variables a form might make use of
517
+ *
518
+ * @param {any} schema
519
+ *
520
+ * @return {string[]}
521
+ */
522
+ function getSchemaVariables(schema, options = {}) {
523
+ const {
524
+ expressionLanguage = new FeelExpressionLanguage(null),
525
+ templating = new FeelersTemplating(),
526
+ inputs = true,
527
+ outputs = true
528
+ } = options;
529
+ if (!schema.components) {
530
+ return [];
531
+ }
532
+ const getAllComponents = node => {
533
+ const components = [];
534
+ if (node.components) {
535
+ node.components.forEach(component => {
536
+ components.push(component);
537
+ components.push(...getAllComponents(component));
538
+ });
539
+ }
540
+ return components;
541
+ };
542
+ const variables = getAllComponents(schema).reduce((variables, component) => {
543
+ const {
544
+ valuesKey
545
+ } = component;
546
+
547
+ // collect input-only variables
548
+ if (inputs) {
549
+ if (valuesKey) {
550
+ variables = [...variables, valuesKey];
551
+ }
552
+ EXPRESSION_PROPERTIES.forEach(prop => {
553
+ const property = get(component, prop.split('.'));
554
+ if (property && expressionLanguage.isExpression(property)) {
555
+ const expressionVariables = expressionLanguage.getVariableNames(property, {
556
+ type: 'expression'
557
+ });
558
+ variables = [...variables, ...expressionVariables];
559
+ }
560
+ });
561
+ TEMPLATE_PROPERTIES.forEach(prop => {
562
+ const property = get(component, prop.split('.'));
563
+ if (property && !expressionLanguage.isExpression(property) && templating.isTemplate(property)) {
564
+ const templateVariables = templating.getVariableNames(property);
565
+ variables = [...variables, ...templateVariables];
566
+ }
567
+ });
568
+ }
569
+ return variables.filter(variable => variable !== undefined || variable !== null);
570
+ }, []);
571
+ const getBindingVariables = node => {
572
+ const bindingVariable = [];
573
+
574
+ // c.f. https://github.com/bpmn-io/form-js/issues/778 @Skaiir to remove?
575
+ if (node.type === 'button') {
576
+ return [];
577
+ } else if (node.key) {
578
+ return [node.key.split('.')[0]];
579
+ } else if (node.path) {
580
+ return [node.path.split('.')[0]];
581
+ } else if (node.components) {
582
+ node.components.forEach(component => {
583
+ bindingVariable.push(...getBindingVariables(component));
584
+ });
585
+ }
586
+ return bindingVariable;
587
+ };
588
+
589
+ // collect binding variables
590
+ if (inputs || outputs) {
591
+ variables.push(...getBindingVariables(schema));
592
+ }
593
+
594
+ // remove duplicates
595
+ return Array.from(new Set(variables));
596
+ }
597
+ function runRecursively(formField, fn) {
598
+ const components = formField.components || [];
599
+ components.forEach((component, index) => {
600
+ runRecursively(component, fn);
601
+ });
602
+ fn(formField);
603
+ }
604
+
605
+ /**
606
+ * @typedef {object} Condition
607
+ * @property {string} [hide]
393
608
  */
394
609
 
395
610
  class ConditionChecker {
396
- constructor(formFieldRegistry, eventBus) {
611
+ constructor(formFieldRegistry, pathRegistry, eventBus) {
397
612
  this._formFieldRegistry = formFieldRegistry;
613
+ this._pathRegistry = pathRegistry;
398
614
  this._eventBus = eventBus;
399
615
  }
400
616
 
401
- /**
402
- * For given data, remove properties based on condition.
403
- *
404
- * @param {Object<string, any>} properties
405
- * @param {Object<string, any>} data
617
+ /**
618
+ * For given data, remove properties based on condition.
619
+ *
620
+ * @param {Object<string, any>} properties
621
+ * @param {Object<string, any>} data
406
622
  */
407
623
  applyConditions(properties, data = {}) {
408
- const conditions = this._getConditions();
409
- const newProperties = {
410
- ...properties
411
- };
412
- for (const {
413
- key,
414
- condition
415
- } of conditions) {
416
- const shouldRemove = this._checkHideCondition(condition, data);
417
- if (shouldRemove) {
418
- delete newProperties[key];
419
- }
624
+ const newProperties = clone(properties);
625
+ const form = this._formFieldRegistry.getAll().find(field => field.type === 'default');
626
+ if (!form) {
627
+ throw new Error('form field registry has no form');
420
628
  }
629
+ this._pathRegistry.executeRecursivelyOnFields(form, ({
630
+ field,
631
+ isClosed,
632
+ context
633
+ }) => {
634
+ const {
635
+ conditional: condition
636
+ } = field;
637
+ context.isHidden = context.isHidden || condition && this._checkHideCondition(condition, data);
638
+
639
+ // only clear the leaf nodes, as groups may both point to the same path
640
+ if (context.isHidden && isClosed) {
641
+ const valuePath = this._pathRegistry.getValuePath(field);
642
+ this._clearObjectValueRecursively(valuePath, newProperties);
643
+ }
644
+ });
421
645
  return newProperties;
422
646
  }
423
647
 
424
- /**
425
- * Check if given condition is met. Returns null for invalid/missing conditions.
426
- *
427
- * @param {string} condition
428
- * @param {import('../../types').Data} [data]
429
- *
430
- * @returns {boolean|null}
648
+ /**
649
+ * Check if given condition is met. Returns null for invalid/missing conditions.
650
+ *
651
+ * @param {string} condition
652
+ * @param {import('../../types').Data} [data]
653
+ *
654
+ * @returns {boolean|null}
431
655
  */
432
656
  check(condition, data = {}) {
433
657
  if (!condition) {
@@ -448,12 +672,12 @@ class ConditionChecker {
448
672
  }
449
673
  }
450
674
 
451
- /**
452
- * Check if hide condition is met.
453
- *
454
- * @param {Condition} condition
455
- * @param {Object<string, any>} data
456
- * @returns {boolean}
675
+ /**
676
+ * Check if hide condition is met.
677
+ *
678
+ * @param {Condition} condition
679
+ * @param {Object<string, any>} data
680
+ * @returns {boolean}
457
681
  */
458
682
  _checkHideCondition(condition, data) {
459
683
  if (!condition.hide) {
@@ -462,24 +686,18 @@ class ConditionChecker {
462
686
  const result = this.check(condition.hide, data);
463
687
  return result === true;
464
688
  }
465
- _getConditions() {
466
- const formFields = this._formFieldRegistry.getAll();
467
- return formFields.reduce((conditions, formField) => {
468
- const {
469
- key,
470
- conditional: condition
471
- } = formField;
472
- if (key && condition) {
473
- return [...conditions, {
474
- key,
475
- condition
476
- }];
477
- }
478
- return conditions;
479
- }, []);
689
+ _clearObjectValueRecursively(valuePath, obj) {
690
+ const workingValuePath = [...valuePath];
691
+ let recurse = false;
692
+ do {
693
+ set(obj, workingValuePath, undefined);
694
+ workingValuePath.pop();
695
+ const parentObject = get(obj, workingValuePath);
696
+ recurse = isObject(parentObject) && !values(parentObject).length && !!workingValuePath.length;
697
+ } while (recurse);
480
698
  }
481
699
  }
482
- ConditionChecker.$inject = ['formFieldRegistry', 'eventBus'];
700
+ ConditionChecker.$inject = ['formFieldRegistry', 'pathRegistry', 'eventBus'];
483
701
 
484
702
  var ExpressionLanguageModule = {
485
703
  __init__: ['expressionLanguage', 'templating', 'conditionChecker'],
@@ -495,12 +713,12 @@ class MarkdownRenderer {
495
713
  this._converter = new showdown.Converter();
496
714
  }
497
715
 
498
- /**
499
- * Render markdown to HTML.
500
- *
501
- * @param {string} markdown - The markdown to render
502
- *
503
- * @returns {string} HTML
716
+ /**
717
+ * Render markdown to HTML.
718
+ *
719
+ * @param {string} markdown - The markdown to render
720
+ *
721
+ * @returns {string} HTML
504
722
  */
505
723
  render(markdown) {
506
724
  return this._converter.makeHtml(markdown);
@@ -984,203 +1202,6 @@ var commandModule = {
984
1202
  commandStack: ['type', CommandStack]
985
1203
  };
986
1204
 
987
- // config ///////////////////
988
-
989
- const MINUTES_IN_DAY = 60 * 24;
990
- const DATETIME_SUBTYPES = {
991
- DATE: 'date',
992
- TIME: 'time',
993
- DATETIME: 'datetime'
994
- };
995
- const TIME_SERIALISING_FORMATS = {
996
- UTC_OFFSET: 'utc_offset',
997
- UTC_NORMALIZED: 'utc_normalized',
998
- NO_TIMEZONE: 'no_timezone'
999
- };
1000
- const DATETIME_SUBTYPES_LABELS = {
1001
- [DATETIME_SUBTYPES.DATE]: 'Date',
1002
- [DATETIME_SUBTYPES.TIME]: 'Time',
1003
- [DATETIME_SUBTYPES.DATETIME]: 'Date & Time'
1004
- };
1005
- const TIME_SERIALISINGFORMAT_LABELS = {
1006
- [TIME_SERIALISING_FORMATS.UTC_OFFSET]: 'UTC offset',
1007
- [TIME_SERIALISING_FORMATS.UTC_NORMALIZED]: 'UTC normalized',
1008
- [TIME_SERIALISING_FORMATS.NO_TIMEZONE]: 'No timezone'
1009
- };
1010
- const DATETIME_SUBTYPE_PATH = ['subtype'];
1011
- const DATE_LABEL_PATH = ['dateLabel'];
1012
- const DATE_DISALLOW_PAST_PATH = ['disallowPassedDates'];
1013
- const TIME_LABEL_PATH = ['timeLabel'];
1014
- const TIME_USE24H_PATH = ['use24h'];
1015
- const TIME_INTERVAL_PATH = ['timeInterval'];
1016
- const TIME_SERIALISING_FORMAT_PATH = ['timeSerializingFormat'];
1017
-
1018
- // config ///////////////////
1019
-
1020
- const VALUES_SOURCES = {
1021
- STATIC: 'static',
1022
- INPUT: 'input',
1023
- EXPRESSION: 'expression'
1024
- };
1025
- const VALUES_SOURCE_DEFAULT = VALUES_SOURCES.STATIC;
1026
- const VALUES_SOURCES_LABELS = {
1027
- [VALUES_SOURCES.STATIC]: 'Static',
1028
- [VALUES_SOURCES.INPUT]: 'Input data',
1029
- [VALUES_SOURCES.EXPRESSION]: 'Expression'
1030
- };
1031
- const VALUES_SOURCES_PATHS = {
1032
- [VALUES_SOURCES.STATIC]: ['values'],
1033
- [VALUES_SOURCES.INPUT]: ['valuesKey'],
1034
- [VALUES_SOURCES.EXPRESSION]: ['valuesExpression']
1035
- };
1036
- const VALUES_SOURCES_DEFAULTS = {
1037
- [VALUES_SOURCES.STATIC]: [{
1038
- label: 'Value',
1039
- value: 'value'
1040
- }],
1041
- [VALUES_SOURCES.INPUT]: '',
1042
- [VALUES_SOURCES.EXPRESSION]: '='
1043
- };
1044
-
1045
- // helpers ///////////////////
1046
-
1047
- function getValuesSource(field) {
1048
- for (const source of Object.values(VALUES_SOURCES)) {
1049
- if (get(field, VALUES_SOURCES_PATHS[source]) !== undefined) {
1050
- return source;
1051
- }
1052
- }
1053
- return VALUES_SOURCE_DEFAULT;
1054
- }
1055
-
1056
- function createInjector(bootstrapModules) {
1057
- const injector = new Injector(bootstrapModules);
1058
- injector.init();
1059
- return injector;
1060
- }
1061
-
1062
- /**
1063
- * @param {string?} prefix
1064
- *
1065
- * @returns Element
1066
- */
1067
- function createFormContainer(prefix = 'fjs') {
1068
- const container = document.createElement('div');
1069
- container.classList.add(`${prefix}-container`);
1070
- return container;
1071
- }
1072
-
1073
- const EXPRESSION_PROPERTIES = ['alt', 'appearance.prefixAdorner', 'appearance.suffixAdorner', 'conditional.hide', 'description', 'label', 'source', 'readonly', 'text', 'validate.min', 'validate.max', 'validate.minLength', 'validate.maxLength', 'valuesExpression'];
1074
- const TEMPLATE_PROPERTIES = ['alt', 'appearance.prefixAdorner', 'appearance.suffixAdorner', 'description', 'label', 'source', 'text'];
1075
- function findErrors(errors, path) {
1076
- return errors[pathStringify(path)];
1077
- }
1078
- function isRequired(field) {
1079
- return field.required;
1080
- }
1081
- function pathParse(path) {
1082
- if (!path) {
1083
- return [];
1084
- }
1085
- return path.split('.').map(key => {
1086
- return isNaN(parseInt(key)) ? key : parseInt(key);
1087
- });
1088
- }
1089
- function pathsEqual(a, b) {
1090
- return a && b && a.length === b.length && a.every((value, index) => value === b[index]);
1091
- }
1092
- function pathStringify(path) {
1093
- if (!path) {
1094
- return '';
1095
- }
1096
- return path.join('.');
1097
- }
1098
- const indices = {};
1099
- function generateIndexForType(type) {
1100
- if (type in indices) {
1101
- indices[type]++;
1102
- } else {
1103
- indices[type] = 1;
1104
- }
1105
- return indices[type];
1106
- }
1107
- function generateIdForType(type) {
1108
- return `${type}${generateIndexForType(type)}`;
1109
- }
1110
-
1111
- /**
1112
- * @template T
1113
- * @param {T} data
1114
- * @param {(this: any, key: string, value: any) => any} [replacer]
1115
- * @return {T}
1116
- */
1117
- function clone(data, replacer) {
1118
- return JSON.parse(JSON.stringify(data, replacer));
1119
- }
1120
-
1121
- /**
1122
- * Parse the schema for input variables a form might make use of
1123
- *
1124
- * @param {any} schema
1125
- *
1126
- * @return {string[]}
1127
- */
1128
- function getSchemaVariables(schema, options = {}) {
1129
- const {
1130
- expressionLanguage = new FeelExpressionLanguage(null),
1131
- templating = new FeelersTemplating(),
1132
- inputs = true,
1133
- outputs = true
1134
- } = options;
1135
- if (!schema.components) {
1136
- return [];
1137
- }
1138
- const variables = schema.components.reduce((variables, component) => {
1139
- const {
1140
- key,
1141
- valuesKey,
1142
- type
1143
- } = component;
1144
- if (['button'].includes(type)) {
1145
- return variables;
1146
- }
1147
-
1148
- // collect bi-directional variables
1149
- if (inputs || outputs) {
1150
- if (key) {
1151
- variables = [...variables, key];
1152
- }
1153
- }
1154
-
1155
- // collect input-only variables
1156
- if (inputs) {
1157
- if (valuesKey) {
1158
- variables = [...variables, valuesKey];
1159
- }
1160
- EXPRESSION_PROPERTIES.forEach(prop => {
1161
- const property = get(component, prop.split('.'));
1162
- if (property && expressionLanguage.isExpression(property)) {
1163
- const expressionVariables = expressionLanguage.getVariableNames(property, {
1164
- type: 'expression'
1165
- });
1166
- variables = [...variables, ...expressionVariables];
1167
- }
1168
- });
1169
- TEMPLATE_PROPERTIES.forEach(prop => {
1170
- const property = get(component, prop.split('.'));
1171
- if (property && !expressionLanguage.isExpression(property) && templating.isTemplate(property)) {
1172
- const templateVariables = templating.getVariableNames(property);
1173
- variables = [...variables, ...templateVariables];
1174
- }
1175
- });
1176
- }
1177
- return variables.filter(variable => variable !== undefined || variable !== null);
1178
- }, []);
1179
-
1180
- // remove duplicates
1181
- return Array.from(new Set(variables));
1182
- }
1183
-
1184
1205
  class UpdateFieldValidationHandler {
1185
1206
  constructor(form, validator) {
1186
1207
  this._form = form;
@@ -1191,15 +1212,12 @@ class UpdateFieldValidationHandler {
1191
1212
  field,
1192
1213
  value
1193
1214
  } = context;
1194
- const {
1195
- _path
1196
- } = field;
1197
1215
  const {
1198
1216
  errors
1199
1217
  } = this._form._getState();
1200
1218
  context.oldErrors = clone(errors);
1201
1219
  const fieldErrors = this._validator.validateField(field, value);
1202
- const updatedErrors = set(errors, [pathStringify(_path)], fieldErrors.length ? fieldErrors : undefined);
1220
+ const updatedErrors = set(errors, [field.id], fieldErrors.length ? fieldErrors : undefined);
1203
1221
  this._form._setState({
1204
1222
  errors: updatedErrors
1205
1223
  });
@@ -1848,8 +1866,8 @@ Validator.$inject = ['expressionLanguage', 'conditionChecker', 'form'];
1848
1866
 
1849
1867
  // helpers //////////
1850
1868
 
1851
- /**
1852
- * Helper function to evaluate optional FEEL validation values.
1869
+ /**
1870
+ * Helper function to evaluate optional FEEL validation values.
1853
1871
  */
1854
1872
  function evaluateFEELValues(validate, expressionLanguage, conditionChecker, form) {
1855
1873
  const evaluatedValidate = {
@@ -1882,72 +1900,415 @@ function evaluateFEELValues(validate, expressionLanguage, conditionChecker, form
1882
1900
  return evaluatedValidate;
1883
1901
  }
1884
1902
 
1885
- class FormFieldRegistry {
1886
- constructor(eventBus) {
1887
- this._eventBus = eventBus;
1888
- this._formFields = {};
1889
- eventBus.on('form.clear', () => this.clear());
1890
- this._ids = new Ids([32, 36, 1]);
1891
- this._keys = new Ids([32, 36, 1]);
1903
+ class Importer {
1904
+ /**
1905
+ * @constructor
1906
+ * @param { import('./FormFieldRegistry').default } formFieldRegistry
1907
+ * @param { import('./PathRegistry').default } pathRegistry
1908
+ * @param { import('./FieldFactory').default } fieldFactory
1909
+ * @param { import('./FormLayouter').default } formLayouter
1910
+ */
1911
+ constructor(formFieldRegistry, pathRegistry, fieldFactory, formLayouter) {
1912
+ this._formFieldRegistry = formFieldRegistry;
1913
+ this._pathRegistry = pathRegistry;
1914
+ this._fieldFactory = fieldFactory;
1915
+ this._formLayouter = formLayouter;
1892
1916
  }
1893
- add(formField) {
1917
+
1918
+ /**
1919
+ * Import schema creating rows, fields, attaching additional
1920
+ * information to each field and adding fields to the
1921
+ * field registry.
1922
+ *
1923
+ * Additional information attached:
1924
+ *
1925
+ * * `id` (unless present)
1926
+ * * `_parent`
1927
+ * * `_path`
1928
+ *
1929
+ * @param {any} schema
1930
+ *
1931
+ * @typedef {{ warnings: Error[], schema: any }} ImportResult
1932
+ * @returns {ImportResult}
1933
+ */
1934
+ importSchema(schema) {
1935
+ // TODO: Add warnings
1936
+ const warnings = [];
1937
+ try {
1938
+ this._cleanup();
1939
+ const importedSchema = this.importFormField(clone(schema));
1940
+ this._formLayouter.calculateLayout(clone(importedSchema));
1941
+ return {
1942
+ schema: importedSchema,
1943
+ warnings
1944
+ };
1945
+ } catch (err) {
1946
+ this._cleanup();
1947
+ err.warnings = warnings;
1948
+ throw err;
1949
+ }
1950
+ }
1951
+ _cleanup() {
1952
+ this._formLayouter.clear();
1953
+ this._formFieldRegistry.clear();
1954
+ this._pathRegistry.clear();
1955
+ }
1956
+
1957
+ /**
1958
+ * @param {{[x: string]: any}} fieldAttrs
1959
+ * @param {String} [parentId]
1960
+ * @param {number} [index]
1961
+ *
1962
+ * @return {any} field
1963
+ */
1964
+ importFormField(fieldAttrs, parentId, index) {
1894
1965
  const {
1895
- id
1896
- } = formField;
1897
- if (this._formFields[id]) {
1898
- throw new Error(`form field with ID ${id} already exists`);
1966
+ components
1967
+ } = fieldAttrs;
1968
+ let parent, path;
1969
+ if (parentId) {
1970
+ parent = this._formFieldRegistry.get(parentId);
1899
1971
  }
1900
- this._eventBus.fire('formField.add', {
1901
- formField
1972
+
1973
+ // set form field path
1974
+ path = parent ? [...parent._path, 'components', index] : [];
1975
+ const field = this._fieldFactory.create({
1976
+ ...fieldAttrs,
1977
+ _path: path,
1978
+ _parent: parentId
1979
+ }, false);
1980
+ this._formFieldRegistry.add(field);
1981
+ if (components) {
1982
+ field.components = this.importFormFields(components, field.id);
1983
+ }
1984
+ return field;
1985
+ }
1986
+
1987
+ /**
1988
+ * @param {Array<any>} components
1989
+ * @param {string} parentId
1990
+ *
1991
+ * @return {Array<any>} imported components
1992
+ */
1993
+ importFormFields(components, parentId) {
1994
+ return components.map((component, index) => {
1995
+ return this.importFormField(component, parentId, index);
1902
1996
  });
1903
- this._formFields[id] = formField;
1904
1997
  }
1905
- remove(formField) {
1998
+ }
1999
+ Importer.$inject = ['formFieldRegistry', 'pathRegistry', 'fieldFactory', 'formLayouter'];
2000
+
2001
+ class FieldFactory {
2002
+ /**
2003
+ * @constructor
2004
+ *
2005
+ * @param formFieldRegistry
2006
+ * @param formFields
2007
+ */
2008
+ constructor(formFieldRegistry, pathRegistry, formFields) {
2009
+ this._formFieldRegistry = formFieldRegistry;
2010
+ this._pathRegistry = pathRegistry;
2011
+ this._formFields = formFields;
2012
+ }
2013
+ create(attrs, applyDefaults = true) {
1906
2014
  const {
1907
- id
1908
- } = formField;
1909
- if (!this._formFields[id]) {
1910
- return;
2015
+ id,
2016
+ type,
2017
+ key,
2018
+ path,
2019
+ _parent
2020
+ } = attrs;
2021
+ const fieldDefinition = this._formFields.get(type);
2022
+ if (!fieldDefinition) {
2023
+ throw new Error(`form field of type <${type}> not supported`);
1911
2024
  }
1912
- this._eventBus.fire('formField.remove', {
1913
- formField
2025
+ const {
2026
+ config
2027
+ } = fieldDefinition;
2028
+ if (!config) {
2029
+ throw new Error(`form field of type <${type}> has no config`);
2030
+ }
2031
+ if (id && this._formFieldRegistry._ids.assigned(id)) {
2032
+ throw new Error(`form field with id <${id}> already exists`);
2033
+ }
2034
+
2035
+ // ensure that we can claim the path
2036
+
2037
+ const parent = _parent && this._formFieldRegistry.get(_parent);
2038
+ const parentPath = parent && this._pathRegistry.getValuePath(parent) || [];
2039
+ if (config.keyed && key && !this._pathRegistry.canClaimPath([...parentPath, ...key.split('.')], true)) {
2040
+ throw new Error(`binding path '${[...parentPath, key].join('.')}' is already claimed`);
2041
+ }
2042
+ if (config.pathed && path && !this._pathRegistry.canClaimPath([...parentPath, ...path.split('.')], false)) {
2043
+ throw new Error(`binding path '${[...parentPath, ...path.split('.')].join('.')}' is already claimed`);
2044
+ }
2045
+ const labelAttrs = applyDefaults && config.label ? {
2046
+ label: config.label
2047
+ } : {};
2048
+ const field = config.create({
2049
+ ...labelAttrs,
2050
+ ...attrs
1914
2051
  });
1915
- delete this._formFields[id];
2052
+ this._ensureId(field);
2053
+ if (config.keyed) {
2054
+ this._ensureKey(field);
2055
+ }
2056
+ if (config.pathed && path) {
2057
+ this._pathRegistry.claimPath(this._pathRegistry.getValuePath(field), false);
2058
+ }
2059
+ return field;
1916
2060
  }
1917
- get(id) {
1918
- return this._formFields[id];
2061
+ _ensureId(field) {
2062
+ if (field.id) {
2063
+ this._formFieldRegistry._ids.claim(field.id, field);
2064
+ return;
2065
+ }
2066
+ let prefix = 'Field';
2067
+ if (field.type === 'default') {
2068
+ prefix = 'Form';
2069
+ }
2070
+ field.id = this._formFieldRegistry._ids.nextPrefixed(`${prefix}_`, field);
1919
2071
  }
1920
- getAll() {
1921
- return Object.values(this._formFields);
2072
+ _ensureKey(field) {
2073
+ if (!field.key) {
2074
+ let random;
2075
+ const parent = this._formFieldRegistry.get(field._parent);
2076
+
2077
+ // ensure key uniqueness at level
2078
+ do {
2079
+ random = Math.random().toString(36).substring(7);
2080
+ } while (parent && parent.components.some(child => child.key === random));
2081
+ field.key = `${field.type}_${random}`;
2082
+ }
2083
+ this._pathRegistry.claimPath(this._pathRegistry.getValuePath(field), true);
1922
2084
  }
1923
- forEach(callback) {
1924
- this.getAll().forEach(formField => callback(formField));
2085
+ }
2086
+ FieldFactory.$inject = ['formFieldRegistry', 'pathRegistry', 'formFields'];
2087
+
2088
+ /**
2089
+ * The PathRegistry class manages a hierarchical structure of paths associated with form fields.
2090
+ * It enables claiming, unclaiming, and validating paths within this structure.
2091
+ *
2092
+ * Example Tree Structure:
2093
+ *
2094
+ * [
2095
+ * {
2096
+ * segment: 'root',
2097
+ * claimCount: 1,
2098
+ * children: [
2099
+ * {
2100
+ * segment: 'child1',
2101
+ * claimCount: 2,
2102
+ * children: null // A leaf node (closed path)
2103
+ * },
2104
+ * {
2105
+ * segment: 'child2',
2106
+ * claimCount: 1,
2107
+ * children: [
2108
+ * {
2109
+ * segment: 'subChild1',
2110
+ * claimCount: 1,
2111
+ * children: [] // An open node (open path)
2112
+ * }
2113
+ * ]
2114
+ * }
2115
+ * ]
2116
+ * }
2117
+ * ]
2118
+ */
2119
+ class PathRegistry {
2120
+ constructor(formFieldRegistry, formFields) {
2121
+ this._formFieldRegistry = formFieldRegistry;
2122
+ this._formFields = formFields;
2123
+ this._dataPaths = [];
2124
+ }
2125
+ canClaimPath(path, closed = false) {
2126
+ let node = {
2127
+ children: this._dataPaths
2128
+ };
2129
+ for (const segment of path) {
2130
+ node = _getNextSegment(node, segment);
2131
+
2132
+ // if no node at that path, we can claim it no matter what
2133
+ if (!node) {
2134
+ return true;
2135
+ }
2136
+
2137
+ // if we reach a leaf node, definitely not claimable
2138
+ if (node.children === null) {
2139
+ return false;
2140
+ }
2141
+ }
2142
+
2143
+ // if after all segments we reach a node with children, we can claim it only openly
2144
+ return !closed;
2145
+ }
2146
+ claimPath(path, closed = false) {
2147
+ if (!this.canClaimPath(path, closed)) {
2148
+ throw new Error(`cannot claim path '${path.join('.')}'`);
2149
+ }
2150
+ let node = {
2151
+ children: this._dataPaths
2152
+ };
2153
+ for (const segment of path) {
2154
+ let child = _getNextSegment(node, segment);
2155
+ if (!child) {
2156
+ child = {
2157
+ segment,
2158
+ claimCount: 1,
2159
+ children: []
2160
+ };
2161
+ node.children.push(child);
2162
+ } else {
2163
+ child.claimCount++;
2164
+ }
2165
+ node = child;
2166
+ }
2167
+ if (closed) {
2168
+ node.children = null;
2169
+ }
2170
+ }
2171
+ unclaimPath(path) {
2172
+ // verification Pass
2173
+ let node = {
2174
+ children: this._dataPaths
2175
+ };
2176
+ for (const segment of path) {
2177
+ const child = _getNextSegment(node, segment);
2178
+ if (!child) {
2179
+ throw new Error(`no open path found for '${path.join('.')}'`);
2180
+ }
2181
+ node = child;
2182
+ }
2183
+
2184
+ // mutation Pass
2185
+ node = {
2186
+ children: this._dataPaths
2187
+ };
2188
+ for (const segment of path) {
2189
+ const child = _getNextSegment(node, segment);
2190
+ child.claimCount--;
2191
+ if (child.claimCount === 0) {
2192
+ node.children.splice(node.children.indexOf(child), 1);
2193
+ break; // Abort early if claimCount reaches zero
2194
+ }
2195
+
2196
+ node = child;
2197
+ }
2198
+ }
2199
+
2200
+ /**
2201
+ * Applies a function (fn) recursively on a given field and its children.
2202
+ *
2203
+ * - `field`: Starting field object.
2204
+ * - `fn`: Function to apply.
2205
+ * - `context`: Optional object for passing data between calls.
2206
+ *
2207
+ * Stops early if `fn` returns `false`. Useful for traversing the form field tree.
2208
+ *
2209
+ * @returns {boolean} Success status based on function execution.
2210
+ */
2211
+ executeRecursivelyOnFields(field, fn, context = {}) {
2212
+ let result = true;
2213
+ const formFieldConfig = this._formFields.get(field.type).config;
2214
+ if (formFieldConfig.keyed) {
2215
+ const callResult = fn({
2216
+ field,
2217
+ isClosed: true,
2218
+ context
2219
+ });
2220
+ return result && callResult;
2221
+ } else if (formFieldConfig.pathed) {
2222
+ const callResult = fn({
2223
+ field,
2224
+ isClosed: false,
2225
+ context
2226
+ });
2227
+ result = result && callResult;
2228
+ }
2229
+ if (field.components) {
2230
+ for (const child of field.components) {
2231
+ const callResult = this.executeRecursivelyOnFields(child, fn, clone(context));
2232
+ result = result && callResult;
2233
+
2234
+ // only stop executing if false is specifically returned, not if undefined
2235
+ if (result === false) {
2236
+ return result;
2237
+ }
2238
+ }
2239
+ }
2240
+ return result;
2241
+ }
2242
+
2243
+ /**
2244
+ * Generates an array representing the binding path to an underlying data object for a form field.
2245
+ *
2246
+ * @param {Object} field - The field object with properties: `key`, `path`, `id`, and optionally `_parent`.
2247
+ * @param {Object} [options={}] - Configuration options.
2248
+ * @param {Object} [options.replacements={}] - A map of field IDs to alternative path arrays.
2249
+ * @param {Object} [options.cutoffNode] - The ID of the parent field at which to stop generating the path.
2250
+ *
2251
+ * @returns {(Array<string>|undefined)} An array of strings representing the binding path, or undefined if not determinable.
2252
+ */
2253
+ getValuePath(field, options = {}) {
2254
+ const {
2255
+ replacements = {},
2256
+ cutoffNode = null
2257
+ } = options;
2258
+ let localValuePath = [];
2259
+ const hasReplacement = Object.prototype.hasOwnProperty.call(replacements, field.id);
2260
+ const formFieldConfig = this._formFields.get(field.type).config;
2261
+ if (hasReplacement) {
2262
+ const replacement = replacements[field.id];
2263
+ if (replacement === null || replacement === undefined || replacement === '') {
2264
+ localValuePath = [];
2265
+ } else if (typeof replacement === 'string') {
2266
+ localValuePath = replacement.split('.');
2267
+ } else if (Array.isArray(replacement)) {
2268
+ localValuePath = replacement;
2269
+ } else {
2270
+ throw new Error(`replacements for field ${field.id} must be a string, array or null/undefined`);
2271
+ }
2272
+ } else if (formFieldConfig.keyed) {
2273
+ localValuePath = field.key.split('.');
2274
+ } else if (formFieldConfig.pathed && field.path) {
2275
+ localValuePath = field.path.split('.');
2276
+ }
2277
+ if (field._parent && field._parent !== cutoffNode) {
2278
+ const parent = this._formFieldRegistry.get(field._parent);
2279
+ return [...(this.getValuePath(parent, options) || []), ...localValuePath];
2280
+ }
2281
+ return localValuePath;
1925
2282
  }
1926
2283
  clear() {
1927
- this._formFields = {};
1928
- this._ids.clear();
1929
- this._keys.clear();
2284
+ this._dataPaths = [];
1930
2285
  }
1931
2286
  }
1932
- FormFieldRegistry.$inject = ['eventBus'];
2287
+ const _getNextSegment = (node, segment) => {
2288
+ if (isArray(node.children)) {
2289
+ return node.children.find(node => node.segment === segment) || null;
2290
+ }
2291
+ return null;
2292
+ };
2293
+ PathRegistry.$inject = ['formFieldRegistry', 'formFields'];
1933
2294
 
1934
- /**
1935
- * @typedef { { id: String, components: Array<String> } } FormRow
1936
- * @typedef { { formFieldId: String, rows: Array<FormRow> } } FormRows
2295
+ /**
2296
+ * @typedef { { id: String, components: Array<String> } } FormRow
2297
+ * @typedef { { formFieldId: String, rows: Array<FormRow> } } FormRows
1937
2298
  */
1938
2299
 
1939
- /**
1940
- * Maintains the Form layout in a given structure, for example
1941
- *
1942
- * [
1943
- * {
1944
- * formFieldId: 'FormField_1',
1945
- * rows: [
1946
- * { id: 'Row_1', components: [ 'Text_1', 'Textdield_1', ... ] }
1947
- * ]
1948
- * }
1949
- * ]
1950
- *
2300
+ /**
2301
+ * Maintains the Form layout in a given structure, for example
2302
+ *
2303
+ * [
2304
+ * {
2305
+ * formFieldId: 'FormField_1',
2306
+ * rows: [
2307
+ * { id: 'Row_1', components: [ 'Text_1', 'Textdield_1', ... ] }
2308
+ * ]
2309
+ * }
2310
+ * ]
2311
+ *
1951
2312
  */
1952
2313
  class FormLayouter {
1953
2314
  constructor(eventBus) {
@@ -1957,8 +2318,8 @@ class FormLayouter {
1957
2318
  this._eventBus = eventBus;
1958
2319
  }
1959
2320
 
1960
- /**
1961
- * @param {FormRow} row
2321
+ /**
2322
+ * @param {FormRow} row
1962
2323
  */
1963
2324
  addRow(formFieldId, row) {
1964
2325
  let rowsPerComponent = this._rows.find(r => r.formFieldId === formFieldId);
@@ -1972,18 +2333,18 @@ class FormLayouter {
1972
2333
  rowsPerComponent.rows.push(row);
1973
2334
  }
1974
2335
 
1975
- /**
1976
- * @param {String} id
1977
- * @returns {FormRow}
2336
+ /**
2337
+ * @param {String} id
2338
+ * @returns {FormRow}
1978
2339
  */
1979
2340
  getRow(id) {
1980
2341
  const rows = allRows(this._rows);
1981
2342
  return rows.find(r => r.id === id);
1982
2343
  }
1983
2344
 
1984
- /**
1985
- * @param {any} formField
1986
- * @returns {FormRow}
2345
+ /**
2346
+ * @param {any} formField
2347
+ * @returns {FormRow}
1987
2348
  */
1988
2349
  getRowForField(formField) {
1989
2350
  return allRows(this._rows).find(r => {
@@ -1994,9 +2355,9 @@ class FormLayouter {
1994
2355
  });
1995
2356
  }
1996
2357
 
1997
- /**
1998
- * @param {String} formFieldId
1999
- * @returns { Array<FormRow> }
2358
+ /**
2359
+ * @param {String} formFieldId
2360
+ * @returns { Array<FormRow> }
2000
2361
  */
2001
2362
  getRows(formFieldId) {
2002
2363
  const rowsForField = this._rows.find(r => formFieldId === r.formFieldId);
@@ -2006,22 +2367,22 @@ class FormLayouter {
2006
2367
  return rowsForField.rows;
2007
2368
  }
2008
2369
 
2009
- /**
2010
- * @returns {string}
2370
+ /**
2371
+ * @returns {string}
2011
2372
  */
2012
2373
  nextRowId() {
2013
2374
  return this._ids.nextPrefixed('Row_');
2014
2375
  }
2015
2376
 
2016
- /**
2017
- * @param {any} formField
2377
+ /**
2378
+ * @param {any} formField
2018
2379
  */
2019
2380
  calculateLayout(formField) {
2020
2381
  const {
2021
2382
  type,
2022
2383
  components
2023
2384
  } = formField;
2024
- if (type !== 'default' || !components) {
2385
+ if (type !== 'default' && type !== 'group' || !components) {
2025
2386
  return;
2026
2387
  }
2027
2388
 
@@ -2068,155 +2429,60 @@ function groupByRow(components, ids) {
2068
2429
  });
2069
2430
  }
2070
2431
 
2071
- /**
2072
- * @param {Array<FormRows>} formRows
2073
- * @returns {Array<FormRow>}
2432
+ /**
2433
+ * @param {Array<FormRows>} formRows
2434
+ * @returns {Array<FormRow>}
2074
2435
  */
2075
2436
  function allRows(formRows) {
2076
2437
  return flatten(formRows.map(c => c.rows));
2077
2438
  }
2078
2439
 
2079
- class Importer {
2080
- /**
2081
- * @constructor
2082
- * @param { import('../core').FormFieldRegistry } formFieldRegistry
2083
- * @param { import('../render/FormFields').default } formFields
2084
- * @param { import('../core').FormLayouter } formLayouter
2085
- */
2086
- constructor(formFieldRegistry, formFields, formLayouter) {
2087
- this._formFieldRegistry = formFieldRegistry;
2088
- this._formFields = formFields;
2089
- this._formLayouter = formLayouter;
2440
+ class FormFieldRegistry {
2441
+ constructor(eventBus) {
2442
+ this._eventBus = eventBus;
2443
+ this._formFields = {};
2444
+ eventBus.on('form.clear', () => this.clear());
2445
+ this._ids = new Ids([32, 36, 1]);
2090
2446
  }
2091
-
2092
- /**
2093
- * Import schema adding `id`, `_parent` and `_path`
2094
- * information to each field and adding it to the
2095
- * form field registry.
2096
- *
2097
- * @param {any} schema
2098
- * @param {any} [data]
2099
- *
2100
- * @return { { warnings: Array<any>, schema: any, data: any } }
2101
- */
2102
- importSchema(schema, data = {}) {
2103
- // TODO: Add warnings - https://github.com/bpmn-io/form-js/issues/289
2104
- const warnings = [];
2105
- try {
2106
- this._formLayouter.clear();
2107
- const importedSchema = this.importFormField(clone(schema)),
2108
- initializedData = this.initializeFieldValues(clone(data));
2109
- this._formLayouter.calculateLayout(clone(importedSchema));
2110
- return {
2111
- warnings,
2112
- schema: importedSchema,
2113
- data: initializedData
2114
- };
2115
- } catch (err) {
2116
- err.warnings = warnings;
2117
- throw err;
2447
+ add(formField) {
2448
+ const {
2449
+ id
2450
+ } = formField;
2451
+ if (this._formFields[id]) {
2452
+ throw new Error(`form field with ID ${id} already exists`);
2118
2453
  }
2454
+ this._eventBus.fire('formField.add', {
2455
+ formField
2456
+ });
2457
+ this._formFields[id] = formField;
2119
2458
  }
2120
-
2121
- /**
2122
- * @param {any} formField
2123
- * @param {string} [parentId]
2124
- *
2125
- * @return {any} importedField
2126
- */
2127
- importFormField(formField, parentId) {
2459
+ remove(formField) {
2128
2460
  const {
2129
- components,
2130
- key,
2131
- type,
2132
- id = generateIdForType(type)
2461
+ id
2133
2462
  } = formField;
2134
- if (parentId) {
2135
- // set form field parent
2136
- formField._parent = parentId;
2137
- }
2138
- if (!this._formFields.get(type)) {
2139
- throw new Error(`form field of type <${type}> not supported`);
2140
- }
2141
- if (key) {
2142
- // validate <key> uniqueness
2143
- if (this._formFieldRegistry._keys.assigned(key)) {
2144
- throw new Error(`form field with key <${key}> already exists`);
2145
- }
2146
- this._formFieldRegistry._keys.claim(key, formField);
2147
-
2148
- // TODO: buttons should not have key
2149
- if (type !== 'button') {
2150
- // set form field path
2151
- formField._path = [key];
2152
- }
2153
- }
2154
- if (id) {
2155
- // validate <id> uniqueness
2156
- if (this._formFieldRegistry._ids.assigned(id)) {
2157
- throw new Error(`form field with id <${id}> already exists`);
2158
- }
2159
- this._formFieldRegistry._ids.claim(id, formField);
2160
- }
2161
-
2162
- // set form field ID
2163
- formField.id = id;
2164
- this._formFieldRegistry.add(formField);
2165
- if (components) {
2166
- this.importFormFields(components, id);
2463
+ if (!this._formFields[id]) {
2464
+ return;
2167
2465
  }
2168
- return formField;
2169
- }
2170
- importFormFields(components, parentId) {
2171
- components.forEach(component => {
2172
- this.importFormField(component, parentId);
2466
+ this._eventBus.fire('formField.remove', {
2467
+ formField
2173
2468
  });
2469
+ delete this._formFields[id];
2470
+ }
2471
+ get(id) {
2472
+ return this._formFields[id];
2473
+ }
2474
+ getAll() {
2475
+ return Object.values(this._formFields);
2174
2476
  }
2175
-
2176
- /**
2177
- * @param {Object} data
2178
- *
2179
- * @return {Object} initializedData
2180
- */
2181
- initializeFieldValues(data) {
2182
- return this._formFieldRegistry.getAll().reduce((initializedData, formField) => {
2183
- const {
2184
- defaultValue,
2185
- _path,
2186
- type
2187
- } = formField;
2188
-
2189
- // try to get value from data
2190
- // if unavailable - try to get default value from form field
2191
- // if unavailable - get empty value from form field
2192
-
2193
- if (_path) {
2194
- const {
2195
- config: fieldConfig
2196
- } = this._formFields.get(type);
2197
- let valueData = get(data, _path);
2198
- if (!isUndefined(valueData) && fieldConfig.sanitizeValue) {
2199
- valueData = fieldConfig.sanitizeValue({
2200
- formField,
2201
- data,
2202
- value: valueData
2203
- });
2204
- }
2205
- const initializedFieldValue = !isUndefined(valueData) ? valueData : !isUndefined(defaultValue) ? defaultValue : fieldConfig.emptyValue;
2206
- initializedData = {
2207
- ...initializedData,
2208
- [_path[0]]: initializedFieldValue
2209
- };
2210
- }
2211
- return initializedData;
2212
- }, data);
2477
+ forEach(callback) {
2478
+ this.getAll().forEach(formField => callback(formField));
2479
+ }
2480
+ clear() {
2481
+ this._formFields = {};
2482
+ this._ids.clear();
2213
2483
  }
2214
2484
  }
2215
- Importer.$inject = ['formFieldRegistry', 'formFields', 'formLayouter'];
2216
-
2217
- var importModule = {
2218
- importer: ['type', Importer]
2219
- };
2485
+ FormFieldRegistry.$inject = ['eventBus'];
2220
2486
 
2221
2487
  function formFieldClasses(type, {
2222
2488
  errors = [],
@@ -2271,7 +2537,7 @@ function Button(props) {
2271
2537
  }
2272
2538
  Button.config = {
2273
2539
  type: type$c,
2274
- keyed: true,
2540
+ keyed: false,
2275
2541
  label: 'Button',
2276
2542
  group: 'action',
2277
2543
  create: (options = {}) => ({
@@ -2281,6 +2547,9 @@ Button.config = {
2281
2547
  };
2282
2548
 
2283
2549
  const FormRenderContext = createContext({
2550
+ EmptyRoot: props => {
2551
+ return null;
2552
+ },
2284
2553
  Empty: props => {
2285
2554
  return null;
2286
2555
  },
@@ -2310,15 +2579,19 @@ const FormRenderContext = createContext({
2310
2579
  class: props.class,
2311
2580
  children: props.children
2312
2581
  });
2582
+ },
2583
+ hoveredId: [],
2584
+ setHoveredId: newValue => {
2585
+ console.log(`setHoveredId not defined, called with '${newValue}'`);
2313
2586
  }
2314
2587
  });
2315
2588
  var FormRenderContext$1 = FormRenderContext;
2316
2589
 
2317
- /**
2318
- * @param {string} type
2319
- * @param {boolean} [strict]
2320
- *
2321
- * @returns {any}
2590
+ /**
2591
+ * @param {string} type
2592
+ * @param {boolean} [strict]
2593
+ *
2594
+ * @returns {any}
2322
2595
  */
2323
2596
  function getService(type, strict) {}
2324
2597
  const FormContext = createContext({
@@ -2334,10 +2607,10 @@ function useService(type, strict) {
2334
2607
  return getService(type, strict);
2335
2608
  }
2336
2609
 
2337
- /**
2338
- * Returns the conditionally filtered data of a form reactively.
2339
- * Memoised to minimize re-renders
2340
- *
2610
+ /**
2611
+ * Returns the conditionally filtered data of a form reactively.
2612
+ * Memoised to minimize re-renders
2613
+ *
2341
2614
  */
2342
2615
  function useFilteredFormData() {
2343
2616
  const {
@@ -2354,12 +2627,12 @@ function useFilteredFormData() {
2354
2627
  }, [conditionChecker, data, initialData]);
2355
2628
  }
2356
2629
 
2357
- /**
2358
- * Evaluate if condition is met reactively based on the conditionChecker and form data.
2359
- *
2360
- * @param {string | undefined} condition
2361
- *
2362
- * @returns {boolean} true if condition is met or no condition or condition checker exists
2630
+ /**
2631
+ * Evaluate if condition is met reactively based on the conditionChecker and form data.
2632
+ *
2633
+ * @param {string | undefined} condition
2634
+ *
2635
+ * @returns {boolean} true if condition is met or no condition or condition checker exists
2363
2636
  */
2364
2637
  function useCondition(condition) {
2365
2638
  const conditionChecker = useService('conditionChecker', false);
@@ -2369,13 +2642,13 @@ function useCondition(condition) {
2369
2642
  }, [conditionChecker, condition, filteredData]);
2370
2643
  }
2371
2644
 
2372
- /**
2373
- * Evaluate a string reactively based on the expressionLanguage and form data.
2374
- * If the string is not an expression, it is returned as is.
2375
- * Memoised to minimize re-renders.
2376
- *
2377
- * @param {string} value
2378
- *
2645
+ /**
2646
+ * Evaluate a string reactively based on the expressionLanguage and form data.
2647
+ * If the string is not an expression, it is returned as is.
2648
+ * Memoised to minimize re-renders.
2649
+ *
2650
+ * @param {string} value
2651
+ *
2379
2652
  */
2380
2653
  function useExpressionEvaluation(value) {
2381
2654
  const formData = useFilteredFormData();
@@ -2404,16 +2677,16 @@ function useKeyDownAction(targetKey, action, listenerElement = window) {
2404
2677
  });
2405
2678
  }
2406
2679
 
2407
- /**
2408
- * Retrieve readonly value of a form field, given it can be an
2409
- * expression optionally or configured globally.
2410
- *
2411
- * @typedef { import('../../types').FormProperties } FormProperties
2412
- *
2413
- * @param {any} formField
2414
- * @param {FormProperties} properties
2415
- *
2416
- * @returns {boolean}
2680
+ /**
2681
+ * Retrieve readonly value of a form field, given it can be an
2682
+ * expression optionally or configured globally.
2683
+ *
2684
+ * @typedef { import('../../types').FormProperties } FormProperties
2685
+ *
2686
+ * @param {any} formField
2687
+ * @param {FormProperties} properties
2688
+ *
2689
+ * @returns {boolean}
2417
2690
  */
2418
2691
  function useReadonly(formField, properties = {}) {
2419
2692
  const expressionLanguage = useService('expressionLanguage');
@@ -2431,16 +2704,16 @@ function useReadonly(formField, properties = {}) {
2431
2704
  return readonly || false;
2432
2705
  }
2433
2706
 
2434
- /**
2435
- * Template a string reactively based on form data. If the string is not a template, it is returned as is.
2436
- * Memoised to minimize re-renders
2437
- *
2438
- * @param {string} value
2439
- * @param {Object} options
2440
- * @param {boolean} [options.debug = false]
2441
- * @param {boolean} [options.strict = false]
2442
- * @param {Function} [options.buildDebugString]
2443
- *
2707
+ /**
2708
+ * Template a string reactively based on form data. If the string is not a template, it is returned as is.
2709
+ * Memoised to minimize re-renders
2710
+ *
2711
+ * @param {string} value
2712
+ * @param {Object} options
2713
+ * @param {boolean} [options.debug = false]
2714
+ * @param {boolean} [options.strict = false]
2715
+ * @param {Function} [options.buildDebugString]
2716
+ *
2444
2717
  */
2445
2718
  function useTemplateEvaluation(value, options) {
2446
2719
  const filteredData = useFilteredFormData();
@@ -2453,17 +2726,17 @@ function useTemplateEvaluation(value, options) {
2453
2726
  }, [filteredData, templating, value, options]);
2454
2727
  }
2455
2728
 
2456
- /**
2457
- * Template a string reactively based on form data. If the string is not a template, it is returned as is.
2458
- * If the string contains multiple lines, only the first line is returned.
2459
- * Memoised to minimize re-renders
2460
- *
2461
- * @param {string} value
2462
- * @param {Object} [options]
2463
- * @param {boolean} [options.debug = false]
2464
- * @param {boolean} [options.strict = false]
2465
- * @param {Function} [options.buildDebugString]
2466
- *
2729
+ /**
2730
+ * Template a string reactively based on form data. If the string is not a template, it is returned as is.
2731
+ * If the string contains multiple lines, only the first line is returned.
2732
+ * Memoised to minimize re-renders
2733
+ *
2734
+ * @param {string} value
2735
+ * @param {Object} [options]
2736
+ * @param {boolean} [options.debug = false]
2737
+ * @param {boolean} [options.strict = false]
2738
+ * @param {Function} [options.buildDebugString]
2739
+ *
2467
2740
  */
2468
2741
  function useSingleLineTemplateEvaluation(value, options = {}) {
2469
2742
  const evaluatedTemplate = useTemplateEvaluation(value, options);
@@ -2651,9 +2924,24 @@ function _isReadableType(value) {
2651
2924
  function _isValueSomething(value) {
2652
2925
  return value || value === 0 || value === false;
2653
2926
  }
2927
+ function createEmptyOptions(options = {}) {
2928
+ const defaults = {};
2654
2929
 
2655
- /**
2656
- * @enum { String }
2930
+ // provide default values if valuesKey and valuesExpression are not set
2931
+ if (!options.valuesKey && !options.valuesExpression) {
2932
+ defaults.values = [{
2933
+ label: 'Value',
2934
+ value: 'value'
2935
+ }];
2936
+ }
2937
+ return {
2938
+ ...defaults,
2939
+ ...options
2940
+ };
2941
+ }
2942
+
2943
+ /**
2944
+ * @enum { String }
2657
2945
  */
2658
2946
  const LOAD_STATES = {
2659
2947
  LOADING: 'loading',
@@ -2661,17 +2949,17 @@ const LOAD_STATES = {
2661
2949
  ERROR: 'error'
2662
2950
  };
2663
2951
 
2664
- /**
2665
- * @typedef {Object} ValuesGetter
2666
- * @property {Object[]} values - The values data
2667
- * @property {(LOAD_STATES)} state - The values data's loading state, to use for conditional rendering
2952
+ /**
2953
+ * @typedef {Object} ValuesGetter
2954
+ * @property {Object[]} values - The values data
2955
+ * @property {(LOAD_STATES)} state - The values data's loading state, to use for conditional rendering
2668
2956
  */
2669
2957
 
2670
- /**
2671
- * A hook to load values for single and multiselect components.
2672
- *
2673
- * @param {Object} field - The form field to handle values for
2674
- * @return {ValuesGetter} valuesGetter - A values getter object providing loading state and values
2958
+ /**
2959
+ * A hook to load values for single and multiselect components.
2960
+ *
2961
+ * @param {Object} field - The form field to handle values for
2962
+ * @return {ValuesGetter} valuesGetter - A values getter object providing loading state and values
2675
2963
  */
2676
2964
  function useValuesAsync (field) {
2677
2965
  const {
@@ -3027,21 +3315,7 @@ Checklist.config = {
3027
3315
  group: 'selection',
3028
3316
  emptyValue: [],
3029
3317
  sanitizeValue: sanitizeMultiSelectValue,
3030
- create: (options = {}) => {
3031
- const defaults = {};
3032
-
3033
- // provide default values if valuesKey isn't set
3034
- if (!options.valuesKey) {
3035
- defaults.values = [{
3036
- label: 'Value',
3037
- value: 'value'
3038
- }];
3039
- }
3040
- return {
3041
- ...defaults,
3042
- ...options
3043
- };
3044
- }
3318
+ create: createEmptyOptions
3045
3319
  };
3046
3320
 
3047
3321
  const noop$1 = () => false;
@@ -3052,6 +3326,7 @@ function FormField(props) {
3052
3326
  } = props;
3053
3327
  const formFields = useService('formFields'),
3054
3328
  viewerCommands = useService('viewerCommands', false),
3329
+ pathRegistry = useService('pathRegistry'),
3055
3330
  form = useService('form');
3056
3331
  const {
3057
3332
  initialData,
@@ -3068,10 +3343,10 @@ function FormField(props) {
3068
3343
  if (!FormFieldComponent) {
3069
3344
  throw new Error(`cannot render field <${field.type}>`);
3070
3345
  }
3071
- const initialValue = useMemo(() => get(initialData, field._path), [initialData, field._path]);
3072
- const value = get(data, field._path);
3073
- const fieldErrors = findErrors(errors, field._path);
3346
+ const valuePath = useMemo(() => pathRegistry.getValuePath(field), [field, pathRegistry]);
3347
+ const initialValue = useMemo(() => get(initialData, valuePath), [initialData, valuePath]);
3074
3348
  const readonly = useReadonly(field, properties);
3349
+ const value = get(data, valuePath);
3075
3350
 
3076
3351
  // add precedence: global readonly > form field disabled
3077
3352
  const disabled = !properties.readOnly && (properties.disabled || field.disabled || false);
@@ -3098,7 +3373,7 @@ function FormField(props) {
3098
3373
  children: jsx(FormFieldComponent, {
3099
3374
  ...props,
3100
3375
  disabled: disabled,
3101
- errors: fieldErrors,
3376
+ errors: errors[field.id],
3102
3377
  onChange: disabled || readonly ? noop$1 : onChange,
3103
3378
  onBlur: disabled || readonly ? noop$1 : onBlur,
3104
3379
  readonly: readonly,
@@ -3108,14 +3383,14 @@ function FormField(props) {
3108
3383
  });
3109
3384
  }
3110
3385
 
3111
- function Default(props) {
3386
+ function Grid(props) {
3112
3387
  const {
3113
3388
  Children,
3114
- Empty,
3115
3389
  Row
3116
3390
  } = useContext(FormRenderContext$1);
3117
3391
  const {
3118
- field
3392
+ field,
3393
+ Empty
3119
3394
  } = props;
3120
3395
  const {
3121
3396
  id,
@@ -3152,7 +3427,20 @@ function Default(props) {
3152
3427
  }), components.length ? null : jsx(Empty, {})]
3153
3428
  });
3154
3429
  }
3155
- Default.config = {
3430
+
3431
+ function FormComponent$1(props) {
3432
+ const {
3433
+ EmptyRoot
3434
+ } = useContext(FormRenderContext$1);
3435
+ const fullProps = {
3436
+ ...props,
3437
+ Empty: EmptyRoot
3438
+ };
3439
+ return jsx(Grid, {
3440
+ ...fullProps
3441
+ });
3442
+ }
3443
+ FormComponent$1.config = {
3156
3444
  type: 'default',
3157
3445
  keyed: false,
3158
3446
  label: null,
@@ -3181,6 +3469,74 @@ var SvgCalendar = function SvgCalendar(props) {
3181
3469
  };
3182
3470
  var CalendarIcon = SvgCalendar;
3183
3471
 
3472
+ /**
3473
+ * Returns date format for the provided locale.
3474
+ * If the locale is not provided, uses the browser's locale.
3475
+ *
3476
+ * @param {string} [locale] - The locale to get date format for.
3477
+ * @returns {string} The date format for the locale.
3478
+ */
3479
+ function getLocaleDateFormat(locale = 'default') {
3480
+ const parts = new Intl.DateTimeFormat(locale).formatToParts(new Date(Date.UTC(2020, 5, 5)));
3481
+ return parts.map(part => {
3482
+ const len = part.value.length;
3483
+ switch (part.type) {
3484
+ case 'day':
3485
+ return 'd'.repeat(len);
3486
+ case 'month':
3487
+ return 'M'.repeat(len);
3488
+ case 'year':
3489
+ return 'y'.repeat(len);
3490
+ default:
3491
+ return part.value;
3492
+ }
3493
+ }).join('');
3494
+ }
3495
+
3496
+ /**
3497
+ * Returns readable date format for the provided locale.
3498
+ * If the locale is not provided, uses the browser's locale.
3499
+ *
3500
+ * @param {string} [locale] - The locale to get readable date format for.
3501
+ * @returns {string} The readable date format for the locale.
3502
+ */
3503
+ function getLocaleReadableDateFormat(locale) {
3504
+ let format = getLocaleDateFormat(locale).toLowerCase();
3505
+
3506
+ // Ensure month is in 'mm' format
3507
+ if (!format.includes('mm')) {
3508
+ format = format.replace('m', 'mm');
3509
+ }
3510
+
3511
+ // Ensure day is in 'dd' format
3512
+ if (!format.includes('dd')) {
3513
+ format = format.replace('d', 'dd');
3514
+ }
3515
+ return format;
3516
+ }
3517
+
3518
+ /**
3519
+ * Returns flatpickr config for the provided locale.
3520
+ * If the locale is not provided, uses the browser's locale.
3521
+ *
3522
+ * @param {string} [locale] - The locale to get flatpickr config for.
3523
+ * @returns {object} The flatpickr config for the locale.
3524
+ */
3525
+ function getLocaleDateFlatpickrConfig(locale) {
3526
+ return flatpickerizeDateFormat(getLocaleDateFormat(locale));
3527
+ }
3528
+ function flatpickerizeDateFormat(dateFormat) {
3529
+ const useLeadingZero = {
3530
+ day: dateFormat.includes('dd'),
3531
+ month: dateFormat.includes('MM'),
3532
+ year: dateFormat.includes('yyyy')
3533
+ };
3534
+ dateFormat = useLeadingZero.day ? dateFormat.replace('dd', 'd') : dateFormat.replace('d', 'j');
3535
+ dateFormat = useLeadingZero.month ? dateFormat.replace('MM', 'm') : dateFormat.replace('M', 'n');
3536
+ dateFormat = useLeadingZero.year ? dateFormat.replace('yyyy', 'Y') : dateFormat.replace('yy', 'y');
3537
+ return dateFormat;
3538
+ }
3539
+
3184
3540
  function InputAdorner(props) {
3185
3541
  const {
3186
3542
  pre,
@@ -3255,7 +3611,7 @@ function Datepicker(props) {
3255
3611
  useEffect(() => {
3256
3612
  let config = {
3257
3613
  allowInput: true,
3258
- dateFormat: 'm/d/Y',
3614
+ dateFormat: getLocaleDateFlatpickrConfig(),
3259
3615
  static: true,
3260
3616
  clickOpens: false,
3261
3617
  // TODO: support dates prior to 1900 (https://github.com/bpmn-io/form-js/issues/533)
@@ -3347,7 +3703,7 @@ function Datepicker(props) {
3347
3703
  class: "fjs-input",
3348
3704
  disabled: disabled,
3349
3705
  readOnly: readonly,
3350
- placeholder: "mm/dd/yyyy",
3706
+ placeholder: getLocaleReadableDateFormat(),
3351
3707
  autoComplete: "off",
3352
3708
  onFocus: onInputFocus,
3353
3709
  onKeyDown: onInputKeyDown,
@@ -3843,10 +4199,10 @@ Datetime.config = {
3843
4199
  }
3844
4200
  };
3845
4201
 
3846
- /**
3847
- * This file must not be changed or exchanged.
3848
- *
3849
- * @see http://bpmn.io/license for more information.
4202
+ /**
4203
+ * This file must not be changed or exchanged.
4204
+ *
4205
+ * @see http://bpmn.io/license for more information.
3850
4206
  */
3851
4207
  function Logo() {
3852
4208
  return jsxs("svg", {
@@ -3968,6 +4324,52 @@ function FormComponent(props) {
3968
4324
  });
3969
4325
  }
3970
4326
 
4327
+ function Group(props) {
4328
+ const {
4329
+ field
4330
+ } = props;
4331
+ const {
4332
+ label,
4333
+ id,
4334
+ type,
4335
+ showOutline
4336
+ } = field;
4337
+ const {
4338
+ formId
4339
+ } = useContext(FormContext$1);
4340
+ const {
4341
+ Empty
4342
+ } = useContext(FormRenderContext$1);
4343
+ const fullProps = {
4344
+ ...props,
4345
+ Empty
4346
+ };
4347
+ return jsxs("div", {
4348
+ className: classNames(formFieldClasses(type), {
4349
+ 'fjs-outlined': showOutline
4350
+ }),
4351
+ role: "group",
4352
+ "aria-labelledby": prefixId(id, formId),
4353
+ children: [jsx(Label, {
4354
+ id: prefixId(id, formId),
4355
+ label: label
4356
+ }), jsx(Grid, {
4357
+ ...fullProps
4358
+ })]
4359
+ });
4360
+ }
4361
+ Group.config = {
4362
+ type: 'group',
4363
+ pathed: true,
4364
+ label: 'Group',
4365
+ group: 'presentation',
4366
+ create: (options = {}) => ({
4367
+ components: [],
4368
+ showOutline: true,
4369
+ ...options
4370
+ })
4371
+ };
4372
+
3971
4373
  const NODE_TYPE_TEXT = 3,
3972
4374
  NODE_TYPE_ELEMENT = 1;
3973
4375
  const ALLOWED_NODES = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'span', 'em', 'a', 'p', 'div', 'ul', 'ol', 'li', 'hr', 'blockquote', 'img', 'pre', 'code', 'br', 'strong', 'table', 'thead', 'tbody', 'tr', 'th', 'td'];
@@ -3978,11 +4380,11 @@ const ATTR_WHITESPACE_PATTERN = /[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u
3978
4380
 
3979
4381
  const FORM_ELEMENT = document.createElement('form');
3980
4382
 
3981
- /**
3982
- * Sanitize a HTML string and return the cleaned, safe version.
3983
- *
3984
- * @param {string} html
3985
- * @return {string}
4383
+ /**
4384
+ * Sanitize a HTML string and return the cleaned, safe version.
4385
+ *
4386
+ * @param {string} html
4387
+ * @return {string}
3986
4388
  */
3987
4389
 
3988
4390
  // see https://github.com/developit/snarkdown/issues/70
@@ -4000,29 +4402,29 @@ function sanitizeHTML(html) {
4000
4402
  }
4001
4403
  }
4002
4404
 
4003
- /**
4004
- * Sanitizes an image source to ensure we only allow for data URI and links
4005
- * that start with http(s).
4006
- *
4007
- * Note: Most browsers anyway do not support script execution in <img> elements.
4008
- *
4009
- * @param {string} src
4010
- * @returns {string}
4405
+ /**
4406
+ * Sanitizes an image source to ensure we only allow for data URI and links
4407
+ * that start with http(s).
4408
+ *
4409
+ * Note: Most browsers anyway do not support script execution in <img> elements.
4410
+ *
4411
+ * @param {string} src
4412
+ * @returns {string}
4011
4413
  */
4012
4414
  function sanitizeImageSource(src) {
4013
4415
  const valid = ALLOWED_IMAGE_SRC_PATTERN.test(src);
4014
4416
  return valid ? src : '';
4015
4417
  }
4016
4418
 
4017
- /**
4018
- * Recursively sanitize a HTML node, potentially
4019
- * removing it, its children or attributes.
4020
- *
4021
- * Inspired by https://github.com/developit/snarkdown/issues/70
4022
- * and https://github.com/cure53/DOMPurify. Simplified
4023
- * for our use-case.
4024
- *
4025
- * @param {Element} node
4419
+ /**
4420
+ * Recursively sanitize a HTML node, potentially
4421
+ * removing it, its children or attributes.
4422
+ *
4423
+ * Inspired by https://github.com/developit/snarkdown/issues/70
4424
+ * and https://github.com/cure53/DOMPurify. Simplified
4425
+ * for our use-case.
4426
+ *
4427
+ * @param {Element} node
4026
4428
  */
4027
4429
  function sanitizeNode(node) {
4028
4430
  // allow text nodes
@@ -4066,13 +4468,13 @@ function sanitizeNode(node) {
4066
4468
  }
4067
4469
  }
4068
4470
 
4069
- /**
4070
- * Validates attributes for validity.
4071
- *
4072
- * @param {string} lcTag
4073
- * @param {string} lcName
4074
- * @param {string} value
4075
- * @return {boolean}
4471
+ /**
4472
+ * Validates attributes for validity.
4473
+ *
4474
+ * @param {string} lcTag
4475
+ * @param {string} lcName
4476
+ * @param {string} value
4477
+ * @return {boolean}
4076
4478
  */
4077
4479
  function isValidAttribute(lcTag, lcName, value) {
4078
4480
  // disallow most attributes based on whitelist
@@ -4556,21 +4958,7 @@ Radio.config = {
4556
4958
  group: 'selection',
4557
4959
  emptyValue: null,
4558
4960
  sanitizeValue: sanitizeSingleSelectValue,
4559
- create: (options = {}) => {
4560
- const defaults = {};
4561
-
4562
- // provide default values if valuesKey isn't set
4563
- if (!options.valuesKey) {
4564
- defaults.values = [{
4565
- label: 'Value',
4566
- value: 'value'
4567
- }];
4568
- }
4569
- return {
4570
- ...defaults,
4571
- ...options
4572
- };
4573
- }
4961
+ create: createEmptyOptions
4574
4962
  };
4575
4963
 
4576
4964
  var _path$d;
@@ -4922,21 +5310,7 @@ Select.config = {
4922
5310
  group: 'selection',
4923
5311
  emptyValue: null,
4924
5312
  sanitizeValue: sanitizeSingleSelectValue,
4925
- create: (options = {}) => {
4926
- const defaults = {};
4927
-
4928
- // provide default values if valuesKey isn't set
4929
- if (!options.valuesKey) {
4930
- defaults.values = [{
4931
- label: 'Value',
4932
- value: 'value'
4933
- }];
4934
- }
4935
- return {
4936
- ...defaults,
4937
- ...options
4938
- };
4939
- }
5313
+ create: createEmptyOptions
4940
5314
  };
4941
5315
 
4942
5316
  const type$4 = 'spacer';
@@ -5164,21 +5538,7 @@ Taglist.config = {
5164
5538
  group: 'selection',
5165
5539
  emptyValue: [],
5166
5540
  sanitizeValue: sanitizeMultiSelectValue,
5167
- create: (options = {}) => {
5168
- const defaults = {};
5169
-
5170
- // provide default values if valuesKey isn't set
5171
- if (!options.valuesKey) {
5172
- defaults.values = [{
5173
- label: 'Value',
5174
- value: 'value'
5175
- }];
5176
- }
5177
- return {
5178
- ...defaults,
5179
- ...options
5180
- };
5181
- }
5541
+ create: createEmptyOptions
5182
5542
  };
5183
5543
 
5184
5544
  const type$2 = 'text';
@@ -5595,13 +5955,14 @@ var SvgGroup = function SvgGroup(props) {
5595
5955
  return /*#__PURE__*/React.createElement("svg", _extends$8({
5596
5956
  xmlns: "http://www.w3.org/2000/svg",
5597
5957
  width: 54,
5598
- height: 54
5958
+ height: 54,
5959
+ fill: "currentcolor"
5599
5960
  }, props), _path$8 || (_path$8 = /*#__PURE__*/React.createElement("path", {
5600
5961
  fillRule: "evenodd",
5601
5962
  d: "M8 33v5a1 1 0 0 0 1 1h4v2H9a3 3 0 0 1-3-3v-5h2Zm18 6v2H15v-2h11Zm13 0v2H28v-2h11Zm9-6v5a3 3 0 0 1-3 3h-4v-2h4a1 1 0 0 0 .993-.883L46 38v-5h2ZM8 22v9H6v-9h2Zm40 0v9h-2v-9h2Zm-35-9v2H9a1 1 0 0 0-.993.883L8 16v4H6v-4a3 3 0 0 1 3-3h4Zm32 0a3 3 0 0 1 3 3v4h-2v-4a1 1 0 0 0-.883-.993L45 15h-4v-2h4Zm-6 0v2H28v-2h11Zm-13 0v2H15v-2h11Z"
5602
5963
  })));
5603
5964
  };
5604
- var ColumnsIcon = SvgGroup;
5965
+ var GroupIcon = SvgGroup;
5605
5966
 
5606
5967
  var _path$7;
5607
5968
  function _extends$7() { _extends$7 = Object.assign ? Object.assign.bind() : 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$7.apply(this, arguments); }
@@ -5654,14 +6015,14 @@ var SvgSpacer = function SvgSpacer(props) {
5654
6015
  xmlns: "http://www.w3.org/2000/svg",
5655
6016
  width: 54,
5656
6017
  height: 54,
5657
- fill: "none"
6018
+ fill: "currentcolor"
5658
6019
  }, props), _path$4 || (_path$4 = /*#__PURE__*/React.createElement("path", {
5659
- stroke: "#000",
6020
+ stroke: "currentcolor",
5660
6021
  strokeLinecap: "square",
5661
6022
  strokeWidth: 2,
5662
6023
  d: "M9 23h36M9 31h36"
5663
6024
  })), _path2$1 || (_path2$1 = /*#__PURE__*/React.createElement("path", {
5664
- stroke: "#000",
6025
+ stroke: "currentcolor",
5665
6026
  strokeLinecap: "round",
5666
6027
  strokeLinejoin: "round",
5667
6028
  strokeWidth: 2,
@@ -5739,8 +6100,9 @@ const iconsByType = type => {
5739
6100
  button: ButtonIcon,
5740
6101
  checkbox: CheckboxIcon,
5741
6102
  checklist: ChecklistIcon,
5742
- columns: ColumnsIcon,
6103
+ columns: GroupIcon,
5743
6104
  datetime: DatetimeIcon,
6105
+ group: GroupIcon,
5744
6106
  image: ImageIcon,
5745
6107
  number: NumberIcon,
5746
6108
  radio: RadioIcon,
@@ -5754,7 +6116,7 @@ const iconsByType = type => {
5754
6116
  }[type];
5755
6117
  };
5756
6118
 
5757
- const formFields = [Button, Checkbox, Checklist, Default, Image, Numberfield, Datetime, Radio, Select, Spacer, Taglist, Text, Textfield, Textarea];
6119
+ const formFields = [Button, Checkbox, Checklist, FormComponent$1, Group, Image, Numberfield, Datetime, Radio, Select, Spacer, Taglist, Text, Textfield, Textarea];
5758
6120
 
5759
6121
  class FormFields {
5760
6122
  constructor() {
@@ -5830,9 +6192,12 @@ var renderModule = {
5830
6192
  };
5831
6193
 
5832
6194
  var core = {
5833
- __depends__: [importModule, renderModule],
6195
+ __depends__: [renderModule],
5834
6196
  eventBus: ['type', EventBus],
6197
+ importer: ['type', Importer],
6198
+ fieldFactory: ['type', FieldFactory],
5835
6199
  formFieldRegistry: ['type', FormFieldRegistry],
6200
+ pathRegistry: ['type', PathRegistry],
5836
6201
  formLayouter: ['type', FormLayouter],
5837
6202
  validator: ['type', Validator]
5838
6203
  };
@@ -5947,9 +6312,9 @@ class Form {
5947
6312
  this.clear();
5948
6313
  const {
5949
6314
  schema: importedSchema,
5950
- data: initializedData,
5951
6315
  warnings
5952
- } = this.get('importer').importSchema(schema, data);
6316
+ } = this.get('importer').importSchema(schema);
6317
+ const initializedData = this._initializeFieldData(clone(data));
5953
6318
  this._setState({
5954
6319
  data: initializedData,
5955
6320
  errors: {},
@@ -6007,21 +6372,21 @@ class Form {
6007
6372
  */
6008
6373
  validate() {
6009
6374
  const formFieldRegistry = this.get('formFieldRegistry'),
6375
+ pathRegistry = this.get('pathRegistry'),
6010
6376
  validator = this.get('validator');
6011
6377
  const {
6012
6378
  data
6013
6379
  } = this._getState();
6014
6380
  const errors = formFieldRegistry.getAll().reduce((errors, field) => {
6015
6381
  const {
6016
- disabled,
6017
- _path
6382
+ disabled
6018
6383
  } = field;
6019
6384
  if (disabled) {
6020
6385
  return errors;
6021
6386
  }
6022
- const value = get(data, _path);
6387
+ const value = get(data, pathRegistry.getValuePath(field));
6023
6388
  const fieldErrors = validator.validateField(field, value);
6024
- return set(errors, [pathStringify(_path)], fieldErrors.length ? fieldErrors : undefined);
6389
+ return set(errors, [field.id], fieldErrors.length ? fieldErrors : undefined);
6025
6390
  }, /** @type {Errors} */{});
6026
6391
  this._setState({
6027
6392
  errors
@@ -6127,16 +6492,14 @@ class Form {
6127
6492
  value
6128
6493
  } = update;
6129
6494
  const {
6130
- _path
6131
- } = field;
6132
- let {
6133
6495
  data,
6134
6496
  errors
6135
6497
  } = this._getState();
6136
- const validator = this.get('validator');
6498
+ const validator = this.get('validator'),
6499
+ pathRegistry = this.get('pathRegistry');
6137
6500
  const fieldErrors = validator.validateField(field, value);
6138
- set(data, _path, value);
6139
- set(errors, [pathStringify(_path)], fieldErrors.length ? fieldErrors : undefined);
6501
+ set(data, pathRegistry.getValuePath(field), value);
6502
+ set(errors, [field.id], fieldErrors.length ? fieldErrors : undefined);
6140
6503
  this._setState({
6141
6504
  data: clone(data),
6142
6505
  errors: clone(errors)
@@ -6179,23 +6542,26 @@ class Form {
6179
6542
  * @internal
6180
6543
  */
6181
6544
  _getSubmitData() {
6182
- const formFieldRegistry = this.get('formFieldRegistry');
6545
+ const formFieldRegistry = this.get('formFieldRegistry'),
6546
+ pathRegistry = this.get('pathRegistry'),
6547
+ formFields = this.get('formFields');
6183
6548
  const formData = this._getState().data;
6184
6549
  const submitData = formFieldRegistry.getAll().reduce((previous, field) => {
6185
6550
  const {
6186
6551
  disabled,
6187
- _path
6552
+ type
6188
6553
  } = field;
6554
+ const {
6555
+ config: fieldConfig
6556
+ } = formFields.get(type);
6189
6557
 
6190
- // do not submit disabled form fields
6191
- if (disabled || !_path) {
6558
+ // do not submit disabled form fields or routing fields
6559
+ if (disabled || !fieldConfig.keyed) {
6192
6560
  return previous;
6193
6561
  }
6194
- const value = get(formData, _path);
6195
- return {
6196
- ...previous,
6197
- [_path[0]]: value
6198
- };
6562
+ const valuePath = pathRegistry.getValuePath(field);
6563
+ const value = get(formData, valuePath);
6564
+ return set(previous, valuePath, value);
6199
6565
  }, {});
6200
6566
  const filteredSubmitData = this._applyConditions(submitData, formData);
6201
6567
  return filteredSubmitData;
@@ -6208,9 +6574,46 @@ class Form {
6208
6574
  const conditionChecker = this.get('conditionChecker');
6209
6575
  return conditionChecker.applyConditions(toFilter, data);
6210
6576
  }
6577
+
6578
+ /**
6579
+ * @internal
6580
+ */
6581
+ _initializeFieldData(data) {
6582
+ const formFieldRegistry = this.get('formFieldRegistry'),
6583
+ formFields = this.get('formFields'),
6584
+ pathRegistry = this.get('pathRegistry');
6585
+ return formFieldRegistry.getAll().reduce((initializedData, formField) => {
6586
+ const {
6587
+ defaultValue,
6588
+ type
6589
+ } = formField;
6590
+
6591
+ // try to get value from data
6592
+ // if unavailable - try to get default value from form field
6593
+ // if unavailable - get empty value from form field
6594
+
6595
+ const valuePath = pathRegistry.getValuePath(formField);
6596
+ if (valuePath) {
6597
+ const {
6598
+ config: fieldConfig
6599
+ } = formFields.get(type);
6600
+ let valueData = get(data, valuePath);
6601
+ if (!isUndefined(valueData) && fieldConfig.sanitizeValue) {
6602
+ valueData = fieldConfig.sanitizeValue({
6603
+ formField,
6604
+ data,
6605
+ value: valueData
6606
+ });
6607
+ }
6608
+ const initializedFieldValue = !isUndefined(valueData) ? valueData : !isUndefined(defaultValue) ? defaultValue : fieldConfig.emptyValue;
6609
+ return set(initializedData, valuePath, initializedFieldValue);
6610
+ }
6611
+ return initializedData;
6612
+ }, data);
6613
+ }
6211
6614
  }
6212
6615
 
6213
- const schemaVersion = 10;
6616
+ const schemaVersion = 11;
6214
6617
 
6215
6618
  /**
6216
6619
  * @typedef { import('./types').CreateFormOptions } CreateFormOptions
@@ -6235,5 +6638,5 @@ function createForm(options) {
6235
6638
  });
6236
6639
  }
6237
6640
 
6238
- export { Button, Checkbox, Checklist, ConditionChecker, DATETIME_SUBTYPES, DATETIME_SUBTYPES_LABELS, DATETIME_SUBTYPE_PATH, DATE_DISALLOW_PAST_PATH, DATE_LABEL_PATH, Datetime, Default, ExpressionLanguageModule, FeelExpressionLanguage, FeelersTemplating, Form, FormComponent, FormContext$1 as FormContext, FormFieldRegistry, FormFields, FormLayouter, FormRenderContext$1 as FormRenderContext, Image, MINUTES_IN_DAY, MarkdownModule, MarkdownRenderer, Numberfield, Radio, Select, Spacer, TIME_INTERVAL_PATH, TIME_LABEL_PATH, TIME_SERIALISINGFORMAT_LABELS, TIME_SERIALISING_FORMATS, TIME_SERIALISING_FORMAT_PATH, TIME_USE24H_PATH, Taglist, Text, Textarea, Textfield, VALUES_SOURCES, VALUES_SOURCES_DEFAULTS, VALUES_SOURCES_LABELS, VALUES_SOURCES_PATHS, VALUES_SOURCE_DEFAULT, ViewerCommands, ViewerCommandsModule, clone, createForm, createFormContainer, createInjector, findErrors, formFields, generateIdForType, generateIndexForType, getSchemaVariables, getValuesSource, iconsByType, isRequired, pathParse, pathStringify, pathsEqual, schemaVersion };
6641
+ export { Button, Checkbox, Checklist, ConditionChecker, DATETIME_SUBTYPES, DATETIME_SUBTYPES_LABELS, DATETIME_SUBTYPE_PATH, DATE_DISALLOW_PAST_PATH, DATE_LABEL_PATH, Datetime, FormComponent$1 as Default, ExpressionLanguageModule, FeelExpressionLanguage, FeelersTemplating, FieldFactory, Form, FormComponent, FormContext$1 as FormContext, FormFieldRegistry, FormFields, FormLayouter, FormRenderContext$1 as FormRenderContext, Group, Image, Importer, MINUTES_IN_DAY, MarkdownModule, MarkdownRenderer, Numberfield, PathRegistry, Radio, Select, Spacer, TIME_INTERVAL_PATH, TIME_LABEL_PATH, TIME_SERIALISINGFORMAT_LABELS, TIME_SERIALISING_FORMATS, TIME_SERIALISING_FORMAT_PATH, TIME_USE24H_PATH, Taglist, Text, Textarea, Textfield, VALUES_SOURCES, VALUES_SOURCES_DEFAULTS, VALUES_SOURCES_LABELS, VALUES_SOURCES_PATHS, VALUES_SOURCE_DEFAULT, ViewerCommands, ViewerCommandsModule, clone, createForm, createFormContainer, createInjector, formFields, generateIdForType, generateIndexForType, getSchemaVariables, getValuesSource, iconsByType, isRequired, pathParse, pathsEqual, runRecursively, schemaVersion };
6239
6642
  //# sourceMappingURL=index.es.js.map