@bpmn-io/form-js-editor 1.21.3 → 1.23.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -11,9 +11,15 @@ var React = require('preact/compat');
11
11
  var dragula = require('@bpmn-io/draggle');
12
12
  var minDom = require('min-dom');
13
13
  var arrayMove = require('array-move');
14
- var feelers = require('feelers');
15
- var Editor = require('@bpmn-io/feel-editor');
16
14
  var view = require('@codemirror/view');
15
+ var state = require('@codemirror/state');
16
+ require('@codemirror/language');
17
+ require('@codemirror/autocomplete');
18
+ require('@codemirror/commands');
19
+ var langJson = require('@codemirror/lang-json');
20
+ require('@codemirror/lint');
21
+ var feelersEditor = require('@bpmn-io/feelers-editor');
22
+ var Editor = require('@bpmn-io/feel-editor');
17
23
  var focusTrap = require('focus-trap');
18
24
  var Big = require('big.js');
19
25
 
@@ -38,7 +44,7 @@ var React__namespace = /*#__PURE__*/_interopNamespaceDefault(React);
38
44
  var focusTrap__namespace = /*#__PURE__*/_interopNamespaceDefault(focusTrap);
39
45
 
40
46
  var FN_REF = '__fn';
41
- var DEFAULT_PRIORITY$3 = 1000;
47
+ var DEFAULT_PRIORITY$4 = 1000;
42
48
  var slice = Array.prototype.slice;
43
49
 
44
50
  /**
@@ -206,7 +212,7 @@ EventBus.prototype.on = function (events, priority, callback, that) {
206
212
  if (minDash.isFunction(priority)) {
207
213
  that = callback;
208
214
  callback = priority;
209
- priority = DEFAULT_PRIORITY$3;
215
+ priority = DEFAULT_PRIORITY$4;
210
216
  }
211
217
  if (!minDash.isNumber(priority)) {
212
218
  throw new Error('priority must be a number');
@@ -257,7 +263,7 @@ EventBus.prototype.once = function (events, priority, callback, that) {
257
263
  if (minDash.isFunction(priority)) {
258
264
  that = callback;
259
265
  callback = priority;
260
- priority = DEFAULT_PRIORITY$3;
266
+ priority = DEFAULT_PRIORITY$4;
261
267
  }
262
268
  if (!minDash.isNumber(priority)) {
263
269
  throw new Error('priority must be a number');
@@ -2291,6 +2297,27 @@ function ContextPad(props) {
2291
2297
  children: props.children
2292
2298
  });
2293
2299
  }
2300
+ function ContextPadContent(props) {
2301
+ const {
2302
+ field
2303
+ } = props;
2304
+ const formFieldContextActions = useService$1('formFieldContextActions');
2305
+ const selection = useService$1('selection');
2306
+ if (!selection.isSelected(field) || field.type === 'default') {
2307
+ return null;
2308
+ }
2309
+ const entries = formFieldContextActions.getEntries(field);
2310
+ const actionEntries = Object.entries(entries).filter(([, entry]) => entry.group !== 'morph');
2311
+ return jsxRuntime.jsx(ContextPad, {
2312
+ children: actionEntries.map(([entryId, entry]) => jsxRuntime.jsx("button", {
2313
+ type: "button",
2314
+ title: entry.title,
2315
+ class: "fjs-context-pad-item",
2316
+ onClick: entry.action,
2317
+ children: jsxRuntime.jsx(SvgDelete, {})
2318
+ }, entryId))
2319
+ });
2320
+ }
2294
2321
  function EmptyGroup() {
2295
2322
  return jsxRuntime.jsx("div", {
2296
2323
  class: "fjs-empty-component",
@@ -2328,9 +2355,6 @@ function Empty(props) {
2328
2355
  }
2329
2356
  function Element$1(props) {
2330
2357
  const eventBus = useService$1('eventBus'),
2331
- formFieldRegistry = useService$1('formFieldRegistry'),
2332
- formFields = useService$1('formFields'),
2333
- modeling = useService$1('modeling'),
2334
2358
  selection = useService$1('selection');
2335
2359
  const {
2336
2360
  hoverInfo
@@ -2403,12 +2427,6 @@ function Element$1(props) {
2403
2427
  }
2404
2428
  return classes.join(' ');
2405
2429
  }, [hovered, isSelected, props.class, showOutline, type]);
2406
- const onRemove = event => {
2407
- event.stopPropagation();
2408
- const parentField = formFieldRegistry.get(field._parent);
2409
- const index = getFormFieldIndex(parentField, field);
2410
- modeling.removeFormField(field, parentField, index);
2411
- };
2412
2430
  const onKeyPress = event => {
2413
2431
  if (event.key === 'Enter') {
2414
2432
  event.stopPropagation();
@@ -2433,14 +2451,8 @@ function Element$1(props) {
2433
2451
  ref: ref,
2434
2452
  children: [jsxRuntime.jsx(DebugColumns, {
2435
2453
  field: field
2436
- }), jsxRuntime.jsx(ContextPad, {
2437
- children: selection.isSelected(field) && field.type !== 'default' ? jsxRuntime.jsx("button", {
2438
- type: "button",
2439
- title: getRemoveButtonTitle(field, formFields),
2440
- class: "fjs-context-pad-item",
2441
- onClick: onRemove,
2442
- children: jsxRuntime.jsx(SvgDelete, {})
2443
- }) : null
2454
+ }), jsxRuntime.jsx(ContextPadContent, {
2455
+ field: field
2444
2456
  }), props.children, jsxRuntime.jsx(FieldResizer, {
2445
2457
  position: "left",
2446
2458
  field: field
@@ -2469,9 +2481,16 @@ function Children(props) {
2469
2481
  field
2470
2482
  } = props;
2471
2483
  const {
2472
- id
2484
+ id,
2485
+ type,
2486
+ _parent
2473
2487
  } = field;
2474
2488
  const classes = ['fjs-children', DROP_CONTAINER_VERTICAL_CLS];
2489
+
2490
+ // mark the top-level (root form) drop container
2491
+ if (type === 'default' && !_parent) {
2492
+ classes.push('fjs-editor-form-root');
2493
+ }
2475
2494
  if (props.class) {
2476
2495
  classes.push(...props.class.split(' '));
2477
2496
  }
@@ -2688,17 +2707,6 @@ function FormEditor$1() {
2688
2707
  })
2689
2708
  });
2690
2709
  }
2691
- function getFormFieldIndex(parent, formField) {
2692
- let fieldFormIndex = parent.components.length;
2693
- parent.components.forEach(({
2694
- id
2695
- }, index) => {
2696
- if (id === formField.id) {
2697
- fieldFormIndex = index;
2698
- }
2699
- });
2700
- return fieldFormIndex;
2701
- }
2702
2710
  function CreatePreview(props) {
2703
2711
  const {
2704
2712
  drake
@@ -2713,7 +2721,7 @@ function CreatePreview(props) {
2713
2721
 
2714
2722
  // (1) field preview
2715
2723
  if (fieldType) {
2716
- const paletteEntry = findPaletteEntry(fieldType, formFields);
2724
+ const paletteEntry = findPaletteEntry$1(fieldType, formFields);
2717
2725
  if (!paletteEntry) {
2718
2726
  return;
2719
2727
  }
@@ -2757,19 +2765,12 @@ function CreatePreview(props) {
2757
2765
 
2758
2766
  // helper //////
2759
2767
 
2760
- function findPaletteEntry(type, formFields) {
2768
+ function findPaletteEntry$1(type, formFields) {
2761
2769
  return collectPaletteEntries(formFields).find(entry => entry.type === type);
2762
2770
  }
2763
2771
  function defaultPropertiesPanel(propertiesPanelConfig) {
2764
2772
  return !(propertiesPanelConfig && propertiesPanelConfig.parent);
2765
2773
  }
2766
- function getRemoveButtonTitle(formField, formFields) {
2767
- const entry = findPaletteEntry(formField.type, formFields);
2768
- if (!entry) {
2769
- return 'Remove form field';
2770
- }
2771
- return `Remove ${entry.label}`;
2772
- }
2773
2774
 
2774
2775
  class Renderer {
2775
2776
  constructor(renderConfig, eventBus, formEditor, injector) {
@@ -2857,6 +2858,155 @@ const CoreModule = {
2857
2858
  fieldFactory: ['type', formJsViewer.FieldFactory]
2858
2859
  };
2859
2860
 
2861
+ const DEFAULT_PRIORITY$3 = 1000;
2862
+
2863
+ /**
2864
+ * A service that manages context pad action providers for form fields.
2865
+ * Providers can register to contribute context pad entries for specific
2866
+ * form field types.
2867
+ *
2868
+ * Inspired by the diagram-js ContextPad + provider pattern.
2869
+ *
2870
+ * @param {import('../../core/EventBus').EventBus} eventBus
2871
+ */
2872
+ class FormFieldContextActions {
2873
+ constructor(eventBus) {
2874
+ this._eventBus = eventBus;
2875
+ }
2876
+
2877
+ /**
2878
+ * Register a context pad provider with the default priority.
2879
+ *
2880
+ * @overlord
2881
+ * @param {Object} provider
2882
+ */
2883
+
2884
+ /**
2885
+ * Register a context pad provider with the given priority.
2886
+ *
2887
+ * @param {number} priority
2888
+ * @param {Object} provider
2889
+ */
2890
+ registerProvider(priority, provider) {
2891
+ if (!provider) {
2892
+ provider = priority;
2893
+ priority = DEFAULT_PRIORITY$3;
2894
+ }
2895
+ this._eventBus.on('formFieldContextActions.getProviders', priority, function (event) {
2896
+ event.providers.push(provider);
2897
+ });
2898
+ }
2899
+
2900
+ /**
2901
+ * Get context pad entries for the given form field.
2902
+ *
2903
+ * @param {Object} formField
2904
+ *
2905
+ * @return {Object} entries map
2906
+ */
2907
+ getEntries(formField) {
2908
+ const providers = this._getProviders();
2909
+ let entries = {};
2910
+ minDash.forEach(providers, function (provider) {
2911
+ if (!minDash.isFunction(provider.getContextPadEntries)) {
2912
+ return;
2913
+ }
2914
+ const entriesOrUpdater = provider.getContextPadEntries(formField);
2915
+ if (minDash.isFunction(entriesOrUpdater)) {
2916
+ entries = entriesOrUpdater(entries);
2917
+ } else {
2918
+ minDash.forEach(entriesOrUpdater, function (entry, id) {
2919
+ entries[id] = entry;
2920
+ });
2921
+ }
2922
+ });
2923
+ return entries;
2924
+ }
2925
+ _getProviders() {
2926
+ const event = this._eventBus.createEvent({
2927
+ type: 'formFieldContextActions.getProviders',
2928
+ providers: []
2929
+ });
2930
+ this._eventBus.fire(event);
2931
+ return event.providers;
2932
+ }
2933
+ }
2934
+ FormFieldContextActions.$inject = ['eventBus'];
2935
+
2936
+ /**
2937
+ * A context pad provider that contributes the delete action for form fields.
2938
+ *
2939
+ * @param {import('./FormFieldContextActions').FormFieldContextActions} formFieldContextActions
2940
+ * @param {import('../modeling/Modeling').Modeling} modeling
2941
+ * @param {import('../../core/FormFieldRegistry').FormFieldRegistry} formFieldRegistry
2942
+ * @param {import('../../render/EditorFormFields').EditorFormFields} formFields
2943
+ */
2944
+ class DeleteActionProvider {
2945
+ constructor(formFieldContextActions, modeling, formFieldRegistry, formFields) {
2946
+ this._modeling = modeling;
2947
+ this._formFieldRegistry = formFieldRegistry;
2948
+ this._formFields = formFields;
2949
+ formFieldContextActions.registerProvider(500, this);
2950
+ }
2951
+
2952
+ /**
2953
+ * @param {Object} formField
2954
+ * @return {Object} entries
2955
+ */
2956
+ getContextPadEntries(formField) {
2957
+ if (formField.type === 'default') {
2958
+ return {};
2959
+ }
2960
+ const modeling = this._modeling;
2961
+ const formFieldRegistry = this._formFieldRegistry;
2962
+ const formFields = this._formFields;
2963
+ return {
2964
+ delete: {
2965
+ action: event => {
2966
+ event.stopPropagation();
2967
+ const parentField = formFieldRegistry.get(formField._parent);
2968
+ const index = getFormFieldIndex(parentField, formField);
2969
+ modeling.removeFormField(formField, parentField, index);
2970
+ },
2971
+ title: getRemoveButtonTitle(formField, formFields),
2972
+ icon: 'delete',
2973
+ group: 'actions'
2974
+ }
2975
+ };
2976
+ }
2977
+ }
2978
+ DeleteActionProvider.$inject = ['formFieldContextActions', 'modeling', 'formFieldRegistry', 'formFields'];
2979
+
2980
+ // helpers //////////
2981
+
2982
+ function getFormFieldIndex(parent, formField) {
2983
+ let fieldFormIndex = parent.components.length;
2984
+ parent.components.forEach(({
2985
+ id
2986
+ }, index) => {
2987
+ if (id === formField.id) {
2988
+ fieldFormIndex = index;
2989
+ }
2990
+ });
2991
+ return fieldFormIndex;
2992
+ }
2993
+ function findPaletteEntry(type, formFields) {
2994
+ return collectPaletteEntries(formFields).find(entry => entry.type === type);
2995
+ }
2996
+ function getRemoveButtonTitle(formField, formFields) {
2997
+ const entry = findPaletteEntry(formField.type, formFields);
2998
+ if (!entry) {
2999
+ return 'Remove form field';
3000
+ }
3001
+ return `Remove ${entry.label}`;
3002
+ }
3003
+
3004
+ const ContextPadModule = {
3005
+ __init__: ['formFieldContextActions', 'deleteActionProvider'],
3006
+ formFieldContextActions: ['type', FormFieldContextActions],
3007
+ deleteActionProvider: ['type', DeleteActionProvider]
3008
+ };
3009
+
2860
3010
  /**
2861
3011
  * @typedef {import('didi').Injector} Injector
2862
3012
  *
@@ -5584,6 +5734,28 @@ OpenPopupIcon.defaultProps = {
5584
5734
  viewBox: "0 0 16 16"
5585
5735
  };
5586
5736
 
5737
+ /**
5738
+ * @typedef { {
5739
+ * [key: string]: string;
5740
+ * } } TranslateReplacements
5741
+ */
5742
+
5743
+ /**
5744
+ * A simple translation stub to be used for multi-language support.
5745
+ * Can be easily replaced with a more sophisticated solution.
5746
+ *
5747
+ * @param {string} template to interpolate
5748
+ * @param {TranslateReplacements} [replacements] a map with substitutes
5749
+ *
5750
+ * @return {string} the translated string
5751
+ */
5752
+ function translateFallback(template, replacements) {
5753
+ replacements = replacements || {};
5754
+ return template.replace(/{([^}]+)}/g, function (_, key) {
5755
+ return replacements[key] || '{' + key + '}';
5756
+ });
5757
+ }
5758
+
5587
5759
  /**
5588
5760
  * @typedef { {
5589
5761
  * getElementLabel: (element: object) => string,
@@ -5595,13 +5767,15 @@ OpenPopupIcon.defaultProps = {
5595
5767
 
5596
5768
  /**
5597
5769
  * @param {Object} props
5598
- * @param {Object} props.element,
5770
+ * @param {Object} props.element
5599
5771
  * @param {HeaderProvider} props.headerProvider
5772
+ * @param {Function} [props.translate]
5600
5773
  */
5601
5774
  function Header(props) {
5602
5775
  const {
5603
5776
  element,
5604
- headerProvider
5777
+ headerProvider,
5778
+ translate = translateFallback
5605
5779
  } = props;
5606
5780
  const {
5607
5781
  getElementIcon,
@@ -5637,7 +5811,7 @@ function Header(props) {
5637
5811
  rel: "noreferrer",
5638
5812
  class: "bio-properties-panel-header-link",
5639
5813
  href: documentationRef,
5640
- title: "Open documentation",
5814
+ title: translate('Open documentation'),
5641
5815
  target: "_blank",
5642
5816
  children: jsxRuntime.jsx(ExternalLinkIcon, {})
5643
5817
  }) : null
@@ -5977,10 +6151,10 @@ function useDescriptionContext(id, element) {
5977
6151
  function useDebounce(callback, debounceFn) {
5978
6152
  const debouncedCallback = hooks.useCallback(debounceFn(callback), [callback, debounceFn]);
5979
6153
 
5980
- // make sure previous call is not stalled
6154
+ // flush pending calls before unmount the debounced function
5981
6155
  hooks.useEffect(() => {
5982
6156
  return () => {
5983
- debouncedCallback.cancel?.();
6157
+ debouncedCallback.flush?.();
5984
6158
  };
5985
6159
  }, [debouncedCallback]);
5986
6160
  return debouncedCallback;
@@ -6228,7 +6402,8 @@ function Group(props) {
6228
6402
  entries = [],
6229
6403
  id,
6230
6404
  label,
6231
- shouldOpen = false
6405
+ shouldOpen = false,
6406
+ translate = translateFallback
6232
6407
  } = props;
6233
6408
  const groupRef = hooks.useRef(null);
6234
6409
  const [open, setOpen] = useLayoutState(['groups', id, 'open'], shouldOpen);
@@ -6281,17 +6456,17 @@ function Group(props) {
6281
6456
  value: props.tooltip,
6282
6457
  forId: 'group-' + id,
6283
6458
  element: element,
6284
- parent: groupRef,
6285
6459
  children: label
6286
6460
  })
6287
6461
  }), jsxRuntime.jsxs("div", {
6288
6462
  class: "bio-properties-panel-group-header-buttons",
6289
6463
  children: [jsxRuntime.jsx(DataMarker, {
6290
6464
  edited: edited,
6291
- hasErrors: hasErrors
6465
+ hasErrors: hasErrors,
6466
+ translate: translate
6292
6467
  }), jsxRuntime.jsx("button", {
6293
6468
  type: "button",
6294
- title: "Toggle section",
6469
+ title: translate('Toggle section'),
6295
6470
  class: "bio-properties-panel-group-header-button bio-properties-panel-arrow",
6296
6471
  children: jsxRuntime.jsx(ArrowIcon, {
6297
6472
  class: open ? 'bio-properties-panel-arrow-down' : 'bio-properties-panel-arrow-right'
@@ -6320,17 +6495,18 @@ function Group(props) {
6320
6495
  function DataMarker(props) {
6321
6496
  const {
6322
6497
  edited,
6323
- hasErrors
6498
+ hasErrors,
6499
+ translate = translateFallback
6324
6500
  } = props;
6325
6501
  if (hasErrors) {
6326
6502
  return jsxRuntime.jsx("div", {
6327
- title: "Section contains an error",
6503
+ title: translate('Section contains an error'),
6328
6504
  class: "bio-properties-panel-dot bio-properties-panel-dot--error"
6329
6505
  });
6330
6506
  }
6331
6507
  if (edited) {
6332
6508
  return jsxRuntime.jsx("div", {
6333
- title: "Section contains edits",
6509
+ title: translate('Section contains edits'),
6334
6510
  class: "bio-properties-panel-dot"
6335
6511
  });
6336
6512
  }
@@ -6379,7 +6555,7 @@ const DEFAULT_TOOLTIP = {};
6379
6555
  * autoOpen?: Boolean,
6380
6556
  * entries: Array<EntryDefinition>,
6381
6557
  * id: String,
6382
- * label: String,
6558
+ * label: String|import('preact').ComponentChildren,
6383
6559
  * remove: (event: MouseEvent) => void
6384
6560
  * } } ListItemDefinition
6385
6561
  *
@@ -6390,7 +6566,8 @@ const DEFAULT_TOOLTIP = {};
6390
6566
  * id: String,
6391
6567
  * items: Array<ListItemDefinition>,
6392
6568
  * label: String,
6393
- * shouldOpen?: Boolean
6569
+ * shouldOpen?: Boolean,
6570
+ * translate?: Function
6394
6571
  * } } ListGroupDefinition
6395
6572
  *
6396
6573
  * @typedef { {
@@ -6398,7 +6575,8 @@ const DEFAULT_TOOLTIP = {};
6398
6575
  * entries: Array<EntryDefinition>,
6399
6576
  * id: String,
6400
6577
  * label: String,
6401
- * shouldOpen?: Boolean
6578
+ * shouldOpen?: Boolean,
6579
+ * translate?: Function
6402
6580
  * } } GroupDefinition
6403
6581
  *
6404
6582
  * @typedef { {
@@ -6432,9 +6610,12 @@ const DEFAULT_TOOLTIP = {};
6432
6610
  * A basic properties panel component. Describes *how* content will be rendered, accepts
6433
6611
  * data from implementor to describe *what* will be rendered.
6434
6612
  *
6613
+ * If `headerProvider` is omitted (or `null`), the built-in `<Header>` is not rendered;
6614
+ * consumers can render `<Header>` standalone elsewhere using the same provider shape.
6615
+ *
6435
6616
  * @param {Object} props
6436
6617
  * @param {Object|Array} props.element
6437
- * @param {import('./components/Header').HeaderProvider} props.headerProvider
6618
+ * @param {import('./components/Header').HeaderProvider} [props.headerProvider]
6438
6619
  * @param {PlaceholderProvider} [props.placeholderProvider]
6439
6620
  * @param {Array<GroupDefinition|ListGroupDefinition>} props.groups
6440
6621
  * @param {Object} [props.layoutConfig]
@@ -6563,10 +6744,10 @@ function PropertiesPanel$1(props) {
6563
6744
  value: eventContext,
6564
6745
  children: jsxRuntime.jsxs("div", {
6565
6746
  class: "bio-properties-panel",
6566
- children: [jsxRuntime.jsx(Header, {
6747
+ children: [headerProvider ? jsxRuntime.jsx(Header, {
6567
6748
  element: element,
6568
6749
  headerProvider: headerProvider
6569
- }), jsxRuntime.jsx("div", {
6750
+ }) : null, jsxRuntime.jsx("div", {
6570
6751
  class: "bio-properties-panel-scroll-container",
6571
6752
  children: groups.map(group => {
6572
6753
  const {
@@ -6628,28 +6809,6 @@ function useUpdateLayoutEffect(effect, deps) {
6628
6809
  }
6629
6810
  }, deps);
6630
6811
  }
6631
-
6632
- /**
6633
- * @typedef { {
6634
- * [key: string]: string;
6635
- * } } TranslateReplacements
6636
- */
6637
-
6638
- /**
6639
- * A simple translation stub to be used for multi-language support.
6640
- * Can be easily replaced with a more sophisticated solution.
6641
- *
6642
- * @param {string} template to interpolate
6643
- * @param {TranslateReplacements} [replacements] a map with substitutes
6644
- *
6645
- * @return {string} the translated string
6646
- */
6647
- function translateFallback(template, replacements) {
6648
- replacements = replacements || {};
6649
- return template.replace(/{([^}]+)}/g, function (_, key) {
6650
- return replacements[key] || '{' + key + '}';
6651
- });
6652
- }
6653
6812
  function CollapsibleEntry(props) {
6654
6813
  const {
6655
6814
  element,
@@ -6827,7 +6986,6 @@ function ListGroup(props) {
6827
6986
  value: props.tooltip,
6828
6987
  forId: 'group-' + id,
6829
6988
  element: element,
6830
- parent: groupRef,
6831
6989
  children: label
6832
6990
  })
6833
6991
  }), jsxRuntime.jsxs("div", {
@@ -7017,7 +7175,7 @@ function CheckboxEntry(props) {
7017
7175
  })]
7018
7176
  });
7019
7177
  }
7020
- function isEdited$8(node) {
7178
+ function isEdited$9(node) {
7021
7179
  return node && !!node.checked;
7022
7180
  }
7023
7181
 
@@ -7026,6 +7184,8 @@ function isEdited$8(node) {
7026
7184
  function prefixId$8(id) {
7027
7185
  return `bio-properties-panel-${id}`;
7028
7186
  }
7187
+ state.Annotation.define();
7188
+ langJson.jsonParseLinter();
7029
7189
 
7030
7190
  /**
7031
7191
  * Button to open popups.
@@ -7033,13 +7193,14 @@ function prefixId$8(id) {
7033
7193
  * @param {Object} props
7034
7194
  * @param {Function} props.onClick - Callback to trigger when the button is clicked.
7035
7195
  * @param {string} [props.title] - Tooltip text for the button.
7036
- * @param {boolean} [props.disabled] - Whether the button is disabled.
7037
- * @param {string} [props.className] - Additional class names for the button.
7196
+ * @param {Function} [props.translate] - Translation function for built-in strings.
7038
7197
  */
7039
7198
  function OpenPopupButton({
7040
7199
  onClick,
7041
- title = 'Open pop-up editor'
7200
+ title,
7201
+ translate = translateFallback
7042
7202
  }) {
7203
+ title = title ?? translate('Open pop-up editor');
7043
7204
  return jsxRuntime.jsx("button", {
7044
7205
  type: "button",
7045
7206
  title: title,
@@ -7099,7 +7260,7 @@ const TemplatingEditor = React.forwardRef((props, ref) => {
7099
7260
  });
7100
7261
  hooks.useEffect(() => {
7101
7262
  let editor;
7102
- editor = new feelers.FeelersEditor({
7263
+ editor = new feelersEditor.FeelersEditor({
7103
7264
  container: inputRef.current,
7104
7265
  onChange: handleInput,
7105
7266
  value: localValue,
@@ -7180,6 +7341,7 @@ const FeelEditor = React.forwardRef((props, ref) => {
7180
7341
  value,
7181
7342
  onInput,
7182
7343
  onKeyDown: onKeyDownProp = noop$4,
7344
+ onBlur = noop$4,
7183
7345
  onFeelToggle = noop$4,
7184
7346
  onLint = noop$4,
7185
7347
  onOpenPopup = noop$4,
@@ -7188,7 +7350,8 @@ const FeelEditor = React.forwardRef((props, ref) => {
7188
7350
  disabled,
7189
7351
  tooltipContainer,
7190
7352
  variables,
7191
- feelLanguageContext
7353
+ feelLanguageContext,
7354
+ translate = translateFallback
7192
7355
  } = props;
7193
7356
  const inputRef = hooks.useRef();
7194
7357
  const [editor, setEditor] = hooks.useState();
@@ -7203,6 +7366,9 @@ const FeelEditor = React.forwardRef((props, ref) => {
7203
7366
  onInput(newValue);
7204
7367
  setLocalValue(newValue);
7205
7368
  });
7369
+ const handleBlur = useStaticCallback(() => {
7370
+ onBlur();
7371
+ });
7206
7372
  hooks.useEffect(() => {
7207
7373
  let editor;
7208
7374
 
@@ -7229,13 +7395,16 @@ const FeelEditor = React.forwardRef((props, ref) => {
7229
7395
  onKeyDown: onKeyDown,
7230
7396
  onLint: onLint,
7231
7397
  placeholder: placeholder,
7398
+ readOnly: disabled,
7232
7399
  tooltipContainer: tooltipContainer,
7233
7400
  value: localValue,
7234
7401
  variables,
7235
7402
  builtins,
7236
7403
  dialect,
7237
7404
  parserDialect,
7238
- extensions: [...(enableGutters ? [view.lineNumbers()] : []), view.EditorView.lineWrapping],
7405
+ extensions: [...(enableGutters ? [view.lineNumbers()] : []), view.EditorView.lineWrapping, view.EditorView.domEventHandlers({
7406
+ blur: handleBlur
7407
+ })],
7239
7408
  contentAttributes
7240
7409
  });
7241
7410
  setEditor(editor);
@@ -7274,14 +7443,15 @@ const FeelEditor = React.forwardRef((props, ref) => {
7274
7443
  class: classnames('bio-properties-panel-feel-editor-container', disabled ? 'disabled' : null, popupOpen ? 'popupOpen' : null),
7275
7444
  children: [popupOpen && jsxRuntime.jsx("div", {
7276
7445
  class: "bio-properties-panel-feel-editor__open-popup-placeholder",
7277
- children: "Opened in editor"
7446
+ children: translate('Opened in editor')
7278
7447
  }), jsxRuntime.jsx("div", {
7279
7448
  name: props.name,
7280
7449
  class: classnames('bio-properties-panel-input', localValue ? 'edited' : null),
7281
7450
  ref: inputRef,
7282
7451
  onClick: handleClick
7283
7452
  }), !disabled && jsxRuntime.jsx(OpenPopupButton, {
7284
- onClick: () => onOpenPopup('feel')
7453
+ onClick: () => onOpenPopup('feel'),
7454
+ translate: translate
7285
7455
  })]
7286
7456
  });
7287
7457
  });
@@ -7303,16 +7473,22 @@ const noop$3 = () => {};
7303
7473
  * @param {Object} props
7304
7474
  * @param {Object} props.label
7305
7475
  * @param {String} props.feel
7476
+ * @param {boolean} props.active
7477
+ * @param {boolean} props.disabled
7478
+ * @param {Function} props.onClick
7479
+ * @param {Function} props.translate
7480
+ * @returns {import('preact').Component}
7306
7481
  */
7307
7482
  function FeelIcon(props) {
7308
7483
  const {
7309
7484
  feel = false,
7310
7485
  active,
7311
7486
  disabled = false,
7312
- onClick = noop$3
7487
+ onClick = noop$3,
7488
+ translate = translateFallback
7313
7489
  } = props;
7314
- const feelRequiredLabel = 'FEEL expression is mandatory';
7315
- const feelOptionalLabel = `Click to ${active ? 'remove' : 'set a'} dynamic value with FEEL expression`;
7490
+ const feelRequiredLabel = translate('FEEL expression is mandatory');
7491
+ const feelOptionalLabel = translate(`Click to ${active ? 'remove' : 'set a'} dynamic value with FEEL expression`);
7316
7492
  const handleClick = e => {
7317
7493
  onClick(e);
7318
7494
 
@@ -7581,12 +7757,14 @@ function NumberFieldEntry(props) {
7581
7757
  }
7582
7758
  }, [value, validate]);
7583
7759
  const onInput = newValue => {
7584
- let newValidationError = null;
7585
- if (minDash.isFunction(validate)) {
7586
- newValidationError = validate(newValue) || null;
7760
+ if (newValue !== value) {
7761
+ let newValidationError = null;
7762
+ if (minDash.isFunction(validate)) {
7763
+ newValidationError = validate(newValue) || null;
7764
+ }
7765
+ setValue(newValue, newValidationError);
7766
+ setLocalError(newValidationError);
7587
7767
  }
7588
- setValue(newValue, newValidationError);
7589
- setLocalError(newValidationError);
7590
7768
  };
7591
7769
  const error = globalError || localError;
7592
7770
  return jsxRuntime.jsxs("div", {
@@ -7649,6 +7827,7 @@ const noop$2 = () => {};
7649
7827
  * @param {Array} props.variables
7650
7828
  * @param {string} [props.placeholder]
7651
7829
  * @param {string | import('preact').Component} props.tooltip
7830
+ * @param {Function} props.translate
7652
7831
  */
7653
7832
  function FeelTextfield(props) {
7654
7833
  const {
@@ -7668,7 +7847,8 @@ function FeelTextfield(props) {
7668
7847
  singleLine,
7669
7848
  tooltipContainer,
7670
7849
  OptionalComponent = OptionalFeelInput,
7671
- tooltip
7850
+ tooltip,
7851
+ translate
7672
7852
  } = props;
7673
7853
  const [localValue, setLocalValue] = hooks.useState(getInitialFeelLocalValue(feel, value));
7674
7854
  const editorRef = useShowEntryEvent(id);
@@ -7699,19 +7879,23 @@ function FeelTextfield(props) {
7699
7879
  * @type { import('min-dash').DebouncedFunction }
7700
7880
  */
7701
7881
  const handleInput = useDebounce(onInput, debounce);
7882
+ const setAndCommitValue = newValue => {
7883
+ // cancel any pending debounced value
7884
+ handleInput.cancel?.();
7885
+ setLocalValue(newValue);
7886
+ onInput(newValue);
7887
+ };
7702
7888
  const handleFeelToggle = useStaticCallback(() => {
7703
7889
  if (feel === 'required') {
7704
7890
  return;
7705
7891
  }
7706
7892
  if (!feelActive) {
7707
- setLocalValue('=' + localValue);
7708
- handleInput('=' + localValue);
7893
+ setAndCommitValue('=' + localValue);
7709
7894
  } else {
7710
- setLocalValue(feelOnlyValue);
7711
- handleInput(feelOnlyValue);
7895
+ setAndCommitValue(feelOnlyValue);
7712
7896
  }
7713
7897
  });
7714
- const handleLocalInput = (newValue, useDebounce = true) => {
7898
+ const handleInputChange = newValue => {
7715
7899
  if (feelActive) {
7716
7900
  newValue = '=' + newValue;
7717
7901
  }
@@ -7719,28 +7903,32 @@ function FeelTextfield(props) {
7719
7903
  return;
7720
7904
  }
7721
7905
  setLocalValue(newValue);
7722
- if (useDebounce) {
7723
- handleInput(newValue);
7724
- } else {
7725
- onInput(newValue);
7726
- }
7906
+ handleInput(newValue);
7727
7907
  if (!feelActive && minDash.isString(newValue) && newValue.startsWith('=')) {
7728
7908
  // focus is behind `=` sign that will be removed
7729
7909
  setFocus(-1);
7730
7910
  }
7731
7911
  };
7732
- const handleOnBlur = e => {
7733
- handleInput.cancel?.();
7912
+ const handleOptionalInputOnBlur = e => {
7734
7913
  if (e.target.type === 'checkbox') {
7735
- onInput(e.target.checked);
7914
+ setAndCommitValue(e.target.checked);
7736
7915
  } else {
7737
7916
  const trimmedValue = e.target.value.trim();
7738
- handleLocalInput(trimmedValue, false);
7917
+ if (trimmedValue !== localValue) {
7918
+ // Trim changed the value — commit trimmed
7919
+ setAndCommitValue(trimmedValue);
7920
+ } else {
7921
+ // Value unchanged — flush any pending debounce
7922
+ handleInput.flush?.();
7923
+ }
7739
7924
  }
7740
7925
  if (onBlur) {
7741
7926
  onBlur(e);
7742
7927
  }
7743
7928
  };
7929
+ const handleFeelEditorOnBlur = () => {
7930
+ handleInput.flush?.();
7931
+ };
7744
7932
  const handleOnKeyDown = e => {
7745
7933
  if (isCmdWithChar(e)) {
7746
7934
  handleInput.flush?.();
@@ -7749,7 +7937,7 @@ function FeelTextfield(props) {
7749
7937
  const handleLint = useStaticCallback((lint = []) => {
7750
7938
  const syntaxError = lint.some(report => report.type === 'Syntax Error');
7751
7939
  if (syntaxError) {
7752
- onError('Unparsable FEEL expression.');
7940
+ onError(translate('Unparsable FEEL expression.'));
7753
7941
  } else {
7754
7942
  onError(undefined);
7755
7943
  }
@@ -7760,7 +7948,7 @@ function FeelTextfield(props) {
7760
7948
  entryId: id,
7761
7949
  hostLanguage,
7762
7950
  label,
7763
- onInput: handleLocalInput,
7951
+ onInput: handleInputChange,
7764
7952
  singleLine,
7765
7953
  sourceElement: editorRef.current,
7766
7954
  tooltipContainer,
@@ -7820,14 +8008,18 @@ function FeelTextfield(props) {
7820
8008
  });
7821
8009
  return;
7822
8010
  }
7823
- const input = event.target;
7824
- const isFieldEmpty = !input.value;
7825
- const isAllSelected = input.selectionStart === 0 && input.selectionEnd === input.value.length;
8011
+ const target = event.target;
8012
+
8013
+ // Skip for non-input/textArea elements (e.g. CodeMirror contenteditable)
8014
+ if (!(target instanceof HTMLInputElement) && !(target instanceof HTMLTextAreaElement)) {
8015
+ return;
8016
+ }
8017
+ const isFieldEmpty = !target.value;
8018
+ const isAllSelected = target.selectionStart === 0 && target.selectionEnd === target.value.length;
7826
8019
  if (isFieldEmpty || isAllSelected) {
7827
8020
  const textData = event.clipboardData.getData('text');
7828
8021
  const trimmedValue = textData.trim();
7829
- setLocalValue(trimmedValue);
7830
- handleInput(trimmedValue);
8022
+ setAndCommitValue(trimmedValue);
7831
8023
  if (!feelActive && minDash.isString(trimmedValue) && trimmedValue.startsWith('=')) {
7832
8024
  setFocus(trimmedValue.length - 1);
7833
8025
  }
@@ -7860,7 +8052,8 @@ function FeelTextfield(props) {
7860
8052
  label: label,
7861
8053
  feel: feel,
7862
8054
  onClick: handleFeelToggle,
7863
- active: feelActive
8055
+ active: feelActive,
8056
+ translate: translate
7864
8057
  })]
7865
8058
  }), jsxRuntime.jsxs("div", {
7866
8059
  class: "bio-properties-panel-feel-container",
@@ -7871,7 +8064,8 @@ function FeelTextfield(props) {
7871
8064
  onClick: handleFeelToggle
7872
8065
  }), feelActive ? jsxRuntime.jsx(FeelEditor, {
7873
8066
  name: id,
7874
- onInput: handleLocalInput,
8067
+ onInput: handleInputChange,
8068
+ onBlur: handleFeelEditorOnBlur,
7875
8069
  onKeyDown: handleOnKeyDown,
7876
8070
  contentAttributes: {
7877
8071
  'id': prefixId$5(id),
@@ -7890,13 +8084,14 @@ function FeelTextfield(props) {
7890
8084
  variables: variables,
7891
8085
  feelLanguageContext: feelLanguageContext,
7892
8086
  ref: editorRef,
7893
- tooltipContainer: tooltipContainer
8087
+ tooltipContainer: tooltipContainer,
8088
+ translate: translate
7894
8089
  }) : jsxRuntime.jsx(OptionalComponent, {
7895
8090
  ...props,
7896
8091
  popupOpen: isPopupOpen,
7897
- onInput: handleLocalInput,
8092
+ onInput: handleInputChange,
7898
8093
  onKeyDown: handleOnKeyDown,
7899
- onBlur: handleOnBlur,
8094
+ onBlur: handleOptionalInputOnBlur,
7900
8095
  contentAttributes: {
7901
8096
  'id': prefixId$5(id),
7902
8097
  'aria-label': label
@@ -8159,7 +8354,8 @@ function FeelEntry(props) {
8159
8354
  onFocus,
8160
8355
  onBlur,
8161
8356
  placeholder,
8162
- tooltip
8357
+ tooltip,
8358
+ translate = translateFallback
8163
8359
  } = props;
8164
8360
  const [validationError, setValidationError] = hooks.useState(null);
8165
8361
  const [localError, setLocalError] = hooks.useState(null);
@@ -8172,16 +8368,16 @@ function FeelEntry(props) {
8172
8368
  }, [value, validate]);
8173
8369
  const onInput = useStaticCallback(newValue => {
8174
8370
  const value = getValue(element);
8175
- let newValidationError = null;
8176
- if (minDash.isFunction(validate)) {
8177
- newValidationError = validate(newValue) || null;
8178
- }
8179
8371
 
8180
- // don't create multiple commandStack entries for the same value
8372
+ // don't create multiple commandStack entries and do validation for the same value
8181
8373
  if (newValue !== value) {
8374
+ let newValidationError = null;
8375
+ if (minDash.isFunction(validate)) {
8376
+ newValidationError = validate(newValue) || null;
8377
+ }
8182
8378
  setValue(newValue, newValidationError);
8379
+ setValidationError(newValidationError);
8183
8380
  }
8184
- setValidationError(newValidationError);
8185
8381
  });
8186
8382
  const onError = hooks.useCallback(err => {
8187
8383
  setLocalError(err);
@@ -8208,6 +8404,7 @@ function FeelEntry(props) {
8208
8404
  hostLanguage: hostLanguage,
8209
8405
  singleLine: singleLine,
8210
8406
  show: show,
8407
+ translate: translate,
8211
8408
  value: value,
8212
8409
  variables: variables,
8213
8410
  tooltipContainer: tooltipContainer,
@@ -8517,12 +8714,14 @@ function SelectEntry(props) {
8517
8714
  }
8518
8715
  }, [value, validate]);
8519
8716
  const onChange = newValue => {
8520
- let newValidationError = null;
8521
- if (minDash.isFunction(validate)) {
8522
- newValidationError = validate(newValue) || null;
8717
+ if (newValue !== value) {
8718
+ let newValidationError = null;
8719
+ if (minDash.isFunction(validate)) {
8720
+ newValidationError = validate(newValue) || null;
8721
+ }
8722
+ setValue(newValue, newValidationError);
8723
+ setLocalError(newValidationError);
8523
8724
  }
8524
- setValue(newValue, newValidationError);
8525
- setLocalError(newValidationError);
8526
8725
  };
8527
8726
  const error = globalError || localError;
8528
8727
  return jsxRuntime.jsxs("div", {
@@ -8731,14 +8930,14 @@ function TextAreaEntry(props) {
8731
8930
  }, [value, validate]);
8732
8931
  const onInput = useStaticCallback(newValue => {
8733
8932
  const value = getValue(element);
8734
- let newValidationError = null;
8735
- if (minDash.isFunction(validate)) {
8736
- newValidationError = validate(newValue) || null;
8737
- }
8738
8933
  if (newValue !== value) {
8934
+ let newValidationError = null;
8935
+ if (minDash.isFunction(validate)) {
8936
+ newValidationError = validate(newValue) || null;
8937
+ }
8739
8938
  setValue(newValue, newValidationError);
8939
+ setLocalError(newValidationError);
8740
8940
  }
8741
- setLocalError(newValidationError);
8742
8941
  });
8743
8942
  const error = globalError || localError;
8744
8943
  return jsxRuntime.jsxs("div", {
@@ -8929,14 +9128,14 @@ function TextfieldEntry(props) {
8929
9128
  }, [value, validate]);
8930
9129
  const onInput = useStaticCallback(newValue => {
8931
9130
  const value = getValue(element);
8932
- let newValidationError = null;
8933
- if (minDash.isFunction(validate)) {
8934
- newValidationError = validate(newValue) || null;
8935
- }
8936
9131
  if (newValue !== value) {
9132
+ let newValidationError = null;
9133
+ if (minDash.isFunction(validate)) {
9134
+ newValidationError = validate(newValue) || null;
9135
+ }
8937
9136
  setValue(newValue, newValidationError);
9137
+ setLocalError(newValidationError);
8938
9138
  }
8939
- setLocalError(newValidationError);
8940
9139
  });
8941
9140
  const error = globalError || localError;
8942
9141
  return jsxRuntime.jsxs("div", {
@@ -11319,7 +11518,7 @@ function Text(props) {
11319
11518
  };
11320
11519
  return FeelTemplatingEntry({
11321
11520
  debounce,
11322
- description: description$3,
11521
+ description: description$2,
11323
11522
  element: field,
11324
11523
  getValue,
11325
11524
  id,
@@ -11329,7 +11528,7 @@ function Text(props) {
11329
11528
  variables
11330
11529
  });
11331
11530
  }
11332
- const description$3 = jsxRuntime.jsxs(jsxRuntime.Fragment, {
11531
+ const description$2 = jsxRuntime.jsxs(jsxRuntime.Fragment, {
11333
11532
  children: ["Supports markdown and templating.", ' ', jsxRuntime.jsx("a", {
11334
11533
  href: "https://docs.camunda.io/docs/components/modeler/forms/form-element-library/forms-element-library-text/",
11335
11534
  target: "_blank",
@@ -11372,7 +11571,7 @@ function Content(props) {
11372
11571
  };
11373
11572
  return FeelTemplatingEntry({
11374
11573
  debounce,
11375
- description: description$2,
11574
+ description: description$1,
11376
11575
  element: field,
11377
11576
  getValue,
11378
11577
  id,
@@ -11386,7 +11585,7 @@ function Content(props) {
11386
11585
 
11387
11586
  // helpers //////////
11388
11587
 
11389
- const description$2 = jsxRuntime.jsxs(jsxRuntime.Fragment, {
11588
+ const description$1 = jsxRuntime.jsxs(jsxRuntime.Fragment, {
11390
11589
  children: ["Supports HTML, styling, and templating. Styles are automatically scoped to the HTML component.", ' ', jsxRuntime.jsx("a", {
11391
11590
  href: "https://docs.camunda.io/docs/components/modeler/forms/form-element-library/forms-element-library-html/",
11392
11591
  target: "_blank",
@@ -11623,7 +11822,7 @@ function NumberSerializationEntry(props) {
11623
11822
  entries.push({
11624
11823
  id: 'serialize-to-string',
11625
11824
  component: SerializeToString,
11626
- isEdited: isEdited$8,
11825
+ isEdited: isEdited$9,
11627
11826
  editField,
11628
11827
  field,
11629
11828
  isDefaultVisible: field => field.type === 'number'
@@ -11676,7 +11875,7 @@ function DateTimeEntry(props) {
11676
11875
  entries.push({
11677
11876
  id: 'use24h',
11678
11877
  component: Use24h,
11679
- isEdited: isEdited$8,
11878
+ isEdited: isEdited$9,
11680
11879
  editField,
11681
11880
  field,
11682
11881
  isDefaultVisible: field => field.type === 'datetime' && (field.subtype === formJsViewer.DATETIME_SUBTYPES.TIME || field.subtype === formJsViewer.DATETIME_SUBTYPES.DATETIME)
@@ -11789,7 +11988,7 @@ function DateTimeConstraintsEntry(props) {
11789
11988
  entries.push({
11790
11989
  id: id + '-disallowPassedDates',
11791
11990
  component: DisallowPassedDates,
11792
- isEdited: isEdited$8,
11991
+ isEdited: isEdited$9,
11793
11992
  editField,
11794
11993
  field,
11795
11994
  isDefaultVisible: isDefaultVisible([formJsViewer.DATETIME_SUBTYPES.DATE, formJsViewer.DATETIME_SUBTYPES.DATETIME])
@@ -13262,13 +13461,13 @@ function Accept(props) {
13262
13461
  singleLine: true,
13263
13462
  setValue,
13264
13463
  variables,
13265
- description: description$1
13464
+ description
13266
13465
  });
13267
13466
  }
13268
13467
 
13269
13468
  // helpers //////////
13270
13469
 
13271
- const description$1 = jsxRuntime.jsxs(jsxRuntime.Fragment, {
13470
+ const description = jsxRuntime.jsxs(jsxRuntime.Fragment, {
13272
13471
  children: ["A comma-separated list of", ' ', jsxRuntime.jsx("a", {
13273
13472
  href: "https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/file#unique_file_type_specifiers",
13274
13473
  target: "_blank",
@@ -13453,7 +13652,7 @@ function MaxHeight(props) {
13453
13652
  getValue,
13454
13653
  setValue,
13455
13654
  validate,
13456
- description
13655
+ tooltip
13457
13656
  });
13458
13657
  }
13459
13658
 
@@ -13477,9 +13676,7 @@ const validate = value => {
13477
13676
  return 'Should be greater than zero.';
13478
13677
  }
13479
13678
  };
13480
- const description = jsxRuntime.jsx(jsxRuntime.Fragment, {
13481
- children: "Documents with height that exceeds the defined value will be vertically scrollable"
13482
- });
13679
+ const tooltip = 'Documents whose height exceeds the defined value in pixels will be vertically scrollable';
13483
13680
 
13484
13681
  function GeneralGroup(field, editField, getService) {
13485
13682
  const entries = [...IdEntry({
@@ -13648,7 +13845,7 @@ function ValidationGroup(field, editField) {
13648
13845
  component: Required,
13649
13846
  getValue,
13650
13847
  field,
13651
- isEdited: isEdited$8,
13848
+ isEdited: isEdited$9,
13652
13849
  onChange,
13653
13850
  isDefaultVisible: field => INPUTS.includes(field.type)
13654
13851
  }];
@@ -14781,7 +14978,7 @@ class FormEditor {
14781
14978
  * @internal
14782
14979
  */
14783
14980
  _getModules() {
14784
- return [ModelingModule, EditorActionsModule, FormEditorKeyboardModule, DraggingModule, SelectionModule, PaletteModule, EditorExpressionLanguageModule, formJsViewer.MarkdownRendererModule, PropertiesPanelModule, RenderInjectionModule, RepeatRenderModule];
14981
+ return [ContextPadModule, ModelingModule, EditorActionsModule, FormEditorKeyboardModule, DraggingModule, SelectionModule, PaletteModule, EditorExpressionLanguageModule, formJsViewer.MarkdownRendererModule, PropertiesPanelModule, RenderInjectionModule, RepeatRenderModule];
14785
14982
  }
14786
14983
 
14787
14984
  /**
@@ -14837,7 +15034,9 @@ Object.defineProperty(exports, "schemaVersion", {
14837
15034
  enumerable: true,
14838
15035
  get: function () { return formJsViewer.schemaVersion; }
14839
15036
  });
15037
+ exports.ContextPadModule = ContextPadModule;
14840
15038
  exports.FormEditor = FormEditor;
15039
+ exports.FormFieldContextActions = FormFieldContextActions;
14841
15040
  exports.createFormEditor = createFormEditor;
14842
15041
  exports.useDebounce = useDebounce$1;
14843
15042
  exports.usePrevious = usePrevious$1;