@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.cjs CHANGED
@@ -67,26 +67,26 @@ const getFlavouredFeelVariableNames = (feelString, feelFlavour = 'expression', o
67
67
  return [...new Set(variables)];
68
68
  };
69
69
 
70
- /**
71
- * Get the variable name at the specified index in a given path expression.
72
- *
73
- * @param {Object} root - The root node of the path expression tree.
74
- * @param {number} index - The index of the variable name to retrieve.
75
- * @returns {string|null} The variable name at the specified index or null if index is out of bounds.
70
+ /**
71
+ * Get the variable name at the specified index in a given path expression.
72
+ *
73
+ * @param {Object} root - The root node of the path expression tree.
74
+ * @param {number} index - The index of the variable name to retrieve.
75
+ * @returns {string|null} The variable name at the specified index or null if index is out of bounds.
76
76
  */
77
77
  const _getVariableNameAtPathIndex = (root, index) => {
78
78
  const accessors = _deconstructPathExpression(root);
79
79
  return accessors[index] || null;
80
80
  };
81
81
 
82
- /**
83
- * Extracts the variables which are required of the external context for a given path expression.
84
- * This is done by traversing the path expression tree and keeping track of the current depth relative to the external context.
85
- *
86
- * @param {Object} node - The root node of the path expression tree.
87
- * @param {number} initialDepth - The depth at which the root node is located in the outer context.
88
- * @param {Object} specialDepthAccessors - Definitions of special keywords which represent more complex accesses of the outer context.
89
- * @returns {Set} - A set containing the extracted variable names.
82
+ /**
83
+ * Extracts the variables which are required of the external context for a given path expression.
84
+ * This is done by traversing the path expression tree and keeping track of the current depth relative to the external context.
85
+ *
86
+ * @param {Object} node - The root node of the path expression tree.
87
+ * @param {number} initialDepth - The depth at which the root node is located in the outer context.
88
+ * @param {Object} specialDepthAccessors - Definitions of special keywords which represent more complex accesses of the outer context.
89
+ * @returns {Set} - A set containing the extracted variable names.
90
90
  */
91
91
  const _smartExtractVariableNames = (node, initialDepth, specialDepthAccessors) => {
92
92
  // depth info represents the previous (initialised as null) and current depth of the current accessor in the path expression
@@ -132,11 +132,11 @@ const _smartExtractVariableNames = (node, initialDepth, specialDepthAccessors) =
132
132
  return new Set(extractedVariables);
133
133
  };
134
134
 
135
- /**
136
- * Deconstructs a path expression tree into an array of components.
137
- *
138
- * @param {Object} root - The root node of the path expression tree.
139
- * @returns {Array<string>} An array of components in the path expression, in the correct order.
135
+ /**
136
+ * Deconstructs a path expression tree into an array of components.
137
+ *
138
+ * @param {Object} root - The root node of the path expression tree.
139
+ * @returns {Array<string>} An array of components in the path expression, in the correct order.
140
140
  */
141
141
  const _deconstructPathExpression = root => {
142
142
  let node = root;
@@ -155,13 +155,13 @@ const _deconstructPathExpression = root => {
155
155
  return parts.reverse();
156
156
  };
157
157
 
158
- /**
159
- * Builds a simplified feel structure tree from the given parse tree and feel string.
160
- * The nodes follow this structure: `{ name: string, children: Array, variableName?: string }`
161
- *
162
- * @param {Object} parseTree - The parse tree generated by a parser.
163
- * @param {string} feelString - The feel string used for parsing.
164
- * @returns {Object} The simplified feel structure tree.
158
+ /**
159
+ * Builds a simplified feel structure tree from the given parse tree and feel string.
160
+ * The nodes follow this structure: `{ name: string, children: Array, variableName?: string }`
161
+ *
162
+ * @param {Object} parseTree - The parse tree generated by a parser.
163
+ * @param {string} feelString - The feel string used for parsing.
164
+ * @returns {Object} The simplified feel structure tree.
165
165
  */
166
166
  const _buildSimpleFeelStructureTree = (parseTree, feelString) => {
167
167
  const stack = [{
@@ -187,9 +187,9 @@ const _buildSimpleFeelStructureTree = (parseTree, feelString) => {
187
187
  return _extractFilterExpressions(stack[0].children[0]);
188
188
  };
189
189
 
190
- /**
191
- * Restructure the tree in such a way to bring filters (which create new contexts) to the root of the tree.
192
- * This is done to simplify the extraction of variables and match the context hierarchy.
190
+ /**
191
+ * Restructure the tree in such a way to bring filters (which create new contexts) to the root of the tree.
192
+ * This is done to simplify the extraction of variables and match the context hierarchy.
193
193
  */
194
194
  const _extractFilterExpressions = tree => {
195
195
  const flattenedExpressionTree = {
@@ -230,25 +230,25 @@ class FeelExpressionLanguage {
230
230
  this._eventBus = eventBus;
231
231
  }
232
232
 
233
- /**
234
- * Determines if the given value is a FEEL expression.
235
- *
236
- * @param {any} value
237
- * @returns {boolean}
238
- *
233
+ /**
234
+ * Determines if the given value is a FEEL expression.
235
+ *
236
+ * @param {any} value
237
+ * @returns {boolean}
238
+ *
239
239
  */
240
240
  isExpression(value) {
241
241
  return minDash.isString(value) && value.startsWith('=');
242
242
  }
243
243
 
244
- /**
245
- * Retrieve variable names from a given FEEL expression.
246
- *
247
- * @param {string} expression
248
- * @param {object} [options]
249
- * @param {string} [options.type]
250
- *
251
- * @returns {string[]}
244
+ /**
245
+ * Retrieve variable names from a given FEEL expression.
246
+ *
247
+ * @param {string} expression
248
+ * @param {object} [options]
249
+ * @param {string} [options.type]
250
+ *
251
+ * @returns {string[]}
252
252
  */
253
253
  getVariableNames(expression, options = {}) {
254
254
  const {
@@ -263,13 +263,13 @@ class FeelExpressionLanguage {
263
263
  return getFlavouredFeelVariableNames(expression, type);
264
264
  }
265
265
 
266
- /**
267
- * Evaluate an expression.
268
- *
269
- * @param {string} expression
270
- * @param {import('../../types').Data} [data]
271
- *
272
- * @returns {any}
266
+ /**
267
+ * Evaluate an expression.
268
+ *
269
+ * @param {string} expression
270
+ * @param {import('../../types').Data} [data]
271
+ *
272
+ * @returns {any}
273
273
  */
274
274
  evaluate(expression, data = {}) {
275
275
  if (!expression) {
@@ -294,23 +294,23 @@ FeelExpressionLanguage.$inject = ['eventBus'];
294
294
  class FeelersTemplating {
295
295
  constructor() {}
296
296
 
297
- /**
298
- * Determines if the given value is a feelers template.
299
- *
300
- * @param {any} value
301
- * @returns {boolean}
302
- *
297
+ /**
298
+ * Determines if the given value is a feelers template.
299
+ *
300
+ * @param {any} value
301
+ * @returns {boolean}
302
+ *
303
303
  */
304
304
  isTemplate(value) {
305
305
  return minDash.isString(value) && (value.startsWith('=') || /{{.*?}}/.test(value));
306
306
  }
307
307
 
308
- /**
309
- * Retrieve variable names from a given feelers template.
310
- *
311
- * @param {string} template
312
- *
313
- * @returns {string[]}
308
+ /**
309
+ * Retrieve variable names from a given feelers template.
310
+ *
311
+ * @param {string} template
312
+ *
313
+ * @returns {string[]}
314
314
  */
315
315
  getVariableNames(template) {
316
316
  if (!this.isTemplate(template)) {
@@ -336,17 +336,17 @@ class FeelersTemplating {
336
336
  }, []);
337
337
  }
338
338
 
339
- /**
340
- * Evaluate a template.
341
- *
342
- * @param {string} template
343
- * @param {Object<string, any>} context
344
- * @param {Object} options
345
- * @param {boolean} [options.debug = false]
346
- * @param {boolean} [options.strict = false]
347
- * @param {Function} [options.buildDebugString]
348
- *
349
- * @returns
339
+ /**
340
+ * Evaluate a template.
341
+ *
342
+ * @param {string} template
343
+ * @param {Object<string, any>} context
344
+ * @param {Object} options
345
+ * @param {boolean} [options.debug = false]
346
+ * @param {boolean} [options.strict = false]
347
+ * @param {Function} [options.buildDebugString]
348
+ *
349
+ * @returns
350
350
  */
351
351
  evaluate(template, context = {}, options = {}) {
352
352
  const {
@@ -361,22 +361,22 @@ class FeelersTemplating {
361
361
  });
362
362
  }
363
363
 
364
- /**
365
- * @typedef {Object} ExpressionWithDepth
366
- * @property {number} depth - The depth of the expression in the syntax tree.
367
- * @property {string} expression - The extracted expression
364
+ /**
365
+ * @typedef {Object} ExpressionWithDepth
366
+ * @property {number} depth - The depth of the expression in the syntax tree.
367
+ * @property {string} expression - The extracted expression
368
368
  */
369
369
 
370
- /**
371
- * Extracts all feel expressions in the template along with their depth in the syntax tree.
372
- * The depth is incremented for child expressions of loops to account for context drilling.
373
- * @name extractExpressionsWithDepth
374
- * @param {string} template - A feelers template string.
375
- * @returns {Array<ExpressionWithDepth>} An array of objects, each containing the depth and the extracted expression.
376
- *
377
- * @example
378
- * const template = "Hello {{user}}, you have:{{#loop items}}\n- {{amount}} {{name}}{{/loop}}.";
379
- * const extractedExpressions = _extractExpressionsWithDepth(template);
370
+ /**
371
+ * Extracts all feel expressions in the template along with their depth in the syntax tree.
372
+ * The depth is incremented for child expressions of loops to account for context drilling.
373
+ * @name extractExpressionsWithDepth
374
+ * @param {string} template - A feelers template string.
375
+ * @returns {Array<ExpressionWithDepth>} An array of objects, each containing the depth and the extracted expression.
376
+ *
377
+ * @example
378
+ * const template = "Hello {{user}}, you have:{{#loop items}}\n- {{amount}} {{name}}{{/loop}}.";
379
+ * const extractedExpressions = _extractExpressionsWithDepth(template);
380
380
  */
381
381
  _extractExpressionsWithDepth(template) {
382
382
  // build simplified feelers syntax tree
@@ -407,47 +407,271 @@ class FeelersTemplating {
407
407
  }
408
408
  FeelersTemplating.$inject = [];
409
409
 
410
- /**
411
- * @typedef {object} Condition
412
- * @property {string} [hide]
410
+ // config ///////////////////
411
+
412
+ const MINUTES_IN_DAY = 60 * 24;
413
+ const DATETIME_SUBTYPES = {
414
+ DATE: 'date',
415
+ TIME: 'time',
416
+ DATETIME: 'datetime'
417
+ };
418
+ const TIME_SERIALISING_FORMATS = {
419
+ UTC_OFFSET: 'utc_offset',
420
+ UTC_NORMALIZED: 'utc_normalized',
421
+ NO_TIMEZONE: 'no_timezone'
422
+ };
423
+ const DATETIME_SUBTYPES_LABELS = {
424
+ [DATETIME_SUBTYPES.DATE]: 'Date',
425
+ [DATETIME_SUBTYPES.TIME]: 'Time',
426
+ [DATETIME_SUBTYPES.DATETIME]: 'Date & Time'
427
+ };
428
+ const TIME_SERIALISINGFORMAT_LABELS = {
429
+ [TIME_SERIALISING_FORMATS.UTC_OFFSET]: 'UTC offset',
430
+ [TIME_SERIALISING_FORMATS.UTC_NORMALIZED]: 'UTC normalized',
431
+ [TIME_SERIALISING_FORMATS.NO_TIMEZONE]: 'No timezone'
432
+ };
433
+ const DATETIME_SUBTYPE_PATH = ['subtype'];
434
+ const DATE_LABEL_PATH = ['dateLabel'];
435
+ const DATE_DISALLOW_PAST_PATH = ['disallowPassedDates'];
436
+ const TIME_LABEL_PATH = ['timeLabel'];
437
+ const TIME_USE24H_PATH = ['use24h'];
438
+ const TIME_INTERVAL_PATH = ['timeInterval'];
439
+ const TIME_SERIALISING_FORMAT_PATH = ['timeSerializingFormat'];
440
+
441
+ // config ///////////////////
442
+
443
+ const VALUES_SOURCES = {
444
+ STATIC: 'static',
445
+ INPUT: 'input',
446
+ EXPRESSION: 'expression'
447
+ };
448
+ const VALUES_SOURCE_DEFAULT = VALUES_SOURCES.STATIC;
449
+ const VALUES_SOURCES_LABELS = {
450
+ [VALUES_SOURCES.STATIC]: 'Static',
451
+ [VALUES_SOURCES.INPUT]: 'Input data',
452
+ [VALUES_SOURCES.EXPRESSION]: 'Expression'
453
+ };
454
+ const VALUES_SOURCES_PATHS = {
455
+ [VALUES_SOURCES.STATIC]: ['values'],
456
+ [VALUES_SOURCES.INPUT]: ['valuesKey'],
457
+ [VALUES_SOURCES.EXPRESSION]: ['valuesExpression']
458
+ };
459
+ const VALUES_SOURCES_DEFAULTS = {
460
+ [VALUES_SOURCES.STATIC]: [{
461
+ label: 'Value',
462
+ value: 'value'
463
+ }],
464
+ [VALUES_SOURCES.INPUT]: '',
465
+ [VALUES_SOURCES.EXPRESSION]: '='
466
+ };
467
+
468
+ // helpers ///////////////////
469
+
470
+ function getValuesSource(field) {
471
+ for (const source of Object.values(VALUES_SOURCES)) {
472
+ if (minDash.get(field, VALUES_SOURCES_PATHS[source]) !== undefined) {
473
+ return source;
474
+ }
475
+ }
476
+ return VALUES_SOURCE_DEFAULT;
477
+ }
478
+
479
+ function createInjector(bootstrapModules) {
480
+ const injector = new didi.Injector(bootstrapModules);
481
+ injector.init();
482
+ return injector;
483
+ }
484
+
485
+ /**
486
+ * @param {string?} prefix
487
+ *
488
+ * @returns Element
489
+ */
490
+ function createFormContainer(prefix = 'fjs') {
491
+ const container = document.createElement('div');
492
+ container.classList.add(`${prefix}-container`);
493
+ return container;
494
+ }
495
+
496
+ const EXPRESSION_PROPERTIES = ['alt', 'appearance.prefixAdorner', 'appearance.suffixAdorner', 'conditional.hide', 'description', 'label', 'source', 'readonly', 'text', 'validate.min', 'validate.max', 'validate.minLength', 'validate.maxLength', 'valuesExpression'];
497
+ const TEMPLATE_PROPERTIES = ['alt', 'appearance.prefixAdorner', 'appearance.suffixAdorner', 'description', 'label', 'source', 'text'];
498
+ function isRequired(field) {
499
+ return field.required;
500
+ }
501
+ function pathParse(path) {
502
+ if (!path) {
503
+ return [];
504
+ }
505
+ return path.split('.').map(key => {
506
+ return isNaN(parseInt(key)) ? key : parseInt(key);
507
+ });
508
+ }
509
+ function pathsEqual(a, b) {
510
+ return a && b && a.length === b.length && a.every((value, index) => value === b[index]);
511
+ }
512
+ const indices = {};
513
+ function generateIndexForType(type) {
514
+ if (type in indices) {
515
+ indices[type]++;
516
+ } else {
517
+ indices[type] = 1;
518
+ }
519
+ return indices[type];
520
+ }
521
+ function generateIdForType(type) {
522
+ return `${type}${generateIndexForType(type)}`;
523
+ }
524
+
525
+ /**
526
+ * @template T
527
+ * @param {T} data
528
+ * @param {(this: any, key: string, value: any) => any} [replacer]
529
+ * @return {T}
530
+ */
531
+ function clone(data, replacer) {
532
+ return JSON.parse(JSON.stringify(data, replacer));
533
+ }
534
+
535
+ /**
536
+ * Parse the schema for input variables a form might make use of
537
+ *
538
+ * @param {any} schema
539
+ *
540
+ * @return {string[]}
541
+ */
542
+ function getSchemaVariables(schema, options = {}) {
543
+ const {
544
+ expressionLanguage = new FeelExpressionLanguage(null),
545
+ templating = new FeelersTemplating(),
546
+ inputs = true,
547
+ outputs = true
548
+ } = options;
549
+ if (!schema.components) {
550
+ return [];
551
+ }
552
+ const getAllComponents = node => {
553
+ const components = [];
554
+ if (node.components) {
555
+ node.components.forEach(component => {
556
+ components.push(component);
557
+ components.push(...getAllComponents(component));
558
+ });
559
+ }
560
+ return components;
561
+ };
562
+ const variables = getAllComponents(schema).reduce((variables, component) => {
563
+ const {
564
+ valuesKey
565
+ } = component;
566
+
567
+ // collect input-only variables
568
+ if (inputs) {
569
+ if (valuesKey) {
570
+ variables = [...variables, valuesKey];
571
+ }
572
+ EXPRESSION_PROPERTIES.forEach(prop => {
573
+ const property = minDash.get(component, prop.split('.'));
574
+ if (property && expressionLanguage.isExpression(property)) {
575
+ const expressionVariables = expressionLanguage.getVariableNames(property, {
576
+ type: 'expression'
577
+ });
578
+ variables = [...variables, ...expressionVariables];
579
+ }
580
+ });
581
+ TEMPLATE_PROPERTIES.forEach(prop => {
582
+ const property = minDash.get(component, prop.split('.'));
583
+ if (property && !expressionLanguage.isExpression(property) && templating.isTemplate(property)) {
584
+ const templateVariables = templating.getVariableNames(property);
585
+ variables = [...variables, ...templateVariables];
586
+ }
587
+ });
588
+ }
589
+ return variables.filter(variable => variable !== undefined || variable !== null);
590
+ }, []);
591
+ const getBindingVariables = node => {
592
+ const bindingVariable = [];
593
+
594
+ // c.f. https://github.com/bpmn-io/form-js/issues/778 @Skaiir to remove?
595
+ if (node.type === 'button') {
596
+ return [];
597
+ } else if (node.key) {
598
+ return [node.key.split('.')[0]];
599
+ } else if (node.path) {
600
+ return [node.path.split('.')[0]];
601
+ } else if (node.components) {
602
+ node.components.forEach(component => {
603
+ bindingVariable.push(...getBindingVariables(component));
604
+ });
605
+ }
606
+ return bindingVariable;
607
+ };
608
+
609
+ // collect binding variables
610
+ if (inputs || outputs) {
611
+ variables.push(...getBindingVariables(schema));
612
+ }
613
+
614
+ // remove duplicates
615
+ return Array.from(new Set(variables));
616
+ }
617
+ function runRecursively(formField, fn) {
618
+ const components = formField.components || [];
619
+ components.forEach((component, index) => {
620
+ runRecursively(component, fn);
621
+ });
622
+ fn(formField);
623
+ }
624
+
625
+ /**
626
+ * @typedef {object} Condition
627
+ * @property {string} [hide]
413
628
  */
414
629
 
415
630
  class ConditionChecker {
416
- constructor(formFieldRegistry, eventBus) {
631
+ constructor(formFieldRegistry, pathRegistry, eventBus) {
417
632
  this._formFieldRegistry = formFieldRegistry;
633
+ this._pathRegistry = pathRegistry;
418
634
  this._eventBus = eventBus;
419
635
  }
420
636
 
421
- /**
422
- * For given data, remove properties based on condition.
423
- *
424
- * @param {Object<string, any>} properties
425
- * @param {Object<string, any>} data
637
+ /**
638
+ * For given data, remove properties based on condition.
639
+ *
640
+ * @param {Object<string, any>} properties
641
+ * @param {Object<string, any>} data
426
642
  */
427
643
  applyConditions(properties, data = {}) {
428
- const conditions = this._getConditions();
429
- const newProperties = {
430
- ...properties
431
- };
432
- for (const {
433
- key,
434
- condition
435
- } of conditions) {
436
- const shouldRemove = this._checkHideCondition(condition, data);
437
- if (shouldRemove) {
438
- delete newProperties[key];
439
- }
644
+ const newProperties = clone(properties);
645
+ const form = this._formFieldRegistry.getAll().find(field => field.type === 'default');
646
+ if (!form) {
647
+ throw new Error('form field registry has no form');
440
648
  }
649
+ this._pathRegistry.executeRecursivelyOnFields(form, ({
650
+ field,
651
+ isClosed,
652
+ context
653
+ }) => {
654
+ const {
655
+ conditional: condition
656
+ } = field;
657
+ context.isHidden = context.isHidden || condition && this._checkHideCondition(condition, data);
658
+
659
+ // only clear the leaf nodes, as groups may both point to the same path
660
+ if (context.isHidden && isClosed) {
661
+ const valuePath = this._pathRegistry.getValuePath(field);
662
+ this._clearObjectValueRecursively(valuePath, newProperties);
663
+ }
664
+ });
441
665
  return newProperties;
442
666
  }
443
667
 
444
- /**
445
- * Check if given condition is met. Returns null for invalid/missing conditions.
446
- *
447
- * @param {string} condition
448
- * @param {import('../../types').Data} [data]
449
- *
450
- * @returns {boolean|null}
668
+ /**
669
+ * Check if given condition is met. Returns null for invalid/missing conditions.
670
+ *
671
+ * @param {string} condition
672
+ * @param {import('../../types').Data} [data]
673
+ *
674
+ * @returns {boolean|null}
451
675
  */
452
676
  check(condition, data = {}) {
453
677
  if (!condition) {
@@ -468,12 +692,12 @@ class ConditionChecker {
468
692
  }
469
693
  }
470
694
 
471
- /**
472
- * Check if hide condition is met.
473
- *
474
- * @param {Condition} condition
475
- * @param {Object<string, any>} data
476
- * @returns {boolean}
695
+ /**
696
+ * Check if hide condition is met.
697
+ *
698
+ * @param {Condition} condition
699
+ * @param {Object<string, any>} data
700
+ * @returns {boolean}
477
701
  */
478
702
  _checkHideCondition(condition, data) {
479
703
  if (!condition.hide) {
@@ -482,24 +706,18 @@ class ConditionChecker {
482
706
  const result = this.check(condition.hide, data);
483
707
  return result === true;
484
708
  }
485
- _getConditions() {
486
- const formFields = this._formFieldRegistry.getAll();
487
- return formFields.reduce((conditions, formField) => {
488
- const {
489
- key,
490
- conditional: condition
491
- } = formField;
492
- if (key && condition) {
493
- return [...conditions, {
494
- key,
495
- condition
496
- }];
497
- }
498
- return conditions;
499
- }, []);
709
+ _clearObjectValueRecursively(valuePath, obj) {
710
+ const workingValuePath = [...valuePath];
711
+ let recurse = false;
712
+ do {
713
+ minDash.set(obj, workingValuePath, undefined);
714
+ workingValuePath.pop();
715
+ const parentObject = minDash.get(obj, workingValuePath);
716
+ recurse = minDash.isObject(parentObject) && !minDash.values(parentObject).length && !!workingValuePath.length;
717
+ } while (recurse);
500
718
  }
501
719
  }
502
- ConditionChecker.$inject = ['formFieldRegistry', 'eventBus'];
720
+ ConditionChecker.$inject = ['formFieldRegistry', 'pathRegistry', 'eventBus'];
503
721
 
504
722
  var ExpressionLanguageModule = {
505
723
  __init__: ['expressionLanguage', 'templating', 'conditionChecker'],
@@ -515,12 +733,12 @@ class MarkdownRenderer {
515
733
  this._converter = new showdown.Converter();
516
734
  }
517
735
 
518
- /**
519
- * Render markdown to HTML.
520
- *
521
- * @param {string} markdown - The markdown to render
522
- *
523
- * @returns {string} HTML
736
+ /**
737
+ * Render markdown to HTML.
738
+ *
739
+ * @param {string} markdown - The markdown to render
740
+ *
741
+ * @returns {string} HTML
524
742
  */
525
743
  render(markdown) {
526
744
  return this._converter.makeHtml(markdown);
@@ -1004,203 +1222,6 @@ var commandModule = {
1004
1222
  commandStack: ['type', CommandStack]
1005
1223
  };
1006
1224
 
1007
- // config ///////////////////
1008
-
1009
- const MINUTES_IN_DAY = 60 * 24;
1010
- const DATETIME_SUBTYPES = {
1011
- DATE: 'date',
1012
- TIME: 'time',
1013
- DATETIME: 'datetime'
1014
- };
1015
- const TIME_SERIALISING_FORMATS = {
1016
- UTC_OFFSET: 'utc_offset',
1017
- UTC_NORMALIZED: 'utc_normalized',
1018
- NO_TIMEZONE: 'no_timezone'
1019
- };
1020
- const DATETIME_SUBTYPES_LABELS = {
1021
- [DATETIME_SUBTYPES.DATE]: 'Date',
1022
- [DATETIME_SUBTYPES.TIME]: 'Time',
1023
- [DATETIME_SUBTYPES.DATETIME]: 'Date & Time'
1024
- };
1025
- const TIME_SERIALISINGFORMAT_LABELS = {
1026
- [TIME_SERIALISING_FORMATS.UTC_OFFSET]: 'UTC offset',
1027
- [TIME_SERIALISING_FORMATS.UTC_NORMALIZED]: 'UTC normalized',
1028
- [TIME_SERIALISING_FORMATS.NO_TIMEZONE]: 'No timezone'
1029
- };
1030
- const DATETIME_SUBTYPE_PATH = ['subtype'];
1031
- const DATE_LABEL_PATH = ['dateLabel'];
1032
- const DATE_DISALLOW_PAST_PATH = ['disallowPassedDates'];
1033
- const TIME_LABEL_PATH = ['timeLabel'];
1034
- const TIME_USE24H_PATH = ['use24h'];
1035
- const TIME_INTERVAL_PATH = ['timeInterval'];
1036
- const TIME_SERIALISING_FORMAT_PATH = ['timeSerializingFormat'];
1037
-
1038
- // config ///////////////////
1039
-
1040
- const VALUES_SOURCES = {
1041
- STATIC: 'static',
1042
- INPUT: 'input',
1043
- EXPRESSION: 'expression'
1044
- };
1045
- const VALUES_SOURCE_DEFAULT = VALUES_SOURCES.STATIC;
1046
- const VALUES_SOURCES_LABELS = {
1047
- [VALUES_SOURCES.STATIC]: 'Static',
1048
- [VALUES_SOURCES.INPUT]: 'Input data',
1049
- [VALUES_SOURCES.EXPRESSION]: 'Expression'
1050
- };
1051
- const VALUES_SOURCES_PATHS = {
1052
- [VALUES_SOURCES.STATIC]: ['values'],
1053
- [VALUES_SOURCES.INPUT]: ['valuesKey'],
1054
- [VALUES_SOURCES.EXPRESSION]: ['valuesExpression']
1055
- };
1056
- const VALUES_SOURCES_DEFAULTS = {
1057
- [VALUES_SOURCES.STATIC]: [{
1058
- label: 'Value',
1059
- value: 'value'
1060
- }],
1061
- [VALUES_SOURCES.INPUT]: '',
1062
- [VALUES_SOURCES.EXPRESSION]: '='
1063
- };
1064
-
1065
- // helpers ///////////////////
1066
-
1067
- function getValuesSource(field) {
1068
- for (const source of Object.values(VALUES_SOURCES)) {
1069
- if (minDash.get(field, VALUES_SOURCES_PATHS[source]) !== undefined) {
1070
- return source;
1071
- }
1072
- }
1073
- return VALUES_SOURCE_DEFAULT;
1074
- }
1075
-
1076
- function createInjector(bootstrapModules) {
1077
- const injector = new didi.Injector(bootstrapModules);
1078
- injector.init();
1079
- return injector;
1080
- }
1081
-
1082
- /**
1083
- * @param {string?} prefix
1084
- *
1085
- * @returns Element
1086
- */
1087
- function createFormContainer(prefix = 'fjs') {
1088
- const container = document.createElement('div');
1089
- container.classList.add(`${prefix}-container`);
1090
- return container;
1091
- }
1092
-
1093
- const EXPRESSION_PROPERTIES = ['alt', 'appearance.prefixAdorner', 'appearance.suffixAdorner', 'conditional.hide', 'description', 'label', 'source', 'readonly', 'text', 'validate.min', 'validate.max', 'validate.minLength', 'validate.maxLength', 'valuesExpression'];
1094
- const TEMPLATE_PROPERTIES = ['alt', 'appearance.prefixAdorner', 'appearance.suffixAdorner', 'description', 'label', 'source', 'text'];
1095
- function findErrors(errors, path) {
1096
- return errors[pathStringify(path)];
1097
- }
1098
- function isRequired(field) {
1099
- return field.required;
1100
- }
1101
- function pathParse(path) {
1102
- if (!path) {
1103
- return [];
1104
- }
1105
- return path.split('.').map(key => {
1106
- return isNaN(parseInt(key)) ? key : parseInt(key);
1107
- });
1108
- }
1109
- function pathsEqual(a, b) {
1110
- return a && b && a.length === b.length && a.every((value, index) => value === b[index]);
1111
- }
1112
- function pathStringify(path) {
1113
- if (!path) {
1114
- return '';
1115
- }
1116
- return path.join('.');
1117
- }
1118
- const indices = {};
1119
- function generateIndexForType(type) {
1120
- if (type in indices) {
1121
- indices[type]++;
1122
- } else {
1123
- indices[type] = 1;
1124
- }
1125
- return indices[type];
1126
- }
1127
- function generateIdForType(type) {
1128
- return `${type}${generateIndexForType(type)}`;
1129
- }
1130
-
1131
- /**
1132
- * @template T
1133
- * @param {T} data
1134
- * @param {(this: any, key: string, value: any) => any} [replacer]
1135
- * @return {T}
1136
- */
1137
- function clone(data, replacer) {
1138
- return JSON.parse(JSON.stringify(data, replacer));
1139
- }
1140
-
1141
- /**
1142
- * Parse the schema for input variables a form might make use of
1143
- *
1144
- * @param {any} schema
1145
- *
1146
- * @return {string[]}
1147
- */
1148
- function getSchemaVariables(schema, options = {}) {
1149
- const {
1150
- expressionLanguage = new FeelExpressionLanguage(null),
1151
- templating = new FeelersTemplating(),
1152
- inputs = true,
1153
- outputs = true
1154
- } = options;
1155
- if (!schema.components) {
1156
- return [];
1157
- }
1158
- const variables = schema.components.reduce((variables, component) => {
1159
- const {
1160
- key,
1161
- valuesKey,
1162
- type
1163
- } = component;
1164
- if (['button'].includes(type)) {
1165
- return variables;
1166
- }
1167
-
1168
- // collect bi-directional variables
1169
- if (inputs || outputs) {
1170
- if (key) {
1171
- variables = [...variables, key];
1172
- }
1173
- }
1174
-
1175
- // collect input-only variables
1176
- if (inputs) {
1177
- if (valuesKey) {
1178
- variables = [...variables, valuesKey];
1179
- }
1180
- EXPRESSION_PROPERTIES.forEach(prop => {
1181
- const property = minDash.get(component, prop.split('.'));
1182
- if (property && expressionLanguage.isExpression(property)) {
1183
- const expressionVariables = expressionLanguage.getVariableNames(property, {
1184
- type: 'expression'
1185
- });
1186
- variables = [...variables, ...expressionVariables];
1187
- }
1188
- });
1189
- TEMPLATE_PROPERTIES.forEach(prop => {
1190
- const property = minDash.get(component, prop.split('.'));
1191
- if (property && !expressionLanguage.isExpression(property) && templating.isTemplate(property)) {
1192
- const templateVariables = templating.getVariableNames(property);
1193
- variables = [...variables, ...templateVariables];
1194
- }
1195
- });
1196
- }
1197
- return variables.filter(variable => variable !== undefined || variable !== null);
1198
- }, []);
1199
-
1200
- // remove duplicates
1201
- return Array.from(new Set(variables));
1202
- }
1203
-
1204
1225
  class UpdateFieldValidationHandler {
1205
1226
  constructor(form, validator) {
1206
1227
  this._form = form;
@@ -1211,15 +1232,12 @@ class UpdateFieldValidationHandler {
1211
1232
  field,
1212
1233
  value
1213
1234
  } = context;
1214
- const {
1215
- _path
1216
- } = field;
1217
1235
  const {
1218
1236
  errors
1219
1237
  } = this._form._getState();
1220
1238
  context.oldErrors = clone(errors);
1221
1239
  const fieldErrors = this._validator.validateField(field, value);
1222
- const updatedErrors = minDash.set(errors, [pathStringify(_path)], fieldErrors.length ? fieldErrors : undefined);
1240
+ const updatedErrors = minDash.set(errors, [field.id], fieldErrors.length ? fieldErrors : undefined);
1223
1241
  this._form._setState({
1224
1242
  errors: updatedErrors
1225
1243
  });
@@ -1868,8 +1886,8 @@ Validator.$inject = ['expressionLanguage', 'conditionChecker', 'form'];
1868
1886
 
1869
1887
  // helpers //////////
1870
1888
 
1871
- /**
1872
- * Helper function to evaluate optional FEEL validation values.
1889
+ /**
1890
+ * Helper function to evaluate optional FEEL validation values.
1873
1891
  */
1874
1892
  function evaluateFEELValues(validate, expressionLanguage, conditionChecker, form) {
1875
1893
  const evaluatedValidate = {
@@ -1902,72 +1920,415 @@ function evaluateFEELValues(validate, expressionLanguage, conditionChecker, form
1902
1920
  return evaluatedValidate;
1903
1921
  }
1904
1922
 
1905
- class FormFieldRegistry {
1906
- constructor(eventBus) {
1907
- this._eventBus = eventBus;
1908
- this._formFields = {};
1909
- eventBus.on('form.clear', () => this.clear());
1910
- this._ids = new Ids([32, 36, 1]);
1911
- this._keys = new Ids([32, 36, 1]);
1923
+ class Importer {
1924
+ /**
1925
+ * @constructor
1926
+ * @param { import('./FormFieldRegistry').default } formFieldRegistry
1927
+ * @param { import('./PathRegistry').default } pathRegistry
1928
+ * @param { import('./FieldFactory').default } fieldFactory
1929
+ * @param { import('./FormLayouter').default } formLayouter
1930
+ */
1931
+ constructor(formFieldRegistry, pathRegistry, fieldFactory, formLayouter) {
1932
+ this._formFieldRegistry = formFieldRegistry;
1933
+ this._pathRegistry = pathRegistry;
1934
+ this._fieldFactory = fieldFactory;
1935
+ this._formLayouter = formLayouter;
1912
1936
  }
1913
- add(formField) {
1937
+
1938
+ /**
1939
+ * Import schema creating rows, fields, attaching additional
1940
+ * information to each field and adding fields to the
1941
+ * field registry.
1942
+ *
1943
+ * Additional information attached:
1944
+ *
1945
+ * * `id` (unless present)
1946
+ * * `_parent`
1947
+ * * `_path`
1948
+ *
1949
+ * @param {any} schema
1950
+ *
1951
+ * @typedef {{ warnings: Error[], schema: any }} ImportResult
1952
+ * @returns {ImportResult}
1953
+ */
1954
+ importSchema(schema) {
1955
+ // TODO: Add warnings
1956
+ const warnings = [];
1957
+ try {
1958
+ this._cleanup();
1959
+ const importedSchema = this.importFormField(clone(schema));
1960
+ this._formLayouter.calculateLayout(clone(importedSchema));
1961
+ return {
1962
+ schema: importedSchema,
1963
+ warnings
1964
+ };
1965
+ } catch (err) {
1966
+ this._cleanup();
1967
+ err.warnings = warnings;
1968
+ throw err;
1969
+ }
1970
+ }
1971
+ _cleanup() {
1972
+ this._formLayouter.clear();
1973
+ this._formFieldRegistry.clear();
1974
+ this._pathRegistry.clear();
1975
+ }
1976
+
1977
+ /**
1978
+ * @param {{[x: string]: any}} fieldAttrs
1979
+ * @param {String} [parentId]
1980
+ * @param {number} [index]
1981
+ *
1982
+ * @return {any} field
1983
+ */
1984
+ importFormField(fieldAttrs, parentId, index) {
1914
1985
  const {
1915
- id
1916
- } = formField;
1917
- if (this._formFields[id]) {
1918
- throw new Error(`form field with ID ${id} already exists`);
1986
+ components
1987
+ } = fieldAttrs;
1988
+ let parent, path;
1989
+ if (parentId) {
1990
+ parent = this._formFieldRegistry.get(parentId);
1919
1991
  }
1920
- this._eventBus.fire('formField.add', {
1921
- formField
1992
+
1993
+ // set form field path
1994
+ path = parent ? [...parent._path, 'components', index] : [];
1995
+ const field = this._fieldFactory.create({
1996
+ ...fieldAttrs,
1997
+ _path: path,
1998
+ _parent: parentId
1999
+ }, false);
2000
+ this._formFieldRegistry.add(field);
2001
+ if (components) {
2002
+ field.components = this.importFormFields(components, field.id);
2003
+ }
2004
+ return field;
2005
+ }
2006
+
2007
+ /**
2008
+ * @param {Array<any>} components
2009
+ * @param {string} parentId
2010
+ *
2011
+ * @return {Array<any>} imported components
2012
+ */
2013
+ importFormFields(components, parentId) {
2014
+ return components.map((component, index) => {
2015
+ return this.importFormField(component, parentId, index);
1922
2016
  });
1923
- this._formFields[id] = formField;
1924
2017
  }
1925
- remove(formField) {
2018
+ }
2019
+ Importer.$inject = ['formFieldRegistry', 'pathRegistry', 'fieldFactory', 'formLayouter'];
2020
+
2021
+ class FieldFactory {
2022
+ /**
2023
+ * @constructor
2024
+ *
2025
+ * @param formFieldRegistry
2026
+ * @param formFields
2027
+ */
2028
+ constructor(formFieldRegistry, pathRegistry, formFields) {
2029
+ this._formFieldRegistry = formFieldRegistry;
2030
+ this._pathRegistry = pathRegistry;
2031
+ this._formFields = formFields;
2032
+ }
2033
+ create(attrs, applyDefaults = true) {
1926
2034
  const {
1927
- id
1928
- } = formField;
1929
- if (!this._formFields[id]) {
1930
- return;
2035
+ id,
2036
+ type,
2037
+ key,
2038
+ path,
2039
+ _parent
2040
+ } = attrs;
2041
+ const fieldDefinition = this._formFields.get(type);
2042
+ if (!fieldDefinition) {
2043
+ throw new Error(`form field of type <${type}> not supported`);
1931
2044
  }
1932
- this._eventBus.fire('formField.remove', {
1933
- formField
2045
+ const {
2046
+ config
2047
+ } = fieldDefinition;
2048
+ if (!config) {
2049
+ throw new Error(`form field of type <${type}> has no config`);
2050
+ }
2051
+ if (id && this._formFieldRegistry._ids.assigned(id)) {
2052
+ throw new Error(`form field with id <${id}> already exists`);
2053
+ }
2054
+
2055
+ // ensure that we can claim the path
2056
+
2057
+ const parent = _parent && this._formFieldRegistry.get(_parent);
2058
+ const parentPath = parent && this._pathRegistry.getValuePath(parent) || [];
2059
+ if (config.keyed && key && !this._pathRegistry.canClaimPath([...parentPath, ...key.split('.')], true)) {
2060
+ throw new Error(`binding path '${[...parentPath, key].join('.')}' is already claimed`);
2061
+ }
2062
+ if (config.pathed && path && !this._pathRegistry.canClaimPath([...parentPath, ...path.split('.')], false)) {
2063
+ throw new Error(`binding path '${[...parentPath, ...path.split('.')].join('.')}' is already claimed`);
2064
+ }
2065
+ const labelAttrs = applyDefaults && config.label ? {
2066
+ label: config.label
2067
+ } : {};
2068
+ const field = config.create({
2069
+ ...labelAttrs,
2070
+ ...attrs
1934
2071
  });
1935
- delete this._formFields[id];
2072
+ this._ensureId(field);
2073
+ if (config.keyed) {
2074
+ this._ensureKey(field);
2075
+ }
2076
+ if (config.pathed && path) {
2077
+ this._pathRegistry.claimPath(this._pathRegistry.getValuePath(field), false);
2078
+ }
2079
+ return field;
1936
2080
  }
1937
- get(id) {
1938
- return this._formFields[id];
2081
+ _ensureId(field) {
2082
+ if (field.id) {
2083
+ this._formFieldRegistry._ids.claim(field.id, field);
2084
+ return;
2085
+ }
2086
+ let prefix = 'Field';
2087
+ if (field.type === 'default') {
2088
+ prefix = 'Form';
2089
+ }
2090
+ field.id = this._formFieldRegistry._ids.nextPrefixed(`${prefix}_`, field);
1939
2091
  }
1940
- getAll() {
1941
- return Object.values(this._formFields);
2092
+ _ensureKey(field) {
2093
+ if (!field.key) {
2094
+ let random;
2095
+ const parent = this._formFieldRegistry.get(field._parent);
2096
+
2097
+ // ensure key uniqueness at level
2098
+ do {
2099
+ random = Math.random().toString(36).substring(7);
2100
+ } while (parent && parent.components.some(child => child.key === random));
2101
+ field.key = `${field.type}_${random}`;
2102
+ }
2103
+ this._pathRegistry.claimPath(this._pathRegistry.getValuePath(field), true);
1942
2104
  }
1943
- forEach(callback) {
1944
- this.getAll().forEach(formField => callback(formField));
2105
+ }
2106
+ FieldFactory.$inject = ['formFieldRegistry', 'pathRegistry', 'formFields'];
2107
+
2108
+ /**
2109
+ * The PathRegistry class manages a hierarchical structure of paths associated with form fields.
2110
+ * It enables claiming, unclaiming, and validating paths within this structure.
2111
+ *
2112
+ * Example Tree Structure:
2113
+ *
2114
+ * [
2115
+ * {
2116
+ * segment: 'root',
2117
+ * claimCount: 1,
2118
+ * children: [
2119
+ * {
2120
+ * segment: 'child1',
2121
+ * claimCount: 2,
2122
+ * children: null // A leaf node (closed path)
2123
+ * },
2124
+ * {
2125
+ * segment: 'child2',
2126
+ * claimCount: 1,
2127
+ * children: [
2128
+ * {
2129
+ * segment: 'subChild1',
2130
+ * claimCount: 1,
2131
+ * children: [] // An open node (open path)
2132
+ * }
2133
+ * ]
2134
+ * }
2135
+ * ]
2136
+ * }
2137
+ * ]
2138
+ */
2139
+ class PathRegistry {
2140
+ constructor(formFieldRegistry, formFields) {
2141
+ this._formFieldRegistry = formFieldRegistry;
2142
+ this._formFields = formFields;
2143
+ this._dataPaths = [];
2144
+ }
2145
+ canClaimPath(path, closed = false) {
2146
+ let node = {
2147
+ children: this._dataPaths
2148
+ };
2149
+ for (const segment of path) {
2150
+ node = _getNextSegment(node, segment);
2151
+
2152
+ // if no node at that path, we can claim it no matter what
2153
+ if (!node) {
2154
+ return true;
2155
+ }
2156
+
2157
+ // if we reach a leaf node, definitely not claimable
2158
+ if (node.children === null) {
2159
+ return false;
2160
+ }
2161
+ }
2162
+
2163
+ // if after all segments we reach a node with children, we can claim it only openly
2164
+ return !closed;
2165
+ }
2166
+ claimPath(path, closed = false) {
2167
+ if (!this.canClaimPath(path, closed)) {
2168
+ throw new Error(`cannot claim path '${path.join('.')}'`);
2169
+ }
2170
+ let node = {
2171
+ children: this._dataPaths
2172
+ };
2173
+ for (const segment of path) {
2174
+ let child = _getNextSegment(node, segment);
2175
+ if (!child) {
2176
+ child = {
2177
+ segment,
2178
+ claimCount: 1,
2179
+ children: []
2180
+ };
2181
+ node.children.push(child);
2182
+ } else {
2183
+ child.claimCount++;
2184
+ }
2185
+ node = child;
2186
+ }
2187
+ if (closed) {
2188
+ node.children = null;
2189
+ }
2190
+ }
2191
+ unclaimPath(path) {
2192
+ // verification Pass
2193
+ let node = {
2194
+ children: this._dataPaths
2195
+ };
2196
+ for (const segment of path) {
2197
+ const child = _getNextSegment(node, segment);
2198
+ if (!child) {
2199
+ throw new Error(`no open path found for '${path.join('.')}'`);
2200
+ }
2201
+ node = child;
2202
+ }
2203
+
2204
+ // mutation Pass
2205
+ node = {
2206
+ children: this._dataPaths
2207
+ };
2208
+ for (const segment of path) {
2209
+ const child = _getNextSegment(node, segment);
2210
+ child.claimCount--;
2211
+ if (child.claimCount === 0) {
2212
+ node.children.splice(node.children.indexOf(child), 1);
2213
+ break; // Abort early if claimCount reaches zero
2214
+ }
2215
+
2216
+ node = child;
2217
+ }
2218
+ }
2219
+
2220
+ /**
2221
+ * Applies a function (fn) recursively on a given field and its children.
2222
+ *
2223
+ * - `field`: Starting field object.
2224
+ * - `fn`: Function to apply.
2225
+ * - `context`: Optional object for passing data between calls.
2226
+ *
2227
+ * Stops early if `fn` returns `false`. Useful for traversing the form field tree.
2228
+ *
2229
+ * @returns {boolean} Success status based on function execution.
2230
+ */
2231
+ executeRecursivelyOnFields(field, fn, context = {}) {
2232
+ let result = true;
2233
+ const formFieldConfig = this._formFields.get(field.type).config;
2234
+ if (formFieldConfig.keyed) {
2235
+ const callResult = fn({
2236
+ field,
2237
+ isClosed: true,
2238
+ context
2239
+ });
2240
+ return result && callResult;
2241
+ } else if (formFieldConfig.pathed) {
2242
+ const callResult = fn({
2243
+ field,
2244
+ isClosed: false,
2245
+ context
2246
+ });
2247
+ result = result && callResult;
2248
+ }
2249
+ if (field.components) {
2250
+ for (const child of field.components) {
2251
+ const callResult = this.executeRecursivelyOnFields(child, fn, clone(context));
2252
+ result = result && callResult;
2253
+
2254
+ // only stop executing if false is specifically returned, not if undefined
2255
+ if (result === false) {
2256
+ return result;
2257
+ }
2258
+ }
2259
+ }
2260
+ return result;
2261
+ }
2262
+
2263
+ /**
2264
+ * Generates an array representing the binding path to an underlying data object for a form field.
2265
+ *
2266
+ * @param {Object} field - The field object with properties: `key`, `path`, `id`, and optionally `_parent`.
2267
+ * @param {Object} [options={}] - Configuration options.
2268
+ * @param {Object} [options.replacements={}] - A map of field IDs to alternative path arrays.
2269
+ * @param {Object} [options.cutoffNode] - The ID of the parent field at which to stop generating the path.
2270
+ *
2271
+ * @returns {(Array<string>|undefined)} An array of strings representing the binding path, or undefined if not determinable.
2272
+ */
2273
+ getValuePath(field, options = {}) {
2274
+ const {
2275
+ replacements = {},
2276
+ cutoffNode = null
2277
+ } = options;
2278
+ let localValuePath = [];
2279
+ const hasReplacement = Object.prototype.hasOwnProperty.call(replacements, field.id);
2280
+ const formFieldConfig = this._formFields.get(field.type).config;
2281
+ if (hasReplacement) {
2282
+ const replacement = replacements[field.id];
2283
+ if (replacement === null || replacement === undefined || replacement === '') {
2284
+ localValuePath = [];
2285
+ } else if (typeof replacement === 'string') {
2286
+ localValuePath = replacement.split('.');
2287
+ } else if (Array.isArray(replacement)) {
2288
+ localValuePath = replacement;
2289
+ } else {
2290
+ throw new Error(`replacements for field ${field.id} must be a string, array or null/undefined`);
2291
+ }
2292
+ } else if (formFieldConfig.keyed) {
2293
+ localValuePath = field.key.split('.');
2294
+ } else if (formFieldConfig.pathed && field.path) {
2295
+ localValuePath = field.path.split('.');
2296
+ }
2297
+ if (field._parent && field._parent !== cutoffNode) {
2298
+ const parent = this._formFieldRegistry.get(field._parent);
2299
+ return [...(this.getValuePath(parent, options) || []), ...localValuePath];
2300
+ }
2301
+ return localValuePath;
1945
2302
  }
1946
2303
  clear() {
1947
- this._formFields = {};
1948
- this._ids.clear();
1949
- this._keys.clear();
2304
+ this._dataPaths = [];
1950
2305
  }
1951
2306
  }
1952
- FormFieldRegistry.$inject = ['eventBus'];
2307
+ const _getNextSegment = (node, segment) => {
2308
+ if (minDash.isArray(node.children)) {
2309
+ return node.children.find(node => node.segment === segment) || null;
2310
+ }
2311
+ return null;
2312
+ };
2313
+ PathRegistry.$inject = ['formFieldRegistry', 'formFields'];
1953
2314
 
1954
- /**
1955
- * @typedef { { id: String, components: Array<String> } } FormRow
1956
- * @typedef { { formFieldId: String, rows: Array<FormRow> } } FormRows
2315
+ /**
2316
+ * @typedef { { id: String, components: Array<String> } } FormRow
2317
+ * @typedef { { formFieldId: String, rows: Array<FormRow> } } FormRows
1957
2318
  */
1958
2319
 
1959
- /**
1960
- * Maintains the Form layout in a given structure, for example
1961
- *
1962
- * [
1963
- * {
1964
- * formFieldId: 'FormField_1',
1965
- * rows: [
1966
- * { id: 'Row_1', components: [ 'Text_1', 'Textdield_1', ... ] }
1967
- * ]
1968
- * }
1969
- * ]
1970
- *
2320
+ /**
2321
+ * Maintains the Form layout in a given structure, for example
2322
+ *
2323
+ * [
2324
+ * {
2325
+ * formFieldId: 'FormField_1',
2326
+ * rows: [
2327
+ * { id: 'Row_1', components: [ 'Text_1', 'Textdield_1', ... ] }
2328
+ * ]
2329
+ * }
2330
+ * ]
2331
+ *
1971
2332
  */
1972
2333
  class FormLayouter {
1973
2334
  constructor(eventBus) {
@@ -1977,8 +2338,8 @@ class FormLayouter {
1977
2338
  this._eventBus = eventBus;
1978
2339
  }
1979
2340
 
1980
- /**
1981
- * @param {FormRow} row
2341
+ /**
2342
+ * @param {FormRow} row
1982
2343
  */
1983
2344
  addRow(formFieldId, row) {
1984
2345
  let rowsPerComponent = this._rows.find(r => r.formFieldId === formFieldId);
@@ -1992,18 +2353,18 @@ class FormLayouter {
1992
2353
  rowsPerComponent.rows.push(row);
1993
2354
  }
1994
2355
 
1995
- /**
1996
- * @param {String} id
1997
- * @returns {FormRow}
2356
+ /**
2357
+ * @param {String} id
2358
+ * @returns {FormRow}
1998
2359
  */
1999
2360
  getRow(id) {
2000
2361
  const rows = allRows(this._rows);
2001
2362
  return rows.find(r => r.id === id);
2002
2363
  }
2003
2364
 
2004
- /**
2005
- * @param {any} formField
2006
- * @returns {FormRow}
2365
+ /**
2366
+ * @param {any} formField
2367
+ * @returns {FormRow}
2007
2368
  */
2008
2369
  getRowForField(formField) {
2009
2370
  return allRows(this._rows).find(r => {
@@ -2014,9 +2375,9 @@ class FormLayouter {
2014
2375
  });
2015
2376
  }
2016
2377
 
2017
- /**
2018
- * @param {String} formFieldId
2019
- * @returns { Array<FormRow> }
2378
+ /**
2379
+ * @param {String} formFieldId
2380
+ * @returns { Array<FormRow> }
2020
2381
  */
2021
2382
  getRows(formFieldId) {
2022
2383
  const rowsForField = this._rows.find(r => formFieldId === r.formFieldId);
@@ -2026,22 +2387,22 @@ class FormLayouter {
2026
2387
  return rowsForField.rows;
2027
2388
  }
2028
2389
 
2029
- /**
2030
- * @returns {string}
2390
+ /**
2391
+ * @returns {string}
2031
2392
  */
2032
2393
  nextRowId() {
2033
2394
  return this._ids.nextPrefixed('Row_');
2034
2395
  }
2035
2396
 
2036
- /**
2037
- * @param {any} formField
2397
+ /**
2398
+ * @param {any} formField
2038
2399
  */
2039
2400
  calculateLayout(formField) {
2040
2401
  const {
2041
2402
  type,
2042
2403
  components
2043
2404
  } = formField;
2044
- if (type !== 'default' || !components) {
2405
+ if (type !== 'default' && type !== 'group' || !components) {
2045
2406
  return;
2046
2407
  }
2047
2408
 
@@ -2088,155 +2449,60 @@ function groupByRow(components, ids) {
2088
2449
  });
2089
2450
  }
2090
2451
 
2091
- /**
2092
- * @param {Array<FormRows>} formRows
2093
- * @returns {Array<FormRow>}
2452
+ /**
2453
+ * @param {Array<FormRows>} formRows
2454
+ * @returns {Array<FormRow>}
2094
2455
  */
2095
2456
  function allRows(formRows) {
2096
2457
  return minDash.flatten(formRows.map(c => c.rows));
2097
2458
  }
2098
2459
 
2099
- class Importer {
2100
- /**
2101
- * @constructor
2102
- * @param { import('../core').FormFieldRegistry } formFieldRegistry
2103
- * @param { import('../render/FormFields').default } formFields
2104
- * @param { import('../core').FormLayouter } formLayouter
2105
- */
2106
- constructor(formFieldRegistry, formFields, formLayouter) {
2107
- this._formFieldRegistry = formFieldRegistry;
2108
- this._formFields = formFields;
2109
- this._formLayouter = formLayouter;
2460
+ class FormFieldRegistry {
2461
+ constructor(eventBus) {
2462
+ this._eventBus = eventBus;
2463
+ this._formFields = {};
2464
+ eventBus.on('form.clear', () => this.clear());
2465
+ this._ids = new Ids([32, 36, 1]);
2110
2466
  }
2111
-
2112
- /**
2113
- * Import schema adding `id`, `_parent` and `_path`
2114
- * information to each field and adding it to the
2115
- * form field registry.
2116
- *
2117
- * @param {any} schema
2118
- * @param {any} [data]
2119
- *
2120
- * @return { { warnings: Array<any>, schema: any, data: any } }
2121
- */
2122
- importSchema(schema, data = {}) {
2123
- // TODO: Add warnings - https://github.com/bpmn-io/form-js/issues/289
2124
- const warnings = [];
2125
- try {
2126
- this._formLayouter.clear();
2127
- const importedSchema = this.importFormField(clone(schema)),
2128
- initializedData = this.initializeFieldValues(clone(data));
2129
- this._formLayouter.calculateLayout(clone(importedSchema));
2130
- return {
2131
- warnings,
2132
- schema: importedSchema,
2133
- data: initializedData
2134
- };
2135
- } catch (err) {
2136
- err.warnings = warnings;
2137
- throw err;
2467
+ add(formField) {
2468
+ const {
2469
+ id
2470
+ } = formField;
2471
+ if (this._formFields[id]) {
2472
+ throw new Error(`form field with ID ${id} already exists`);
2138
2473
  }
2474
+ this._eventBus.fire('formField.add', {
2475
+ formField
2476
+ });
2477
+ this._formFields[id] = formField;
2139
2478
  }
2140
-
2141
- /**
2142
- * @param {any} formField
2143
- * @param {string} [parentId]
2144
- *
2145
- * @return {any} importedField
2146
- */
2147
- importFormField(formField, parentId) {
2479
+ remove(formField) {
2148
2480
  const {
2149
- components,
2150
- key,
2151
- type,
2152
- id = generateIdForType(type)
2481
+ id
2153
2482
  } = formField;
2154
- if (parentId) {
2155
- // set form field parent
2156
- formField._parent = parentId;
2157
- }
2158
- if (!this._formFields.get(type)) {
2159
- throw new Error(`form field of type <${type}> not supported`);
2160
- }
2161
- if (key) {
2162
- // validate <key> uniqueness
2163
- if (this._formFieldRegistry._keys.assigned(key)) {
2164
- throw new Error(`form field with key <${key}> already exists`);
2165
- }
2166
- this._formFieldRegistry._keys.claim(key, formField);
2167
-
2168
- // TODO: buttons should not have key
2169
- if (type !== 'button') {
2170
- // set form field path
2171
- formField._path = [key];
2172
- }
2173
- }
2174
- if (id) {
2175
- // validate <id> uniqueness
2176
- if (this._formFieldRegistry._ids.assigned(id)) {
2177
- throw new Error(`form field with id <${id}> already exists`);
2178
- }
2179
- this._formFieldRegistry._ids.claim(id, formField);
2180
- }
2181
-
2182
- // set form field ID
2183
- formField.id = id;
2184
- this._formFieldRegistry.add(formField);
2185
- if (components) {
2186
- this.importFormFields(components, id);
2483
+ if (!this._formFields[id]) {
2484
+ return;
2187
2485
  }
2188
- return formField;
2189
- }
2190
- importFormFields(components, parentId) {
2191
- components.forEach(component => {
2192
- this.importFormField(component, parentId);
2486
+ this._eventBus.fire('formField.remove', {
2487
+ formField
2193
2488
  });
2489
+ delete this._formFields[id];
2490
+ }
2491
+ get(id) {
2492
+ return this._formFields[id];
2493
+ }
2494
+ getAll() {
2495
+ return Object.values(this._formFields);
2194
2496
  }
2195
-
2196
- /**
2197
- * @param {Object} data
2198
- *
2199
- * @return {Object} initializedData
2200
- */
2201
- initializeFieldValues(data) {
2202
- return this._formFieldRegistry.getAll().reduce((initializedData, formField) => {
2203
- const {
2204
- defaultValue,
2205
- _path,
2206
- type
2207
- } = formField;
2208
-
2209
- // try to get value from data
2210
- // if unavailable - try to get default value from form field
2211
- // if unavailable - get empty value from form field
2212
-
2213
- if (_path) {
2214
- const {
2215
- config: fieldConfig
2216
- } = this._formFields.get(type);
2217
- let valueData = minDash.get(data, _path);
2218
- if (!minDash.isUndefined(valueData) && fieldConfig.sanitizeValue) {
2219
- valueData = fieldConfig.sanitizeValue({
2220
- formField,
2221
- data,
2222
- value: valueData
2223
- });
2224
- }
2225
- const initializedFieldValue = !minDash.isUndefined(valueData) ? valueData : !minDash.isUndefined(defaultValue) ? defaultValue : fieldConfig.emptyValue;
2226
- initializedData = {
2227
- ...initializedData,
2228
- [_path[0]]: initializedFieldValue
2229
- };
2230
- }
2231
- return initializedData;
2232
- }, data);
2497
+ forEach(callback) {
2498
+ this.getAll().forEach(formField => callback(formField));
2499
+ }
2500
+ clear() {
2501
+ this._formFields = {};
2502
+ this._ids.clear();
2233
2503
  }
2234
2504
  }
2235
- Importer.$inject = ['formFieldRegistry', 'formFields', 'formLayouter'];
2236
-
2237
- var importModule = {
2238
- importer: ['type', Importer]
2239
- };
2505
+ FormFieldRegistry.$inject = ['eventBus'];
2240
2506
 
2241
2507
  function formFieldClasses(type, {
2242
2508
  errors = [],
@@ -2291,7 +2557,7 @@ function Button(props) {
2291
2557
  }
2292
2558
  Button.config = {
2293
2559
  type: type$c,
2294
- keyed: true,
2560
+ keyed: false,
2295
2561
  label: 'Button',
2296
2562
  group: 'action',
2297
2563
  create: (options = {}) => ({
@@ -2301,6 +2567,9 @@ Button.config = {
2301
2567
  };
2302
2568
 
2303
2569
  const FormRenderContext = preact.createContext({
2570
+ EmptyRoot: props => {
2571
+ return null;
2572
+ },
2304
2573
  Empty: props => {
2305
2574
  return null;
2306
2575
  },
@@ -2330,15 +2599,19 @@ const FormRenderContext = preact.createContext({
2330
2599
  class: props.class,
2331
2600
  children: props.children
2332
2601
  });
2602
+ },
2603
+ hoveredId: [],
2604
+ setHoveredId: newValue => {
2605
+ console.log(`setHoveredId not defined, called with '${newValue}'`);
2333
2606
  }
2334
2607
  });
2335
2608
  var FormRenderContext$1 = FormRenderContext;
2336
2609
 
2337
- /**
2338
- * @param {string} type
2339
- * @param {boolean} [strict]
2340
- *
2341
- * @returns {any}
2610
+ /**
2611
+ * @param {string} type
2612
+ * @param {boolean} [strict]
2613
+ *
2614
+ * @returns {any}
2342
2615
  */
2343
2616
  function getService(type, strict) {}
2344
2617
  const FormContext = preact.createContext({
@@ -2354,10 +2627,10 @@ function useService(type, strict) {
2354
2627
  return getService(type, strict);
2355
2628
  }
2356
2629
 
2357
- /**
2358
- * Returns the conditionally filtered data of a form reactively.
2359
- * Memoised to minimize re-renders
2360
- *
2630
+ /**
2631
+ * Returns the conditionally filtered data of a form reactively.
2632
+ * Memoised to minimize re-renders
2633
+ *
2361
2634
  */
2362
2635
  function useFilteredFormData() {
2363
2636
  const {
@@ -2374,12 +2647,12 @@ function useFilteredFormData() {
2374
2647
  }, [conditionChecker, data, initialData]);
2375
2648
  }
2376
2649
 
2377
- /**
2378
- * Evaluate if condition is met reactively based on the conditionChecker and form data.
2379
- *
2380
- * @param {string | undefined} condition
2381
- *
2382
- * @returns {boolean} true if condition is met or no condition or condition checker exists
2650
+ /**
2651
+ * Evaluate if condition is met reactively based on the conditionChecker and form data.
2652
+ *
2653
+ * @param {string | undefined} condition
2654
+ *
2655
+ * @returns {boolean} true if condition is met or no condition or condition checker exists
2383
2656
  */
2384
2657
  function useCondition(condition) {
2385
2658
  const conditionChecker = useService('conditionChecker', false);
@@ -2389,13 +2662,13 @@ function useCondition(condition) {
2389
2662
  }, [conditionChecker, condition, filteredData]);
2390
2663
  }
2391
2664
 
2392
- /**
2393
- * Evaluate a string reactively based on the expressionLanguage and form data.
2394
- * If the string is not an expression, it is returned as is.
2395
- * Memoised to minimize re-renders.
2396
- *
2397
- * @param {string} value
2398
- *
2665
+ /**
2666
+ * Evaluate a string reactively based on the expressionLanguage and form data.
2667
+ * If the string is not an expression, it is returned as is.
2668
+ * Memoised to minimize re-renders.
2669
+ *
2670
+ * @param {string} value
2671
+ *
2399
2672
  */
2400
2673
  function useExpressionEvaluation(value) {
2401
2674
  const formData = useFilteredFormData();
@@ -2424,16 +2697,16 @@ function useKeyDownAction(targetKey, action, listenerElement = window) {
2424
2697
  });
2425
2698
  }
2426
2699
 
2427
- /**
2428
- * Retrieve readonly value of a form field, given it can be an
2429
- * expression optionally or configured globally.
2430
- *
2431
- * @typedef { import('../../types').FormProperties } FormProperties
2432
- *
2433
- * @param {any} formField
2434
- * @param {FormProperties} properties
2435
- *
2436
- * @returns {boolean}
2700
+ /**
2701
+ * Retrieve readonly value of a form field, given it can be an
2702
+ * expression optionally or configured globally.
2703
+ *
2704
+ * @typedef { import('../../types').FormProperties } FormProperties
2705
+ *
2706
+ * @param {any} formField
2707
+ * @param {FormProperties} properties
2708
+ *
2709
+ * @returns {boolean}
2437
2710
  */
2438
2711
  function useReadonly(formField, properties = {}) {
2439
2712
  const expressionLanguage = useService('expressionLanguage');
@@ -2451,16 +2724,16 @@ function useReadonly(formField, properties = {}) {
2451
2724
  return readonly || false;
2452
2725
  }
2453
2726
 
2454
- /**
2455
- * Template a string reactively based on form data. If the string is not a template, it is returned as is.
2456
- * Memoised to minimize re-renders
2457
- *
2458
- * @param {string} value
2459
- * @param {Object} options
2460
- * @param {boolean} [options.debug = false]
2461
- * @param {boolean} [options.strict = false]
2462
- * @param {Function} [options.buildDebugString]
2463
- *
2727
+ /**
2728
+ * Template a string reactively based on form data. If the string is not a template, it is returned as is.
2729
+ * Memoised to minimize re-renders
2730
+ *
2731
+ * @param {string} value
2732
+ * @param {Object} options
2733
+ * @param {boolean} [options.debug = false]
2734
+ * @param {boolean} [options.strict = false]
2735
+ * @param {Function} [options.buildDebugString]
2736
+ *
2464
2737
  */
2465
2738
  function useTemplateEvaluation(value, options) {
2466
2739
  const filteredData = useFilteredFormData();
@@ -2473,17 +2746,17 @@ function useTemplateEvaluation(value, options) {
2473
2746
  }, [filteredData, templating, value, options]);
2474
2747
  }
2475
2748
 
2476
- /**
2477
- * Template a string reactively based on form data. If the string is not a template, it is returned as is.
2478
- * If the string contains multiple lines, only the first line is returned.
2479
- * Memoised to minimize re-renders
2480
- *
2481
- * @param {string} value
2482
- * @param {Object} [options]
2483
- * @param {boolean} [options.debug = false]
2484
- * @param {boolean} [options.strict = false]
2485
- * @param {Function} [options.buildDebugString]
2486
- *
2749
+ /**
2750
+ * Template a string reactively based on form data. If the string is not a template, it is returned as is.
2751
+ * If the string contains multiple lines, only the first line is returned.
2752
+ * Memoised to minimize re-renders
2753
+ *
2754
+ * @param {string} value
2755
+ * @param {Object} [options]
2756
+ * @param {boolean} [options.debug = false]
2757
+ * @param {boolean} [options.strict = false]
2758
+ * @param {Function} [options.buildDebugString]
2759
+ *
2487
2760
  */
2488
2761
  function useSingleLineTemplateEvaluation(value, options = {}) {
2489
2762
  const evaluatedTemplate = useTemplateEvaluation(value, options);
@@ -2671,9 +2944,24 @@ function _isReadableType(value) {
2671
2944
  function _isValueSomething(value) {
2672
2945
  return value || value === 0 || value === false;
2673
2946
  }
2947
+ function createEmptyOptions(options = {}) {
2948
+ const defaults = {};
2674
2949
 
2675
- /**
2676
- * @enum { String }
2950
+ // provide default values if valuesKey and valuesExpression are not set
2951
+ if (!options.valuesKey && !options.valuesExpression) {
2952
+ defaults.values = [{
2953
+ label: 'Value',
2954
+ value: 'value'
2955
+ }];
2956
+ }
2957
+ return {
2958
+ ...defaults,
2959
+ ...options
2960
+ };
2961
+ }
2962
+
2963
+ /**
2964
+ * @enum { String }
2677
2965
  */
2678
2966
  const LOAD_STATES = {
2679
2967
  LOADING: 'loading',
@@ -2681,17 +2969,17 @@ const LOAD_STATES = {
2681
2969
  ERROR: 'error'
2682
2970
  };
2683
2971
 
2684
- /**
2685
- * @typedef {Object} ValuesGetter
2686
- * @property {Object[]} values - The values data
2687
- * @property {(LOAD_STATES)} state - The values data's loading state, to use for conditional rendering
2972
+ /**
2973
+ * @typedef {Object} ValuesGetter
2974
+ * @property {Object[]} values - The values data
2975
+ * @property {(LOAD_STATES)} state - The values data's loading state, to use for conditional rendering
2688
2976
  */
2689
2977
 
2690
- /**
2691
- * A hook to load values for single and multiselect components.
2692
- *
2693
- * @param {Object} field - The form field to handle values for
2694
- * @return {ValuesGetter} valuesGetter - A values getter object providing loading state and values
2978
+ /**
2979
+ * A hook to load values for single and multiselect components.
2980
+ *
2981
+ * @param {Object} field - The form field to handle values for
2982
+ * @return {ValuesGetter} valuesGetter - A values getter object providing loading state and values
2695
2983
  */
2696
2984
  function useValuesAsync (field) {
2697
2985
  const {
@@ -3047,21 +3335,7 @@ Checklist.config = {
3047
3335
  group: 'selection',
3048
3336
  emptyValue: [],
3049
3337
  sanitizeValue: sanitizeMultiSelectValue,
3050
- create: (options = {}) => {
3051
- const defaults = {};
3052
-
3053
- // provide default values if valuesKey isn't set
3054
- if (!options.valuesKey) {
3055
- defaults.values = [{
3056
- label: 'Value',
3057
- value: 'value'
3058
- }];
3059
- }
3060
- return {
3061
- ...defaults,
3062
- ...options
3063
- };
3064
- }
3338
+ create: createEmptyOptions
3065
3339
  };
3066
3340
 
3067
3341
  const noop$1 = () => false;
@@ -3072,6 +3346,7 @@ function FormField(props) {
3072
3346
  } = props;
3073
3347
  const formFields = useService('formFields'),
3074
3348
  viewerCommands = useService('viewerCommands', false),
3349
+ pathRegistry = useService('pathRegistry'),
3075
3350
  form = useService('form');
3076
3351
  const {
3077
3352
  initialData,
@@ -3088,10 +3363,10 @@ function FormField(props) {
3088
3363
  if (!FormFieldComponent) {
3089
3364
  throw new Error(`cannot render field <${field.type}>`);
3090
3365
  }
3091
- const initialValue = hooks.useMemo(() => minDash.get(initialData, field._path), [initialData, field._path]);
3092
- const value = minDash.get(data, field._path);
3093
- const fieldErrors = findErrors(errors, field._path);
3366
+ const valuePath = hooks.useMemo(() => pathRegistry.getValuePath(field), [field, pathRegistry]);
3367
+ const initialValue = hooks.useMemo(() => minDash.get(initialData, valuePath), [initialData, valuePath]);
3094
3368
  const readonly = useReadonly(field, properties);
3369
+ const value = minDash.get(data, valuePath);
3095
3370
 
3096
3371
  // add precedence: global readonly > form field disabled
3097
3372
  const disabled = !properties.readOnly && (properties.disabled || field.disabled || false);
@@ -3118,7 +3393,7 @@ function FormField(props) {
3118
3393
  children: jsxRuntime.jsx(FormFieldComponent, {
3119
3394
  ...props,
3120
3395
  disabled: disabled,
3121
- errors: fieldErrors,
3396
+ errors: errors[field.id],
3122
3397
  onChange: disabled || readonly ? noop$1 : onChange,
3123
3398
  onBlur: disabled || readonly ? noop$1 : onBlur,
3124
3399
  readonly: readonly,
@@ -3128,14 +3403,14 @@ function FormField(props) {
3128
3403
  });
3129
3404
  }
3130
3405
 
3131
- function Default(props) {
3406
+ function Grid(props) {
3132
3407
  const {
3133
3408
  Children,
3134
- Empty,
3135
3409
  Row
3136
3410
  } = hooks.useContext(FormRenderContext$1);
3137
3411
  const {
3138
- field
3412
+ field,
3413
+ Empty
3139
3414
  } = props;
3140
3415
  const {
3141
3416
  id,
@@ -3172,7 +3447,20 @@ function Default(props) {
3172
3447
  }), components.length ? null : jsxRuntime.jsx(Empty, {})]
3173
3448
  });
3174
3449
  }
3175
- Default.config = {
3450
+
3451
+ function FormComponent$1(props) {
3452
+ const {
3453
+ EmptyRoot
3454
+ } = hooks.useContext(FormRenderContext$1);
3455
+ const fullProps = {
3456
+ ...props,
3457
+ Empty: EmptyRoot
3458
+ };
3459
+ return jsxRuntime.jsx(Grid, {
3460
+ ...fullProps
3461
+ });
3462
+ }
3463
+ FormComponent$1.config = {
3176
3464
  type: 'default',
3177
3465
  keyed: false,
3178
3466
  label: null,
@@ -3201,6 +3489,74 @@ var SvgCalendar = function SvgCalendar(props) {
3201
3489
  };
3202
3490
  var CalendarIcon = SvgCalendar;
3203
3491
 
3492
+ /**
3493
+ * Returns date format for the provided locale.
3494
+ * If the locale is not provided, uses the browser's locale.
3495
+ *
3496
+ * @param {string} [locale] - The locale to get date format for.
3497
+ * @returns {string} The date format for the locale.
3498
+ */
3499
+ function getLocaleDateFormat(locale = 'default') {
3500
+ const parts = new Intl.DateTimeFormat(locale).formatToParts(new Date(Date.UTC(2020, 5, 5)));
3501
+ return parts.map(part => {
3502
+ const len = part.value.length;
3503
+ switch (part.type) {
3504
+ case 'day':
3505
+ return 'd'.repeat(len);
3506
+ case 'month':
3507
+ return 'M'.repeat(len);
3508
+ case 'year':
3509
+ return 'y'.repeat(len);
3510
+ default:
3511
+ return part.value;
3512
+ }
3513
+ }).join('');
3514
+ }
3515
+
3516
+ /**
3517
+ * Returns readable date format for the provided locale.
3518
+ * If the locale is not provided, uses the browser's locale.
3519
+ *
3520
+ * @param {string} [locale] - The locale to get readable date format for.
3521
+ * @returns {string} The readable date format for the locale.
3522
+ */
3523
+ function getLocaleReadableDateFormat(locale) {
3524
+ let format = getLocaleDateFormat(locale).toLowerCase();
3525
+
3526
+ // Ensure month is in 'mm' format
3527
+ if (!format.includes('mm')) {
3528
+ format = format.replace('m', 'mm');
3529
+ }
3530
+
3531
+ // Ensure day is in 'dd' format
3532
+ if (!format.includes('dd')) {
3533
+ format = format.replace('d', 'dd');
3534
+ }
3535
+ return format;
3536
+ }
3537
+
3538
+ /**
3539
+ * Returns flatpickr config for the provided locale.
3540
+ * If the locale is not provided, uses the browser's locale.
3541
+ *
3542
+ * @param {string} [locale] - The locale to get flatpickr config for.
3543
+ * @returns {object} The flatpickr config for the locale.
3544
+ */
3545
+ function getLocaleDateFlatpickrConfig(locale) {
3546
+ return flatpickerizeDateFormat(getLocaleDateFormat(locale));
3547
+ }
3548
+ function flatpickerizeDateFormat(dateFormat) {
3549
+ const useLeadingZero = {
3550
+ day: dateFormat.includes('dd'),
3551
+ month: dateFormat.includes('MM'),
3552
+ year: dateFormat.includes('yyyy')
3553
+ };
3554
+ dateFormat = useLeadingZero.day ? dateFormat.replace('dd', 'd') : dateFormat.replace('d', 'j');
3555
+ dateFormat = useLeadingZero.month ? dateFormat.replace('MM', 'm') : dateFormat.replace('M', 'n');
3556
+ dateFormat = useLeadingZero.year ? dateFormat.replace('yyyy', 'Y') : dateFormat.replace('yy', 'y');
3557
+ return dateFormat;
3558
+ }
3559
+
3204
3560
  function InputAdorner(props) {
3205
3561
  const {
3206
3562
  pre,
@@ -3275,7 +3631,7 @@ function Datepicker(props) {
3275
3631
  hooks.useEffect(() => {
3276
3632
  let config = {
3277
3633
  allowInput: true,
3278
- dateFormat: 'm/d/Y',
3634
+ dateFormat: getLocaleDateFlatpickrConfig(),
3279
3635
  static: true,
3280
3636
  clickOpens: false,
3281
3637
  // TODO: support dates prior to 1900 (https://github.com/bpmn-io/form-js/issues/533)
@@ -3367,7 +3723,7 @@ function Datepicker(props) {
3367
3723
  class: "fjs-input",
3368
3724
  disabled: disabled,
3369
3725
  readOnly: readonly,
3370
- placeholder: "mm/dd/yyyy",
3726
+ placeholder: getLocaleReadableDateFormat(),
3371
3727
  autoComplete: "off",
3372
3728
  onFocus: onInputFocus,
3373
3729
  onKeyDown: onInputKeyDown,
@@ -3863,10 +4219,10 @@ Datetime.config = {
3863
4219
  }
3864
4220
  };
3865
4221
 
3866
- /**
3867
- * This file must not be changed or exchanged.
3868
- *
3869
- * @see http://bpmn.io/license for more information.
4222
+ /**
4223
+ * This file must not be changed or exchanged.
4224
+ *
4225
+ * @see http://bpmn.io/license for more information.
3870
4226
  */
3871
4227
  function Logo() {
3872
4228
  return jsxRuntime.jsxs("svg", {
@@ -3988,6 +4344,52 @@ function FormComponent(props) {
3988
4344
  });
3989
4345
  }
3990
4346
 
4347
+ function Group(props) {
4348
+ const {
4349
+ field
4350
+ } = props;
4351
+ const {
4352
+ label,
4353
+ id,
4354
+ type,
4355
+ showOutline
4356
+ } = field;
4357
+ const {
4358
+ formId
4359
+ } = hooks.useContext(FormContext$1);
4360
+ const {
4361
+ Empty
4362
+ } = hooks.useContext(FormRenderContext$1);
4363
+ const fullProps = {
4364
+ ...props,
4365
+ Empty
4366
+ };
4367
+ return jsxRuntime.jsxs("div", {
4368
+ className: classNames(formFieldClasses(type), {
4369
+ 'fjs-outlined': showOutline
4370
+ }),
4371
+ role: "group",
4372
+ "aria-labelledby": prefixId(id, formId),
4373
+ children: [jsxRuntime.jsx(Label, {
4374
+ id: prefixId(id, formId),
4375
+ label: label
4376
+ }), jsxRuntime.jsx(Grid, {
4377
+ ...fullProps
4378
+ })]
4379
+ });
4380
+ }
4381
+ Group.config = {
4382
+ type: 'group',
4383
+ pathed: true,
4384
+ label: 'Group',
4385
+ group: 'presentation',
4386
+ create: (options = {}) => ({
4387
+ components: [],
4388
+ showOutline: true,
4389
+ ...options
4390
+ })
4391
+ };
4392
+
3991
4393
  const NODE_TYPE_TEXT = 3,
3992
4394
  NODE_TYPE_ELEMENT = 1;
3993
4395
  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'];
@@ -3998,11 +4400,11 @@ const ATTR_WHITESPACE_PATTERN = /[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u
3998
4400
 
3999
4401
  const FORM_ELEMENT = document.createElement('form');
4000
4402
 
4001
- /**
4002
- * Sanitize a HTML string and return the cleaned, safe version.
4003
- *
4004
- * @param {string} html
4005
- * @return {string}
4403
+ /**
4404
+ * Sanitize a HTML string and return the cleaned, safe version.
4405
+ *
4406
+ * @param {string} html
4407
+ * @return {string}
4006
4408
  */
4007
4409
 
4008
4410
  // see https://github.com/developit/snarkdown/issues/70
@@ -4020,29 +4422,29 @@ function sanitizeHTML(html) {
4020
4422
  }
4021
4423
  }
4022
4424
 
4023
- /**
4024
- * Sanitizes an image source to ensure we only allow for data URI and links
4025
- * that start with http(s).
4026
- *
4027
- * Note: Most browsers anyway do not support script execution in <img> elements.
4028
- *
4029
- * @param {string} src
4030
- * @returns {string}
4425
+ /**
4426
+ * Sanitizes an image source to ensure we only allow for data URI and links
4427
+ * that start with http(s).
4428
+ *
4429
+ * Note: Most browsers anyway do not support script execution in <img> elements.
4430
+ *
4431
+ * @param {string} src
4432
+ * @returns {string}
4031
4433
  */
4032
4434
  function sanitizeImageSource(src) {
4033
4435
  const valid = ALLOWED_IMAGE_SRC_PATTERN.test(src);
4034
4436
  return valid ? src : '';
4035
4437
  }
4036
4438
 
4037
- /**
4038
- * Recursively sanitize a HTML node, potentially
4039
- * removing it, its children or attributes.
4040
- *
4041
- * Inspired by https://github.com/developit/snarkdown/issues/70
4042
- * and https://github.com/cure53/DOMPurify. Simplified
4043
- * for our use-case.
4044
- *
4045
- * @param {Element} node
4439
+ /**
4440
+ * Recursively sanitize a HTML node, potentially
4441
+ * removing it, its children or attributes.
4442
+ *
4443
+ * Inspired by https://github.com/developit/snarkdown/issues/70
4444
+ * and https://github.com/cure53/DOMPurify. Simplified
4445
+ * for our use-case.
4446
+ *
4447
+ * @param {Element} node
4046
4448
  */
4047
4449
  function sanitizeNode(node) {
4048
4450
  // allow text nodes
@@ -4086,13 +4488,13 @@ function sanitizeNode(node) {
4086
4488
  }
4087
4489
  }
4088
4490
 
4089
- /**
4090
- * Validates attributes for validity.
4091
- *
4092
- * @param {string} lcTag
4093
- * @param {string} lcName
4094
- * @param {string} value
4095
- * @return {boolean}
4491
+ /**
4492
+ * Validates attributes for validity.
4493
+ *
4494
+ * @param {string} lcTag
4495
+ * @param {string} lcName
4496
+ * @param {string} value
4497
+ * @return {boolean}
4096
4498
  */
4097
4499
  function isValidAttribute(lcTag, lcName, value) {
4098
4500
  // disallow most attributes based on whitelist
@@ -4576,21 +4978,7 @@ Radio.config = {
4576
4978
  group: 'selection',
4577
4979
  emptyValue: null,
4578
4980
  sanitizeValue: sanitizeSingleSelectValue,
4579
- create: (options = {}) => {
4580
- const defaults = {};
4581
-
4582
- // provide default values if valuesKey isn't set
4583
- if (!options.valuesKey) {
4584
- defaults.values = [{
4585
- label: 'Value',
4586
- value: 'value'
4587
- }];
4588
- }
4589
- return {
4590
- ...defaults,
4591
- ...options
4592
- };
4593
- }
4981
+ create: createEmptyOptions
4594
4982
  };
4595
4983
 
4596
4984
  var _path$d;
@@ -4942,21 +5330,7 @@ Select.config = {
4942
5330
  group: 'selection',
4943
5331
  emptyValue: null,
4944
5332
  sanitizeValue: sanitizeSingleSelectValue,
4945
- create: (options = {}) => {
4946
- const defaults = {};
4947
-
4948
- // provide default values if valuesKey isn't set
4949
- if (!options.valuesKey) {
4950
- defaults.values = [{
4951
- label: 'Value',
4952
- value: 'value'
4953
- }];
4954
- }
4955
- return {
4956
- ...defaults,
4957
- ...options
4958
- };
4959
- }
5333
+ create: createEmptyOptions
4960
5334
  };
4961
5335
 
4962
5336
  const type$4 = 'spacer';
@@ -5184,21 +5558,7 @@ Taglist.config = {
5184
5558
  group: 'selection',
5185
5559
  emptyValue: [],
5186
5560
  sanitizeValue: sanitizeMultiSelectValue,
5187
- create: (options = {}) => {
5188
- const defaults = {};
5189
-
5190
- // provide default values if valuesKey isn't set
5191
- if (!options.valuesKey) {
5192
- defaults.values = [{
5193
- label: 'Value',
5194
- value: 'value'
5195
- }];
5196
- }
5197
- return {
5198
- ...defaults,
5199
- ...options
5200
- };
5201
- }
5561
+ create: createEmptyOptions
5202
5562
  };
5203
5563
 
5204
5564
  const type$2 = 'text';
@@ -5615,13 +5975,14 @@ var SvgGroup = function SvgGroup(props) {
5615
5975
  return /*#__PURE__*/React__namespace.createElement("svg", _extends$8({
5616
5976
  xmlns: "http://www.w3.org/2000/svg",
5617
5977
  width: 54,
5618
- height: 54
5978
+ height: 54,
5979
+ fill: "currentcolor"
5619
5980
  }, props), _path$8 || (_path$8 = /*#__PURE__*/React__namespace.createElement("path", {
5620
5981
  fillRule: "evenodd",
5621
5982
  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"
5622
5983
  })));
5623
5984
  };
5624
- var ColumnsIcon = SvgGroup;
5985
+ var GroupIcon = SvgGroup;
5625
5986
 
5626
5987
  var _path$7;
5627
5988
  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); }
@@ -5674,14 +6035,14 @@ var SvgSpacer = function SvgSpacer(props) {
5674
6035
  xmlns: "http://www.w3.org/2000/svg",
5675
6036
  width: 54,
5676
6037
  height: 54,
5677
- fill: "none"
6038
+ fill: "currentcolor"
5678
6039
  }, props), _path$4 || (_path$4 = /*#__PURE__*/React__namespace.createElement("path", {
5679
- stroke: "#000",
6040
+ stroke: "currentcolor",
5680
6041
  strokeLinecap: "square",
5681
6042
  strokeWidth: 2,
5682
6043
  d: "M9 23h36M9 31h36"
5683
6044
  })), _path2$1 || (_path2$1 = /*#__PURE__*/React__namespace.createElement("path", {
5684
- stroke: "#000",
6045
+ stroke: "currentcolor",
5685
6046
  strokeLinecap: "round",
5686
6047
  strokeLinejoin: "round",
5687
6048
  strokeWidth: 2,
@@ -5759,8 +6120,9 @@ const iconsByType = type => {
5759
6120
  button: ButtonIcon,
5760
6121
  checkbox: CheckboxIcon,
5761
6122
  checklist: ChecklistIcon,
5762
- columns: ColumnsIcon,
6123
+ columns: GroupIcon,
5763
6124
  datetime: DatetimeIcon,
6125
+ group: GroupIcon,
5764
6126
  image: ImageIcon,
5765
6127
  number: NumberIcon,
5766
6128
  radio: RadioIcon,
@@ -5774,7 +6136,7 @@ const iconsByType = type => {
5774
6136
  }[type];
5775
6137
  };
5776
6138
 
5777
- const formFields = [Button, Checkbox, Checklist, Default, Image, Numberfield, Datetime, Radio, Select, Spacer, Taglist, Text, Textfield, Textarea];
6139
+ const formFields = [Button, Checkbox, Checklist, FormComponent$1, Group, Image, Numberfield, Datetime, Radio, Select, Spacer, Taglist, Text, Textfield, Textarea];
5778
6140
 
5779
6141
  class FormFields {
5780
6142
  constructor() {
@@ -5850,9 +6212,12 @@ var renderModule = {
5850
6212
  };
5851
6213
 
5852
6214
  var core = {
5853
- __depends__: [importModule, renderModule],
6215
+ __depends__: [renderModule],
5854
6216
  eventBus: ['type', EventBus],
6217
+ importer: ['type', Importer],
6218
+ fieldFactory: ['type', FieldFactory],
5855
6219
  formFieldRegistry: ['type', FormFieldRegistry],
6220
+ pathRegistry: ['type', PathRegistry],
5856
6221
  formLayouter: ['type', FormLayouter],
5857
6222
  validator: ['type', Validator]
5858
6223
  };
@@ -5967,9 +6332,9 @@ class Form {
5967
6332
  this.clear();
5968
6333
  const {
5969
6334
  schema: importedSchema,
5970
- data: initializedData,
5971
6335
  warnings
5972
- } = this.get('importer').importSchema(schema, data);
6336
+ } = this.get('importer').importSchema(schema);
6337
+ const initializedData = this._initializeFieldData(clone(data));
5973
6338
  this._setState({
5974
6339
  data: initializedData,
5975
6340
  errors: {},
@@ -6027,21 +6392,21 @@ class Form {
6027
6392
  */
6028
6393
  validate() {
6029
6394
  const formFieldRegistry = this.get('formFieldRegistry'),
6395
+ pathRegistry = this.get('pathRegistry'),
6030
6396
  validator = this.get('validator');
6031
6397
  const {
6032
6398
  data
6033
6399
  } = this._getState();
6034
6400
  const errors = formFieldRegistry.getAll().reduce((errors, field) => {
6035
6401
  const {
6036
- disabled,
6037
- _path
6402
+ disabled
6038
6403
  } = field;
6039
6404
  if (disabled) {
6040
6405
  return errors;
6041
6406
  }
6042
- const value = minDash.get(data, _path);
6407
+ const value = minDash.get(data, pathRegistry.getValuePath(field));
6043
6408
  const fieldErrors = validator.validateField(field, value);
6044
- return minDash.set(errors, [pathStringify(_path)], fieldErrors.length ? fieldErrors : undefined);
6409
+ return minDash.set(errors, [field.id], fieldErrors.length ? fieldErrors : undefined);
6045
6410
  }, /** @type {Errors} */{});
6046
6411
  this._setState({
6047
6412
  errors
@@ -6147,16 +6512,14 @@ class Form {
6147
6512
  value
6148
6513
  } = update;
6149
6514
  const {
6150
- _path
6151
- } = field;
6152
- let {
6153
6515
  data,
6154
6516
  errors
6155
6517
  } = this._getState();
6156
- const validator = this.get('validator');
6518
+ const validator = this.get('validator'),
6519
+ pathRegistry = this.get('pathRegistry');
6157
6520
  const fieldErrors = validator.validateField(field, value);
6158
- minDash.set(data, _path, value);
6159
- minDash.set(errors, [pathStringify(_path)], fieldErrors.length ? fieldErrors : undefined);
6521
+ minDash.set(data, pathRegistry.getValuePath(field), value);
6522
+ minDash.set(errors, [field.id], fieldErrors.length ? fieldErrors : undefined);
6160
6523
  this._setState({
6161
6524
  data: clone(data),
6162
6525
  errors: clone(errors)
@@ -6199,23 +6562,26 @@ class Form {
6199
6562
  * @internal
6200
6563
  */
6201
6564
  _getSubmitData() {
6202
- const formFieldRegistry = this.get('formFieldRegistry');
6565
+ const formFieldRegistry = this.get('formFieldRegistry'),
6566
+ pathRegistry = this.get('pathRegistry'),
6567
+ formFields = this.get('formFields');
6203
6568
  const formData = this._getState().data;
6204
6569
  const submitData = formFieldRegistry.getAll().reduce((previous, field) => {
6205
6570
  const {
6206
6571
  disabled,
6207
- _path
6572
+ type
6208
6573
  } = field;
6574
+ const {
6575
+ config: fieldConfig
6576
+ } = formFields.get(type);
6209
6577
 
6210
- // do not submit disabled form fields
6211
- if (disabled || !_path) {
6578
+ // do not submit disabled form fields or routing fields
6579
+ if (disabled || !fieldConfig.keyed) {
6212
6580
  return previous;
6213
6581
  }
6214
- const value = minDash.get(formData, _path);
6215
- return {
6216
- ...previous,
6217
- [_path[0]]: value
6218
- };
6582
+ const valuePath = pathRegistry.getValuePath(field);
6583
+ const value = minDash.get(formData, valuePath);
6584
+ return minDash.set(previous, valuePath, value);
6219
6585
  }, {});
6220
6586
  const filteredSubmitData = this._applyConditions(submitData, formData);
6221
6587
  return filteredSubmitData;
@@ -6228,9 +6594,46 @@ class Form {
6228
6594
  const conditionChecker = this.get('conditionChecker');
6229
6595
  return conditionChecker.applyConditions(toFilter, data);
6230
6596
  }
6597
+
6598
+ /**
6599
+ * @internal
6600
+ */
6601
+ _initializeFieldData(data) {
6602
+ const formFieldRegistry = this.get('formFieldRegistry'),
6603
+ formFields = this.get('formFields'),
6604
+ pathRegistry = this.get('pathRegistry');
6605
+ return formFieldRegistry.getAll().reduce((initializedData, formField) => {
6606
+ const {
6607
+ defaultValue,
6608
+ type
6609
+ } = formField;
6610
+
6611
+ // try to get value from data
6612
+ // if unavailable - try to get default value from form field
6613
+ // if unavailable - get empty value from form field
6614
+
6615
+ const valuePath = pathRegistry.getValuePath(formField);
6616
+ if (valuePath) {
6617
+ const {
6618
+ config: fieldConfig
6619
+ } = formFields.get(type);
6620
+ let valueData = minDash.get(data, valuePath);
6621
+ if (!minDash.isUndefined(valueData) && fieldConfig.sanitizeValue) {
6622
+ valueData = fieldConfig.sanitizeValue({
6623
+ formField,
6624
+ data,
6625
+ value: valueData
6626
+ });
6627
+ }
6628
+ const initializedFieldValue = !minDash.isUndefined(valueData) ? valueData : !minDash.isUndefined(defaultValue) ? defaultValue : fieldConfig.emptyValue;
6629
+ return minDash.set(initializedData, valuePath, initializedFieldValue);
6630
+ }
6631
+ return initializedData;
6632
+ }, data);
6633
+ }
6231
6634
  }
6232
6635
 
6233
- const schemaVersion = 10;
6636
+ const schemaVersion = 11;
6234
6637
 
6235
6638
  /**
6236
6639
  * @typedef { import('./types').CreateFormOptions } CreateFormOptions
@@ -6265,10 +6668,11 @@ exports.DATETIME_SUBTYPE_PATH = DATETIME_SUBTYPE_PATH;
6265
6668
  exports.DATE_DISALLOW_PAST_PATH = DATE_DISALLOW_PAST_PATH;
6266
6669
  exports.DATE_LABEL_PATH = DATE_LABEL_PATH;
6267
6670
  exports.Datetime = Datetime;
6268
- exports.Default = Default;
6671
+ exports.Default = FormComponent$1;
6269
6672
  exports.ExpressionLanguageModule = ExpressionLanguageModule;
6270
6673
  exports.FeelExpressionLanguage = FeelExpressionLanguage;
6271
6674
  exports.FeelersTemplating = FeelersTemplating;
6675
+ exports.FieldFactory = FieldFactory;
6272
6676
  exports.Form = Form;
6273
6677
  exports.FormComponent = FormComponent;
6274
6678
  exports.FormContext = FormContext$1;
@@ -6276,11 +6680,14 @@ exports.FormFieldRegistry = FormFieldRegistry;
6276
6680
  exports.FormFields = FormFields;
6277
6681
  exports.FormLayouter = FormLayouter;
6278
6682
  exports.FormRenderContext = FormRenderContext$1;
6683
+ exports.Group = Group;
6279
6684
  exports.Image = Image;
6685
+ exports.Importer = Importer;
6280
6686
  exports.MINUTES_IN_DAY = MINUTES_IN_DAY;
6281
6687
  exports.MarkdownModule = MarkdownModule;
6282
6688
  exports.MarkdownRenderer = MarkdownRenderer;
6283
6689
  exports.Numberfield = Numberfield;
6690
+ exports.PathRegistry = PathRegistry;
6284
6691
  exports.Radio = Radio;
6285
6692
  exports.Select = Select;
6286
6693
  exports.Spacer = Spacer;
@@ -6305,7 +6712,6 @@ exports.clone = clone;
6305
6712
  exports.createForm = createForm;
6306
6713
  exports.createFormContainer = createFormContainer;
6307
6714
  exports.createInjector = createInjector;
6308
- exports.findErrors = findErrors;
6309
6715
  exports.formFields = formFields;
6310
6716
  exports.generateIdForType = generateIdForType;
6311
6717
  exports.generateIndexForType = generateIndexForType;
@@ -6314,7 +6720,7 @@ exports.getValuesSource = getValuesSource;
6314
6720
  exports.iconsByType = iconsByType;
6315
6721
  exports.isRequired = isRequired;
6316
6722
  exports.pathParse = pathParse;
6317
- exports.pathStringify = pathStringify;
6318
6723
  exports.pathsEqual = pathsEqual;
6724
+ exports.runRecursively = runRecursively;
6319
6725
  exports.schemaVersion = schemaVersion;
6320
6726
  //# sourceMappingURL=index.cjs.map