@bpmn-io/form-js-playground 0.13.1 → 0.14.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.
@@ -193,6 +193,11 @@
193
193
  overflow: hidden;
194
194
  }
195
195
 
196
+ .fjs-pgl-form-container .fjs-container .cds--grid .cds--row {
197
+ margin-left: -1rem;
198
+ margin-right: -1rem;
199
+ }
200
+
196
201
  .fjs-pgl-form-container .fjs-form-editor {
197
202
  width: 100%;
198
203
  }
@@ -49251,15 +49251,163 @@
49251
49251
  return value;
49252
49252
  }
49253
49253
 
49254
+ const getFlavouredFeelVariableNames = (feelString, feelFlavour, options = {}) => {
49255
+ const {
49256
+ depth = 0,
49257
+ specialDepthAccessors = {}
49258
+ } = options;
49259
+ if (!['expression', 'unaryTest'].includes(feelFlavour)) return [];
49260
+ const tree = feelFlavour === 'expression' ? parseExpressions$1(feelString) : parseUnaryTests$1(feelString);
49261
+ const simpleExpressionTree = _buildSimpleFeelStructureTree(tree, feelString);
49262
+ return function _unfoldVariables(node) {
49263
+ if (node.name === 'PathExpression') {
49264
+ if (Object.keys(specialDepthAccessors).length === 0) {
49265
+ return depth === 0 ? [_getVariableNameAtPathIndex(node, 0)] : [];
49266
+ }
49267
+
49268
+ // if using special depth accessors, use a more complex extraction
49269
+ return Array.from(_smartExtractVariableNames(node, depth, specialDepthAccessors));
49270
+ }
49271
+ if (depth === 0 && node.name === 'VariableName') return [node.variableName];
49272
+
49273
+ // for any other kind of node, traverse its children and flatten the result
49274
+ if (node.children) {
49275
+ return node.children.reduce((acc, child) => {
49276
+ return acc.concat(_unfoldVariables(child));
49277
+ }, []);
49278
+ }
49279
+ return [];
49280
+ }(simpleExpressionTree);
49281
+ };
49282
+
49283
+ /**
49284
+ * Get the variable name at the specified index in a given path expression.
49285
+ *
49286
+ * @param {Object} root - The root node of the path expression tree.
49287
+ * @param {number} index - The index of the variable name to retrieve.
49288
+ * @returns {string|null} The variable name at the specified index or null if index is out of bounds.
49289
+ */
49290
+ const _getVariableNameAtPathIndex = (root, index) => {
49291
+ const accessors = _deconstructPathExpression(root);
49292
+ return accessors[index] || null;
49293
+ };
49294
+
49295
+ /**
49296
+ * Extracts the variables which are required of the external context for a given path expression.
49297
+ * This is done by traversing the path expression tree and keeping track of the current depth relative to the external context.
49298
+ *
49299
+ * @param {Object} node - The root node of the path expression tree.
49300
+ * @param {number} initialDepth - The depth at which the root node is located in the outer context.
49301
+ * @param {Object} specialDepthAccessors - Definitions of special keywords which represent more complex accesses of the outer context.
49302
+ * @returns {Set} - A set containing the extracted variable names.
49303
+ */
49304
+ const _smartExtractVariableNames = (node, initialDepth, specialDepthAccessors) => {
49305
+ // depth info represents the previous (initialised as null) and current depth of the current accessor in the path expression
49306
+ // we track multiple of these to account for the fact that a path expression may be ambiguous due to special keywords
49307
+ let accessorDepthInfos = [{
49308
+ previous: null,
49309
+ current: initialDepth - 1
49310
+ }];
49311
+ const extractedVariables = new Set();
49312
+ const nodeAccessors = _deconstructPathExpression(node);
49313
+ for (let i = 0; i < nodeAccessors.length; i++) {
49314
+ const currentAccessor = nodeAccessors[i];
49315
+ if (currentAccessor in specialDepthAccessors) {
49316
+ const depthOffsets = specialDepthAccessors[currentAccessor];
49317
+
49318
+ // if the current accessor is a special keyword, we need to expand the current depth info set
49319
+ // this is done to account for the ambiguity of keywords like parent, which may be used to access
49320
+ // the parent of the current node, or a child variable of the same name
49321
+ accessorDepthInfos = depthOffsets.reduce((accumulator, offset) => {
49322
+ return [...accumulator, ...accessorDepthInfos.map(depthInfo => ({
49323
+ previous: depthInfo.current,
49324
+ current: depthInfo.current + offset
49325
+ }))];
49326
+ }, []).filter(depthInfo => depthInfo.current >= -1); // discard all depth infos which are out of bounds
49327
+ } else {
49328
+ // if the current accessor is not a special keyword, we know it's simply accessing a child
49329
+ // hence we are now one level deeper in the tree and simply increment
49330
+ accessorDepthInfos = accessorDepthInfos.map(depthInfo => ({
49331
+ previous: depthInfo.current,
49332
+ current: depthInfo.current + 1
49333
+ }));
49334
+ }
49335
+
49336
+ // finally, we check if for the current accessor, there is a scenario where:
49337
+ // previous it was at depth -1 (i.e. the root context), and is now at depth 0 (i.e. a variable)
49338
+ // these are the variables we need to request, so we add them to the set
49339
+ if (accessorDepthInfos.some(depthInfo => depthInfo.previous === -1 && depthInfo.current === 0)) {
49340
+ extractedVariables.add(currentAccessor);
49341
+ }
49342
+ }
49343
+
49344
+ // we return a set to avoid duplicates
49345
+ return new Set(extractedVariables);
49346
+ };
49347
+
49348
+ /**
49349
+ * Deconstructs a path expression tree into an array of components.
49350
+ *
49351
+ * @param {Object} root - The root node of the path expression tree.
49352
+ * @returns {Array<string>} An array of components in the path expression, in the correct order.
49353
+ */
49354
+ const _deconstructPathExpression = root => {
49355
+ let node = root;
49356
+ let parts = [];
49357
+
49358
+ // Traverse the tree and collect path components
49359
+ while (node.name === 'PathExpression') {
49360
+ parts.push(node.children[1].variableName);
49361
+ node = node.children[0];
49362
+ }
49363
+
49364
+ // Add the last component to the array
49365
+ parts.push(node.variableName);
49366
+
49367
+ // Reverse and return the array to get the correct order
49368
+ return parts.reverse();
49369
+ };
49370
+
49371
+ /**
49372
+ * Builds a simplified feel structure tree from the given parse tree and feel string.
49373
+ * The nodes follow this structure: `{ name: string, children: Array, variableName?: string }`
49374
+ *
49375
+ * @param {Object} parseTree - The parse tree generated by a parser.
49376
+ * @param {string} feelString - The feel string used for parsing.
49377
+ * @returns {Object} The simplified feel structure tree.
49378
+ */
49379
+ const _buildSimpleFeelStructureTree = (parseTree, feelString) => {
49380
+ const stack = [{
49381
+ children: []
49382
+ }];
49383
+ parseTree.iterate({
49384
+ enter: node => {
49385
+ const nodeRepresentation = {
49386
+ name: node.type.name,
49387
+ children: []
49388
+ };
49389
+ if (node.type.name === 'VariableName') {
49390
+ nodeRepresentation.variableName = feelString.slice(node.from, node.to);
49391
+ }
49392
+ stack.push(nodeRepresentation);
49393
+ },
49394
+ leave: () => {
49395
+ const result = stack.pop();
49396
+ const parent = stack[stack.length - 1];
49397
+ parent.children.push(result);
49398
+ }
49399
+ });
49400
+ return stack[0].children[0];
49401
+ };
49254
49402
  class FeelExpressionLanguage {
49255
49403
  constructor(eventBus) {
49256
49404
  this._eventBus = eventBus;
49257
49405
  }
49258
49406
 
49259
49407
  /**
49260
- * Determines if the given string is a FEEL expression.
49408
+ * Determines if the given value is a FEEL expression.
49261
49409
  *
49262
- * @param {string} value
49410
+ * @param {any} value
49263
49411
  * @returns {boolean}
49264
49412
  *
49265
49413
  */
@@ -49283,12 +49431,10 @@
49283
49431
  if (!this.isExpression(expression)) {
49284
49432
  return [];
49285
49433
  }
49286
- if (type === 'unaryTest') {
49287
- return this._getUnaryVariableNames(expression);
49288
- } else if (type === 'expression') {
49289
- return this._getExpressionVariableNames(expression);
49434
+ if (!['unaryTest', 'expression'].includes(type)) {
49435
+ throw new Error('Unknown expression type: ' + type);
49290
49436
  }
49291
- throw new Error('Unknown expression type: ' + options.type);
49437
+ return getFlavouredFeelVariableNames(expression, type);
49292
49438
  }
49293
49439
 
49294
49440
  /**
@@ -49316,36 +49462,51 @@
49316
49462
  return null;
49317
49463
  }
49318
49464
  }
49319
- _getExpressionVariableNames(expression) {
49320
- const tree = parseExpressions$1(expression);
49321
- const cursor = tree.cursor();
49322
- const variables = new Set();
49323
- do {
49324
- const node = cursor.node;
49325
- if (node.type.name === 'VariableName') {
49326
- variables.add(expression.slice(node.from, node.to));
49327
- }
49328
- } while (cursor.next());
49329
- return Array.from(variables);
49330
- }
49331
- _getUnaryVariableNames(unaryTest) {
49332
- const tree = parseUnaryTests$1(unaryTest);
49333
- const cursor = tree.cursor();
49334
- const variables = new Set();
49335
- do {
49336
- const node = cursor.node;
49337
- if (node.type.name === 'VariableName') {
49338
- variables.add(unaryTest.slice(node.from, node.to));
49339
- }
49340
- } while (cursor.next());
49341
- return Array.from(variables);
49342
- }
49343
49465
  }
49344
49466
  FeelExpressionLanguage.$inject = ['eventBus'];
49345
49467
  class FeelersTemplating {
49346
49468
  constructor() {}
49469
+
49470
+ /**
49471
+ * Determines if the given value is a feelers template.
49472
+ *
49473
+ * @param {any} value
49474
+ * @returns {boolean}
49475
+ *
49476
+ */
49347
49477
  isTemplate(value) {
49348
- return isString$4(value) && (value.startsWith('=') || /{{/.test(value));
49478
+ return isString$4(value) && (value.startsWith('=') || /{{.*?}}/.test(value));
49479
+ }
49480
+
49481
+ /**
49482
+ * Retrieve variable names from a given feelers template.
49483
+ *
49484
+ * @param {string} template
49485
+ *
49486
+ * @returns {string[]}
49487
+ */
49488
+ getVariableNames(template) {
49489
+ if (!this.isTemplate(template)) {
49490
+ return [];
49491
+ }
49492
+ const expressions = this._extractExpressionsWithDepth(template);
49493
+
49494
+ // defines special accessors, and the change(s) in depth they could imply (e.g. parent can be used to access the parent context (depth - 1) or a child variable named parent (depth + 1)
49495
+ const specialDepthAccessors = {
49496
+ parent: [-1, 1],
49497
+ _parent_: [-1],
49498
+ this: [0, 1],
49499
+ _this_: [0]
49500
+ };
49501
+ return expressions.reduce((variables, {
49502
+ expression,
49503
+ depth
49504
+ }) => {
49505
+ return variables.concat(getFlavouredFeelVariableNames(expression, 'expression', {
49506
+ depth,
49507
+ specialDepthAccessors
49508
+ }));
49509
+ }, []);
49349
49510
  }
49350
49511
 
49351
49512
  /**
@@ -49372,6 +49533,50 @@
49372
49533
  buildDebugString
49373
49534
  });
49374
49535
  }
49536
+
49537
+ /**
49538
+ * @typedef {Object} ExpressionWithDepth
49539
+ * @property {number} depth - The depth of the expression in the syntax tree.
49540
+ * @property {string} expression - The extracted expression
49541
+ */
49542
+
49543
+ /**
49544
+ * Extracts all feel expressions in the template along with their depth in the syntax tree.
49545
+ * The depth is incremented for child expressions of loops to account for context drilling.
49546
+ * @name extractExpressionsWithDepth
49547
+ * @param {string} template - A feelers template string.
49548
+ * @returns {Array<ExpressionWithDepth>} An array of objects, each containing the depth and the extracted expression.
49549
+ *
49550
+ * @example
49551
+ * const template = "Hello {{user}}, you have:{{#loop items}}\n- {{amount}} {{name}}{{/loop}}.";
49552
+ * const extractedExpressions = _extractExpressionsWithDepth(template);
49553
+ */
49554
+ _extractExpressionsWithDepth(template) {
49555
+ // build simplified feelers syntax tree
49556
+ const parseTree = parser$1.parse(template);
49557
+ const tree = buildSimpleTree(parseTree, template);
49558
+ return function _traverse(n, depth = 0) {
49559
+ if (['Feel', 'FeelBlock'].includes(n.name)) {
49560
+ return [{
49561
+ depth,
49562
+ expression: n.content
49563
+ }];
49564
+ }
49565
+ if (n.name === 'LoopSpanner') {
49566
+ const loopExpression = n.children[0].content;
49567
+ const childResults = n.children.slice(1).reduce((acc, child) => {
49568
+ return acc.concat(_traverse(child, depth + 1));
49569
+ }, []);
49570
+ return [{
49571
+ depth,
49572
+ expression: loopExpression
49573
+ }, ...childResults];
49574
+ }
49575
+ return n.children.reduce((acc, child) => {
49576
+ return acc.concat(_traverse(child, depth));
49577
+ }, []);
49578
+ }(tree);
49579
+ }
49375
49580
  }
49376
49581
  FeelersTemplating.$inject = [];
49377
49582
 
@@ -49992,8 +50197,13 @@
49992
50197
  if (validate.pattern && value && !new RegExp(validate.pattern).test(value)) {
49993
50198
  errors = [...errors, `Field must match pattern ${validate.pattern}.`];
49994
50199
  }
49995
- if (validate.required && (isNil$1(value) || value === '')) {
49996
- errors = [...errors, 'Field is required.'];
50200
+ if (validate.required) {
50201
+ const isUncheckedCheckbox = type === 'checkbox' && value === false;
50202
+ const isUnsetValue = isNil$1(value) || value === '';
50203
+ const isEmptyMultiselect = Array.isArray(value) && value.length === 0;
50204
+ if (isUncheckedCheckbox || isUnsetValue || isEmptyMultiselect) {
50205
+ errors = [...errors, 'Field is required.'];
50206
+ }
49997
50207
  }
49998
50208
  if ('min' in validate && (value || value === 0) && value < validate.min) {
49999
50209
  errors = [...errors, `Field must have minimum value of ${validate.min}.`];
@@ -50292,6 +50502,7 @@
50292
50502
  return container;
50293
50503
  }
50294
50504
  const EXPRESSION_PROPERTIES = ['alt', 'source', 'text'];
50505
+ const TEMPLATE_PROPERTIES = ['text'];
50295
50506
  function findErrors(errors, path) {
50296
50507
  return errors[pathStringify(path)];
50297
50508
  }
@@ -50331,7 +50542,7 @@
50331
50542
  *
50332
50543
  * @return {string[]}
50333
50544
  */
50334
- function getSchemaVariables(schema, expressionLanguage = new FeelExpressionLanguage(null)) {
50545
+ function getSchemaVariables(schema, expressionLanguage = new FeelExpressionLanguage(null), templating = new FeelersTemplating()) {
50335
50546
  if (!schema.components) {
50336
50547
  return [];
50337
50548
  }
@@ -50366,6 +50577,13 @@
50366
50577
  variables = [...variables, ...expressionVariables];
50367
50578
  }
50368
50579
  });
50580
+ TEMPLATE_PROPERTIES.forEach(prop => {
50581
+ const property = component[prop];
50582
+ if (property && !expressionLanguage.isExpression(property) && templating.isTemplate(property)) {
50583
+ const templateVariables = templating.getVariableNames(property);
50584
+ variables = [...variables, ...templateVariables];
50585
+ }
50586
+ });
50369
50587
  return variables;
50370
50588
  }, []);
50371
50589
 
@@ -50671,8 +50889,12 @@
50671
50889
  const {
50672
50890
  description,
50673
50891
  id,
50674
- label
50892
+ label,
50893
+ validate = {}
50675
50894
  } = field;
50895
+ const {
50896
+ required
50897
+ } = validate;
50676
50898
  const onChange = ({
50677
50899
  target
50678
50900
  }) => {
@@ -50694,7 +50916,7 @@
50694
50916
  children: [e$1(Label$1, {
50695
50917
  id: prefixId(id, formId),
50696
50918
  label: label,
50697
- required: false,
50919
+ required: required,
50698
50920
  children: e$1("input", {
50699
50921
  checked: value,
50700
50922
  class: "fjs-input",
@@ -51057,8 +51279,12 @@
51057
51279
  const {
51058
51280
  description,
51059
51281
  id,
51060
- label
51282
+ label,
51283
+ validate = {}
51061
51284
  } = field;
51285
+ const {
51286
+ required
51287
+ } = validate;
51062
51288
  const toggleCheckbox = v => {
51063
51289
  let newValue = [...value];
51064
51290
  if (!newValue.includes(v)) {
@@ -51084,7 +51310,8 @@
51084
51310
  disabled
51085
51311
  })),
51086
51312
  children: [e$1(Label$1, {
51087
- label: label
51313
+ label: label,
51314
+ required: required
51088
51315
  }), loadState == LOAD_STATES.LOADED && options.map((v, index) => {
51089
51316
  return e$1(Label$1, {
51090
51317
  id: prefixId(`${id}-${index}`, formId),
@@ -52964,6 +53191,12 @@
52964
53191
  errors,
52965
53192
  disabled
52966
53193
  }),
53194
+ onKeyDown: event => {
53195
+ if (event.key === 'Enter') {
53196
+ event.preventDefault();
53197
+ event.stopPropagation();
53198
+ }
53199
+ },
52967
53200
  children: [e$1(Label$1, {
52968
53201
  id: prefixId(id, formId),
52969
53202
  label: label,
@@ -53011,8 +53244,12 @@
53011
53244
  const {
53012
53245
  description,
53013
53246
  id,
53014
- label
53247
+ label,
53248
+ validate = {}
53015
53249
  } = field;
53250
+ const {
53251
+ required
53252
+ } = validate;
53016
53253
  const {
53017
53254
  formId
53018
53255
  } = F$1(FormContext$1);
@@ -53109,8 +53346,15 @@
53109
53346
  errors,
53110
53347
  disabled
53111
53348
  }),
53349
+ onKeyDown: event => {
53350
+ if (event.key === 'Enter') {
53351
+ event.stopPropagation();
53352
+ event.preventDefault();
53353
+ }
53354
+ },
53112
53355
  children: [e$1(Label$1, {
53113
53356
  label: label,
53357
+ required: required,
53114
53358
  id: prefixId(`${id}-search`, formId)
53115
53359
  }), e$1("div", {
53116
53360
  class: classNames('fjs-taglist', {
@@ -65103,7 +65347,7 @@
65103
65347
  id,
65104
65348
  label: 'Label',
65105
65349
  setValue,
65106
- validate: validateFactory(getValue())
65350
+ validate: validateFactory(getValue(), entry => entry.label)
65107
65351
  });
65108
65352
  }
65109
65353
  function Value$1(props) {
@@ -65129,7 +65373,7 @@
65129
65373
  id,
65130
65374
  label: 'Value',
65131
65375
  setValue,
65132
- validate: validateFactory(getValue())
65376
+ validate: validateFactory(getValue(), entry => entry.value)
65133
65377
  });
65134
65378
  }
65135
65379
  function CustomValueEntry(props) {
@@ -65367,13 +65611,13 @@
65367
65611
  const addEntry = e => {
65368
65612
  e.stopPropagation();
65369
65613
  const index = values.length + 1;
65370
- const entry = getIndexedEntry(index);
65614
+ const entry = getIndexedEntry(index, values);
65371
65615
  editField(field, VALUES_SOURCES_PATHS[VALUES_SOURCES.STATIC], arrayAdd(values, values.length, entry));
65372
65616
  };
65373
65617
  const removeEntry = entry => {
65374
65618
  editField(field, VALUES_SOURCES_PATHS[VALUES_SOURCES.STATIC], without(values, entry));
65375
65619
  };
65376
- const validateFactory = key => {
65620
+ const validateFactory = (key, getValue) => {
65377
65621
  return value => {
65378
65622
  if (value === key) {
65379
65623
  return;
@@ -65381,7 +65625,7 @@
65381
65625
  if (isUndefined(value) || !value.length) {
65382
65626
  return 'Must not be empty.';
65383
65627
  }
65384
- const isValueAssigned = values.find(entry => entry.value === value);
65628
+ const isValueAssigned = values.find(entry => getValue(entry) === value);
65385
65629
  if (isValueAssigned) {
65386
65630
  return 'Must be unique.';
65387
65631
  }
@@ -65412,17 +65656,23 @@
65412
65656
 
65413
65657
  // helper
65414
65658
 
65415
- function getIndexedEntry(index) {
65659
+ function getIndexedEntry(index, values) {
65416
65660
  const entry = {
65417
65661
  label: 'Value',
65418
65662
  value: 'value'
65419
65663
  };
65664
+ while (labelOrValueIsAlreadyAssignedForIndex(index, values)) {
65665
+ index++;
65666
+ }
65420
65667
  if (index > 1) {
65421
65668
  entry.label += ` ${index}`;
65422
65669
  entry.value += `${index}`;
65423
65670
  }
65424
65671
  return entry;
65425
65672
  }
65673
+ function labelOrValueIsAlreadyAssignedForIndex(index, values) {
65674
+ return values.some(existingEntry => existingEntry.label === `Value ${index}` || existingEntry.value === `value${index}`);
65675
+ }
65426
65676
  function AdornerEntry(props) {
65427
65677
  const {
65428
65678
  editField,
@@ -65645,7 +65895,7 @@
65645
65895
  } = field;
65646
65896
  const validate = get(field, ['validate'], {});
65647
65897
  const isCustomValidation = [undefined, VALIDATION_TYPE_OPTIONS.custom.value].includes(validate.validationType);
65648
- if (!(INPUTS.includes(type) && type !== 'checkbox' && type !== 'checklist' && type !== 'taglist')) {
65898
+ if (!INPUTS.includes(type)) {
65649
65899
  return null;
65650
65900
  }
65651
65901
  const onChange = key => {
@@ -65915,12 +66165,13 @@
65915
66165
  }
65916
66166
  const addEntry = event => {
65917
66167
  event.stopPropagation();
65918
- const index = Object.keys(properties).length + 1;
65919
- const key = `key${index}`,
65920
- value = 'value';
66168
+ let index = Object.keys(properties).length + 1;
66169
+ while (`key${index}` in properties) {
66170
+ index++;
66171
+ }
65921
66172
  editField(field, ['properties'], {
65922
66173
  ...properties,
65923
- [key]: value
66174
+ [`key${index}`]: 'value'
65924
66175
  });
65925
66176
  };
65926
66177
  const validateFactory = key => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bpmn-io/form-js-playground",
3
- "version": "0.13.1",
3
+ "version": "0.14.1",
4
4
  "description": "A form-js playground",
5
5
  "files": [
6
6
  "dist"
@@ -44,8 +44,8 @@
44
44
  "url": "https://github.com/bpmn-io"
45
45
  },
46
46
  "dependencies": {
47
- "@bpmn-io/form-js-editor": "^0.13.1",
48
- "@bpmn-io/form-js-viewer": "^0.13.1",
47
+ "@bpmn-io/form-js-editor": "^0.14.1",
48
+ "@bpmn-io/form-js-viewer": "^0.14.1",
49
49
  "@codemirror/autocomplete": "^6.3.4",
50
50
  "@codemirror/commands": "^6.1.2",
51
51
  "@codemirror/lang-json": "^6.0.0",
@@ -70,5 +70,5 @@
70
70
  "rollup-plugin-css-only": "^4.0.0",
71
71
  "style-loader": "^3.3.0"
72
72
  },
73
- "gitHead": "0b139786808d65c52aa030be7b9ae26abda34fde"
73
+ "gitHead": "a9ad771bc8f87d2fc428bd5bd1429b96bb03fe7a"
74
74
  }