@bpmn-io/form-js-viewer 0.9.9 → 0.10.0-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.es.js CHANGED
@@ -1,17 +1,18 @@
1
1
  import Ids from 'ids';
2
2
  import { isArray, isFunction, isNumber, bind, assign, isNil, get, isUndefined, isObject, set, isString } from 'min-dash';
3
3
  import snarkdown from '@bpmn-io/snarkdown';
4
+ import classNames from 'classnames';
4
5
  import { jsx, jsxs } from 'preact/jsx-runtime';
5
6
  import { useContext, useState, useEffect, useRef, useMemo, useCallback } from 'preact/hooks';
6
7
  import { createContext, createElement, Fragment, render } from 'preact';
7
8
  import React, { createPortal } from 'preact/compat';
8
- import classNames from 'classnames';
9
9
  import Markup from 'preact-markup';
10
10
  import { Injector } from 'didi';
11
11
 
12
12
  var FN_REF = '__fn';
13
13
  var DEFAULT_PRIORITY = 1000;
14
14
  var slice = Array.prototype.slice;
15
+
15
16
  /**
16
17
  * A general purpose event bus.
17
18
  *
@@ -95,13 +96,14 @@ var slice = Array.prototype.slice;
95
96
  * console.log(sum); // 3
96
97
  * ```
97
98
  */
98
-
99
99
  function EventBus() {
100
- this._listeners = {}; // cleanup on destroy on lowest priority to allow
101
- // message passing until the bitter end
100
+ this._listeners = {};
102
101
 
102
+ // cleanup on destroy on lowest priority to allow
103
+ // message passing until the bitter end
103
104
  this.on('diagram.destroy', 1, this._destroy, this);
104
105
  }
106
+
105
107
  /**
106
108
  * Register an event listener for events with the given name.
107
109
  *
@@ -119,30 +121,25 @@ function EventBus() {
119
121
  * @param {Function} callback
120
122
  * @param {Object} [that] Pass context (`this`) to the callback
121
123
  */
122
-
123
124
  EventBus.prototype.on = function (events, priority, callback, that) {
124
125
  events = isArray(events) ? events : [events];
125
-
126
126
  if (isFunction(priority)) {
127
127
  that = callback;
128
128
  callback = priority;
129
129
  priority = DEFAULT_PRIORITY;
130
130
  }
131
-
132
131
  if (!isNumber(priority)) {
133
132
  throw new Error('priority must be a number');
134
133
  }
135
-
136
134
  var actualCallback = callback;
137
-
138
135
  if (that) {
139
- actualCallback = bind(callback, that); // make sure we remember and are able to remove
136
+ actualCallback = bind(callback, that);
137
+
138
+ // make sure we remember and are able to remove
140
139
  // bound callbacks via {@link #off} using the original
141
140
  // callback
142
-
143
141
  actualCallback[FN_REF] = callback[FN_REF] || callback;
144
142
  }
145
-
146
143
  var self = this;
147
144
  events.forEach(function (e) {
148
145
  self._addListener(e, {
@@ -152,6 +149,7 @@ EventBus.prototype.on = function (events, priority, callback, that) {
152
149
  });
153
150
  });
154
151
  };
152
+
155
153
  /**
156
154
  * Register an event listener that is executed only once.
157
155
  *
@@ -160,34 +158,30 @@ EventBus.prototype.on = function (events, priority, callback, that) {
160
158
  * @param {Function} callback the callback to execute
161
159
  * @param {Object} [that] Pass context (`this`) to the callback
162
160
  */
163
-
164
-
165
161
  EventBus.prototype.once = function (event, priority, callback, that) {
166
162
  var self = this;
167
-
168
163
  if (isFunction(priority)) {
169
164
  that = callback;
170
165
  callback = priority;
171
166
  priority = DEFAULT_PRIORITY;
172
167
  }
173
-
174
168
  if (!isNumber(priority)) {
175
169
  throw new Error('priority must be a number');
176
170
  }
177
-
178
171
  function wrappedCallback() {
179
172
  wrappedCallback.__isTomb = true;
180
173
  var result = callback.apply(that, arguments);
181
174
  self.off(event, wrappedCallback);
182
175
  return result;
183
- } // make sure we remember and are able to remove
176
+ }
177
+
178
+ // make sure we remember and are able to remove
184
179
  // bound callbacks via {@link #off} using the original
185
180
  // callback
186
-
187
-
188
181
  wrappedCallback[FN_REF] = callback;
189
182
  this.on(event, priority, wrappedCallback);
190
183
  };
184
+
191
185
  /**
192
186
  * Removes event listeners by event and callback.
193
187
  *
@@ -196,8 +190,6 @@ EventBus.prototype.once = function (event, priority, callback, that) {
196
190
  * @param {string|Array<string>} events
197
191
  * @param {Function} [callback]
198
192
  */
199
-
200
-
201
193
  EventBus.prototype.off = function (events, callback) {
202
194
  events = isArray(events) ? events : [events];
203
195
  var self = this;
@@ -205,6 +197,7 @@ EventBus.prototype.off = function (events, callback) {
205
197
  self._removeListener(event, callback);
206
198
  });
207
199
  };
200
+
208
201
  /**
209
202
  * Create an EventBus event.
210
203
  *
@@ -212,13 +205,12 @@ EventBus.prototype.off = function (events, callback) {
212
205
  *
213
206
  * @return {Object} event, recognized by the eventBus
214
207
  */
215
-
216
-
217
208
  EventBus.prototype.createEvent = function (data) {
218
209
  var event = new InternalEvent();
219
210
  event.init(data);
220
211
  return event;
221
212
  };
213
+
222
214
  /**
223
215
  * Fires a named event.
224
216
  *
@@ -249,45 +241,40 @@ EventBus.prototype.createEvent = function (data) {
249
241
  * @return {boolean} the events return value, if specified or false if the
250
242
  * default action was prevented by listeners
251
243
  */
252
-
253
-
254
244
  EventBus.prototype.fire = function (type, data) {
255
245
  var event, firstListener, returnValue, args;
256
246
  args = slice.call(arguments);
257
-
258
247
  if (typeof type === 'object') {
259
248
  data = type;
260
249
  type = data.type;
261
250
  }
262
-
263
251
  if (!type) {
264
252
  throw new Error('no event type specified');
265
253
  }
266
-
267
254
  firstListener = this._listeners[type];
268
-
269
255
  if (!firstListener) {
270
256
  return;
271
- } // we make sure we fire instances of our home made
272
- // events here. We wrap them only once, though
273
-
257
+ }
274
258
 
259
+ // we make sure we fire instances of our home made
260
+ // events here. We wrap them only once, though
275
261
  if (data instanceof InternalEvent) {
276
262
  // we are fine, we alread have an event
277
263
  event = data;
278
264
  } else {
279
265
  event = this.createEvent(data);
280
- } // ensure we pass the event as the first parameter
281
-
266
+ }
282
267
 
283
- args[0] = event; // original event type (in case we delegate)
268
+ // ensure we pass the event as the first parameter
269
+ args[0] = event;
284
270
 
285
- var originalType = event.type; // update event type before delegation
271
+ // original event type (in case we delegate)
272
+ var originalType = event.type;
286
273
 
274
+ // update event type before delegation
287
275
  if (type !== originalType) {
288
276
  event.type = type;
289
277
  }
290
-
291
278
  try {
292
279
  returnValue = this._invokeListeners(event, args, firstListener);
293
280
  } finally {
@@ -295,60 +282,51 @@ EventBus.prototype.fire = function (type, data) {
295
282
  if (type !== originalType) {
296
283
  event.type = originalType;
297
284
  }
298
- } // set the return value to false if the event default
299
- // got prevented and no other return value exists
300
-
285
+ }
301
286
 
287
+ // set the return value to false if the event default
288
+ // got prevented and no other return value exists
302
289
  if (returnValue === undefined && event.defaultPrevented) {
303
290
  returnValue = false;
304
291
  }
305
-
306
292
  return returnValue;
307
293
  };
308
-
309
294
  EventBus.prototype.handleError = function (error) {
310
295
  return this.fire('error', {
311
296
  error: error
312
297
  }) === false;
313
298
  };
314
-
315
299
  EventBus.prototype._destroy = function () {
316
300
  this._listeners = {};
317
301
  };
318
-
319
302
  EventBus.prototype._invokeListeners = function (event, args, listener) {
320
303
  var returnValue;
321
-
322
304
  while (listener) {
323
305
  // handle stopped propagation
324
306
  if (event.cancelBubble) {
325
307
  break;
326
308
  }
327
-
328
309
  returnValue = this._invokeListener(event, args, listener);
329
310
  listener = listener.next;
330
311
  }
331
-
332
312
  return returnValue;
333
313
  };
334
-
335
314
  EventBus.prototype._invokeListener = function (event, args, listener) {
336
315
  var returnValue;
337
-
338
316
  if (listener.callback.__isTomb) {
339
317
  return returnValue;
340
318
  }
341
-
342
319
  try {
343
320
  // returning false prevents the default action
344
- returnValue = invokeFunction(listener.callback, args); // stop propagation on return value
321
+ returnValue = invokeFunction(listener.callback, args);
345
322
 
323
+ // stop propagation on return value
346
324
  if (returnValue !== undefined) {
347
325
  event.returnValue = returnValue;
348
326
  event.stopPropagation();
349
- } // prevent default on return false
350
-
327
+ }
351
328
 
329
+ // prevent default on return false
352
330
  if (returnValue === false) {
353
331
  event.preventDefault();
354
332
  }
@@ -358,9 +336,9 @@ EventBus.prototype._invokeListener = function (event, args, listener) {
358
336
  throw error;
359
337
  }
360
338
  }
361
-
362
339
  return returnValue;
363
340
  };
341
+
364
342
  /*
365
343
  * Add new listener with a certain priority to the list
366
344
  * of listeners (for the given event).
@@ -377,67 +355,54 @@ EventBus.prototype._invokeListener = function (event, args, listener) {
377
355
  * @param {string} event
378
356
  * @param {Object} listener { priority, callback }
379
357
  */
380
-
381
-
382
358
  EventBus.prototype._addListener = function (event, newListener) {
383
359
  var listener = this._getListeners(event),
384
- previousListener; // no prior listeners
385
-
360
+ previousListener;
386
361
 
362
+ // no prior listeners
387
363
  if (!listener) {
388
364
  this._setListeners(event, newListener);
389
-
390
365
  return;
391
- } // ensure we order listeners by priority from
392
- // 0 (high) to n > 0 (low)
393
-
366
+ }
394
367
 
368
+ // ensure we order listeners by priority from
369
+ // 0 (high) to n > 0 (low)
395
370
  while (listener) {
396
371
  if (listener.priority < newListener.priority) {
397
372
  newListener.next = listener;
398
-
399
373
  if (previousListener) {
400
374
  previousListener.next = newListener;
401
375
  } else {
402
376
  this._setListeners(event, newListener);
403
377
  }
404
-
405
378
  return;
406
379
  }
407
-
408
380
  previousListener = listener;
409
381
  listener = listener.next;
410
- } // add new listener to back
411
-
382
+ }
412
383
 
384
+ // add new listener to back
413
385
  previousListener.next = newListener;
414
386
  };
415
-
416
387
  EventBus.prototype._getListeners = function (name) {
417
388
  return this._listeners[name];
418
389
  };
419
-
420
390
  EventBus.prototype._setListeners = function (name, listener) {
421
391
  this._listeners[name] = listener;
422
392
  };
423
-
424
393
  EventBus.prototype._removeListener = function (event, callback) {
425
394
  var listener = this._getListeners(event),
426
- nextListener,
427
- previousListener,
428
- listenerCallback;
429
-
395
+ nextListener,
396
+ previousListener,
397
+ listenerCallback;
430
398
  if (!callback) {
431
399
  // clear listeners
432
400
  this._setListeners(event, null);
433
-
434
401
  return;
435
402
  }
436
-
437
403
  while (listener) {
438
404
  nextListener = listener.next;
439
405
  listenerCallback = listener.callback;
440
-
441
406
  if (listenerCallback === callback || listenerCallback[FN_REF] === callback) {
442
407
  if (previousListener) {
443
408
  previousListener.next = nextListener;
@@ -446,29 +411,25 @@ EventBus.prototype._removeListener = function (event, callback) {
446
411
  this._setListeners(event, nextListener);
447
412
  }
448
413
  }
449
-
450
414
  previousListener = listener;
451
415
  listener = nextListener;
452
416
  }
453
417
  };
418
+
454
419
  /**
455
420
  * A event that is emitted via the event bus.
456
421
  */
457
-
458
-
459
422
  function InternalEvent() {}
460
-
461
423
  InternalEvent.prototype.stopPropagation = function () {
462
424
  this.cancelBubble = true;
463
425
  };
464
-
465
426
  InternalEvent.prototype.preventDefault = function () {
466
427
  this.defaultPrevented = true;
467
428
  };
468
-
469
429
  InternalEvent.prototype.init = function (data) {
470
430
  assign(this, data || {});
471
431
  };
432
+
472
433
  /**
473
434
  * Invoke function. Be fast...
474
435
  *
@@ -477,50 +438,47 @@ InternalEvent.prototype.init = function (data) {
477
438
  *
478
439
  * @return {Any}
479
440
  */
480
-
481
-
482
441
  function invokeFunction(fn, args) {
483
442
  return fn.apply(null, args);
484
443
  }
485
444
 
445
+ const EMAIL_PATTERN = /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/;
446
+ const PHONE_PATTERN = /(\+|00)(297|93|244|1264|358|355|376|971|54|374|1684|1268|61|43|994|257|32|229|226|880|359|973|1242|387|590|375|501|1441|591|55|1246|673|975|267|236|1|61|41|56|86|225|237|243|242|682|57|269|238|506|53|5999|61|1345|357|420|49|253|1767|45|1809|1829|1849|213|593|20|291|212|34|372|251|358|679|500|33|298|691|241|44|995|44|233|350|224|590|220|245|240|30|1473|299|502|594|1671|592|852|504|385|509|36|62|44|91|246|353|98|964|354|972|39|1876|44|962|81|76|77|254|996|855|686|1869|82|383|965|856|961|231|218|1758|423|94|266|370|352|371|853|590|212|377|373|261|960|52|692|389|223|356|95|382|976|1670|258|222|1664|596|230|265|60|262|264|687|227|672|234|505|683|31|47|977|674|64|968|92|507|64|51|63|680|675|48|1787|1939|850|351|595|970|689|974|262|40|7|250|966|249|221|65|500|4779|677|232|503|378|252|508|381|211|239|597|421|386|46|268|1721|248|963|1649|235|228|66|992|690|993|670|676|1868|216|90|688|886|255|256|380|598|1|998|3906698|379|1784|58|1284|1340|84|678|681|685|967|27|260|263)(9[976]\d|8[987530]\d|6[987]\d|5[90]\d|42\d|3[875]\d|2[98654321]\d|9[8543210]|8[6421]|6[6543210]|5[87654321]|4[987654310]|3[9643210]|2[70]|7|1)\d{4,20}$/;
486
447
  class Validator {
487
448
  validateField(field, value) {
488
449
  const {
489
450
  validate
490
451
  } = field;
491
452
  let errors = [];
492
-
493
453
  if (!validate) {
494
454
  return errors;
495
455
  }
496
-
497
456
  if (validate.pattern && value && !new RegExp(validate.pattern).test(value)) {
498
457
  errors = [...errors, `Field must match pattern ${validate.pattern}.`];
499
458
  }
500
-
501
459
  if (validate.required && (isNil(value) || value === '')) {
502
460
  errors = [...errors, 'Field is required.'];
503
461
  }
504
-
505
462
  if ('min' in validate && value && value < validate.min) {
506
463
  errors = [...errors, `Field must have minimum value of ${validate.min}.`];
507
464
  }
508
-
509
465
  if ('max' in validate && value && value > validate.max) {
510
466
  errors = [...errors, `Field must have maximum value of ${validate.max}.`];
511
467
  }
512
-
513
468
  if ('minLength' in validate && value && value.trim().length < validate.minLength) {
514
469
  errors = [...errors, `Field must have minimum length of ${validate.minLength}.`];
515
470
  }
516
-
517
471
  if ('maxLength' in validate && value && value.trim().length > validate.maxLength) {
518
472
  errors = [...errors, `Field must have maximum length of ${validate.maxLength}.`];
519
473
  }
520
-
474
+ if ('validationType' in validate && value && validate.validationType === 'phone' && !PHONE_PATTERN.test(value)) {
475
+ errors = [...errors, 'Field must be a valid international phone number. (e.g. +4930664040900)'];
476
+ }
477
+ if ('validationType' in validate && value && validate.validationType === 'email' && !EMAIL_PATTERN.test(value)) {
478
+ errors = [...errors, 'Field must be a valid email.'];
479
+ }
521
480
  return errors;
522
481
  }
523
-
524
482
  }
525
483
  Validator.$inject = [];
526
484
 
@@ -532,59 +490,44 @@ class FormFieldRegistry {
532
490
  this._ids = new Ids([32, 36, 1]);
533
491
  this._keys = new Ids([32, 36, 1]);
534
492
  }
535
-
536
493
  add(formField) {
537
494
  const {
538
495
  id
539
496
  } = formField;
540
-
541
497
  if (this._formFields[id]) {
542
498
  throw new Error(`form field with ID ${id} already exists`);
543
499
  }
544
-
545
500
  this._eventBus.fire('formField.add', {
546
501
  formField
547
502
  });
548
-
549
503
  this._formFields[id] = formField;
550
504
  }
551
-
552
505
  remove(formField) {
553
506
  const {
554
507
  id
555
508
  } = formField;
556
-
557
509
  if (!this._formFields[id]) {
558
510
  return;
559
511
  }
560
-
561
512
  this._eventBus.fire('formField.remove', {
562
513
  formField
563
514
  });
564
-
565
515
  delete this._formFields[id];
566
516
  }
567
-
568
517
  get(id) {
569
518
  return this._formFields[id];
570
519
  }
571
-
572
520
  getAll() {
573
521
  return Object.values(this._formFields);
574
522
  }
575
-
576
523
  forEach(callback) {
577
524
  this.getAll().forEach(formField => callback(formField));
578
525
  }
579
-
580
526
  clear() {
581
527
  this._formFields = {};
582
-
583
528
  this._ids.clear();
584
-
585
529
  this._keys.clear();
586
530
  }
587
-
588
531
  }
589
532
  FormFieldRegistry.$inject = ['eventBus'];
590
533
 
@@ -615,7 +558,6 @@ function pathParse(path) {
615
558
  if (!path) {
616
559
  return [];
617
560
  }
618
-
619
561
  return path.split('.').map(key => {
620
562
  return isNaN(parseInt(key)) ? key : parseInt(key);
621
563
  });
@@ -627,7 +569,6 @@ function pathStringify(path) {
627
569
  if (!path) {
628
570
  return '';
629
571
  }
630
-
631
572
  return path.join('.');
632
573
  }
633
574
  const indices = {};
@@ -637,22 +578,22 @@ function generateIndexForType(type) {
637
578
  } else {
638
579
  indices[type] = 1;
639
580
  }
640
-
641
581
  return indices[type];
642
582
  }
643
583
  function generateIdForType(type) {
644
584
  return `${type}${generateIndexForType(type)}`;
645
585
  }
586
+
646
587
  /**
647
588
  * @template T
648
589
  * @param {T} data
649
590
  * @param {(this: any, key: string, value: any) => any} [replacer]
650
591
  * @return {T}
651
592
  */
652
-
653
593
  function clone(data, replacer) {
654
594
  return JSON.parse(JSON.stringify(data, replacer));
655
595
  }
596
+
656
597
  /**
657
598
  * Parse the schema for input variables a form might make use of
658
599
  *
@@ -665,26 +606,21 @@ function getSchemaVariables(schema) {
665
606
  if (!schema.components) {
666
607
  return [];
667
608
  }
668
-
669
609
  return schema.components.reduce((variables, component) => {
670
610
  const {
671
611
  key,
672
612
  valuesKey,
673
613
  type
674
614
  } = component;
675
-
676
615
  if (['text', 'button'].includes(type)) {
677
616
  return variables;
678
617
  }
679
-
680
618
  if (key) {
681
619
  variables = [...variables, key];
682
620
  }
683
-
684
621
  if (valuesKey && !variables.includes(valuesKey)) {
685
622
  variables = [...variables, valuesKey];
686
623
  }
687
-
688
624
  return variables;
689
625
  }, []);
690
626
  }
@@ -699,6 +635,7 @@ class Importer {
699
635
  this._formFieldRegistry = formFieldRegistry;
700
636
  this._formFields = formFields;
701
637
  }
638
+
702
639
  /**
703
640
  * Import schema adding `id`, `_parent` and `_path`
704
641
  * information to each field and adding it to the
@@ -709,15 +646,12 @@ class Importer {
709
646
  *
710
647
  * @return { { warnings: Array<any>, schema: any, data: any } }
711
648
  */
712
-
713
-
714
649
  importSchema(schema, data = {}) {
715
650
  // TODO: Add warnings - https://github.com/bpmn-io/form-js/issues/289
716
651
  const warnings = [];
717
-
718
652
  try {
719
653
  const importedSchema = this.importFormField(clone(schema)),
720
- importedData = this.importData(clone(data));
654
+ importedData = this.importData(clone(data));
721
655
  return {
722
656
  warnings,
723
657
  schema: importedSchema,
@@ -728,14 +662,13 @@ class Importer {
728
662
  throw err;
729
663
  }
730
664
  }
665
+
731
666
  /**
732
667
  * @param {any} formField
733
668
  * @param {string} [parentId]
734
669
  *
735
670
  * @return {any} importedField
736
671
  */
737
-
738
-
739
672
  importFormField(formField, parentId) {
740
673
  const {
741
674
  components,
@@ -743,64 +676,53 @@ class Importer {
743
676
  type,
744
677
  id = generateIdForType(type)
745
678
  } = formField;
746
-
747
679
  if (parentId) {
748
680
  // set form field parent
749
681
  formField._parent = parentId;
750
682
  }
751
-
752
683
  if (!this._formFields.get(type)) {
753
684
  throw new Error(`form field of type <${type}> not supported`);
754
685
  }
755
-
756
686
  if (key) {
757
687
  // validate <key> uniqueness
758
688
  if (this._formFieldRegistry._keys.assigned(key)) {
759
689
  throw new Error(`form field with key <${key}> already exists`);
760
690
  }
691
+ this._formFieldRegistry._keys.claim(key, formField);
761
692
 
762
- this._formFieldRegistry._keys.claim(key, formField); // TODO: buttons should not have key
763
-
764
-
693
+ // TODO: buttons should not have key
765
694
  if (type !== 'button') {
766
695
  // set form field path
767
696
  formField._path = [key];
768
697
  }
769
698
  }
770
-
771
699
  if (id) {
772
700
  // validate <id> uniqueness
773
701
  if (this._formFieldRegistry._ids.assigned(id)) {
774
702
  throw new Error(`form field with id <${id}> already exists`);
775
703
  }
776
-
777
704
  this._formFieldRegistry._ids.claim(id, formField);
778
- } // set form field ID
779
-
705
+ }
780
706
 
707
+ // set form field ID
781
708
  formField.id = id;
782
-
783
709
  this._formFieldRegistry.add(formField);
784
-
785
710
  if (components) {
786
711
  this.importFormFields(components, id);
787
712
  }
788
-
789
713
  return formField;
790
714
  }
791
-
792
715
  importFormFields(components, parentId) {
793
716
  components.forEach(component => {
794
717
  this.importFormField(component, parentId);
795
718
  });
796
719
  }
720
+
797
721
  /**
798
722
  * @param {Object} data
799
723
  *
800
724
  * @return {Object} importedData
801
725
  */
802
-
803
-
804
726
  importData(data) {
805
727
  return this._formFieldRegistry.getAll().reduce((importedData, formField) => {
806
728
  const {
@@ -808,22 +730,24 @@ class Importer {
808
730
  _path,
809
731
  type,
810
732
  valuesKey
811
- } = formField; // get values defined via valuesKey
733
+ } = formField;
734
+
735
+ // get values defined via valuesKey
812
736
 
813
737
  if (valuesKey) {
814
- importedData = { ...importedData,
738
+ importedData = {
739
+ ...importedData,
815
740
  [valuesKey]: get(data, [valuesKey])
816
741
  };
817
- } // try to get value from data
742
+ }
743
+
744
+ // try to get value from data
818
745
  // if unavailable - try to get default value from form field
819
746
  // if unavailable - get empty value from form field
820
747
 
821
-
822
748
  if (_path) {
823
749
  const fieldImplementation = this._formFields.get(type);
824
-
825
750
  let valueData = get(data, _path);
826
-
827
751
  if (!isUndefined(valueData) && fieldImplementation.sanitizeValue) {
828
752
  valueData = fieldImplementation.sanitizeValue({
829
753
  formField,
@@ -831,17 +755,15 @@ class Importer {
831
755
  value: valueData
832
756
  });
833
757
  }
834
-
835
758
  const initialFieldValue = !isUndefined(valueData) ? valueData : !isUndefined(defaultValue) ? defaultValue : fieldImplementation.emptyValue;
836
- importedData = { ...importedData,
759
+ importedData = {
760
+ ...importedData,
837
761
  [_path[0]]: initialFieldValue
838
762
  };
839
763
  }
840
-
841
764
  return importedData;
842
765
  }, {});
843
766
  }
844
-
845
767
  }
846
768
  Importer.$inject = ['formFieldRegistry', 'formFields'];
847
769
 
@@ -850,30 +772,26 @@ var importModule = {
850
772
  };
851
773
 
852
774
  const NODE_TYPE_TEXT = 3,
853
- NODE_TYPE_ELEMENT = 1;
775
+ NODE_TYPE_ELEMENT = 1;
854
776
  const ALLOWED_NODES = ['h1', 'h2', 'h3', 'h4', 'h5', 'span', 'em', 'a', 'p', 'div', 'ul', 'ol', 'li', 'hr', 'blockquote', 'img', 'pre', 'code', 'br', 'strong'];
855
777
  const ALLOWED_ATTRIBUTES = ['align', 'alt', 'class', 'href', 'id', 'name', 'rel', 'target', 'src'];
856
778
  const ALLOWED_URI_PATTERN = /^(?:(?:(?:f|ht)tps?|mailto|tel|callto|cid|xmpp):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i; // eslint-disable-line no-useless-escape
857
-
858
779
  const ATTR_WHITESPACE_PATTERN = /[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205F\u3000]/g; // eslint-disable-line no-control-regex
859
780
 
860
781
  const FORM_ELEMENT = document.createElement('form');
782
+
861
783
  /**
862
784
  * Sanitize a HTML string and return the cleaned, safe version.
863
785
  *
864
786
  * @param {string} html
865
787
  * @return {string}
866
788
  */
867
-
868
789
  function sanitizeHTML(html) {
869
790
  const doc = new DOMParser().parseFromString(`<!DOCTYPE html>\n<html><body><div>${html}`, 'text/html');
870
791
  doc.normalize();
871
792
  const element = doc.body.firstChild;
872
-
873
793
  if (element) {
874
- sanitizeNode(
875
- /** @type Element */
876
- element);
794
+ sanitizeNode( /** @type Element */element);
877
795
  return new XMLSerializer().serializeToString(element);
878
796
  } else {
879
797
  // handle the case that document parsing
@@ -881,6 +799,7 @@ function sanitizeHTML(html) {
881
799
  return '';
882
800
  }
883
801
  }
802
+
884
803
  /**
885
804
  * Recursively sanitize a HTML node, potentially
886
805
  * removing it, its children or attributes.
@@ -891,51 +810,48 @@ function sanitizeHTML(html) {
891
810
  *
892
811
  * @param {Element} node
893
812
  */
894
-
895
813
  function sanitizeNode(node) {
896
814
  // allow text nodes
897
815
  if (node.nodeType === NODE_TYPE_TEXT) {
898
816
  return;
899
- } // disallow all other nodes but Element
900
-
817
+ }
901
818
 
819
+ // disallow all other nodes but Element
902
820
  if (node.nodeType !== NODE_TYPE_ELEMENT) {
903
821
  return node.remove();
904
822
  }
823
+ const lcTag = node.tagName.toLowerCase();
905
824
 
906
- const lcTag = node.tagName.toLowerCase(); // disallow non-whitelisted tags
907
-
825
+ // disallow non-whitelisted tags
908
826
  if (!ALLOWED_NODES.includes(lcTag)) {
909
827
  return node.remove();
910
828
  }
829
+ const attributes = node.attributes;
911
830
 
912
- const attributes = node.attributes; // clean attributes
913
-
831
+ // clean attributes
914
832
  for (let i = attributes.length; i--;) {
915
833
  const attribute = attributes[i];
916
834
  const name = attribute.name;
917
- const lcName = name.toLowerCase(); // normalize node value
835
+ const lcName = name.toLowerCase();
918
836
 
837
+ // normalize node value
919
838
  const value = attribute.value.trim();
920
839
  node.removeAttribute(name);
921
840
  const valid = isValidAttribute(lcTag, lcName, value);
922
-
923
841
  if (valid) {
924
842
  node.setAttribute(name, value);
925
843
  }
926
- } // force noopener on target="_blank" links
927
-
844
+ }
928
845
 
846
+ // force noopener on target="_blank" links
929
847
  if (lcTag === 'a' && node.getAttribute('target') === '_blank' && node.getAttribute('rel') !== 'noopener') {
930
848
  node.setAttribute('rel', 'noopener');
931
849
  }
932
-
933
850
  for (let i = node.childNodes.length; i--;) {
934
- sanitizeNode(
935
- /** @type Element */
936
- node.childNodes[i]);
851
+ sanitizeNode( /** @type Element */node.childNodes[i]);
937
852
  }
938
853
  }
854
+
939
855
  /**
940
856
  * Validates attributes for validity.
941
857
  *
@@ -944,56 +860,51 @@ function sanitizeNode(node) {
944
860
  * @param {string} value
945
861
  * @return {boolean}
946
862
  */
947
-
948
-
949
863
  function isValidAttribute(lcTag, lcName, value) {
950
864
  // disallow most attributes based on whitelist
951
865
  if (!ALLOWED_ATTRIBUTES.includes(lcName)) {
952
866
  return false;
953
- } // disallow "DOM clobbering" / polution of document and wrapping form elements
954
-
867
+ }
955
868
 
869
+ // disallow "DOM clobbering" / polution of document and wrapping form elements
956
870
  if ((lcName === 'id' || lcName === 'name') && (value in document || value in FORM_ELEMENT)) {
957
871
  return false;
958
872
  }
959
-
960
873
  if (lcName === 'target' && value !== '_blank') {
961
874
  return false;
962
- } // allow valid url links only
963
-
875
+ }
964
876
 
877
+ // allow valid url links only
965
878
  if (lcName === 'href' && !ALLOWED_URI_PATTERN.test(value.replace(ATTR_WHITESPACE_PATTERN, ''))) {
966
879
  return false;
967
880
  }
968
-
969
881
  return true;
970
882
  }
971
883
 
972
- function formFieldClasses(type, errors = []) {
884
+ function formFieldClasses(type, {
885
+ errors = [],
886
+ disabled = false
887
+ } = {}) {
973
888
  if (!type) {
974
889
  throw new Error('type required');
975
890
  }
976
-
977
- const classes = ['fjs-form-field', `fjs-form-field-${type}`];
978
-
979
- if (errors.length) {
980
- classes.push('fjs-has-errors');
981
- }
982
-
983
- return classes.join(' ');
891
+ return classNames('fjs-form-field', `fjs-form-field-${type}`, {
892
+ 'fjs-has-errors': errors.length > 0,
893
+ 'fjs-disabled': disabled
894
+ });
984
895
  }
985
896
  function prefixId(id, formId) {
986
897
  if (formId) {
987
898
  return `fjs-form-${formId}-${id}`;
988
899
  }
989
-
990
900
  return `fjs-form-${id}`;
991
901
  }
992
902
  function markdownToHTML(markdown) {
993
903
  const htmls = markdown.split(/(?:\r?\n){2,}/).map(line => /^((\d+.)|[><\s#-*])/.test(line) ? snarkdown(line) : `<p>${snarkdown(line)}</p>`);
994
904
  return htmls.join('\n\n');
995
- } // see https://github.com/developit/snarkdown/issues/70
905
+ }
996
906
 
907
+ // see https://github.com/developit/snarkdown/issues/70
997
908
  function safeMarkdown(markdown) {
998
909
  const html = markdownToHTML(markdown);
999
910
  return sanitizeHTML(html);
@@ -1008,7 +919,6 @@ function sanitizeSingleSelectValue(options) {
1008
919
  valuesKey,
1009
920
  values
1010
921
  } = formField;
1011
-
1012
922
  try {
1013
923
  const validValues = (valuesKey ? get(data, [valuesKey]) : values).map(v => v.value) || [];
1014
924
  return validValues.includes(value) ? value : null;
@@ -1028,7 +938,6 @@ function sanitizeMultiSelectValue(options) {
1028
938
  valuesKey,
1029
939
  values
1030
940
  } = formField;
1031
-
1032
941
  try {
1033
942
  const validValues = (valuesKey ? get(data, [valuesKey]) : values).map(v => v.value) || [];
1034
943
  return value.filter(v => validValues.includes(v));
@@ -1039,7 +948,7 @@ function sanitizeMultiSelectValue(options) {
1039
948
  }
1040
949
  }
1041
950
 
1042
- const type$8 = 'button';
951
+ const type$9 = 'button';
1043
952
  function Button(props) {
1044
953
  const {
1045
954
  disabled,
@@ -1049,7 +958,7 @@ function Button(props) {
1049
958
  action = 'submit'
1050
959
  } = field;
1051
960
  return jsx("div", {
1052
- class: formFieldClasses(type$8),
961
+ class: formFieldClasses(type$9),
1053
962
  children: jsx("button", {
1054
963
  class: "fjs-button",
1055
964
  type: action,
@@ -1058,15 +967,13 @@ function Button(props) {
1058
967
  })
1059
968
  });
1060
969
  }
1061
-
1062
970
  Button.create = function (options = {}) {
1063
971
  return {
1064
972
  action: 'submit',
1065
973
  ...options
1066
974
  };
1067
975
  };
1068
-
1069
- Button.type = type$8;
976
+ Button.type = type$9;
1070
977
  Button.label = 'Button';
1071
978
  Button.keyed = true;
1072
979
 
@@ -1088,9 +995,7 @@ const FormRenderContext = createContext({
1088
995
  *
1089
996
  * @returns {any}
1090
997
  */
1091
-
1092
998
  function getService(type, strict) {}
1093
-
1094
999
  const FormContext = createContext({
1095
1000
  getService,
1096
1001
  formId: null
@@ -1100,11 +1005,9 @@ function Description(props) {
1100
1005
  const {
1101
1006
  description
1102
1007
  } = props;
1103
-
1104
1008
  if (!description) {
1105
1009
  return null;
1106
1010
  }
1107
-
1108
1011
  return jsx("div", {
1109
1012
  class: "fjs-form-field-description",
1110
1013
  children: description
@@ -1115,11 +1018,9 @@ function Errors(props) {
1115
1018
  const {
1116
1019
  errors
1117
1020
  } = props;
1118
-
1119
1021
  if (!errors.length) {
1120
1022
  return null;
1121
1023
  }
1122
-
1123
1024
  return jsx("div", {
1124
1025
  class: "fjs-form-field-error",
1125
1026
  children: jsx("ul", {
@@ -1140,7 +1041,7 @@ function Label(props) {
1140
1041
  } = props;
1141
1042
  return jsxs("label", {
1142
1043
  for: id,
1143
- class: "fjs-form-field-label",
1044
+ class: classNames('fjs-form-field-label', props['class']),
1144
1045
  children: [props.children, label || '', required && jsx("span", {
1145
1046
  class: "fjs-asterix",
1146
1047
  children: "*"
@@ -1148,7 +1049,7 @@ function Label(props) {
1148
1049
  });
1149
1050
  }
1150
1051
 
1151
- const type$7 = 'checkbox';
1052
+ const type$8 = 'checkbox';
1152
1053
  function Checkbox(props) {
1153
1054
  const {
1154
1055
  disabled,
@@ -1161,7 +1062,6 @@ function Checkbox(props) {
1161
1062
  id,
1162
1063
  label
1163
1064
  } = field;
1164
-
1165
1065
  const onChange = ({
1166
1066
  target
1167
1067
  }) => {
@@ -1170,12 +1070,16 @@ function Checkbox(props) {
1170
1070
  value: target.checked
1171
1071
  });
1172
1072
  };
1173
-
1174
1073
  const {
1175
1074
  formId
1176
1075
  } = useContext(FormContext);
1177
1076
  return jsxs("div", {
1178
- class: formFieldClasses(type$7, errors),
1077
+ class: classNames(formFieldClasses(type$8, {
1078
+ errors,
1079
+ disabled
1080
+ }), {
1081
+ 'fjs-checked': value
1082
+ }),
1179
1083
  children: [jsx(Label, {
1180
1084
  id: prefixId(id, formId),
1181
1085
  label: label,
@@ -1195,17 +1099,15 @@ function Checkbox(props) {
1195
1099
  })]
1196
1100
  });
1197
1101
  }
1198
-
1199
1102
  Checkbox.create = function (options = {}) {
1200
- return { ...options
1103
+ return {
1104
+ ...options
1201
1105
  };
1202
1106
  };
1203
-
1204
- Checkbox.type = type$7;
1107
+ Checkbox.type = type$8;
1205
1108
  Checkbox.label = 'Checkbox';
1206
1109
  Checkbox.keyed = true;
1207
1110
  Checkbox.emptyValue = false;
1208
-
1209
1111
  Checkbox.sanitizeValue = ({
1210
1112
  value
1211
1113
  }) => value === true;
@@ -1220,12 +1122,12 @@ function useService (type, strict) {
1220
1122
  /**
1221
1123
  * @enum { String }
1222
1124
  */
1223
-
1224
1125
  const LOAD_STATES = {
1225
1126
  LOADING: 'loading',
1226
1127
  LOADED: 'loaded',
1227
1128
  ERROR: 'error'
1228
1129
  };
1130
+
1229
1131
  /**
1230
1132
  * @typedef {Object} ValuesGetter
1231
1133
  * @property {Object[]} values - The values data
@@ -1238,7 +1140,6 @@ const LOAD_STATES = {
1238
1140
  * @param {Object} field - The form field to handle values for
1239
1141
  * @return {ValuesGetter} valuesGetter - A values getter object providing loading state and values
1240
1142
  */
1241
-
1242
1143
  function useValuesAsync (field) {
1243
1144
  const {
1244
1145
  valuesKey,
@@ -1249,15 +1150,11 @@ function useValuesAsync (field) {
1249
1150
  error: undefined,
1250
1151
  state: LOAD_STATES.LOADING
1251
1152
  });
1252
-
1253
1153
  const initialData = useService('form')._getState().initialData;
1254
-
1255
1154
  useEffect(() => {
1256
1155
  let values = [];
1257
-
1258
1156
  if (valuesKey !== undefined) {
1259
1157
  const keyedValues = (initialData || {})[valuesKey];
1260
-
1261
1158
  if (keyedValues && Array.isArray(keyedValues)) {
1262
1159
  values = keyedValues;
1263
1160
  }
@@ -1267,25 +1164,22 @@ function useValuesAsync (field) {
1267
1164
  setValuesGetter(getErrorState('No values source defined in the form definition'));
1268
1165
  return;
1269
1166
  }
1270
-
1271
1167
  setValuesGetter(buildLoadedState(values));
1272
1168
  }, [valuesKey, staticValues, initialData]);
1273
1169
  return valuesGetter;
1274
1170
  }
1275
-
1276
1171
  const getErrorState = error => ({
1277
1172
  values: [],
1278
1173
  error,
1279
1174
  state: LOAD_STATES.ERROR
1280
1175
  });
1281
-
1282
1176
  const buildLoadedState = values => ({
1283
1177
  values,
1284
1178
  error: undefined,
1285
1179
  state: LOAD_STATES.LOADED
1286
1180
  });
1287
1181
 
1288
- const type$6 = 'checklist';
1182
+ const type$7 = 'checklist';
1289
1183
  function Checklist(props) {
1290
1184
  const {
1291
1185
  disabled,
@@ -1298,22 +1192,18 @@ function Checklist(props) {
1298
1192
  id,
1299
1193
  label
1300
1194
  } = field;
1301
-
1302
1195
  const toggleCheckbox = v => {
1303
1196
  let newValue = [...value];
1304
-
1305
1197
  if (!newValue.includes(v)) {
1306
1198
  newValue.push(v);
1307
1199
  } else {
1308
1200
  newValue = newValue.filter(x => x != v);
1309
1201
  }
1310
-
1311
1202
  props.onChange({
1312
1203
  field,
1313
1204
  value: newValue
1314
1205
  });
1315
1206
  };
1316
-
1317
1207
  const {
1318
1208
  state: loadState,
1319
1209
  values: options
@@ -1322,13 +1212,19 @@ function Checklist(props) {
1322
1212
  formId
1323
1213
  } = useContext(FormContext);
1324
1214
  return jsxs("div", {
1325
- class: formFieldClasses(type$6, errors),
1215
+ class: classNames(formFieldClasses(type$7, {
1216
+ errors,
1217
+ disabled
1218
+ })),
1326
1219
  children: [jsx(Label, {
1327
1220
  label: label
1328
1221
  }), loadState == LOAD_STATES.LOADED && options.map((v, index) => {
1329
1222
  return jsx(Label, {
1330
1223
  id: prefixId(`${id}-${index}`, formId),
1331
1224
  label: v.label,
1225
+ class: classNames({
1226
+ 'fjs-checked': value.includes(v.value)
1227
+ }),
1332
1228
  required: false,
1333
1229
  children: jsx("input", {
1334
1230
  checked: value.includes(v.value),
@@ -1346,7 +1242,6 @@ function Checklist(props) {
1346
1242
  })]
1347
1243
  });
1348
1244
  }
1349
-
1350
1245
  Checklist.create = function (options = {}) {
1351
1246
  if (options.valuesKey) return options;
1352
1247
  return {
@@ -1357,15 +1252,13 @@ Checklist.create = function (options = {}) {
1357
1252
  ...options
1358
1253
  };
1359
1254
  };
1360
-
1361
- Checklist.type = type$6;
1255
+ Checklist.type = type$7;
1362
1256
  Checklist.label = 'Checklist';
1363
1257
  Checklist.keyed = true;
1364
1258
  Checklist.emptyValue = [];
1365
1259
  Checklist.sanitizeValue = sanitizeMultiSelectValue;
1366
1260
 
1367
1261
  const noop$1 = () => false;
1368
-
1369
1262
  function FormField(props) {
1370
1263
  const {
1371
1264
  field,
@@ -1375,29 +1268,26 @@ function FormField(props) {
1375
1268
  _path
1376
1269
  } = field;
1377
1270
  const formFields = useService('formFields'),
1378
- form = useService('form');
1379
-
1271
+ form = useService('form');
1380
1272
  const {
1381
1273
  data,
1382
1274
  errors,
1383
1275
  properties
1384
1276
  } = form._getState();
1385
-
1386
1277
  const {
1387
1278
  Element
1388
1279
  } = useContext(FormRenderContext);
1389
1280
  const FormFieldComponent = formFields.get(field.type);
1390
-
1391
1281
  if (!FormFieldComponent) {
1392
1282
  throw new Error(`cannot render field <${field.type}>`);
1393
1283
  }
1394
-
1395
1284
  const value = get(data, _path);
1396
1285
  const fieldErrors = findErrors(errors, _path);
1397
1286
  const disabled = properties.readOnly || field.disabled || false;
1398
1287
  return jsx(Element, {
1399
1288
  field: field,
1400
- children: jsx(FormFieldComponent, { ...props,
1289
+ children: jsx(FormFieldComponent, {
1290
+ ...props,
1401
1291
  disabled: disabled,
1402
1292
  errors: fieldErrors,
1403
1293
  onChange: disabled ? noop$1 : onChange,
@@ -1421,21 +1311,20 @@ function Default(props) {
1421
1311
  class: "fjs-vertical-layout",
1422
1312
  field: field,
1423
1313
  children: [components.map(childField => {
1424
- return createElement(FormField, { ...props,
1314
+ return createElement(FormField, {
1315
+ ...props,
1425
1316
  key: childField.id,
1426
1317
  field: childField
1427
1318
  });
1428
1319
  }), components.length ? null : jsx(Empty, {})]
1429
1320
  });
1430
1321
  }
1431
-
1432
1322
  Default.create = function (options = {}) {
1433
1323
  return {
1434
1324
  components: [],
1435
1325
  ...options
1436
1326
  };
1437
1327
  };
1438
-
1439
1328
  Default.type = 'default';
1440
1329
  Default.keyed = false;
1441
1330
 
@@ -1444,7 +1333,6 @@ Default.keyed = false;
1444
1333
  *
1445
1334
  * @see http://bpmn.io/license for more information.
1446
1335
  */
1447
-
1448
1336
  function Logo() {
1449
1337
  return jsxs("svg", {
1450
1338
  xmlns: "http://www.w3.org/2000/svg",
@@ -1461,16 +1349,13 @@ function Logo() {
1461
1349
  })]
1462
1350
  });
1463
1351
  }
1464
-
1465
1352
  function Lightbox(props) {
1466
1353
  const {
1467
1354
  open
1468
1355
  } = props;
1469
-
1470
1356
  if (!open) {
1471
1357
  return null;
1472
1358
  }
1473
-
1474
1359
  return jsxs("div", {
1475
1360
  class: "fjs-powered-by-lightbox",
1476
1361
  style: "z-index: 100; position: fixed; top: 0; left: 0;right: 0; bottom: 0",
@@ -1498,7 +1383,6 @@ function Lightbox(props) {
1498
1383
  })]
1499
1384
  });
1500
1385
  }
1501
-
1502
1386
  function Link(props) {
1503
1387
  return jsx("div", {
1504
1388
  class: "fjs-powered-by fjs-form-field",
@@ -1515,17 +1399,14 @@ function Link(props) {
1515
1399
  })
1516
1400
  });
1517
1401
  }
1518
-
1519
1402
  function PoweredBy(props) {
1520
1403
  const [open, setOpen] = useState(false);
1521
-
1522
1404
  function toggleOpen(open) {
1523
1405
  return event => {
1524
1406
  event.preventDefault();
1525
1407
  setOpen(open);
1526
1408
  };
1527
1409
  }
1528
-
1529
1410
  return jsxs(Fragment, {
1530
1411
  children: [createPortal(jsx(Lightbox, {
1531
1412
  open: open,
@@ -1537,30 +1418,24 @@ function PoweredBy(props) {
1537
1418
  }
1538
1419
 
1539
1420
  const noop = () => {};
1540
-
1541
1421
  function FormComponent(props) {
1542
1422
  const form = useService('form');
1543
-
1544
1423
  const {
1545
1424
  schema
1546
1425
  } = form._getState();
1547
-
1548
1426
  const {
1549
1427
  onSubmit = noop,
1550
1428
  onReset = noop,
1551
1429
  onChange = noop
1552
1430
  } = props;
1553
-
1554
1431
  const handleSubmit = event => {
1555
1432
  event.preventDefault();
1556
1433
  onSubmit();
1557
1434
  };
1558
-
1559
1435
  const handleReset = event => {
1560
1436
  event.preventDefault();
1561
1437
  onReset();
1562
1438
  };
1563
-
1564
1439
  return jsxs("form", {
1565
1440
  class: "fjs-form",
1566
1441
  onSubmit: handleSubmit,
@@ -1572,7 +1447,7 @@ function FormComponent(props) {
1572
1447
  });
1573
1448
  }
1574
1449
 
1575
- const type$5 = 'number';
1450
+ const type$6 = 'number';
1576
1451
  function Number(props) {
1577
1452
  const {
1578
1453
  disabled,
@@ -1589,7 +1464,6 @@ function Number(props) {
1589
1464
  const {
1590
1465
  required
1591
1466
  } = validate;
1592
-
1593
1467
  const onChange = ({
1594
1468
  target
1595
1469
  }) => {
@@ -1600,12 +1474,14 @@ function Number(props) {
1600
1474
  })
1601
1475
  });
1602
1476
  };
1603
-
1604
1477
  const {
1605
1478
  formId
1606
1479
  } = useContext(FormContext);
1607
1480
  return jsxs("div", {
1608
- class: formFieldClasses(type$5, errors),
1481
+ class: formFieldClasses(type$6, {
1482
+ errors,
1483
+ disabled
1484
+ }),
1609
1485
  children: [jsx(Label, {
1610
1486
  id: prefixId(id, formId),
1611
1487
  label: label,
@@ -1624,25 +1500,23 @@ function Number(props) {
1624
1500
  })]
1625
1501
  });
1626
1502
  }
1627
-
1628
1503
  Number.create = function (options = {}) {
1629
- return { ...options
1504
+ return {
1505
+ ...options
1630
1506
  };
1631
1507
  };
1632
-
1633
1508
  Number.sanitizeValue = ({
1634
1509
  value
1635
1510
  }) => {
1636
1511
  const parsedValue = parseInt(value, 10);
1637
1512
  return isNaN(parsedValue) ? null : parsedValue;
1638
1513
  };
1639
-
1640
- Number.type = type$5;
1514
+ Number.type = type$6;
1641
1515
  Number.keyed = true;
1642
1516
  Number.label = 'Number';
1643
1517
  Number.emptyValue = null;
1644
1518
 
1645
- const type$4 = 'radio';
1519
+ const type$5 = 'radio';
1646
1520
  function Radio(props) {
1647
1521
  const {
1648
1522
  disabled,
@@ -1659,14 +1533,12 @@ function Radio(props) {
1659
1533
  const {
1660
1534
  required
1661
1535
  } = validate;
1662
-
1663
1536
  const onChange = v => {
1664
1537
  props.onChange({
1665
1538
  field,
1666
1539
  value: v
1667
1540
  });
1668
1541
  };
1669
-
1670
1542
  const {
1671
1543
  state: loadState,
1672
1544
  values: options
@@ -1675,7 +1547,10 @@ function Radio(props) {
1675
1547
  formId
1676
1548
  } = useContext(FormContext);
1677
1549
  return jsxs("div", {
1678
- class: formFieldClasses(type$4, errors),
1550
+ class: formFieldClasses(type$5, {
1551
+ errors,
1552
+ disabled
1553
+ }),
1679
1554
  children: [jsx(Label, {
1680
1555
  label: label,
1681
1556
  required: required
@@ -1683,6 +1558,9 @@ function Radio(props) {
1683
1558
  return jsx(Label, {
1684
1559
  id: prefixId(`${id}-${index}`, formId),
1685
1560
  label: option.label,
1561
+ class: classNames({
1562
+ 'fjs-checked': option.value === value
1563
+ }),
1686
1564
  required: false,
1687
1565
  children: jsx("input", {
1688
1566
  checked: option.value === value,
@@ -1700,7 +1578,6 @@ function Radio(props) {
1700
1578
  })]
1701
1579
  });
1702
1580
  }
1703
-
1704
1581
  Radio.create = function (options = {}) {
1705
1582
  if (options.valuesKey) return options;
1706
1583
  return {
@@ -1711,14 +1588,13 @@ Radio.create = function (options = {}) {
1711
1588
  ...options
1712
1589
  };
1713
1590
  };
1714
-
1715
- Radio.type = type$4;
1591
+ Radio.type = type$5;
1716
1592
  Radio.label = 'Radio';
1717
1593
  Radio.keyed = true;
1718
1594
  Radio.emptyValue = null;
1719
1595
  Radio.sanitizeValue = sanitizeSingleSelectValue;
1720
1596
 
1721
- const type$3 = 'select';
1597
+ const type$4 = 'select';
1722
1598
  function Select(props) {
1723
1599
  const {
1724
1600
  disabled,
@@ -1735,7 +1611,6 @@ function Select(props) {
1735
1611
  const {
1736
1612
  required
1737
1613
  } = validate;
1738
-
1739
1614
  const onChange = ({
1740
1615
  target
1741
1616
  }) => {
@@ -1744,7 +1619,6 @@ function Select(props) {
1744
1619
  value: target.value === '' ? null : target.value
1745
1620
  });
1746
1621
  };
1747
-
1748
1622
  const {
1749
1623
  state: loadState,
1750
1624
  values: options
@@ -1753,7 +1627,10 @@ function Select(props) {
1753
1627
  formId
1754
1628
  } = useContext(FormContext);
1755
1629
  return jsxs("div", {
1756
- class: formFieldClasses(type$3, errors),
1630
+ class: formFieldClasses(type$4, {
1631
+ errors,
1632
+ disabled
1633
+ }),
1757
1634
  children: [jsx(Label, {
1758
1635
  id: prefixId(id, formId),
1759
1636
  label: label,
@@ -1779,7 +1656,6 @@ function Select(props) {
1779
1656
  })]
1780
1657
  });
1781
1658
  }
1782
-
1783
1659
  Select.create = function (options = {}) {
1784
1660
  if (options.valuesKey) return options;
1785
1661
  return {
@@ -1790,8 +1666,7 @@ Select.create = function (options = {}) {
1790
1666
  ...options
1791
1667
  };
1792
1668
  };
1793
-
1794
- Select.type = type$3;
1669
+ Select.type = type$4;
1795
1670
  Select.label = 'Select';
1796
1671
  Select.keyed = true;
1797
1672
  Select.emptyValue = null;
@@ -1810,7 +1685,7 @@ var CloseIcon = (({
1810
1685
  fillRule: "evenodd",
1811
1686
  clipRule: "evenodd",
1812
1687
  d: "M12 4.7l-.7-.7L8 7.3 4.7 4l-.7.7L7.3 8 4 11.3l.7.7L8 8.7l3.3 3.3.7-.7L8.7 8 12 4.7z",
1813
- fill: "#000"
1688
+ fill: "currentColor"
1814
1689
  })));
1815
1690
 
1816
1691
  function useKeyDownAction(targetKey, action, listenerElement = window) {
@@ -1821,7 +1696,6 @@ function useKeyDownAction(targetKey, action, listenerElement = window) {
1821
1696
  action();
1822
1697
  }
1823
1698
  }
1824
-
1825
1699
  useEffect(() => {
1826
1700
  listenerElement.addEventListener('keydown', downHandler);
1827
1701
  return () => {
@@ -1831,9 +1705,7 @@ function useKeyDownAction(targetKey, action, listenerElement = window) {
1831
1705
  }
1832
1706
 
1833
1707
  const DEFAULT_LABEL_GETTER = value => value;
1834
-
1835
1708
  const NOOP = () => {};
1836
-
1837
1709
  function DropdownList(props) {
1838
1710
  const {
1839
1711
  keyEventsListener = window,
@@ -1853,7 +1725,6 @@ function DropdownList(props) {
1853
1725
  }, [values.length]);
1854
1726
  useEffect(() => {
1855
1727
  if (focusedValueIndex === 0) return;
1856
-
1857
1728
  if (!focusedValueIndex || !values.length) {
1858
1729
  setFocusedValueIndex(0);
1859
1730
  } else if (focusedValueIndex >= values.length) {
@@ -1879,7 +1750,6 @@ function DropdownList(props) {
1879
1750
  }, keyEventsListener);
1880
1751
  useEffect(() => {
1881
1752
  const individualEntries = dropdownContainer.current.children;
1882
-
1883
1753
  if (individualEntries.length && !mouseControl) {
1884
1754
  individualEntries[focusedValueIndex].scrollIntoView({
1885
1755
  block: 'nearest',
@@ -1887,23 +1757,19 @@ function DropdownList(props) {
1887
1757
  });
1888
1758
  }
1889
1759
  }, [focusedValueIndex, mouseControl]);
1890
-
1891
1760
  const mouseMove = (e, i) => {
1892
1761
  const userMoved = !mouseScreenPos.current || mouseScreenPos.current.x !== e.screenX && mouseScreenPos.current.y !== e.screenY;
1893
-
1894
1762
  if (userMoved) {
1895
1763
  mouseScreenPos.current = {
1896
1764
  x: e.screenX,
1897
1765
  y: e.screenY
1898
1766
  };
1899
-
1900
1767
  if (!mouseControl) {
1901
1768
  setMouseControl(true);
1902
1769
  setFocusedValueIndex(i);
1903
1770
  }
1904
1771
  }
1905
1772
  };
1906
-
1907
1773
  return jsxs("div", {
1908
1774
  ref: dropdownContainer,
1909
1775
  tabIndex: -1,
@@ -1929,7 +1795,7 @@ function DropdownList(props) {
1929
1795
  });
1930
1796
  }
1931
1797
 
1932
- const type$2 = 'taglist';
1798
+ const type$3 = 'taglist';
1933
1799
  function Taglist(props) {
1934
1800
  const {
1935
1801
  disabled,
@@ -1954,12 +1820,14 @@ function Taglist(props) {
1954
1820
  const {
1955
1821
  state: loadState,
1956
1822
  values: options
1957
- } = useValuesAsync(field); // We cache a map of option values to their index so that we don't need to search the whole options array every time to correlate the label
1823
+ } = useValuesAsync(field);
1958
1824
 
1825
+ // We cache a map of option values to their index so that we don't need to search the whole options array every time to correlate the label
1959
1826
  const valueToOptionMap = useMemo(() => Object.assign({}, ...options.map((o, x) => ({
1960
1827
  [o.value]: options[x]
1961
- }))), [options]); // Usage of stringify is necessary here because we want this effect to only trigger when there is a value change to the array
1828
+ }))), [options]);
1962
1829
 
1830
+ // Usage of stringify is necessary here because we want this effect to only trigger when there is a value change to the array
1963
1831
  useEffect(() => {
1964
1832
  if (loadState === LOAD_STATES.LOADED) {
1965
1833
  setFilteredOptions(options.filter(o => o.label && o.value && o.label.toLowerCase().includes(filter.toLowerCase()) && !values.includes(o.value)));
@@ -1970,37 +1838,32 @@ function Taglist(props) {
1970
1838
  useEffect(() => {
1971
1839
  setHasOptionsLeft(options.length > values.length);
1972
1840
  }, [options.length, values.length]);
1973
-
1974
1841
  const onFilterChange = ({
1975
1842
  target
1976
1843
  }) => {
1977
1844
  setIsEscapeClose(false);
1978
1845
  setFilter(target.value);
1979
1846
  };
1980
-
1981
1847
  const selectValue = value => {
1982
1848
  if (filter) {
1983
1849
  setFilter('');
1984
- } // Ensure values cannot be double selected due to latency
1985
-
1850
+ }
1986
1851
 
1852
+ // Ensure values cannot be double selected due to latency
1987
1853
  if (values.at(-1) === value) {
1988
1854
  return;
1989
1855
  }
1990
-
1991
1856
  props.onChange({
1992
1857
  value: [...values, value],
1993
1858
  field
1994
1859
  });
1995
1860
  };
1996
-
1997
1861
  const deselectValue = value => {
1998
1862
  props.onChange({
1999
1863
  value: values.filter(v => v != value),
2000
1864
  field
2001
1865
  });
2002
1866
  };
2003
-
2004
1867
  const onInputKeyDown = e => {
2005
1868
  switch (e.key) {
2006
1869
  case 'ArrowUp':
@@ -2008,49 +1871,63 @@ function Taglist(props) {
2008
1871
  // We do not want the cursor to seek in the search field when we press up and down
2009
1872
  e.preventDefault();
2010
1873
  break;
2011
-
2012
1874
  case 'Backspace':
2013
1875
  if (!filter && values.length) {
2014
1876
  deselectValue(values[values.length - 1]);
2015
1877
  }
2016
-
2017
1878
  break;
2018
-
2019
1879
  case 'Escape':
2020
1880
  setIsEscapeClose(true);
2021
1881
  break;
2022
-
2023
1882
  case 'Enter':
2024
1883
  if (isEscapeClosed) {
2025
1884
  setIsEscapeClose(false);
2026
1885
  }
2027
-
2028
1886
  break;
2029
1887
  }
2030
1888
  };
2031
-
1889
+ const onTagRemoveClick = (event, value) => {
1890
+ const {
1891
+ target
1892
+ } = event;
1893
+ deselectValue(value);
1894
+
1895
+ // restore focus if there is no next sibling to focus
1896
+ const nextTag = target.closest('.fjs-taglist-tag').nextSibling;
1897
+ if (!nextTag) {
1898
+ searchbarRef.current.focus();
1899
+ }
1900
+ };
2032
1901
  return jsxs("div", {
2033
- class: formFieldClasses(type$2, errors),
1902
+ class: formFieldClasses(type$3, {
1903
+ errors,
1904
+ disabled
1905
+ }),
2034
1906
  children: [jsx(Label, {
2035
1907
  label: label,
2036
- id: prefixId(id, formId)
1908
+ id: prefixId(`${id}-search`, formId)
2037
1909
  }), jsxs("div", {
2038
1910
  class: classNames('fjs-taglist', {
2039
1911
  'disabled': disabled
2040
1912
  }),
2041
- children: [!disabled && loadState === LOAD_STATES.LOADED && values.map(v => {
2042
- return jsxs("div", {
2043
- class: "fjs-taglist-tag",
2044
- onMouseDown: e => e.preventDefault(),
2045
- children: [jsx("span", {
2046
- class: "fjs-taglist-tag-label",
2047
- children: valueToOptionMap[v] ? valueToOptionMap[v].label : `unexpected value{${v}}`
2048
- }), jsx("span", {
2049
- class: "fjs-taglist-tag-remove",
2050
- onMouseDown: () => deselectValue(v),
2051
- children: jsx(CloseIcon, {})
2052
- })]
2053
- });
1913
+ children: [!disabled && loadState === LOAD_STATES.LOADED && jsx("div", {
1914
+ class: "fjs-taglist-tags",
1915
+ children: values.map(v => {
1916
+ return jsxs("div", {
1917
+ class: "fjs-taglist-tag",
1918
+ onMouseDown: e => e.preventDefault(),
1919
+ children: [jsx("span", {
1920
+ class: "fjs-taglist-tag-label",
1921
+ children: valueToOptionMap[v] ? valueToOptionMap[v].label : `unexpected value{${v}}`
1922
+ }), jsx("button", {
1923
+ type: "button",
1924
+ title: "Remove tag",
1925
+ class: "fjs-taglist-tag-remove",
1926
+ onClick: event => onTagRemoveClick(event, v),
1927
+ children: jsx(CloseIcon, {})
1928
+ })]
1929
+ });
1930
+ })
2054
1931
  }), jsx("input", {
2055
1932
  disabled: disabled,
2056
1933
  class: "fjs-taglist-input",
@@ -2085,7 +1962,6 @@ function Taglist(props) {
2085
1962
  })]
2086
1963
  });
2087
1964
  }
2088
-
2089
1965
  Taglist.create = function (options = {}) {
2090
1966
  if (options.valuesKey) return options;
2091
1967
  return {
@@ -2096,14 +1972,13 @@ Taglist.create = function (options = {}) {
2096
1972
  ...options
2097
1973
  };
2098
1974
  };
2099
-
2100
- Taglist.type = type$2;
1975
+ Taglist.type = type$3;
2101
1976
  Taglist.label = 'Taglist';
2102
1977
  Taglist.keyed = true;
2103
1978
  Taglist.emptyValue = [];
2104
1979
  Taglist.sanitizeValue = sanitizeMultiSelectValue;
2105
1980
 
2106
- const type$1 = 'text';
1981
+ const type$2 = 'text';
2107
1982
  function Text(props) {
2108
1983
  const {
2109
1984
  field
@@ -2112,25 +1987,23 @@ function Text(props) {
2112
1987
  text = ''
2113
1988
  } = field;
2114
1989
  return jsx("div", {
2115
- class: formFieldClasses(type$1),
1990
+ class: formFieldClasses(type$2),
2116
1991
  children: jsx(Markup, {
2117
1992
  markup: safeMarkdown(text),
2118
1993
  trim: false
2119
1994
  })
2120
1995
  });
2121
1996
  }
2122
-
2123
1997
  Text.create = function (options = {}) {
2124
1998
  return {
2125
1999
  text: '# Text',
2126
2000
  ...options
2127
2001
  };
2128
2002
  };
2129
-
2130
- Text.type = type$1;
2003
+ Text.type = type$2;
2131
2004
  Text.keyed = false;
2132
2005
 
2133
- const type = 'textfield';
2006
+ const type$1 = 'textfield';
2134
2007
  function Textfield(props) {
2135
2008
  const {
2136
2009
  disabled,
@@ -2147,7 +2020,6 @@ function Textfield(props) {
2147
2020
  const {
2148
2021
  required
2149
2022
  } = validate;
2150
-
2151
2023
  const onChange = ({
2152
2024
  target
2153
2025
  }) => {
@@ -2156,12 +2028,14 @@ function Textfield(props) {
2156
2028
  value: target.value
2157
2029
  });
2158
2030
  };
2159
-
2160
2031
  const {
2161
2032
  formId
2162
2033
  } = useContext(FormContext);
2163
2034
  return jsxs("div", {
2164
- class: formFieldClasses(type, errors),
2035
+ class: formFieldClasses(type$1, {
2036
+ errors,
2037
+ disabled
2038
+ }),
2165
2039
  children: [jsx(Label, {
2166
2040
  id: prefixId(id, formId),
2167
2041
  label: label,
@@ -2180,22 +2054,101 @@ function Textfield(props) {
2180
2054
  })]
2181
2055
  });
2182
2056
  }
2183
-
2184
2057
  Textfield.create = function (options = {}) {
2185
- return { ...options
2058
+ return {
2059
+ ...options
2186
2060
  };
2187
2061
  };
2188
-
2189
- Textfield.type = type;
2190
- Textfield.label = 'Text Field';
2062
+ Textfield.type = type$1;
2063
+ Textfield.label = 'Text field';
2191
2064
  Textfield.keyed = true;
2192
2065
  Textfield.emptyValue = '';
2193
-
2194
2066
  Textfield.sanitizeValue = ({
2195
2067
  value
2196
- }) => isArray(value) || isObject(value) ? null : String(value);
2068
+ }) => isArray(value) || isObject(value) ? '' : String(value);
2069
+
2070
+ const type = 'textarea';
2071
+ function Textarea(props) {
2072
+ const {
2073
+ disabled,
2074
+ errors = [],
2075
+ field,
2076
+ value = ''
2077
+ } = props;
2078
+ const {
2079
+ description,
2080
+ id,
2081
+ label,
2082
+ validate = {}
2083
+ } = field;
2084
+ const {
2085
+ required
2086
+ } = validate;
2087
+ const textareaRef = useRef();
2088
+ const onInput = ({
2089
+ target
2090
+ }) => {
2091
+ props.onChange({
2092
+ field,
2093
+ value: target.value
2094
+ });
2095
+ };
2096
+ const autoSizeTextarea = useCallback(textarea => {
2097
+ // Ensures the textarea shrinks back, and improves resizing behavior consistency
2098
+ textarea.style.height = '0px';
2099
+ const computed = window.getComputedStyle(textarea);
2100
+ const calculatedHeight = parseInt(computed.getPropertyValue('border-top-width')) + parseInt(computed.getPropertyValue('padding-top')) + textarea.scrollHeight + parseInt(computed.getPropertyValue('padding-bottom')) + parseInt(computed.getPropertyValue('border-bottom-width'));
2101
+ const minHeight = 75;
2102
+ const maxHeight = 350;
2103
+ const displayHeight = Math.max(Math.min(calculatedHeight, maxHeight), minHeight);
2104
+ textarea.style.height = `${displayHeight}px`;
2105
+
2106
+ // Overflow is hidden by default to hide scrollbar flickering
2107
+ textarea.style.overflow = calculatedHeight > maxHeight ? 'visible' : 'hidden';
2108
+ }, []);
2109
+ useEffect(() => {
2110
+ autoSizeTextarea(textareaRef.current);
2111
+ }, [autoSizeTextarea, value]);
2112
+ const {
2113
+ formId
2114
+ } = useContext(FormContext);
2115
+ return jsxs("div", {
2116
+ class: formFieldClasses(type, {
2117
+ errors,
2118
+ disabled
2119
+ }),
2120
+ children: [jsx(Label, {
2121
+ id: prefixId(id, formId),
2122
+ label: label,
2123
+ required: required
2124
+ }), jsx("textarea", {
2125
+ class: "fjs-textarea",
2126
+ disabled: disabled,
2127
+ id: prefixId(id, formId),
2128
+ onInput: onInput,
2129
+ value: value,
2130
+ ref: textareaRef
2131
+ }), jsx(Description, {
2132
+ description: description
2133
+ }), jsx(Errors, {
2134
+ errors: errors
2135
+ })]
2136
+ });
2137
+ }
2138
+ Textarea.create = function (options = {}) {
2139
+ return {
2140
+ ...options
2141
+ };
2142
+ };
2143
+ Textarea.type = type;
2144
+ Textarea.label = 'Text area';
2145
+ Textarea.keyed = true;
2146
+ Textarea.emptyValue = '';
2147
+ Textarea.sanitizeValue = ({
2148
+ value
2149
+ }) => isArray(value) || isObject(value) ? '' : String(value);
2197
2150
 
2198
- const formFields = [Button, Checkbox, Checklist, Default, Number, Radio, Select, Taglist, Text, Textfield];
2151
+ const formFields = [Button, Checkbox, Checklist, Default, Number, Radio, Select, Taglist, Text, Textfield, Textarea];
2199
2152
 
2200
2153
  class FormFields {
2201
2154
  constructor() {
@@ -2207,15 +2160,12 @@ class FormFields {
2207
2160
  this.register(type, formField);
2208
2161
  });
2209
2162
  }
2210
-
2211
2163
  register(type, formField) {
2212
2164
  this._formFields[type] = formField;
2213
2165
  }
2214
-
2215
2166
  get(type) {
2216
2167
  return this._formFields[type];
2217
2168
  }
2218
-
2219
2169
  }
2220
2170
 
2221
2171
  function Renderer(config, eventBus, form, injector) {
@@ -2225,7 +2175,6 @@ function Renderer(config, eventBus, form, injector) {
2225
2175
  getService(type, strict = true) {
2226
2176
  return injector.get(type, strict);
2227
2177
  },
2228
-
2229
2178
  formId: form._id
2230
2179
  };
2231
2180
  eventBus.on('changed', newState => {
@@ -2247,11 +2196,9 @@ function Renderer(config, eventBus, form, injector) {
2247
2196
  const {
2248
2197
  schema
2249
2198
  } = state;
2250
-
2251
2199
  if (!schema) {
2252
2200
  return null;
2253
2201
  }
2254
-
2255
2202
  return jsx(FormContext.Provider, {
2256
2203
  value: formContext,
2257
2204
  children: jsx(FormComponent, {
@@ -2261,7 +2208,6 @@ function Renderer(config, eventBus, form, injector) {
2261
2208
  })
2262
2209
  });
2263
2210
  };
2264
-
2265
2211
  const {
2266
2212
  container
2267
2213
  } = config;
@@ -2311,10 +2257,10 @@ var core = {
2311
2257
  */
2312
2258
 
2313
2259
  const ids = new Ids([32, 36, 1]);
2260
+
2314
2261
  /**
2315
2262
  * The form.
2316
2263
  */
2317
-
2318
2264
  class Form {
2319
2265
  /**
2320
2266
  * @constructor
@@ -2326,28 +2272,28 @@ class Form {
2326
2272
  * @type {OnEventType}
2327
2273
  */
2328
2274
  this.on = this._onEvent;
2275
+
2329
2276
  /**
2330
2277
  * @public
2331
2278
  * @type {String}
2332
2279
  */
2333
-
2334
2280
  this._id = ids.next();
2281
+
2335
2282
  /**
2336
2283
  * @private
2337
2284
  * @type {Element}
2338
2285
  */
2339
-
2340
2286
  this._container = createFormContainer();
2341
2287
  const {
2342
2288
  container,
2343
2289
  injector = this._createInjector(options, this._container),
2344
2290
  properties = {}
2345
2291
  } = options;
2292
+
2346
2293
  /**
2347
2294
  * @private
2348
2295
  * @type {State}
2349
2296
  */
2350
-
2351
2297
  this._state = {
2352
2298
  initialData: null,
2353
2299
  data: null,
@@ -2358,33 +2304,31 @@ class Form {
2358
2304
  this.get = injector.get;
2359
2305
  this.invoke = injector.invoke;
2360
2306
  this.get('eventBus').fire('form.init');
2361
-
2362
2307
  if (container) {
2363
2308
  this.attachTo(container);
2364
2309
  }
2365
2310
  }
2366
-
2367
2311
  clear() {
2368
2312
  // clear form services
2369
- this._emit('diagram.clear'); // clear diagram services (e.g. EventBus)
2370
-
2313
+ this._emit('diagram.clear');
2371
2314
 
2315
+ // clear diagram services (e.g. EventBus)
2372
2316
  this._emit('form.clear');
2373
2317
  }
2318
+
2374
2319
  /**
2375
2320
  * Destroy the form, removing it from DOM,
2376
2321
  * if attached.
2377
2322
  */
2378
-
2379
-
2380
2323
  destroy() {
2381
2324
  // destroy form services
2382
- this.get('eventBus').fire('form.destroy'); // destroy diagram services (e.g. EventBus)
2325
+ this.get('eventBus').fire('form.destroy');
2383
2326
 
2327
+ // destroy diagram services (e.g. EventBus)
2384
2328
  this.get('eventBus').fire('diagram.destroy');
2385
-
2386
2329
  this._detach(false);
2387
2330
  }
2331
+
2388
2332
  /**
2389
2333
  * Open a form schema with the given initial data.
2390
2334
  *
@@ -2393,8 +2337,6 @@ class Form {
2393
2337
  *
2394
2338
  * @return Promise<{ warnings: Array<any> }>
2395
2339
  */
2396
-
2397
-
2398
2340
  importSchema(schema, data = {}) {
2399
2341
  return new Promise((resolve, reject) => {
2400
2342
  try {
@@ -2404,18 +2346,15 @@ class Form {
2404
2346
  data: importedData,
2405
2347
  warnings
2406
2348
  } = this.get('importer').importSchema(schema, data);
2407
-
2408
2349
  this._setState({
2409
2350
  data: importedData,
2410
2351
  errors: {},
2411
2352
  schema: importedSchema,
2412
2353
  initialData: clone(importedData)
2413
2354
  });
2414
-
2415
2355
  this._emit('import.done', {
2416
2356
  warnings
2417
2357
  });
2418
-
2419
2358
  return resolve({
2420
2359
  warnings
2421
2360
  });
@@ -2424,154 +2363,124 @@ class Form {
2424
2363
  error,
2425
2364
  warnings: error.warnings || []
2426
2365
  });
2427
-
2428
2366
  return reject(error);
2429
2367
  }
2430
2368
  });
2431
2369
  }
2370
+
2432
2371
  /**
2433
2372
  * Submit the form, triggering all field validations.
2434
2373
  *
2435
2374
  * @returns { { data: Data, errors: Errors } }
2436
2375
  */
2437
-
2438
-
2439
2376
  submit() {
2440
2377
  const {
2441
2378
  properties
2442
2379
  } = this._getState();
2443
-
2444
2380
  if (properties.readOnly) {
2445
2381
  throw new Error('form is read-only');
2446
2382
  }
2447
-
2448
2383
  const data = this._getSubmitData();
2449
-
2450
2384
  const errors = this.validate();
2451
-
2452
2385
  this._emit('submit', {
2453
2386
  data,
2454
2387
  errors
2455
2388
  });
2456
-
2457
2389
  return {
2458
2390
  data,
2459
2391
  errors
2460
2392
  };
2461
2393
  }
2462
-
2463
2394
  reset() {
2464
2395
  this._emit('reset');
2465
-
2466
2396
  this._setState({
2467
2397
  data: clone(this._state.initialData),
2468
2398
  errors: {}
2469
2399
  });
2470
2400
  }
2401
+
2471
2402
  /**
2472
2403
  * @returns {Errors}
2473
2404
  */
2474
-
2475
-
2476
2405
  validate() {
2477
2406
  const formFieldRegistry = this.get('formFieldRegistry'),
2478
- validator = this.get('validator');
2479
-
2407
+ validator = this.get('validator');
2480
2408
  const {
2481
2409
  data
2482
2410
  } = this._getState();
2483
-
2484
2411
  const errors = formFieldRegistry.getAll().reduce((errors, field) => {
2485
2412
  const {
2486
2413
  disabled,
2487
2414
  _path
2488
2415
  } = field;
2489
-
2490
2416
  if (disabled) {
2491
2417
  return errors;
2492
2418
  }
2493
-
2494
2419
  const value = get(data, _path);
2495
2420
  const fieldErrors = validator.validateField(field, value);
2496
2421
  return set(errors, [pathStringify(_path)], fieldErrors.length ? fieldErrors : undefined);
2497
- },
2498
- /** @type {Errors} */
2499
- {});
2500
-
2422
+ }, /** @type {Errors} */{});
2501
2423
  this._setState({
2502
2424
  errors
2503
2425
  });
2504
-
2505
2426
  return errors;
2506
2427
  }
2428
+
2507
2429
  /**
2508
2430
  * @param {Element|string} parentNode
2509
2431
  */
2510
-
2511
-
2512
2432
  attachTo(parentNode) {
2513
2433
  if (!parentNode) {
2514
2434
  throw new Error('parentNode required');
2515
2435
  }
2516
-
2517
2436
  this.detach();
2518
-
2519
2437
  if (isString(parentNode)) {
2520
2438
  parentNode = document.querySelector(parentNode);
2521
2439
  }
2522
-
2523
2440
  const container = this._container;
2524
2441
  parentNode.appendChild(container);
2525
-
2526
2442
  this._emit('attach');
2527
2443
  }
2528
-
2529
2444
  detach() {
2530
2445
  this._detach();
2531
2446
  }
2447
+
2532
2448
  /**
2533
2449
  * @private
2534
2450
  *
2535
2451
  * @param {boolean} [emit]
2536
2452
  */
2537
-
2538
-
2539
2453
  _detach(emit = true) {
2540
2454
  const container = this._container,
2541
- parentNode = container.parentNode;
2542
-
2455
+ parentNode = container.parentNode;
2543
2456
  if (!parentNode) {
2544
2457
  return;
2545
2458
  }
2546
-
2547
2459
  if (emit) {
2548
2460
  this._emit('detach');
2549
2461
  }
2550
-
2551
2462
  parentNode.removeChild(container);
2552
2463
  }
2464
+
2553
2465
  /**
2554
2466
  * @param {FormProperty} property
2555
2467
  * @param {any} value
2556
2468
  */
2557
-
2558
-
2559
2469
  setProperty(property, value) {
2560
2470
  const properties = set(this._getState().properties, [property], value);
2561
-
2562
2471
  this._setState({
2563
2472
  properties
2564
2473
  });
2565
2474
  }
2475
+
2566
2476
  /**
2567
2477
  * @param {FormEvent} type
2568
2478
  * @param {Function} handler
2569
2479
  */
2570
-
2571
-
2572
2480
  off(type, handler) {
2573
2481
  this.get('eventBus').off(type, handler);
2574
2482
  }
2483
+
2575
2484
  /**
2576
2485
  * @private
2577
2486
  *
@@ -2580,8 +2489,6 @@ class Form {
2580
2489
  *
2581
2490
  * @returns {Injector}
2582
2491
  */
2583
-
2584
-
2585
2492
  _createInjector(options, container) {
2586
2493
  const {
2587
2494
  additionalModules = [],
@@ -2598,21 +2505,19 @@ class Form {
2598
2505
  form: ['value', this]
2599
2506
  }, core, ...modules, ...additionalModules]);
2600
2507
  }
2508
+
2601
2509
  /**
2602
2510
  * @private
2603
2511
  */
2604
-
2605
-
2606
2512
  _emit(type, data) {
2607
2513
  this.get('eventBus').fire(type, data);
2608
2514
  }
2515
+
2609
2516
  /**
2610
2517
  * @internal
2611
2518
  *
2612
2519
  * @param { { add?: boolean, field: any, remove?: number, value?: any } } update
2613
2520
  */
2614
-
2615
-
2616
2521
  _update(update) {
2617
2522
  const {
2618
2523
  field,
@@ -2621,77 +2526,71 @@ class Form {
2621
2526
  const {
2622
2527
  _path
2623
2528
  } = field;
2624
-
2625
2529
  let {
2626
2530
  data,
2627
2531
  errors
2628
2532
  } = this._getState();
2629
-
2630
2533
  const validator = this.get('validator');
2631
2534
  const fieldErrors = validator.validateField(field, value);
2632
2535
  set(data, _path, value);
2633
2536
  set(errors, [pathStringify(_path)], fieldErrors.length ? fieldErrors : undefined);
2634
-
2635
2537
  this._setState({
2636
2538
  data: clone(data),
2637
2539
  errors: clone(errors)
2638
2540
  });
2639
2541
  }
2542
+
2640
2543
  /**
2641
2544
  * @internal
2642
2545
  */
2643
-
2644
-
2645
2546
  _getState() {
2646
2547
  return this._state;
2647
2548
  }
2549
+
2648
2550
  /**
2649
2551
  * @internal
2650
2552
  */
2651
-
2652
-
2653
2553
  _setState(state) {
2654
- this._state = { ...this._state,
2554
+ this._state = {
2555
+ ...this._state,
2655
2556
  ...state
2656
2557
  };
2657
-
2658
2558
  this._emit('changed', this._getState());
2659
2559
  }
2560
+
2660
2561
  /**
2661
2562
  * @internal
2662
2563
  */
2663
-
2664
-
2665
2564
  _onEvent(type, priority, handler) {
2666
2565
  this.get('eventBus').on(type, priority, handler);
2667
2566
  }
2567
+
2668
2568
  /**
2669
2569
  * @internal
2670
2570
  */
2671
-
2672
-
2673
2571
  _getSubmitData() {
2674
2572
  const formFieldRegistry = this.get('formFieldRegistry');
2675
2573
  return formFieldRegistry.getAll().reduce((data, field) => {
2676
2574
  const {
2677
2575
  disabled,
2678
2576
  _path
2679
- } = field; // do not submit disabled form fields
2577
+ } = field;
2680
2578
 
2579
+ // do not submit disabled form fields
2681
2580
  if (disabled || !_path) {
2682
2581
  return data;
2683
2582
  }
2684
-
2685
2583
  const value = get(this._getState().data, _path);
2686
- return { ...data,
2584
+ return {
2585
+ ...data,
2687
2586
  [_path[0]]: value
2688
2587
  };
2689
2588
  }, {});
2690
2589
  }
2691
-
2692
2590
  }
2693
2591
 
2694
- const schemaVersion = 5;
2592
+ const schemaVersion = 6;
2593
+
2695
2594
  /**
2696
2595
  * @typedef { import('./types').CreateFormOptions } CreateFormOptions
2697
2596
  */
@@ -2703,7 +2602,6 @@ const schemaVersion = 5;
2703
2602
  *
2704
2603
  * @return {Promise<Form>}
2705
2604
  */
2706
-
2707
2605
  function createForm(options) {
2708
2606
  const {
2709
2607
  data,
@@ -2716,5 +2614,5 @@ function createForm(options) {
2716
2614
  });
2717
2615
  }
2718
2616
 
2719
- export { Button, Checkbox, Checklist, Default, Form, FormComponent, FormContext, FormFieldRegistry, FormFields, FormRenderContext, Number, Radio, Select, Taglist, Text, Textfield, clone, createForm, createFormContainer, createInjector, findErrors, formFields, generateIdForType, generateIndexForType, getSchemaVariables, isRequired, pathParse, pathStringify, pathsEqual, schemaVersion };
2617
+ export { Button, Checkbox, Checklist, Default, Form, FormComponent, FormContext, FormFieldRegistry, FormFields, FormRenderContext, Number, Radio, Select, Taglist, Text, Textarea, Textfield, clone, createForm, createFormContainer, createInjector, findErrors, formFields, generateIdForType, generateIndexForType, getSchemaVariables, isRequired, pathParse, pathStringify, pathsEqual, schemaVersion };
2720
2618
  //# sourceMappingURL=index.es.js.map