@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.es.js CHANGED
@@ -11,14 +11,20 @@ import { createPortal, useRef as useRef$1, useContext as useContext$1, useEffect
11
11
  import dragula from '@bpmn-io/draggle';
12
12
  import { classes, query, event, domify } from 'min-dom';
13
13
  import { arrayMoveMutable } from 'array-move';
14
- import { FeelersEditor } from 'feelers';
15
- import Editor from '@bpmn-io/feel-editor';
16
14
  import { EditorView, lineNumbers } from '@codemirror/view';
15
+ import { Annotation } from '@codemirror/state';
16
+ import '@codemirror/language';
17
+ import '@codemirror/autocomplete';
18
+ import '@codemirror/commands';
19
+ import { jsonParseLinter } from '@codemirror/lang-json';
20
+ import '@codemirror/lint';
21
+ import { FeelersEditor } from '@bpmn-io/feelers-editor';
22
+ import Editor from '@bpmn-io/feel-editor';
17
23
  import * as focusTrap from 'focus-trap';
18
24
  import Big from 'big.js';
19
25
 
20
26
  var FN_REF = '__fn';
21
- var DEFAULT_PRIORITY$3 = 1000;
27
+ var DEFAULT_PRIORITY$4 = 1000;
22
28
  var slice = Array.prototype.slice;
23
29
 
24
30
  /**
@@ -186,7 +192,7 @@ EventBus.prototype.on = function (events, priority, callback, that) {
186
192
  if (isFunction(priority)) {
187
193
  that = callback;
188
194
  callback = priority;
189
- priority = DEFAULT_PRIORITY$3;
195
+ priority = DEFAULT_PRIORITY$4;
190
196
  }
191
197
  if (!isNumber(priority)) {
192
198
  throw new Error('priority must be a number');
@@ -237,7 +243,7 @@ EventBus.prototype.once = function (events, priority, callback, that) {
237
243
  if (isFunction(priority)) {
238
244
  that = callback;
239
245
  callback = priority;
240
- priority = DEFAULT_PRIORITY$3;
246
+ priority = DEFAULT_PRIORITY$4;
241
247
  }
242
248
  if (!isNumber(priority)) {
243
249
  throw new Error('priority must be a number');
@@ -2271,6 +2277,27 @@ function ContextPad(props) {
2271
2277
  children: props.children
2272
2278
  });
2273
2279
  }
2280
+ function ContextPadContent(props) {
2281
+ const {
2282
+ field
2283
+ } = props;
2284
+ const formFieldContextActions = useService$1('formFieldContextActions');
2285
+ const selection = useService$1('selection');
2286
+ if (!selection.isSelected(field) || field.type === 'default') {
2287
+ return null;
2288
+ }
2289
+ const entries = formFieldContextActions.getEntries(field);
2290
+ const actionEntries = Object.entries(entries).filter(([, entry]) => entry.group !== 'morph');
2291
+ return jsx(ContextPad, {
2292
+ children: actionEntries.map(([entryId, entry]) => jsx("button", {
2293
+ type: "button",
2294
+ title: entry.title,
2295
+ class: "fjs-context-pad-item",
2296
+ onClick: entry.action,
2297
+ children: jsx(SvgDelete, {})
2298
+ }, entryId))
2299
+ });
2300
+ }
2274
2301
  function EmptyGroup() {
2275
2302
  return jsx("div", {
2276
2303
  class: "fjs-empty-component",
@@ -2308,9 +2335,6 @@ function Empty(props) {
2308
2335
  }
2309
2336
  function Element$1(props) {
2310
2337
  const eventBus = useService$1('eventBus'),
2311
- formFieldRegistry = useService$1('formFieldRegistry'),
2312
- formFields = useService$1('formFields'),
2313
- modeling = useService$1('modeling'),
2314
2338
  selection = useService$1('selection');
2315
2339
  const {
2316
2340
  hoverInfo
@@ -2383,12 +2407,6 @@ function Element$1(props) {
2383
2407
  }
2384
2408
  return classes.join(' ');
2385
2409
  }, [hovered, isSelected, props.class, showOutline, type]);
2386
- const onRemove = event => {
2387
- event.stopPropagation();
2388
- const parentField = formFieldRegistry.get(field._parent);
2389
- const index = getFormFieldIndex(parentField, field);
2390
- modeling.removeFormField(field, parentField, index);
2391
- };
2392
2410
  const onKeyPress = event => {
2393
2411
  if (event.key === 'Enter') {
2394
2412
  event.stopPropagation();
@@ -2413,14 +2431,8 @@ function Element$1(props) {
2413
2431
  ref: ref,
2414
2432
  children: [jsx(DebugColumns, {
2415
2433
  field: field
2416
- }), jsx(ContextPad, {
2417
- children: selection.isSelected(field) && field.type !== 'default' ? jsx("button", {
2418
- type: "button",
2419
- title: getRemoveButtonTitle(field, formFields),
2420
- class: "fjs-context-pad-item",
2421
- onClick: onRemove,
2422
- children: jsx(SvgDelete, {})
2423
- }) : null
2434
+ }), jsx(ContextPadContent, {
2435
+ field: field
2424
2436
  }), props.children, jsx(FieldResizer, {
2425
2437
  position: "left",
2426
2438
  field: field
@@ -2449,9 +2461,16 @@ function Children(props) {
2449
2461
  field
2450
2462
  } = props;
2451
2463
  const {
2452
- id
2464
+ id,
2465
+ type,
2466
+ _parent
2453
2467
  } = field;
2454
2468
  const classes = ['fjs-children', DROP_CONTAINER_VERTICAL_CLS];
2469
+
2470
+ // mark the top-level (root form) drop container
2471
+ if (type === 'default' && !_parent) {
2472
+ classes.push('fjs-editor-form-root');
2473
+ }
2455
2474
  if (props.class) {
2456
2475
  classes.push(...props.class.split(' '));
2457
2476
  }
@@ -2668,17 +2687,6 @@ function FormEditor$1() {
2668
2687
  })
2669
2688
  });
2670
2689
  }
2671
- function getFormFieldIndex(parent, formField) {
2672
- let fieldFormIndex = parent.components.length;
2673
- parent.components.forEach(({
2674
- id
2675
- }, index) => {
2676
- if (id === formField.id) {
2677
- fieldFormIndex = index;
2678
- }
2679
- });
2680
- return fieldFormIndex;
2681
- }
2682
2690
  function CreatePreview(props) {
2683
2691
  const {
2684
2692
  drake
@@ -2693,7 +2701,7 @@ function CreatePreview(props) {
2693
2701
 
2694
2702
  // (1) field preview
2695
2703
  if (fieldType) {
2696
- const paletteEntry = findPaletteEntry(fieldType, formFields);
2704
+ const paletteEntry = findPaletteEntry$1(fieldType, formFields);
2697
2705
  if (!paletteEntry) {
2698
2706
  return;
2699
2707
  }
@@ -2737,19 +2745,12 @@ function CreatePreview(props) {
2737
2745
 
2738
2746
  // helper //////
2739
2747
 
2740
- function findPaletteEntry(type, formFields) {
2748
+ function findPaletteEntry$1(type, formFields) {
2741
2749
  return collectPaletteEntries(formFields).find(entry => entry.type === type);
2742
2750
  }
2743
2751
  function defaultPropertiesPanel(propertiesPanelConfig) {
2744
2752
  return !(propertiesPanelConfig && propertiesPanelConfig.parent);
2745
2753
  }
2746
- function getRemoveButtonTitle(formField, formFields) {
2747
- const entry = findPaletteEntry(formField.type, formFields);
2748
- if (!entry) {
2749
- return 'Remove form field';
2750
- }
2751
- return `Remove ${entry.label}`;
2752
- }
2753
2754
 
2754
2755
  class Renderer {
2755
2756
  constructor(renderConfig, eventBus, formEditor, injector) {
@@ -2837,6 +2838,155 @@ const CoreModule = {
2837
2838
  fieldFactory: ['type', FieldFactory]
2838
2839
  };
2839
2840
 
2841
+ const DEFAULT_PRIORITY$3 = 1000;
2842
+
2843
+ /**
2844
+ * A service that manages context pad action providers for form fields.
2845
+ * Providers can register to contribute context pad entries for specific
2846
+ * form field types.
2847
+ *
2848
+ * Inspired by the diagram-js ContextPad + provider pattern.
2849
+ *
2850
+ * @param {import('../../core/EventBus').EventBus} eventBus
2851
+ */
2852
+ class FormFieldContextActions {
2853
+ constructor(eventBus) {
2854
+ this._eventBus = eventBus;
2855
+ }
2856
+
2857
+ /**
2858
+ * Register a context pad provider with the default priority.
2859
+ *
2860
+ * @overlord
2861
+ * @param {Object} provider
2862
+ */
2863
+
2864
+ /**
2865
+ * Register a context pad provider with the given priority.
2866
+ *
2867
+ * @param {number} priority
2868
+ * @param {Object} provider
2869
+ */
2870
+ registerProvider(priority, provider) {
2871
+ if (!provider) {
2872
+ provider = priority;
2873
+ priority = DEFAULT_PRIORITY$3;
2874
+ }
2875
+ this._eventBus.on('formFieldContextActions.getProviders', priority, function (event) {
2876
+ event.providers.push(provider);
2877
+ });
2878
+ }
2879
+
2880
+ /**
2881
+ * Get context pad entries for the given form field.
2882
+ *
2883
+ * @param {Object} formField
2884
+ *
2885
+ * @return {Object} entries map
2886
+ */
2887
+ getEntries(formField) {
2888
+ const providers = this._getProviders();
2889
+ let entries = {};
2890
+ forEach(providers, function (provider) {
2891
+ if (!isFunction(provider.getContextPadEntries)) {
2892
+ return;
2893
+ }
2894
+ const entriesOrUpdater = provider.getContextPadEntries(formField);
2895
+ if (isFunction(entriesOrUpdater)) {
2896
+ entries = entriesOrUpdater(entries);
2897
+ } else {
2898
+ forEach(entriesOrUpdater, function (entry, id) {
2899
+ entries[id] = entry;
2900
+ });
2901
+ }
2902
+ });
2903
+ return entries;
2904
+ }
2905
+ _getProviders() {
2906
+ const event = this._eventBus.createEvent({
2907
+ type: 'formFieldContextActions.getProviders',
2908
+ providers: []
2909
+ });
2910
+ this._eventBus.fire(event);
2911
+ return event.providers;
2912
+ }
2913
+ }
2914
+ FormFieldContextActions.$inject = ['eventBus'];
2915
+
2916
+ /**
2917
+ * A context pad provider that contributes the delete action for form fields.
2918
+ *
2919
+ * @param {import('./FormFieldContextActions').FormFieldContextActions} formFieldContextActions
2920
+ * @param {import('../modeling/Modeling').Modeling} modeling
2921
+ * @param {import('../../core/FormFieldRegistry').FormFieldRegistry} formFieldRegistry
2922
+ * @param {import('../../render/EditorFormFields').EditorFormFields} formFields
2923
+ */
2924
+ class DeleteActionProvider {
2925
+ constructor(formFieldContextActions, modeling, formFieldRegistry, formFields) {
2926
+ this._modeling = modeling;
2927
+ this._formFieldRegistry = formFieldRegistry;
2928
+ this._formFields = formFields;
2929
+ formFieldContextActions.registerProvider(500, this);
2930
+ }
2931
+
2932
+ /**
2933
+ * @param {Object} formField
2934
+ * @return {Object} entries
2935
+ */
2936
+ getContextPadEntries(formField) {
2937
+ if (formField.type === 'default') {
2938
+ return {};
2939
+ }
2940
+ const modeling = this._modeling;
2941
+ const formFieldRegistry = this._formFieldRegistry;
2942
+ const formFields = this._formFields;
2943
+ return {
2944
+ delete: {
2945
+ action: event => {
2946
+ event.stopPropagation();
2947
+ const parentField = formFieldRegistry.get(formField._parent);
2948
+ const index = getFormFieldIndex(parentField, formField);
2949
+ modeling.removeFormField(formField, parentField, index);
2950
+ },
2951
+ title: getRemoveButtonTitle(formField, formFields),
2952
+ icon: 'delete',
2953
+ group: 'actions'
2954
+ }
2955
+ };
2956
+ }
2957
+ }
2958
+ DeleteActionProvider.$inject = ['formFieldContextActions', 'modeling', 'formFieldRegistry', 'formFields'];
2959
+
2960
+ // helpers //////////
2961
+
2962
+ function getFormFieldIndex(parent, formField) {
2963
+ let fieldFormIndex = parent.components.length;
2964
+ parent.components.forEach(({
2965
+ id
2966
+ }, index) => {
2967
+ if (id === formField.id) {
2968
+ fieldFormIndex = index;
2969
+ }
2970
+ });
2971
+ return fieldFormIndex;
2972
+ }
2973
+ function findPaletteEntry(type, formFields) {
2974
+ return collectPaletteEntries(formFields).find(entry => entry.type === type);
2975
+ }
2976
+ function getRemoveButtonTitle(formField, formFields) {
2977
+ const entry = findPaletteEntry(formField.type, formFields);
2978
+ if (!entry) {
2979
+ return 'Remove form field';
2980
+ }
2981
+ return `Remove ${entry.label}`;
2982
+ }
2983
+
2984
+ const ContextPadModule = {
2985
+ __init__: ['formFieldContextActions', 'deleteActionProvider'],
2986
+ formFieldContextActions: ['type', FormFieldContextActions],
2987
+ deleteActionProvider: ['type', DeleteActionProvider]
2988
+ };
2989
+
2840
2990
  /**
2841
2991
  * @typedef {import('didi').Injector} Injector
2842
2992
  *
@@ -5564,6 +5714,28 @@ OpenPopupIcon.defaultProps = {
5564
5714
  viewBox: "0 0 16 16"
5565
5715
  };
5566
5716
 
5717
+ /**
5718
+ * @typedef { {
5719
+ * [key: string]: string;
5720
+ * } } TranslateReplacements
5721
+ */
5722
+
5723
+ /**
5724
+ * A simple translation stub to be used for multi-language support.
5725
+ * Can be easily replaced with a more sophisticated solution.
5726
+ *
5727
+ * @param {string} template to interpolate
5728
+ * @param {TranslateReplacements} [replacements] a map with substitutes
5729
+ *
5730
+ * @return {string} the translated string
5731
+ */
5732
+ function translateFallback(template, replacements) {
5733
+ replacements = replacements || {};
5734
+ return template.replace(/{([^}]+)}/g, function (_, key) {
5735
+ return replacements[key] || '{' + key + '}';
5736
+ });
5737
+ }
5738
+
5567
5739
  /**
5568
5740
  * @typedef { {
5569
5741
  * getElementLabel: (element: object) => string,
@@ -5575,13 +5747,15 @@ OpenPopupIcon.defaultProps = {
5575
5747
 
5576
5748
  /**
5577
5749
  * @param {Object} props
5578
- * @param {Object} props.element,
5750
+ * @param {Object} props.element
5579
5751
  * @param {HeaderProvider} props.headerProvider
5752
+ * @param {Function} [props.translate]
5580
5753
  */
5581
5754
  function Header(props) {
5582
5755
  const {
5583
5756
  element,
5584
- headerProvider
5757
+ headerProvider,
5758
+ translate = translateFallback
5585
5759
  } = props;
5586
5760
  const {
5587
5761
  getElementIcon,
@@ -5617,7 +5791,7 @@ function Header(props) {
5617
5791
  rel: "noreferrer",
5618
5792
  class: "bio-properties-panel-header-link",
5619
5793
  href: documentationRef,
5620
- title: "Open documentation",
5794
+ title: translate('Open documentation'),
5621
5795
  target: "_blank",
5622
5796
  children: jsx(ExternalLinkIcon, {})
5623
5797
  }) : null
@@ -5957,10 +6131,10 @@ function useDescriptionContext(id, element) {
5957
6131
  function useDebounce(callback, debounceFn) {
5958
6132
  const debouncedCallback = useCallback(debounceFn(callback), [callback, debounceFn]);
5959
6133
 
5960
- // make sure previous call is not stalled
6134
+ // flush pending calls before unmount the debounced function
5961
6135
  useEffect(() => {
5962
6136
  return () => {
5963
- debouncedCallback.cancel?.();
6137
+ debouncedCallback.flush?.();
5964
6138
  };
5965
6139
  }, [debouncedCallback]);
5966
6140
  return debouncedCallback;
@@ -6208,7 +6382,8 @@ function Group(props) {
6208
6382
  entries = [],
6209
6383
  id,
6210
6384
  label,
6211
- shouldOpen = false
6385
+ shouldOpen = false,
6386
+ translate = translateFallback
6212
6387
  } = props;
6213
6388
  const groupRef = useRef(null);
6214
6389
  const [open, setOpen] = useLayoutState(['groups', id, 'open'], shouldOpen);
@@ -6261,17 +6436,17 @@ function Group(props) {
6261
6436
  value: props.tooltip,
6262
6437
  forId: 'group-' + id,
6263
6438
  element: element,
6264
- parent: groupRef,
6265
6439
  children: label
6266
6440
  })
6267
6441
  }), jsxs("div", {
6268
6442
  class: "bio-properties-panel-group-header-buttons",
6269
6443
  children: [jsx(DataMarker, {
6270
6444
  edited: edited,
6271
- hasErrors: hasErrors
6445
+ hasErrors: hasErrors,
6446
+ translate: translate
6272
6447
  }), jsx("button", {
6273
6448
  type: "button",
6274
- title: "Toggle section",
6449
+ title: translate('Toggle section'),
6275
6450
  class: "bio-properties-panel-group-header-button bio-properties-panel-arrow",
6276
6451
  children: jsx(ArrowIcon, {
6277
6452
  class: open ? 'bio-properties-panel-arrow-down' : 'bio-properties-panel-arrow-right'
@@ -6300,17 +6475,18 @@ function Group(props) {
6300
6475
  function DataMarker(props) {
6301
6476
  const {
6302
6477
  edited,
6303
- hasErrors
6478
+ hasErrors,
6479
+ translate = translateFallback
6304
6480
  } = props;
6305
6481
  if (hasErrors) {
6306
6482
  return jsx("div", {
6307
- title: "Section contains an error",
6483
+ title: translate('Section contains an error'),
6308
6484
  class: "bio-properties-panel-dot bio-properties-panel-dot--error"
6309
6485
  });
6310
6486
  }
6311
6487
  if (edited) {
6312
6488
  return jsx("div", {
6313
- title: "Section contains edits",
6489
+ title: translate('Section contains edits'),
6314
6490
  class: "bio-properties-panel-dot"
6315
6491
  });
6316
6492
  }
@@ -6359,7 +6535,7 @@ const DEFAULT_TOOLTIP = {};
6359
6535
  * autoOpen?: Boolean,
6360
6536
  * entries: Array<EntryDefinition>,
6361
6537
  * id: String,
6362
- * label: String,
6538
+ * label: String|import('preact').ComponentChildren,
6363
6539
  * remove: (event: MouseEvent) => void
6364
6540
  * } } ListItemDefinition
6365
6541
  *
@@ -6370,7 +6546,8 @@ const DEFAULT_TOOLTIP = {};
6370
6546
  * id: String,
6371
6547
  * items: Array<ListItemDefinition>,
6372
6548
  * label: String,
6373
- * shouldOpen?: Boolean
6549
+ * shouldOpen?: Boolean,
6550
+ * translate?: Function
6374
6551
  * } } ListGroupDefinition
6375
6552
  *
6376
6553
  * @typedef { {
@@ -6378,7 +6555,8 @@ const DEFAULT_TOOLTIP = {};
6378
6555
  * entries: Array<EntryDefinition>,
6379
6556
  * id: String,
6380
6557
  * label: String,
6381
- * shouldOpen?: Boolean
6558
+ * shouldOpen?: Boolean,
6559
+ * translate?: Function
6382
6560
  * } } GroupDefinition
6383
6561
  *
6384
6562
  * @typedef { {
@@ -6412,9 +6590,12 @@ const DEFAULT_TOOLTIP = {};
6412
6590
  * A basic properties panel component. Describes *how* content will be rendered, accepts
6413
6591
  * data from implementor to describe *what* will be rendered.
6414
6592
  *
6593
+ * If `headerProvider` is omitted (or `null`), the built-in `<Header>` is not rendered;
6594
+ * consumers can render `<Header>` standalone elsewhere using the same provider shape.
6595
+ *
6415
6596
  * @param {Object} props
6416
6597
  * @param {Object|Array} props.element
6417
- * @param {import('./components/Header').HeaderProvider} props.headerProvider
6598
+ * @param {import('./components/Header').HeaderProvider} [props.headerProvider]
6418
6599
  * @param {PlaceholderProvider} [props.placeholderProvider]
6419
6600
  * @param {Array<GroupDefinition|ListGroupDefinition>} props.groups
6420
6601
  * @param {Object} [props.layoutConfig]
@@ -6543,10 +6724,10 @@ function PropertiesPanel$1(props) {
6543
6724
  value: eventContext,
6544
6725
  children: jsxs("div", {
6545
6726
  class: "bio-properties-panel",
6546
- children: [jsx(Header, {
6727
+ children: [headerProvider ? jsx(Header, {
6547
6728
  element: element,
6548
6729
  headerProvider: headerProvider
6549
- }), jsx("div", {
6730
+ }) : null, jsx("div", {
6550
6731
  class: "bio-properties-panel-scroll-container",
6551
6732
  children: groups.map(group => {
6552
6733
  const {
@@ -6608,28 +6789,6 @@ function useUpdateLayoutEffect(effect, deps) {
6608
6789
  }
6609
6790
  }, deps);
6610
6791
  }
6611
-
6612
- /**
6613
- * @typedef { {
6614
- * [key: string]: string;
6615
- * } } TranslateReplacements
6616
- */
6617
-
6618
- /**
6619
- * A simple translation stub to be used for multi-language support.
6620
- * Can be easily replaced with a more sophisticated solution.
6621
- *
6622
- * @param {string} template to interpolate
6623
- * @param {TranslateReplacements} [replacements] a map with substitutes
6624
- *
6625
- * @return {string} the translated string
6626
- */
6627
- function translateFallback(template, replacements) {
6628
- replacements = replacements || {};
6629
- return template.replace(/{([^}]+)}/g, function (_, key) {
6630
- return replacements[key] || '{' + key + '}';
6631
- });
6632
- }
6633
6792
  function CollapsibleEntry(props) {
6634
6793
  const {
6635
6794
  element,
@@ -6807,7 +6966,6 @@ function ListGroup(props) {
6807
6966
  value: props.tooltip,
6808
6967
  forId: 'group-' + id,
6809
6968
  element: element,
6810
- parent: groupRef,
6811
6969
  children: label
6812
6970
  })
6813
6971
  }), jsxs("div", {
@@ -6997,7 +7155,7 @@ function CheckboxEntry(props) {
6997
7155
  })]
6998
7156
  });
6999
7157
  }
7000
- function isEdited$8(node) {
7158
+ function isEdited$9(node) {
7001
7159
  return node && !!node.checked;
7002
7160
  }
7003
7161
 
@@ -7006,6 +7164,8 @@ function isEdited$8(node) {
7006
7164
  function prefixId$8(id) {
7007
7165
  return `bio-properties-panel-${id}`;
7008
7166
  }
7167
+ Annotation.define();
7168
+ jsonParseLinter();
7009
7169
 
7010
7170
  /**
7011
7171
  * Button to open popups.
@@ -7013,13 +7173,14 @@ function prefixId$8(id) {
7013
7173
  * @param {Object} props
7014
7174
  * @param {Function} props.onClick - Callback to trigger when the button is clicked.
7015
7175
  * @param {string} [props.title] - Tooltip text for the button.
7016
- * @param {boolean} [props.disabled] - Whether the button is disabled.
7017
- * @param {string} [props.className] - Additional class names for the button.
7176
+ * @param {Function} [props.translate] - Translation function for built-in strings.
7018
7177
  */
7019
7178
  function OpenPopupButton({
7020
7179
  onClick,
7021
- title = 'Open pop-up editor'
7180
+ title,
7181
+ translate = translateFallback
7022
7182
  }) {
7183
+ title = title ?? translate('Open pop-up editor');
7023
7184
  return jsx("button", {
7024
7185
  type: "button",
7025
7186
  title: title,
@@ -7160,6 +7321,7 @@ const FeelEditor = forwardRef((props, ref) => {
7160
7321
  value,
7161
7322
  onInput,
7162
7323
  onKeyDown: onKeyDownProp = noop$4,
7324
+ onBlur = noop$4,
7163
7325
  onFeelToggle = noop$4,
7164
7326
  onLint = noop$4,
7165
7327
  onOpenPopup = noop$4,
@@ -7168,7 +7330,8 @@ const FeelEditor = forwardRef((props, ref) => {
7168
7330
  disabled,
7169
7331
  tooltipContainer,
7170
7332
  variables,
7171
- feelLanguageContext
7333
+ feelLanguageContext,
7334
+ translate = translateFallback
7172
7335
  } = props;
7173
7336
  const inputRef = useRef();
7174
7337
  const [editor, setEditor] = useState();
@@ -7183,6 +7346,9 @@ const FeelEditor = forwardRef((props, ref) => {
7183
7346
  onInput(newValue);
7184
7347
  setLocalValue(newValue);
7185
7348
  });
7349
+ const handleBlur = useStaticCallback(() => {
7350
+ onBlur();
7351
+ });
7186
7352
  useEffect(() => {
7187
7353
  let editor;
7188
7354
 
@@ -7209,13 +7375,16 @@ const FeelEditor = forwardRef((props, ref) => {
7209
7375
  onKeyDown: onKeyDown,
7210
7376
  onLint: onLint,
7211
7377
  placeholder: placeholder,
7378
+ readOnly: disabled,
7212
7379
  tooltipContainer: tooltipContainer,
7213
7380
  value: localValue,
7214
7381
  variables,
7215
7382
  builtins,
7216
7383
  dialect,
7217
7384
  parserDialect,
7218
- extensions: [...(enableGutters ? [lineNumbers()] : []), EditorView.lineWrapping],
7385
+ extensions: [...(enableGutters ? [lineNumbers()] : []), EditorView.lineWrapping, EditorView.domEventHandlers({
7386
+ blur: handleBlur
7387
+ })],
7219
7388
  contentAttributes
7220
7389
  });
7221
7390
  setEditor(editor);
@@ -7254,14 +7423,15 @@ const FeelEditor = forwardRef((props, ref) => {
7254
7423
  class: classnames('bio-properties-panel-feel-editor-container', disabled ? 'disabled' : null, popupOpen ? 'popupOpen' : null),
7255
7424
  children: [popupOpen && jsx("div", {
7256
7425
  class: "bio-properties-panel-feel-editor__open-popup-placeholder",
7257
- children: "Opened in editor"
7426
+ children: translate('Opened in editor')
7258
7427
  }), jsx("div", {
7259
7428
  name: props.name,
7260
7429
  class: classnames('bio-properties-panel-input', localValue ? 'edited' : null),
7261
7430
  ref: inputRef,
7262
7431
  onClick: handleClick
7263
7432
  }), !disabled && jsx(OpenPopupButton, {
7264
- onClick: () => onOpenPopup('feel')
7433
+ onClick: () => onOpenPopup('feel'),
7434
+ translate: translate
7265
7435
  })]
7266
7436
  });
7267
7437
  });
@@ -7283,16 +7453,22 @@ const noop$3 = () => {};
7283
7453
  * @param {Object} props
7284
7454
  * @param {Object} props.label
7285
7455
  * @param {String} props.feel
7456
+ * @param {boolean} props.active
7457
+ * @param {boolean} props.disabled
7458
+ * @param {Function} props.onClick
7459
+ * @param {Function} props.translate
7460
+ * @returns {import('preact').Component}
7286
7461
  */
7287
7462
  function FeelIcon(props) {
7288
7463
  const {
7289
7464
  feel = false,
7290
7465
  active,
7291
7466
  disabled = false,
7292
- onClick = noop$3
7467
+ onClick = noop$3,
7468
+ translate = translateFallback
7293
7469
  } = props;
7294
- const feelRequiredLabel = 'FEEL expression is mandatory';
7295
- const feelOptionalLabel = `Click to ${active ? 'remove' : 'set a'} dynamic value with FEEL expression`;
7470
+ const feelRequiredLabel = translate('FEEL expression is mandatory');
7471
+ const feelOptionalLabel = translate(`Click to ${active ? 'remove' : 'set a'} dynamic value with FEEL expression`);
7296
7472
  const handleClick = e => {
7297
7473
  onClick(e);
7298
7474
 
@@ -7561,12 +7737,14 @@ function NumberFieldEntry(props) {
7561
7737
  }
7562
7738
  }, [value, validate]);
7563
7739
  const onInput = newValue => {
7564
- let newValidationError = null;
7565
- if (isFunction(validate)) {
7566
- newValidationError = validate(newValue) || null;
7740
+ if (newValue !== value) {
7741
+ let newValidationError = null;
7742
+ if (isFunction(validate)) {
7743
+ newValidationError = validate(newValue) || null;
7744
+ }
7745
+ setValue(newValue, newValidationError);
7746
+ setLocalError(newValidationError);
7567
7747
  }
7568
- setValue(newValue, newValidationError);
7569
- setLocalError(newValidationError);
7570
7748
  };
7571
7749
  const error = globalError || localError;
7572
7750
  return jsxs("div", {
@@ -7629,6 +7807,7 @@ const noop$2 = () => {};
7629
7807
  * @param {Array} props.variables
7630
7808
  * @param {string} [props.placeholder]
7631
7809
  * @param {string | import('preact').Component} props.tooltip
7810
+ * @param {Function} props.translate
7632
7811
  */
7633
7812
  function FeelTextfield(props) {
7634
7813
  const {
@@ -7648,7 +7827,8 @@ function FeelTextfield(props) {
7648
7827
  singleLine,
7649
7828
  tooltipContainer,
7650
7829
  OptionalComponent = OptionalFeelInput,
7651
- tooltip
7830
+ tooltip,
7831
+ translate
7652
7832
  } = props;
7653
7833
  const [localValue, setLocalValue] = useState(getInitialFeelLocalValue(feel, value));
7654
7834
  const editorRef = useShowEntryEvent(id);
@@ -7679,19 +7859,23 @@ function FeelTextfield(props) {
7679
7859
  * @type { import('min-dash').DebouncedFunction }
7680
7860
  */
7681
7861
  const handleInput = useDebounce(onInput, debounce);
7862
+ const setAndCommitValue = newValue => {
7863
+ // cancel any pending debounced value
7864
+ handleInput.cancel?.();
7865
+ setLocalValue(newValue);
7866
+ onInput(newValue);
7867
+ };
7682
7868
  const handleFeelToggle = useStaticCallback(() => {
7683
7869
  if (feel === 'required') {
7684
7870
  return;
7685
7871
  }
7686
7872
  if (!feelActive) {
7687
- setLocalValue('=' + localValue);
7688
- handleInput('=' + localValue);
7873
+ setAndCommitValue('=' + localValue);
7689
7874
  } else {
7690
- setLocalValue(feelOnlyValue);
7691
- handleInput(feelOnlyValue);
7875
+ setAndCommitValue(feelOnlyValue);
7692
7876
  }
7693
7877
  });
7694
- const handleLocalInput = (newValue, useDebounce = true) => {
7878
+ const handleInputChange = newValue => {
7695
7879
  if (feelActive) {
7696
7880
  newValue = '=' + newValue;
7697
7881
  }
@@ -7699,28 +7883,32 @@ function FeelTextfield(props) {
7699
7883
  return;
7700
7884
  }
7701
7885
  setLocalValue(newValue);
7702
- if (useDebounce) {
7703
- handleInput(newValue);
7704
- } else {
7705
- onInput(newValue);
7706
- }
7886
+ handleInput(newValue);
7707
7887
  if (!feelActive && isString(newValue) && newValue.startsWith('=')) {
7708
7888
  // focus is behind `=` sign that will be removed
7709
7889
  setFocus(-1);
7710
7890
  }
7711
7891
  };
7712
- const handleOnBlur = e => {
7713
- handleInput.cancel?.();
7892
+ const handleOptionalInputOnBlur = e => {
7714
7893
  if (e.target.type === 'checkbox') {
7715
- onInput(e.target.checked);
7894
+ setAndCommitValue(e.target.checked);
7716
7895
  } else {
7717
7896
  const trimmedValue = e.target.value.trim();
7718
- handleLocalInput(trimmedValue, false);
7897
+ if (trimmedValue !== localValue) {
7898
+ // Trim changed the value — commit trimmed
7899
+ setAndCommitValue(trimmedValue);
7900
+ } else {
7901
+ // Value unchanged — flush any pending debounce
7902
+ handleInput.flush?.();
7903
+ }
7719
7904
  }
7720
7905
  if (onBlur) {
7721
7906
  onBlur(e);
7722
7907
  }
7723
7908
  };
7909
+ const handleFeelEditorOnBlur = () => {
7910
+ handleInput.flush?.();
7911
+ };
7724
7912
  const handleOnKeyDown = e => {
7725
7913
  if (isCmdWithChar(e)) {
7726
7914
  handleInput.flush?.();
@@ -7729,7 +7917,7 @@ function FeelTextfield(props) {
7729
7917
  const handleLint = useStaticCallback((lint = []) => {
7730
7918
  const syntaxError = lint.some(report => report.type === 'Syntax Error');
7731
7919
  if (syntaxError) {
7732
- onError('Unparsable FEEL expression.');
7920
+ onError(translate('Unparsable FEEL expression.'));
7733
7921
  } else {
7734
7922
  onError(undefined);
7735
7923
  }
@@ -7740,7 +7928,7 @@ function FeelTextfield(props) {
7740
7928
  entryId: id,
7741
7929
  hostLanguage,
7742
7930
  label,
7743
- onInput: handleLocalInput,
7931
+ onInput: handleInputChange,
7744
7932
  singleLine,
7745
7933
  sourceElement: editorRef.current,
7746
7934
  tooltipContainer,
@@ -7800,14 +7988,18 @@ function FeelTextfield(props) {
7800
7988
  });
7801
7989
  return;
7802
7990
  }
7803
- const input = event.target;
7804
- const isFieldEmpty = !input.value;
7805
- const isAllSelected = input.selectionStart === 0 && input.selectionEnd === input.value.length;
7991
+ const target = event.target;
7992
+
7993
+ // Skip for non-input/textArea elements (e.g. CodeMirror contenteditable)
7994
+ if (!(target instanceof HTMLInputElement) && !(target instanceof HTMLTextAreaElement)) {
7995
+ return;
7996
+ }
7997
+ const isFieldEmpty = !target.value;
7998
+ const isAllSelected = target.selectionStart === 0 && target.selectionEnd === target.value.length;
7806
7999
  if (isFieldEmpty || isAllSelected) {
7807
8000
  const textData = event.clipboardData.getData('text');
7808
8001
  const trimmedValue = textData.trim();
7809
- setLocalValue(trimmedValue);
7810
- handleInput(trimmedValue);
8002
+ setAndCommitValue(trimmedValue);
7811
8003
  if (!feelActive && isString(trimmedValue) && trimmedValue.startsWith('=')) {
7812
8004
  setFocus(trimmedValue.length - 1);
7813
8005
  }
@@ -7840,7 +8032,8 @@ function FeelTextfield(props) {
7840
8032
  label: label,
7841
8033
  feel: feel,
7842
8034
  onClick: handleFeelToggle,
7843
- active: feelActive
8035
+ active: feelActive,
8036
+ translate: translate
7844
8037
  })]
7845
8038
  }), jsxs("div", {
7846
8039
  class: "bio-properties-panel-feel-container",
@@ -7851,7 +8044,8 @@ function FeelTextfield(props) {
7851
8044
  onClick: handleFeelToggle
7852
8045
  }), feelActive ? jsx(FeelEditor, {
7853
8046
  name: id,
7854
- onInput: handleLocalInput,
8047
+ onInput: handleInputChange,
8048
+ onBlur: handleFeelEditorOnBlur,
7855
8049
  onKeyDown: handleOnKeyDown,
7856
8050
  contentAttributes: {
7857
8051
  'id': prefixId$5(id),
@@ -7870,13 +8064,14 @@ function FeelTextfield(props) {
7870
8064
  variables: variables,
7871
8065
  feelLanguageContext: feelLanguageContext,
7872
8066
  ref: editorRef,
7873
- tooltipContainer: tooltipContainer
8067
+ tooltipContainer: tooltipContainer,
8068
+ translate: translate
7874
8069
  }) : jsx(OptionalComponent, {
7875
8070
  ...props,
7876
8071
  popupOpen: isPopupOpen,
7877
- onInput: handleLocalInput,
8072
+ onInput: handleInputChange,
7878
8073
  onKeyDown: handleOnKeyDown,
7879
- onBlur: handleOnBlur,
8074
+ onBlur: handleOptionalInputOnBlur,
7880
8075
  contentAttributes: {
7881
8076
  'id': prefixId$5(id),
7882
8077
  'aria-label': label
@@ -8139,7 +8334,8 @@ function FeelEntry(props) {
8139
8334
  onFocus,
8140
8335
  onBlur,
8141
8336
  placeholder,
8142
- tooltip
8337
+ tooltip,
8338
+ translate = translateFallback
8143
8339
  } = props;
8144
8340
  const [validationError, setValidationError] = useState(null);
8145
8341
  const [localError, setLocalError] = useState(null);
@@ -8152,16 +8348,16 @@ function FeelEntry(props) {
8152
8348
  }, [value, validate]);
8153
8349
  const onInput = useStaticCallback(newValue => {
8154
8350
  const value = getValue(element);
8155
- let newValidationError = null;
8156
- if (isFunction(validate)) {
8157
- newValidationError = validate(newValue) || null;
8158
- }
8159
8351
 
8160
- // don't create multiple commandStack entries for the same value
8352
+ // don't create multiple commandStack entries and do validation for the same value
8161
8353
  if (newValue !== value) {
8354
+ let newValidationError = null;
8355
+ if (isFunction(validate)) {
8356
+ newValidationError = validate(newValue) || null;
8357
+ }
8162
8358
  setValue(newValue, newValidationError);
8359
+ setValidationError(newValidationError);
8163
8360
  }
8164
- setValidationError(newValidationError);
8165
8361
  });
8166
8362
  const onError = useCallback(err => {
8167
8363
  setLocalError(err);
@@ -8188,6 +8384,7 @@ function FeelEntry(props) {
8188
8384
  hostLanguage: hostLanguage,
8189
8385
  singleLine: singleLine,
8190
8386
  show: show,
8387
+ translate: translate,
8191
8388
  value: value,
8192
8389
  variables: variables,
8193
8390
  tooltipContainer: tooltipContainer,
@@ -8497,12 +8694,14 @@ function SelectEntry(props) {
8497
8694
  }
8498
8695
  }, [value, validate]);
8499
8696
  const onChange = newValue => {
8500
- let newValidationError = null;
8501
- if (isFunction(validate)) {
8502
- newValidationError = validate(newValue) || null;
8697
+ if (newValue !== value) {
8698
+ let newValidationError = null;
8699
+ if (isFunction(validate)) {
8700
+ newValidationError = validate(newValue) || null;
8701
+ }
8702
+ setValue(newValue, newValidationError);
8703
+ setLocalError(newValidationError);
8503
8704
  }
8504
- setValue(newValue, newValidationError);
8505
- setLocalError(newValidationError);
8506
8705
  };
8507
8706
  const error = globalError || localError;
8508
8707
  return jsxs("div", {
@@ -8711,14 +8910,14 @@ function TextAreaEntry(props) {
8711
8910
  }, [value, validate]);
8712
8911
  const onInput = useStaticCallback(newValue => {
8713
8912
  const value = getValue(element);
8714
- let newValidationError = null;
8715
- if (isFunction(validate)) {
8716
- newValidationError = validate(newValue) || null;
8717
- }
8718
8913
  if (newValue !== value) {
8914
+ let newValidationError = null;
8915
+ if (isFunction(validate)) {
8916
+ newValidationError = validate(newValue) || null;
8917
+ }
8719
8918
  setValue(newValue, newValidationError);
8919
+ setLocalError(newValidationError);
8720
8920
  }
8721
- setLocalError(newValidationError);
8722
8921
  });
8723
8922
  const error = globalError || localError;
8724
8923
  return jsxs("div", {
@@ -8909,14 +9108,14 @@ function TextfieldEntry(props) {
8909
9108
  }, [value, validate]);
8910
9109
  const onInput = useStaticCallback(newValue => {
8911
9110
  const value = getValue(element);
8912
- let newValidationError = null;
8913
- if (isFunction(validate)) {
8914
- newValidationError = validate(newValue) || null;
8915
- }
8916
9111
  if (newValue !== value) {
9112
+ let newValidationError = null;
9113
+ if (isFunction(validate)) {
9114
+ newValidationError = validate(newValue) || null;
9115
+ }
8917
9116
  setValue(newValue, newValidationError);
9117
+ setLocalError(newValidationError);
8918
9118
  }
8919
- setLocalError(newValidationError);
8920
9119
  });
8921
9120
  const error = globalError || localError;
8922
9121
  return jsxs("div", {
@@ -11299,7 +11498,7 @@ function Text(props) {
11299
11498
  };
11300
11499
  return FeelTemplatingEntry({
11301
11500
  debounce,
11302
- description: description$3,
11501
+ description: description$2,
11303
11502
  element: field,
11304
11503
  getValue,
11305
11504
  id,
@@ -11309,7 +11508,7 @@ function Text(props) {
11309
11508
  variables
11310
11509
  });
11311
11510
  }
11312
- const description$3 = jsxs(Fragment$1, {
11511
+ const description$2 = jsxs(Fragment$1, {
11313
11512
  children: ["Supports markdown and templating.", ' ', jsx("a", {
11314
11513
  href: "https://docs.camunda.io/docs/components/modeler/forms/form-element-library/forms-element-library-text/",
11315
11514
  target: "_blank",
@@ -11352,7 +11551,7 @@ function Content(props) {
11352
11551
  };
11353
11552
  return FeelTemplatingEntry({
11354
11553
  debounce,
11355
- description: description$2,
11554
+ description: description$1,
11356
11555
  element: field,
11357
11556
  getValue,
11358
11557
  id,
@@ -11366,7 +11565,7 @@ function Content(props) {
11366
11565
 
11367
11566
  // helpers //////////
11368
11567
 
11369
- const description$2 = jsxs(Fragment$1, {
11568
+ const description$1 = jsxs(Fragment$1, {
11370
11569
  children: ["Supports HTML, styling, and templating. Styles are automatically scoped to the HTML component.", ' ', jsx("a", {
11371
11570
  href: "https://docs.camunda.io/docs/components/modeler/forms/form-element-library/forms-element-library-html/",
11372
11571
  target: "_blank",
@@ -11603,7 +11802,7 @@ function NumberSerializationEntry(props) {
11603
11802
  entries.push({
11604
11803
  id: 'serialize-to-string',
11605
11804
  component: SerializeToString,
11606
- isEdited: isEdited$8,
11805
+ isEdited: isEdited$9,
11607
11806
  editField,
11608
11807
  field,
11609
11808
  isDefaultVisible: field => field.type === 'number'
@@ -11656,7 +11855,7 @@ function DateTimeEntry(props) {
11656
11855
  entries.push({
11657
11856
  id: 'use24h',
11658
11857
  component: Use24h,
11659
- isEdited: isEdited$8,
11858
+ isEdited: isEdited$9,
11660
11859
  editField,
11661
11860
  field,
11662
11861
  isDefaultVisible: field => field.type === 'datetime' && (field.subtype === DATETIME_SUBTYPES.TIME || field.subtype === DATETIME_SUBTYPES.DATETIME)
@@ -11769,7 +11968,7 @@ function DateTimeConstraintsEntry(props) {
11769
11968
  entries.push({
11770
11969
  id: id + '-disallowPassedDates',
11771
11970
  component: DisallowPassedDates,
11772
- isEdited: isEdited$8,
11971
+ isEdited: isEdited$9,
11773
11972
  editField,
11774
11973
  field,
11775
11974
  isDefaultVisible: isDefaultVisible([DATETIME_SUBTYPES.DATE, DATETIME_SUBTYPES.DATETIME])
@@ -13242,13 +13441,13 @@ function Accept(props) {
13242
13441
  singleLine: true,
13243
13442
  setValue,
13244
13443
  variables,
13245
- description: description$1
13444
+ description
13246
13445
  });
13247
13446
  }
13248
13447
 
13249
13448
  // helpers //////////
13250
13449
 
13251
- const description$1 = jsxs(Fragment$1, {
13450
+ const description = jsxs(Fragment$1, {
13252
13451
  children: ["A comma-separated list of", ' ', jsx("a", {
13253
13452
  href: "https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/file#unique_file_type_specifiers",
13254
13453
  target: "_blank",
@@ -13433,7 +13632,7 @@ function MaxHeight(props) {
13433
13632
  getValue,
13434
13633
  setValue,
13435
13634
  validate,
13436
- description
13635
+ tooltip
13437
13636
  });
13438
13637
  }
13439
13638
 
@@ -13457,9 +13656,7 @@ const validate = value => {
13457
13656
  return 'Should be greater than zero.';
13458
13657
  }
13459
13658
  };
13460
- const description = jsx(Fragment$1, {
13461
- children: "Documents with height that exceeds the defined value will be vertically scrollable"
13462
- });
13659
+ const tooltip = 'Documents whose height exceeds the defined value in pixels will be vertically scrollable';
13463
13660
 
13464
13661
  function GeneralGroup(field, editField, getService) {
13465
13662
  const entries = [...IdEntry({
@@ -13628,7 +13825,7 @@ function ValidationGroup(field, editField) {
13628
13825
  component: Required,
13629
13826
  getValue,
13630
13827
  field,
13631
- isEdited: isEdited$8,
13828
+ isEdited: isEdited$9,
13632
13829
  onChange,
13633
13830
  isDefaultVisible: field => INPUTS.includes(field.type)
13634
13831
  }];
@@ -14761,7 +14958,7 @@ class FormEditor {
14761
14958
  * @internal
14762
14959
  */
14763
14960
  _getModules() {
14764
- return [ModelingModule, EditorActionsModule, FormEditorKeyboardModule, DraggingModule, SelectionModule, PaletteModule, EditorExpressionLanguageModule, MarkdownRendererModule, PropertiesPanelModule, RenderInjectionModule, RepeatRenderModule];
14961
+ return [ContextPadModule, ModelingModule, EditorActionsModule, FormEditorKeyboardModule, DraggingModule, SelectionModule, PaletteModule, EditorExpressionLanguageModule, MarkdownRendererModule, PropertiesPanelModule, RenderInjectionModule, RepeatRenderModule];
14765
14962
  }
14766
14963
 
14767
14964
  /**
@@ -14813,5 +15010,5 @@ function createFormEditor(options) {
14813
15010
  });
14814
15011
  }
14815
15012
 
14816
- export { FormEditor, createFormEditor, useDebounce$1 as useDebounce, usePrevious$1 as usePrevious, useService as usePropertiesPanelService, useService$1 as useService, useVariables };
15013
+ export { ContextPadModule, FormEditor, FormFieldContextActions, createFormEditor, useDebounce$1 as useDebounce, usePrevious$1 as usePrevious, useService as usePropertiesPanelService, useService$1 as useService, useVariables };
14817
15014
  //# sourceMappingURL=index.es.js.map