@bpmn-io/form-js-editor 1.18.0 → 1.20.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
@@ -1,4 +1,4 @@
1
- import Ids from 'ids';
1
+ import { Ids } from 'ids';
2
2
  import { FormFieldRegistry as FormFieldRegistry$1, iconsByType, Label as Label$3, IFrame, Text as Text$1, Html, Table, ExpressionField, DocumentPreview, FormFields, sanitizeImageSource, getAncestryList, FormContext, FormRenderContext, FormComponent, getScrollContainer, FieldFactory, FormLayouter, PathRegistry, Importer, FeelExpressionLanguage, OPTIONS_SOURCES, OPTIONS_SOURCES_PATHS, clone, runRecursively, getSchemaVariables, DATETIME_SUBTYPES, DATE_LABEL_PATH, TIME_LABEL_PATH, TEXT_VIEW_DEFAULT_TEXT, TIME_USE24H_PATH, DATETIME_SUBTYPE_PATH, DATETIME_SUBTYPES_LABELS, TIME_INTERVAL_PATH, TIME_SERIALISING_FORMAT_PATH, DATE_DISALLOW_PAST_PATH, TIME_SERIALISING_FORMATS, TIME_SERIALISINGFORMAT_LABELS, getOptionsSource, OPTIONS_SOURCES_DEFAULTS, OPTIONS_SOURCES_LABELS, SECURITY_ATTRIBUTES_DEFINITIONS, createFormContainer, createInjector, MarkdownRendererModule, schemaVersion } from '@bpmn-io/form-js-viewer';
3
3
  export { schemaVersion } from '@bpmn-io/form-js-viewer';
4
4
  import { isArray, isFunction, isNumber, bind, assign, debounce, forEach, isString, uniqueBy, isObject, get, isDefined, set as set$1, reduce, isNil, without, has } from 'min-dash';
@@ -13,7 +13,7 @@ import { classes, query, event, domify } from 'min-dom';
13
13
  import { arrayMoveMutable } from 'array-move';
14
14
  import { FeelersEditor } from 'feelers';
15
15
  import Editor from '@bpmn-io/feel-editor';
16
- import { lineNumbers, EditorView } from '@codemirror/view';
16
+ import { EditorView, lineNumbers } from '@codemirror/view';
17
17
  import * as focusTrap from 'focus-trap';
18
18
  import Big from 'big.js';
19
19
 
@@ -2870,11 +2870,35 @@ EditorActions.prototype._registerDefaultActions = function (injector) {
2870
2870
  }
2871
2871
  });
2872
2872
  }
2873
+ if (copyPaste && selection) {
2874
+ this.register('duplicate', function () {
2875
+ var selectedElements = selection.get();
2876
+ if (selectedElements.length) {
2877
+ return copyPaste.duplicate(selectedElements);
2878
+ }
2879
+ });
2880
+ }
2873
2881
  if (copyPaste) {
2874
2882
  this.register('paste', function () {
2875
2883
  copyPaste.paste();
2876
2884
  });
2877
2885
  }
2886
+ if (copyPaste && selection && rules) {
2887
+ this.register('cut', function () {
2888
+ var selectedElements = selection.get();
2889
+ if (!selectedElements.length) {
2890
+ return;
2891
+ }
2892
+ var allowed = rules.allowed('elements.delete', {
2893
+ elements: selectedElements
2894
+ });
2895
+ if (allowed === false) {
2896
+ return;
2897
+ }
2898
+ var cuttableElements = isArray(allowed) ? allowed : selectedElements;
2899
+ return copyPaste.cut(cuttableElements.slice());
2900
+ });
2901
+ }
2878
2902
  if (zoomScroll) {
2879
2903
  this.register('stepZoom', function (opts) {
2880
2904
  zoomScroll.stepZoom(opts.value);
@@ -3093,6 +3117,8 @@ const EditorExpressionLanguageModule = {
3093
3117
 
3094
3118
  var KEYS_COPY = ['c', 'C'];
3095
3119
  var KEYS_PASTE = ['v', 'V'];
3120
+ var KEYS_DUPLICATE = ['d', 'D'];
3121
+ var KEYS_CUT = ['x', 'X'];
3096
3122
  var KEYS_REDO = ['y', 'Y'];
3097
3123
  var KEYS_UNDO = ['z', 'Z'];
3098
3124
 
@@ -3108,7 +3134,7 @@ function hasModifier(event) {
3108
3134
  * @param {KeyboardEvent} event
3109
3135
  * @return {boolean}
3110
3136
  */
3111
- function isCmd(event) {
3137
+ function isCmd$1(event) {
3112
3138
  // ensure we don't react to AltGr
3113
3139
  // (mapped to CTRL + ALT)
3114
3140
  if (event.altKey) {
@@ -3140,28 +3166,42 @@ function isShift(event) {
3140
3166
  * @param {KeyboardEvent} event
3141
3167
  */
3142
3168
  function isCopy(event) {
3143
- return isCmd(event) && isKey(KEYS_COPY, event);
3169
+ return isCmd$1(event) && isKey(KEYS_COPY, event);
3144
3170
  }
3145
3171
 
3146
3172
  /**
3147
3173
  * @param {KeyboardEvent} event
3148
3174
  */
3149
3175
  function isPaste(event) {
3150
- return isCmd(event) && isKey(KEYS_PASTE, event);
3176
+ return isCmd$1(event) && isKey(KEYS_PASTE, event);
3177
+ }
3178
+
3179
+ /**
3180
+ * @param {KeyboardEvent} event
3181
+ */
3182
+ function isDuplicate(event) {
3183
+ return isCmd$1(event) && isKey(KEYS_DUPLICATE, event);
3184
+ }
3185
+
3186
+ /**
3187
+ * @param {KeyboardEvent} event
3188
+ */
3189
+ function isCut(event) {
3190
+ return isCmd$1(event) && isKey(KEYS_CUT, event);
3151
3191
  }
3152
3192
 
3153
3193
  /**
3154
3194
  * @param {KeyboardEvent} event
3155
3195
  */
3156
3196
  function isUndo(event) {
3157
- return isCmd(event) && !isShift(event) && isKey(KEYS_UNDO, event);
3197
+ return isCmd$1(event) && !isShift(event) && isKey(KEYS_UNDO, event);
3158
3198
  }
3159
3199
 
3160
3200
  /**
3161
3201
  * @param {KeyboardEvent} event
3162
3202
  */
3163
3203
  function isRedo(event) {
3164
- return isCmd(event) && (isKey(KEYS_REDO, event) || isKey(KEYS_UNDO, event) && isShift(event));
3204
+ return isCmd$1(event) && (isKey(KEYS_REDO, event) || isKey(KEYS_UNDO, event) && isShift(event));
3165
3205
  }
3166
3206
 
3167
3207
  /**
@@ -3330,7 +3370,7 @@ Keyboard.prototype.removeListener = function (listener, type) {
3330
3370
  this._eventBus.off(type || KEYDOWN_EVENT, listener);
3331
3371
  };
3332
3372
  Keyboard.prototype.hasModifier = hasModifier;
3333
- Keyboard.prototype.isCmd = isCmd;
3373
+ Keyboard.prototype.isCmd = isCmd$1;
3334
3374
  Keyboard.prototype.isShift = isShift;
3335
3375
  Keyboard.prototype.isKey = isKey;
3336
3376
 
@@ -3415,6 +3455,26 @@ KeyboardBindings.prototype.registerBindings = function (keyboard, editorActions)
3415
3455
  }
3416
3456
  });
3417
3457
 
3458
+ // duplicate
3459
+ // CTRL/CMD + D
3460
+ addListener('duplicate', function (context) {
3461
+ var event = context.keyEvent;
3462
+ if (isDuplicate(event)) {
3463
+ editorActions.trigger('duplicate');
3464
+ return true;
3465
+ }
3466
+ });
3467
+
3468
+ // cut
3469
+ // CTRL/CMD + X
3470
+ addListener('cut', function (context) {
3471
+ var event = context.keyEvent;
3472
+ if (isCut(event)) {
3473
+ editorActions.trigger('cut');
3474
+ return true;
3475
+ }
3476
+ });
3477
+
3418
3478
  // zoom in one step
3419
3479
  // CTRL/CMD + +
3420
3480
  addListener('stepZoom', function (context) {
@@ -3422,7 +3482,7 @@ KeyboardBindings.prototype.registerBindings = function (keyboard, editorActions)
3422
3482
 
3423
3483
  // quirk: it has to be triggered by `=` as well to work on international keyboard layout
3424
3484
  // cf: https://github.com/bpmn-io/bpmn-js/issues/1362#issuecomment-722989754
3425
- if (isKey(['+', 'Add', '='], event) && isCmd(event)) {
3485
+ if (isKey(['+', 'Add', '='], event) && isCmd$1(event)) {
3426
3486
  editorActions.trigger('stepZoom', {
3427
3487
  value: 1
3428
3488
  });
@@ -3434,7 +3494,7 @@ KeyboardBindings.prototype.registerBindings = function (keyboard, editorActions)
3434
3494
  // CTRL + -
3435
3495
  addListener('stepZoom', function (context) {
3436
3496
  var event = context.keyEvent;
3437
- if (isKey(['-', 'Subtract'], event) && isCmd(event)) {
3497
+ if (isKey(['-', 'Subtract'], event) && isCmd$1(event)) {
3438
3498
  editorActions.trigger('stepZoom', {
3439
3499
  value: -1
3440
3500
  });
@@ -3446,7 +3506,7 @@ KeyboardBindings.prototype.registerBindings = function (keyboard, editorActions)
3446
3506
  // CTRL + 0
3447
3507
  addListener('zoom', function (context) {
3448
3508
  var event = context.keyEvent;
3449
- if (isKey('0', event) && isCmd(event)) {
3509
+ if (isKey('0', event) && isCmd$1(event)) {
3450
3510
  editorActions.trigger('zoom', {
3451
3511
  value: 1
3452
3512
  });
@@ -5453,6 +5513,21 @@ OpenPopupIcon.defaultProps = {
5453
5513
  xmlns: "http://www.w3.org/2000/svg",
5454
5514
  viewBox: "0 0 16 16"
5455
5515
  };
5516
+
5517
+ /**
5518
+ * @typedef { {
5519
+ * getElementLabel: (element: object) => string,
5520
+ * getTypeLabel: (element: object) => string,
5521
+ * getElementIcon: (element: object) => import('preact').Component,
5522
+ * getDocumentationRef: (element: object) => string
5523
+ * } } HeaderProvider
5524
+ */
5525
+
5526
+ /**
5527
+ * @param {Object} props
5528
+ * @param {Object} props.element,
5529
+ * @param {HeaderProvider} props.headerProvider
5530
+ */
5456
5531
  function Header(props) {
5457
5532
  const {
5458
5533
  element,
@@ -5480,11 +5555,9 @@ function Header(props) {
5480
5555
  }), jsxs("div", {
5481
5556
  class: "bio-properties-panel-header-labels",
5482
5557
  children: [jsx("div", {
5483
- title: type,
5484
5558
  class: "bio-properties-panel-header-type",
5485
5559
  children: type
5486
5560
  }), label ? jsx("div", {
5487
- title: label,
5488
5561
  class: "bio-properties-panel-header-label",
5489
5562
  children: label
5490
5563
  }) : null]
@@ -5572,6 +5645,27 @@ function useTooltipContext(id, element) {
5572
5645
  } = useContext(TooltipContext);
5573
5646
  return getTooltipForId(id, element);
5574
5647
  }
5648
+
5649
+ /**
5650
+ * @typedef {Object} TooltipProps
5651
+ * @property {Object} [parent] - Parent element ref for portal rendering
5652
+ * @property {String} [direction='right'] - Tooltip direction ( 'right', 'top')
5653
+ * @property {String} [position] - Custom CSS position override
5654
+ * @property {Number} [showDelay=250] - Delay in ms before showing tooltip on hover
5655
+ * @property {Number} [hideDelay=250] - Delay in ms before hiding tooltip when mouse leaves, to avoid multiple tooltips from being opened, this should be the same as showDelay
5656
+ * @property {*} [children] - Child elements to render inside the tooltip wrapper
5657
+ */
5658
+
5659
+ /**
5660
+ * Tooltip wrapper that provides context-based tooltip content lookup.
5661
+ * All props are forwarded to the underlying Tooltip component.
5662
+ *
5663
+ * @param {TooltipProps & {
5664
+ * forId: String,
5665
+ * value?: String|Object,
5666
+ * element?: Object
5667
+ * }} props - Shared tooltip props plus wrapper-specific ones
5668
+ */
5575
5669
  function TooltipWrapper(props) {
5576
5670
  const {
5577
5671
  forId,
@@ -5588,35 +5682,93 @@ function TooltipWrapper(props) {
5588
5682
  forId: `bio-properties-panel-${forId}`
5589
5683
  });
5590
5684
  }
5685
+
5686
+ /**
5687
+ * @param {TooltipProps & {
5688
+ * forId: String,
5689
+ * value: String|Object
5690
+ * }} props
5691
+ */
5591
5692
  function Tooltip(props) {
5592
5693
  const {
5593
5694
  forId,
5594
5695
  value,
5595
5696
  parent,
5596
5697
  direction = 'right',
5597
- position
5698
+ position,
5699
+ showDelay = 250,
5700
+ hideDelay = 250
5598
5701
  } = props;
5599
5702
  const [visible, setVisible] = useState(false);
5600
-
5601
- // Tooltip will be shown after SHOW_DELAY ms from hovering over the source element.
5602
- const SHOW_DELAY = 200;
5603
- let timeout = null;
5703
+ const [tooltipPosition, setTooltipPosition] = useState(null);
5704
+ const [arrowOffset, setArrowOffset] = useState(null);
5705
+ const showTimeoutRef = useRef(null);
5706
+ const hideTimeoutRef = useRef(null);
5604
5707
  const wrapperRef = useRef(null);
5605
5708
  const tooltipRef = useRef(null);
5606
5709
  const show = (_, delay) => {
5710
+ clearTimeout(showTimeoutRef.current);
5711
+ clearTimeout(hideTimeoutRef.current);
5607
5712
  if (visible) return;
5608
5713
  if (delay) {
5609
- timeout = setTimeout(() => {
5714
+ showTimeoutRef.current = setTimeout(() => {
5610
5715
  setVisible(true);
5611
- }, SHOW_DELAY);
5716
+ }, showDelay);
5612
5717
  } else {
5613
5718
  setVisible(true);
5614
5719
  }
5615
5720
  };
5616
- const hide = () => {
5617
- clearTimeout(timeout);
5618
- setVisible(false);
5721
+ const handleWrapperMouseEnter = e => {
5722
+ show(e, true);
5723
+ };
5724
+ const hide = (delay = false) => {
5725
+ clearTimeout(showTimeoutRef.current);
5726
+ clearTimeout(hideTimeoutRef.current);
5727
+ if (delay) {
5728
+ hideTimeoutRef.current = setTimeout(() => {
5729
+ setVisible(false);
5730
+ }, hideDelay);
5731
+ } else {
5732
+ setVisible(false);
5733
+ }
5619
5734
  };
5735
+
5736
+ // Cleanup timeouts on unmount
5737
+ useEffect(() => {
5738
+ return () => {
5739
+ clearTimeout(showTimeoutRef.current);
5740
+ clearTimeout(hideTimeoutRef.current);
5741
+ };
5742
+ }, []);
5743
+
5744
+ // Handle click outside to close tooltip for non-focusable elements
5745
+ useEffect(() => {
5746
+ if (!visible) return;
5747
+ const handleClickOutside = e => {
5748
+ // If clicking outside both the wrapper and tooltip, hide it
5749
+ if (wrapperRef.current && !wrapperRef.current.contains(e.target) && tooltipRef.current && !tooltipRef.current.contains(e.target)) {
5750
+ hide(false);
5751
+ }
5752
+ };
5753
+ document.addEventListener('mousedown', handleClickOutside);
5754
+ return () => {
5755
+ document.removeEventListener('mousedown', handleClickOutside);
5756
+ };
5757
+ }, [visible, hide]);
5758
+ useLayoutEffect(() => {
5759
+ if (!visible || position) {
5760
+ setTooltipPosition(null);
5761
+ setArrowOffset(null);
5762
+ return;
5763
+ }
5764
+ if (!wrapperRef.current || !tooltipRef.current) return;
5765
+ const {
5766
+ tooltipPosition: newPosition,
5767
+ arrowOffset: newArrowOffset
5768
+ } = getTooltipPosition(wrapperRef.current, tooltipRef.current, direction);
5769
+ setTooltipPosition(newPosition);
5770
+ setArrowOffset(newArrowOffset);
5771
+ }, [visible, position]);
5620
5772
  const handleMouseLeave = ({
5621
5773
  relatedTarget
5622
5774
  }) => {
@@ -5624,39 +5776,52 @@ function Tooltip(props) {
5624
5776
  if (relatedTarget === wrapperRef.current || relatedTarget === tooltipRef.current || relatedTarget?.parentElement === tooltipRef.current) {
5625
5777
  return;
5626
5778
  }
5627
- hide();
5779
+ const selection = window.getSelection();
5780
+ if (selection && selection.toString().length > 0) {
5781
+ // Check if selection is within tooltip content
5782
+ const selectionRange = selection.getRangeAt(0);
5783
+ if (tooltipRef.current?.contains(selectionRange.commonAncestorContainer) || tooltipRef.current?.contains(selection.anchorNode) || tooltipRef.current?.contains(selection.focusNode)) {
5784
+ return; // Keep tooltip open during text selection
5785
+ }
5786
+ }
5787
+ hide(true);
5788
+ };
5789
+ const handleTooltipMouseEnter = () => {
5790
+ clearTimeout(hideTimeoutRef.current);
5628
5791
  };
5629
5792
  const handleFocusOut = e => {
5630
5793
  const {
5631
- target
5794
+ relatedTarget
5632
5795
  } = e;
5633
5796
 
5634
- // Don't hide the tooltip if the wrapper or the tooltip itself is clicked.
5635
- const isHovered = target.matches(':hover') || tooltipRef.current?.matches(':hover');
5636
- if (target === wrapperRef.current && isHovered) {
5637
- e.stopPropagation();
5797
+ // Don't hide if focus moved to the tooltip or another element within the wrapper
5798
+ if (tooltipRef.current?.contains(relatedTarget) || wrapperRef.current?.contains(relatedTarget)) {
5638
5799
  return;
5639
5800
  }
5640
- hide();
5801
+ hide(false);
5641
5802
  };
5642
5803
  const hideTooltipViaEscape = e => {
5643
- e.code === 'Escape' && hide();
5804
+ e.code === 'Escape' && hide(false);
5644
5805
  };
5645
5806
  const renderTooltip = () => {
5807
+ const tooltipStyle = position || (tooltipPosition ? `right: ${tooltipPosition.right}; top: ${tooltipPosition.top}px;` : undefined);
5808
+ const arrowStyle = arrowOffset != null ? `margin-top: ${arrowOffset}px;` : undefined;
5646
5809
  return jsxs("div", {
5647
5810
  class: `bio-properties-panel-tooltip ${direction}`,
5648
5811
  role: "tooltip",
5649
5812
  id: "bio-properties-panel-tooltip",
5650
5813
  "aria-labelledby": forId,
5651
- style: position || getTooltipPosition(wrapperRef.current),
5814
+ style: tooltipStyle,
5652
5815
  ref: tooltipRef,
5653
5816
  onClick: e => e.stopPropagation(),
5817
+ onMouseEnter: handleTooltipMouseEnter,
5654
5818
  onMouseLeave: handleMouseLeave,
5655
5819
  children: [jsx("div", {
5656
5820
  class: "bio-properties-panel-tooltip-content",
5657
5821
  children: value
5658
5822
  }), jsx("div", {
5659
- class: "bio-properties-panel-tooltip-arrow"
5823
+ class: "bio-properties-panel-tooltip-arrow",
5824
+ style: arrowStyle
5660
5825
  })]
5661
5826
  });
5662
5827
  };
@@ -5664,7 +5829,7 @@ function Tooltip(props) {
5664
5829
  class: "bio-properties-panel-tooltip-wrapper",
5665
5830
  tabIndex: "0",
5666
5831
  ref: wrapperRef,
5667
- onMouseEnter: e => show(e, true),
5832
+ onMouseEnter: handleWrapperMouseEnter,
5668
5833
  onMouseLeave: handleMouseLeave,
5669
5834
  onFocus: show,
5670
5835
  onBlur: handleFocusOut,
@@ -5675,11 +5840,47 @@ function Tooltip(props) {
5675
5840
 
5676
5841
  // helper
5677
5842
 
5678
- function getTooltipPosition(refElement) {
5843
+ function getTooltipPosition(refElement, tooltipElement, direction) {
5844
+ if (!refElement) {
5845
+ return {
5846
+ tooltipPosition: null,
5847
+ arrowOffset: null
5848
+ };
5849
+ }
5679
5850
  const refPosition = refElement.getBoundingClientRect();
5680
5851
  const right = `calc(100% - ${refPosition.x}px)`;
5681
- const top = `${refPosition.top - 10}px`;
5682
- return `right: ${right}; top: ${top};`;
5852
+ let top = refPosition.top - 10;
5853
+ let arrowOffset = null;
5854
+
5855
+ // Ensure that the tooltip is within the viewport, adjust the top position if needed.
5856
+ // This is only relevant for the 'right' direction for now
5857
+ if (tooltipElement && direction === 'right') {
5858
+ const tooltipRect = tooltipElement.getBoundingClientRect();
5859
+ const viewportHeight = window.innerHeight;
5860
+ const minTop = 0;
5861
+ const maxTop = viewportHeight - tooltipRect.height;
5862
+ const originalTop = top;
5863
+ if (top > maxTop) {
5864
+ top = maxTop;
5865
+ }
5866
+ if (top < minTop) {
5867
+ top = minTop;
5868
+ }
5869
+
5870
+ // Adjust the arrow position if the tooltip had to be moved to stay within viewport
5871
+ if (top !== originalTop) {
5872
+ const defaultMarginTop = 16;
5873
+ const topDiff = top - originalTop;
5874
+ arrowOffset = defaultMarginTop - topDiff;
5875
+ }
5876
+ }
5877
+ return {
5878
+ tooltipPosition: {
5879
+ right,
5880
+ top
5881
+ },
5882
+ arrowOffset
5883
+ };
5683
5884
  }
5684
5885
 
5685
5886
  /**
@@ -5917,12 +6118,17 @@ function useStickyIntersectionObserver(ref, scrollContainerSelector, setSticky)
5917
6118
  * The `callback` reference is static and can be safely used in external
5918
6119
  * libraries or as a prop that does not cause rerendering of children.
5919
6120
  *
6121
+ * The ref update is deferred to useLayoutEffect to prevent stale-closure
6122
+ * bugs when Chrome fires blur on elements removed during re-render.
6123
+ *
5920
6124
  * @param {Function} callback function with changing reference
5921
6125
  * @returns {Function} static function reference
5922
6126
  */
5923
6127
  function useStaticCallback(callback) {
5924
6128
  const callbackRef = useRef(callback);
5925
- callbackRef.current = callback;
6129
+ useLayoutEffect(() => {
6130
+ callbackRef.current = callback;
6131
+ });
5926
6132
  return useCallback((...args) => callbackRef.current(...args), []);
5927
6133
  }
5928
6134
  function useElementVisible(element) {
@@ -5942,6 +6148,10 @@ function useElementVisible(element) {
5942
6148
  }, [element, visible]);
5943
6149
  return visible;
5944
6150
  }
6151
+
6152
+ /**
6153
+ * @param {import('../PropertiesPanel').GroupDefinition} props
6154
+ */
5945
6155
  function Group(props) {
5946
6156
  const {
5947
6157
  element,
@@ -5996,8 +6206,6 @@ function Group(props) {
5996
6206
  class: classnames('bio-properties-panel-group-header', edited ? '' : 'empty', open ? 'open' : '', sticky && open ? 'sticky' : ''),
5997
6207
  onClick: toggleOpen,
5998
6208
  children: [jsx("div", {
5999
- title: props.tooltip ? null : label,
6000
- "data-title": label,
6001
6209
  class: "bio-properties-panel-group-header-title",
6002
6210
  children: jsx(TooltipWrapper, {
6003
6211
  value: props.tooltip,
@@ -6201,9 +6409,11 @@ function PropertiesPanel$1(props) {
6201
6409
  return get(layout, key, defaultValue);
6202
6410
  };
6203
6411
  const setLayoutForKey = (key, config) => {
6204
- const newLayout = assign({}, layout);
6205
- set$1(newLayout, key, config);
6206
- setLayout(newLayout);
6412
+ setLayout(prevLayout => {
6413
+ const newLayout = assign({}, prevLayout);
6414
+ set$1(newLayout, key, config);
6415
+ return newLayout;
6416
+ });
6207
6417
  };
6208
6418
  const layoutContext = {
6209
6419
  layout,
@@ -6402,7 +6612,6 @@ function CollapsibleEntry(props) {
6402
6612
  class: "bio-properties-panel-collapsible-entry-header",
6403
6613
  onClick: toggleOpen,
6404
6614
  children: [jsx("div", {
6405
- title: label || placeholderLabel,
6406
6615
  class: classnames('bio-properties-panel-collapsible-entry-header-title', !label && 'empty'),
6407
6616
  children: label || placeholderLabel
6408
6617
  }), jsx("button", {
@@ -6438,6 +6647,10 @@ function CollapsibleEntry(props) {
6438
6647
  })]
6439
6648
  });
6440
6649
  }
6650
+
6651
+ /**
6652
+ * @param {import('../PropertiesPanel').ListItemDefinition} props
6653
+ */
6441
6654
  function ListItem(props) {
6442
6655
  const {
6443
6656
  autoFocusEntry,
@@ -6539,8 +6752,6 @@ function ListGroup(props) {
6539
6752
  class: classnames('bio-properties-panel-group-header', hasItems ? '' : 'empty', hasItems && open ? 'open' : '', sticky && open ? 'sticky' : ''),
6540
6753
  onClick: hasItems ? toggleOpen : noop$6,
6541
6754
  children: [jsx("div", {
6542
- title: props.tooltip ? null : label,
6543
- "data-title": label,
6544
6755
  class: "bio-properties-panel-group-header-title",
6545
6756
  children: jsx(TooltipWrapper, {
6546
6757
  value: props.tooltip,
@@ -6609,6 +6820,13 @@ function getNewItemIds(newItems, oldItems) {
6609
6820
  const oldIds = oldItems.map(item => item.id);
6610
6821
  return newIds.filter(itemId => !oldIds.includes(itemId));
6611
6822
  }
6823
+
6824
+ /**
6825
+ * @param {Object} props
6826
+ * @param {Object} props.element
6827
+ * @param {String} props.forId - id of the entry the description is used for
6828
+ * @param {String} props.value
6829
+ */
6612
6830
  function Description$1(props) {
6613
6831
  const {
6614
6832
  element,
@@ -6738,6 +6956,16 @@ function isEdited$8(node) {
6738
6956
  function prefixId$8(id) {
6739
6957
  return `bio-properties-panel-${id}`;
6740
6958
  }
6959
+
6960
+ /**
6961
+ * Button to open popups.
6962
+ *
6963
+ * @param {Object} props
6964
+ * @param {Function} props.onClick - Callback to trigger when the button is clicked.
6965
+ * @param {string} [props.title] - Tooltip text for the button.
6966
+ * @param {boolean} [props.disabled] - Whether the button is disabled.
6967
+ * @param {string} [props.className] - Additional class names for the button.
6968
+ */
6741
6969
  function OpenPopupButton({
6742
6970
  onClick,
6743
6971
  title = 'Open pop-up editor'
@@ -6881,6 +7109,7 @@ const FeelEditor = forwardRef((props, ref) => {
6881
7109
  enableGutters,
6882
7110
  value,
6883
7111
  onInput,
7112
+ onKeyDown: onKeyDownProp = noop$4,
6884
7113
  onFeelToggle = noop$4,
6885
7114
  onLint = noop$4,
6886
7115
  onOpenPopup = noop$4,
@@ -6913,6 +7142,8 @@ const FeelEditor = forwardRef((props, ref) => {
6913
7142
  * - AND the cursor is at the beginning of the input
6914
7143
  */
6915
7144
  const onKeyDown = e => {
7145
+ // Call parent onKeyDown handler first
7146
+ onKeyDownProp(e);
6916
7147
  if (e.key !== 'Backspace' || !editor) {
6917
7148
  return;
6918
7149
  }
@@ -7029,6 +7260,22 @@ function FeelIcon(props) {
7029
7260
  children: jsx(FeelIcon$1, {})
7030
7261
  });
7031
7262
  }
7263
+
7264
+ /**
7265
+ * @param {KeyboardEvent} event
7266
+ * @return {boolean}
7267
+ */
7268
+ function isCmd(event) {
7269
+ // ensure we don't react to AltGr
7270
+ // (mapped to CTRL + ALT)
7271
+ if (event.altKey) {
7272
+ return false;
7273
+ }
7274
+ return event.ctrlKey || event.metaKey;
7275
+ }
7276
+ function isCmdWithChar(event) {
7277
+ return isCmd(event) && event.key.length === 1 && /^[a-zA-Z]$/.test(event.key);
7278
+ }
7032
7279
  function ToggleSwitch(props) {
7033
7280
  const {
7034
7281
  id,
@@ -7137,7 +7384,7 @@ function ToggleSwitchEntry(props) {
7137
7384
  inline: inline,
7138
7385
  tooltip: tooltip,
7139
7386
  element: element
7140
- }), jsx(Description$1, {
7387
+ }, element), jsx(Description$1, {
7141
7388
  forId: id,
7142
7389
  element: element,
7143
7390
  value: description
@@ -7310,7 +7557,7 @@ function prefixId$6(id) {
7310
7557
  const noop$2 = () => {};
7311
7558
 
7312
7559
  /**
7313
- * @typedef {'required'|'optional'|'static'} FeelType
7560
+ * @typedef {'required'|'optional'|'optional-default-enabled'|'static'} FeelType
7314
7561
  */
7315
7562
 
7316
7563
  /**
@@ -7340,7 +7587,7 @@ function FeelTextfield(props) {
7340
7587
  element,
7341
7588
  label,
7342
7589
  hostLanguage,
7343
- onInput,
7590
+ onInput: commitValue,
7344
7591
  onBlur,
7345
7592
  onError,
7346
7593
  placeholder,
@@ -7353,11 +7600,17 @@ function FeelTextfield(props) {
7353
7600
  OptionalComponent = OptionalFeelInput,
7354
7601
  tooltip
7355
7602
  } = props;
7356
- const [localValue, setLocalValue] = useState(value);
7603
+ const [localValue, setLocalValue] = useState(getInitialFeelLocalValue(feel, value));
7357
7604
  const editorRef = useShowEntryEvent(id);
7358
7605
  const containerRef = useRef();
7359
- const feelActive = isString(localValue) && localValue.startsWith('=') || feel === 'required';
7360
- const feelOnlyValue = isString(localValue) && localValue.startsWith('=') ? localValue.substring(1) : localValue;
7606
+ const onInput = useCallback(newValue => {
7607
+ // we don't commit empty FEEL expressions,
7608
+ // but instead serialize them as <undefined>
7609
+ const newModelValue = newValue === '' || newValue === '=' ? undefined : newValue;
7610
+ commitValue(newModelValue);
7611
+ }, [commitValue]);
7612
+ const feelActive = isFeelActive(feel, localValue);
7613
+ const feelOnlyValue = getFeelValue(localValue);
7361
7614
  const feelLanguageContext = useContext(FeelLanguageContext);
7362
7615
  const [focus, _setFocus] = useState(undefined);
7363
7616
  const {
@@ -7375,13 +7628,7 @@ function FeelTextfield(props) {
7375
7628
  /**
7376
7629
  * @type { import('min-dash').DebouncedFunction }
7377
7630
  */
7378
- const handleInputCallback = useDebounce(onInput, debounce);
7379
- const handleInput = newValue => {
7380
- // we don't commit empty FEEL expressions,
7381
- // but instead serialize them as <undefined>
7382
- const newModelValue = newValue === '' || newValue === '=' ? undefined : newValue;
7383
- handleInputCallback(newModelValue);
7384
- };
7631
+ const handleInput = useDebounce(onInput, debounce);
7385
7632
  const handleFeelToggle = useStaticCallback(() => {
7386
7633
  if (feel === 'required') {
7387
7634
  return;
@@ -7394,7 +7641,7 @@ function FeelTextfield(props) {
7394
7641
  handleInput(feelOnlyValue);
7395
7642
  }
7396
7643
  });
7397
- const handleLocalInput = newValue => {
7644
+ const handleLocalInput = (newValue, useDebounce = true) => {
7398
7645
  if (feelActive) {
7399
7646
  newValue = '=' + newValue;
7400
7647
  }
@@ -7402,23 +7649,33 @@ function FeelTextfield(props) {
7402
7649
  return;
7403
7650
  }
7404
7651
  setLocalValue(newValue);
7405
- handleInput(newValue);
7652
+ if (useDebounce) {
7653
+ handleInput(newValue);
7654
+ } else {
7655
+ onInput(newValue);
7656
+ }
7406
7657
  if (!feelActive && isString(newValue) && newValue.startsWith('=')) {
7407
7658
  // focus is behind `=` sign that will be removed
7408
7659
  setFocus(-1);
7409
7660
  }
7410
7661
  };
7411
7662
  const handleOnBlur = e => {
7663
+ handleInput.cancel?.();
7412
7664
  if (e.target.type === 'checkbox') {
7413
7665
  onInput(e.target.checked);
7414
7666
  } else {
7415
7667
  const trimmedValue = e.target.value.trim();
7416
- onInput(trimmedValue);
7668
+ handleLocalInput(trimmedValue, false);
7417
7669
  }
7418
7670
  if (onBlur) {
7419
7671
  onBlur(e);
7420
7672
  }
7421
7673
  };
7674
+ const handleOnKeyDown = e => {
7675
+ if (isCmdWithChar(e)) {
7676
+ handleInput.flush?.();
7677
+ }
7678
+ };
7422
7679
  const handleLint = useStaticCallback((lint = []) => {
7423
7680
  const syntaxError = lint.some(report => report.type === 'Syntax Error');
7424
7681
  if (syntaxError) {
@@ -7485,12 +7742,26 @@ function FeelTextfield(props) {
7485
7742
  if (feelActive || isPopupOpen) {
7486
7743
  return;
7487
7744
  }
7488
- const data = event.clipboardData.getData('application/FEEL');
7489
- if (data) {
7745
+ const feelData = event.clipboardData.getData('application/FEEL');
7746
+ if (feelData) {
7490
7747
  setTimeout(() => {
7491
7748
  handleFeelToggle();
7492
7749
  setFocus();
7493
7750
  });
7751
+ return;
7752
+ }
7753
+ const input = event.target;
7754
+ const isFieldEmpty = !input.value;
7755
+ const isAllSelected = input.selectionStart === 0 && input.selectionEnd === input.value.length;
7756
+ if (isFieldEmpty || isAllSelected) {
7757
+ const textData = event.clipboardData.getData('text');
7758
+ const trimmedValue = textData.trim();
7759
+ setLocalValue(trimmedValue);
7760
+ handleInput(trimmedValue);
7761
+ if (!feelActive && isString(trimmedValue) && trimmedValue.startsWith('=')) {
7762
+ setFocus(trimmedValue.length - 1);
7763
+ }
7764
+ event.preventDefault();
7494
7765
  }
7495
7766
  };
7496
7767
  containerRef.current.addEventListener('copy', copyHandler);
@@ -7526,11 +7797,12 @@ function FeelTextfield(props) {
7526
7797
  ref: containerRef,
7527
7798
  children: [jsx(FeelIndicator, {
7528
7799
  active: feelActive,
7529
- disabled: feel !== 'optional' || disabled,
7800
+ disabled: !isFeelOptional(feel) || disabled,
7530
7801
  onClick: handleFeelToggle
7531
7802
  }), feelActive ? jsx(FeelEditor, {
7532
7803
  name: id,
7533
7804
  onInput: handleLocalInput,
7805
+ onKeyDown: handleOnKeyDown,
7534
7806
  contentAttributes: {
7535
7807
  'id': prefixId$5(id),
7536
7808
  'aria-label': label
@@ -7553,6 +7825,7 @@ function FeelTextfield(props) {
7553
7825
  ...props,
7554
7826
  popupOpen: isPopupOpen,
7555
7827
  onInput: handleLocalInput,
7828
+ onKeyDown: handleOnKeyDown,
7556
7829
  onBlur: handleOnBlur,
7557
7830
  contentAttributes: {
7558
7831
  'id': prefixId$5(id),
@@ -7571,6 +7844,7 @@ const OptionalFeelInput = forwardRef((props, ref) => {
7571
7844
  id,
7572
7845
  disabled,
7573
7846
  onInput,
7847
+ onKeyDown,
7574
7848
  value,
7575
7849
  onFocus,
7576
7850
  onBlur,
@@ -7606,6 +7880,7 @@ const OptionalFeelInput = forwardRef((props, ref) => {
7606
7880
  class: "bio-properties-panel-input",
7607
7881
  onInput: e => onInput(e.target.value),
7608
7882
  onFocus: onFocus,
7883
+ onKeyDown: onKeyDown,
7609
7884
  onBlur: onBlur,
7610
7885
  placeholder: placeholder,
7611
7886
  value: value || ''
@@ -7979,6 +8254,87 @@ function isEdited$5(node) {
7979
8254
  function prefixId$5(id) {
7980
8255
  return `bio-properties-panel-${id}`;
7981
8256
  }
8257
+
8258
+ /**
8259
+ * Determine if FEEL is optional for the configured {@link FeelType}.
8260
+ *
8261
+ * @param {FeelType} feelType
8262
+ *
8263
+ * @return {boolean}
8264
+ */
8265
+ function isFeelOptional(feelType) {
8266
+ return feelType === 'optional' || feelType === 'optional-default-enabled';
8267
+ }
8268
+
8269
+ /**
8270
+ * Determine if FEEL editing is currently active.
8271
+ *
8272
+ * @param {FeelType} feelType
8273
+ * @param {string} localValue
8274
+ *
8275
+ * @return {boolean}
8276
+ */
8277
+ function isFeelActive(feelType, localValue) {
8278
+ if (feelType === 'required') {
8279
+ return true;
8280
+ }
8281
+ if (isString(localValue)) {
8282
+ if (localValue.startsWith('=')) {
8283
+ return true;
8284
+ }
8285
+ }
8286
+ return false;
8287
+ }
8288
+
8289
+ /**
8290
+ * @template T
8291
+ * @param {T} value
8292
+ *
8293
+ * @return {string|T}
8294
+ */
8295
+ function getFeelValue(value) {
8296
+ if (isString(value) && value.startsWith('=')) {
8297
+ return value.substring(1);
8298
+ }
8299
+ return value;
8300
+ }
8301
+
8302
+ /**
8303
+ * Initialize local FEEL value.
8304
+ *
8305
+ * `optional-default-enabled` starts in FEEL mode if no value or empty string is provided.
8306
+ *
8307
+ * @template T
8308
+ * @param {FeelType} feelType
8309
+ * @param {T} value
8310
+ *
8311
+ * @return {string|T}
8312
+ */
8313
+ function getInitialFeelLocalValue(feelType, value) {
8314
+ if (feelType === 'optional-default-enabled' && (value === undefined || value === '')) {
8315
+ return '=';
8316
+ }
8317
+ return value;
8318
+ }
8319
+
8320
+ /**
8321
+ * @typedef { { value: string, label: string, disabled: boolean, children: { value: string, label: string, disabled: boolean } } } Option
8322
+ */
8323
+
8324
+ /**
8325
+ * Provides basic select input.
8326
+ *
8327
+ * @param {object} props
8328
+ * @param {string} props.id
8329
+ * @param {string[]} props.path
8330
+ * @param {string} props.label
8331
+ * @param {Function} props.onChange
8332
+ * @param {Function} props.onFocus
8333
+ * @param {Function} props.onBlur
8334
+ * @param {Array<Option>} [props.options]
8335
+ * @param {string} props.value
8336
+ * @param {boolean} [props.disabled]
8337
+ */
7982
8338
  function Select(props) {
7983
8339
  const {
7984
8340
  id,
@@ -8144,12 +8500,13 @@ function TextArea(props) {
8144
8500
  id,
8145
8501
  label,
8146
8502
  debounce,
8147
- onInput,
8503
+ onInput: commitValue,
8148
8504
  value = '',
8149
8505
  disabled,
8150
8506
  monospace,
8151
8507
  onFocus,
8152
8508
  onBlur,
8509
+ onPaste,
8153
8510
  autoResize = true,
8154
8511
  placeholder,
8155
8512
  rows = autoResize ? 1 : 2,
@@ -8157,16 +8514,16 @@ function TextArea(props) {
8157
8514
  } = props;
8158
8515
  const [localValue, setLocalValue] = useState(value);
8159
8516
  const ref = useShowEntryEvent(id);
8517
+ const onInput = useCallback(newValue => {
8518
+ const newModelValue = newValue === '' ? undefined : newValue;
8519
+ commitValue(newModelValue);
8520
+ }, [commitValue]);
8160
8521
  const visible = useElementVisible(ref.current);
8161
8522
 
8162
8523
  /**
8163
8524
  * @type { import('min-dash').DebouncedFunction }
8164
8525
  */
8165
- const handleInputCallback = useDebounce(onInput, debounce);
8166
- const handleInput = newValue => {
8167
- const newModelValue = newValue === '' ? undefined : newValue;
8168
- handleInputCallback(newModelValue);
8169
- };
8526
+ const handleInput = useDebounce(onInput, debounce);
8170
8527
  const handleLocalInput = e => {
8171
8528
  autoResize && resizeToContents(e.target);
8172
8529
  if (e.target.value === localValue) {
@@ -8179,11 +8536,40 @@ function TextArea(props) {
8179
8536
  const trimmedValue = e.target.value.trim();
8180
8537
 
8181
8538
  // trim and commit on blur
8539
+ handleInput.cancel?.();
8182
8540
  onInput(trimmedValue);
8541
+ setLocalValue(trimmedValue);
8183
8542
  if (onBlur) {
8184
8543
  onBlur(e);
8185
8544
  }
8186
8545
  };
8546
+ const handleOnPaste = e => {
8547
+ const input = e.target;
8548
+ const isFieldEmpty = !input.value;
8549
+ const isAllSelected = input.selectionStart === 0 && input.selectionEnd === input.value.length;
8550
+
8551
+ // Trim and handle paste if field is empty or all content is selected
8552
+ if (isFieldEmpty || isAllSelected) {
8553
+ const trimmedValue = e.clipboardData.getData('text').trim();
8554
+ setLocalValue(trimmedValue);
8555
+ handleInput(trimmedValue);
8556
+ if (onPaste) {
8557
+ onPaste(e);
8558
+ }
8559
+ e.preventDefault();
8560
+ return;
8561
+ }
8562
+
8563
+ // Allow default paste behavior for normal text editing
8564
+ if (onPaste) {
8565
+ onPaste(e);
8566
+ }
8567
+ };
8568
+ const handleOnKeyDown = e => {
8569
+ if (isCmdWithChar(e)) {
8570
+ handleInput.flush?.();
8571
+ }
8572
+ };
8187
8573
  useLayoutEffect(() => {
8188
8574
  autoResize && resizeToContents(ref.current);
8189
8575
  }, []);
@@ -8215,7 +8601,9 @@ function TextArea(props) {
8215
8601
  class: classnames('bio-properties-panel-input', monospace ? 'bio-properties-panel-input-monospace' : '', autoResize ? 'auto-resize' : ''),
8216
8602
  onInput: handleLocalInput,
8217
8603
  onFocus: onFocus,
8604
+ onKeyDown: handleOnKeyDown,
8218
8605
  onBlur: handleOnBlur,
8606
+ onPaste: handleOnPaste,
8219
8607
  placeholder: placeholder,
8220
8608
  rows: rows,
8221
8609
  value: localValue,
@@ -8236,6 +8624,7 @@ function TextArea(props) {
8236
8624
  * @param {Function} props.setValue
8237
8625
  * @param {Function} props.onFocus
8238
8626
  * @param {Function} props.onBlur
8627
+ * @param {Function} props.onPaste
8239
8628
  * @param {number} props.rows
8240
8629
  * @param {boolean} props.monospace
8241
8630
  * @param {Function} [props.validate]
@@ -8256,6 +8645,7 @@ function TextAreaEntry(props) {
8256
8645
  validate,
8257
8646
  onFocus,
8258
8647
  onBlur,
8648
+ onPaste,
8259
8649
  placeholder,
8260
8650
  autoResize,
8261
8651
  tooltip
@@ -8291,6 +8681,7 @@ function TextAreaEntry(props) {
8291
8681
  onInput: onInput,
8292
8682
  onFocus: onFocus,
8293
8683
  onBlur: onBlur,
8684
+ onPaste: onPaste,
8294
8685
  rows: rows,
8295
8686
  debounce: debounce,
8296
8687
  monospace: monospace,
@@ -8324,32 +8715,57 @@ function Textfield(props) {
8324
8715
  disabled = false,
8325
8716
  id,
8326
8717
  label,
8327
- onInput,
8718
+ onInput: commitValue,
8328
8719
  onFocus,
8329
8720
  onBlur,
8721
+ onPaste,
8330
8722
  placeholder,
8331
8723
  value = '',
8332
8724
  tooltip
8333
8725
  } = props;
8334
8726
  const [localValue, setLocalValue] = useState(value || '');
8335
8727
  const ref = useShowEntryEvent(id);
8728
+ const onInput = useCallback(newValue => {
8729
+ const newModelValue = newValue === '' ? undefined : newValue;
8730
+ commitValue(newModelValue);
8731
+ }, [commitValue]);
8336
8732
 
8337
8733
  /**
8338
8734
  * @type { import('min-dash').DebouncedFunction }
8339
8735
  */
8340
- const handleInputCallback = useDebounce(onInput, debounce);
8736
+ const handleInput = useDebounce(onInput, debounce);
8341
8737
  const handleOnBlur = e => {
8342
8738
  const trimmedValue = e.target.value.trim();
8343
8739
 
8344
8740
  // trim and commit on blur
8741
+ handleInput.cancel?.();
8345
8742
  onInput(trimmedValue);
8743
+ setLocalValue(trimmedValue);
8346
8744
  if (onBlur) {
8347
8745
  onBlur(e);
8348
8746
  }
8349
8747
  };
8350
- const handleInput = newValue => {
8351
- const newModelValue = newValue === '' ? undefined : newValue;
8352
- handleInputCallback(newModelValue);
8748
+ const handleOnPaste = e => {
8749
+ const input = e.target;
8750
+ const isFieldEmpty = !input.value;
8751
+ const isAllSelected = input.selectionStart === 0 && input.selectionEnd === input.value.length;
8752
+
8753
+ // Trim and handle paste if field is empty or all content is selected (overwrite)
8754
+ if (isFieldEmpty || isAllSelected) {
8755
+ const trimmedValue = e.clipboardData.getData('text').trim();
8756
+ setLocalValue(trimmedValue);
8757
+ handleInput(trimmedValue);
8758
+ if (onPaste) {
8759
+ onPaste(e);
8760
+ }
8761
+ e.preventDefault();
8762
+ return;
8763
+ }
8764
+
8765
+ // Allow default paste behavior for normal text editing
8766
+ if (onPaste) {
8767
+ onPaste(e);
8768
+ }
8353
8769
  };
8354
8770
  const handleLocalInput = e => {
8355
8771
  if (e.target.value === localValue) {
@@ -8364,6 +8780,11 @@ function Textfield(props) {
8364
8780
  }
8365
8781
  setLocalValue(value);
8366
8782
  }, [value]);
8783
+ const handleOnKeyDown = e => {
8784
+ if (isCmdWithChar(e)) {
8785
+ handleInput.flush?.();
8786
+ }
8787
+ };
8367
8788
  return jsxs("div", {
8368
8789
  class: "bio-properties-panel-textfield",
8369
8790
  children: [jsx("label", {
@@ -8386,7 +8807,9 @@ function Textfield(props) {
8386
8807
  class: "bio-properties-panel-input",
8387
8808
  onInput: handleLocalInput,
8388
8809
  onFocus: onFocus,
8810
+ onKeyDown: handleOnKeyDown,
8389
8811
  onBlur: handleOnBlur,
8812
+ onPaste: handleOnPaste,
8390
8813
  placeholder: placeholder,
8391
8814
  value: localValue
8392
8815
  })]
@@ -8421,6 +8844,7 @@ function TextfieldEntry(props) {
8421
8844
  validate,
8422
8845
  onFocus,
8423
8846
  onBlur,
8847
+ onPaste,
8424
8848
  placeholder,
8425
8849
  tooltip
8426
8850
  } = props;
@@ -8456,6 +8880,7 @@ function TextfieldEntry(props) {
8456
8880
  onInput: onInput,
8457
8881
  onFocus: onFocus,
8458
8882
  onBlur: onBlur,
8883
+ onPaste: onPaste,
8459
8884
  placeholder: placeholder,
8460
8885
  value: value,
8461
8886
  tooltip: tooltip,
@@ -8725,6 +9150,7 @@ function Title(props) {
8725
9150
  class: "bio-properties-panel-popup__title",
8726
9151
  children: title
8727
9152
  }), children, showCloseButton && jsx("button", {
9153
+ type: "button",
8728
9154
  title: closeButtonTooltip,
8729
9155
  class: "bio-properties-panel-popup__close",
8730
9156
  onClick: onClose,
@@ -8766,6 +9192,25 @@ function cancel(event) {
8766
9192
  event.preventDefault();
8767
9193
  event.stopPropagation();
8768
9194
  }
9195
+
9196
+ /**
9197
+ * @typedef {Object} FeelPopupProps
9198
+ * @property {string} entryId
9199
+ * @property {Function} onInput
9200
+ * @property {Function} onClose
9201
+ * @property {string} title
9202
+ * @property {'feel'|'feelers'} type
9203
+ * @property {string} value
9204
+ * @property {Array} [links]
9205
+ * @property {Array|Object} [variables]
9206
+ * @property {Object} [position]
9207
+ * @property {string} [hostLanguage]
9208
+ * @property {boolean} [singleLine]
9209
+ * @property {HTMLElement} [sourceElement]
9210
+ * @property {HTMLElement|string} [tooltipContainer]
9211
+ * @property {Object} [eventBus]
9212
+ */
9213
+
8769
9214
  const FEEL_POPUP_WIDTH = 700;
8770
9215
  const FEEL_POPUP_HEIGHT = 250;
8771
9216
 
@@ -13478,7 +13923,7 @@ function CustomPropertiesGroup(field, editField) {
13478
13923
  event.stopPropagation();
13479
13924
  return editField(field, ['properties'], removeKey(properties, key));
13480
13925
  };
13481
- const id = `property-${field.id}-${index}`;
13926
+ const id = `property-${index}`;
13482
13927
  return {
13483
13928
  autoFocusEntry: id + '-key',
13484
13929
  entries: CustomValueEntry({