@bpmn-io/form-js-editor 1.15.4 → 1.15.5

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
@@ -3128,7 +3128,7 @@ function hasModifier(event) {
3128
3128
  * @param {KeyboardEvent} event
3129
3129
  * @return {boolean}
3130
3130
  */
3131
- function isCmd(event) {
3131
+ function isCmd$1(event) {
3132
3132
  // ensure we don't react to AltGr
3133
3133
  // (mapped to CTRL + ALT)
3134
3134
  if (event.altKey) {
@@ -3160,28 +3160,28 @@ function isShift(event) {
3160
3160
  * @param {KeyboardEvent} event
3161
3161
  */
3162
3162
  function isCopy(event) {
3163
- return isCmd(event) && isKey(KEYS_COPY, event);
3163
+ return isCmd$1(event) && isKey(KEYS_COPY, event);
3164
3164
  }
3165
3165
 
3166
3166
  /**
3167
3167
  * @param {KeyboardEvent} event
3168
3168
  */
3169
3169
  function isPaste(event) {
3170
- return isCmd(event) && isKey(KEYS_PASTE, event);
3170
+ return isCmd$1(event) && isKey(KEYS_PASTE, event);
3171
3171
  }
3172
3172
 
3173
3173
  /**
3174
3174
  * @param {KeyboardEvent} event
3175
3175
  */
3176
3176
  function isUndo(event) {
3177
- return isCmd(event) && !isShift(event) && isKey(KEYS_UNDO, event);
3177
+ return isCmd$1(event) && !isShift(event) && isKey(KEYS_UNDO, event);
3178
3178
  }
3179
3179
 
3180
3180
  /**
3181
3181
  * @param {KeyboardEvent} event
3182
3182
  */
3183
3183
  function isRedo(event) {
3184
- return isCmd(event) && (isKey(KEYS_REDO, event) || isKey(KEYS_UNDO, event) && isShift(event));
3184
+ return isCmd$1(event) && (isKey(KEYS_REDO, event) || isKey(KEYS_UNDO, event) && isShift(event));
3185
3185
  }
3186
3186
 
3187
3187
  /**
@@ -3350,7 +3350,7 @@ Keyboard.prototype.removeListener = function (listener, type) {
3350
3350
  this._eventBus.off(type || KEYDOWN_EVENT, listener);
3351
3351
  };
3352
3352
  Keyboard.prototype.hasModifier = hasModifier;
3353
- Keyboard.prototype.isCmd = isCmd;
3353
+ Keyboard.prototype.isCmd = isCmd$1;
3354
3354
  Keyboard.prototype.isShift = isShift;
3355
3355
  Keyboard.prototype.isKey = isKey;
3356
3356
 
@@ -3442,7 +3442,7 @@ KeyboardBindings.prototype.registerBindings = function (keyboard, editorActions)
3442
3442
 
3443
3443
  // quirk: it has to be triggered by `=` as well to work on international keyboard layout
3444
3444
  // cf: https://github.com/bpmn-io/bpmn-js/issues/1362#issuecomment-722989754
3445
- if (isKey(['+', 'Add', '='], event) && isCmd(event)) {
3445
+ if (isKey(['+', 'Add', '='], event) && isCmd$1(event)) {
3446
3446
  editorActions.trigger('stepZoom', {
3447
3447
  value: 1
3448
3448
  });
@@ -3454,7 +3454,7 @@ KeyboardBindings.prototype.registerBindings = function (keyboard, editorActions)
3454
3454
  // CTRL + -
3455
3455
  addListener('stepZoom', function (context) {
3456
3456
  var event = context.keyEvent;
3457
- if (isKey(['-', 'Subtract'], event) && isCmd(event)) {
3457
+ if (isKey(['-', 'Subtract'], event) && isCmd$1(event)) {
3458
3458
  editorActions.trigger('stepZoom', {
3459
3459
  value: -1
3460
3460
  });
@@ -3466,7 +3466,7 @@ KeyboardBindings.prototype.registerBindings = function (keyboard, editorActions)
3466
3466
  // CTRL + 0
3467
3467
  addListener('zoom', function (context) {
3468
3468
  var event = context.keyEvent;
3469
- if (isKey('0', event) && isCmd(event)) {
3469
+ if (isKey('0', event) && isCmd$1(event)) {
3470
3470
  editorActions.trigger('zoom', {
3471
3471
  value: 1
3472
3472
  });
@@ -5473,6 +5473,21 @@ OpenPopupIcon.defaultProps = {
5473
5473
  xmlns: "http://www.w3.org/2000/svg",
5474
5474
  viewBox: "0 0 16 16"
5475
5475
  };
5476
+
5477
+ /**
5478
+ * @typedef { {
5479
+ * getElementLabel: (element: object) => string,
5480
+ * getTypeLabel: (element: object) => string,
5481
+ * getElementIcon: (element: object) => import('preact').Component,
5482
+ * getDocumentationRef: (element: object) => string
5483
+ * } } HeaderProvider
5484
+ */
5485
+
5486
+ /**
5487
+ * @param {Object} props
5488
+ * @param {Object} props.element,
5489
+ * @param {HeaderProvider} props.headerProvider
5490
+ */
5476
5491
  function Header(props) {
5477
5492
  const {
5478
5493
  element,
@@ -5500,11 +5515,9 @@ function Header(props) {
5500
5515
  }), jsxRuntime.jsxs("div", {
5501
5516
  class: "bio-properties-panel-header-labels",
5502
5517
  children: [jsxRuntime.jsx("div", {
5503
- title: type,
5504
5518
  class: "bio-properties-panel-header-type",
5505
5519
  children: type
5506
5520
  }), label ? jsxRuntime.jsx("div", {
5507
- title: label,
5508
5521
  class: "bio-properties-panel-header-label",
5509
5522
  children: label
5510
5523
  }) : null]
@@ -5592,6 +5605,27 @@ function useTooltipContext(id, element) {
5592
5605
  } = hooks.useContext(TooltipContext);
5593
5606
  return getTooltipForId(id, element);
5594
5607
  }
5608
+
5609
+ /**
5610
+ * @typedef {Object} TooltipProps
5611
+ * @property {Object} [parent] - Parent element ref for portal rendering
5612
+ * @property {String} [direction='right'] - Tooltip direction ( 'right', 'top')
5613
+ * @property {String} [position] - Custom CSS position override
5614
+ * @property {Number} [showDelay=250] - Delay in ms before showing tooltip on hover
5615
+ * @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
5616
+ * @property {*} [children] - Child elements to render inside the tooltip wrapper
5617
+ */
5618
+
5619
+ /**
5620
+ * Tooltip wrapper that provides context-based tooltip content lookup.
5621
+ * All props are forwarded to the underlying Tooltip component.
5622
+ *
5623
+ * @param {TooltipProps & {
5624
+ * forId: String,
5625
+ * value?: String|Object,
5626
+ * element?: Object
5627
+ * }} props - Shared tooltip props plus wrapper-specific ones
5628
+ */
5595
5629
  function TooltipWrapper(props) {
5596
5630
  const {
5597
5631
  forId,
@@ -5608,35 +5642,93 @@ function TooltipWrapper(props) {
5608
5642
  forId: `bio-properties-panel-${forId}`
5609
5643
  });
5610
5644
  }
5645
+
5646
+ /**
5647
+ * @param {TooltipProps & {
5648
+ * forId: String,
5649
+ * value: String|Object
5650
+ * }} props
5651
+ */
5611
5652
  function Tooltip(props) {
5612
5653
  const {
5613
5654
  forId,
5614
5655
  value,
5615
5656
  parent,
5616
5657
  direction = 'right',
5617
- position
5658
+ position,
5659
+ showDelay = 250,
5660
+ hideDelay = 250
5618
5661
  } = props;
5619
5662
  const [visible, setVisible] = hooks.useState(false);
5620
-
5621
- // Tooltip will be shown after SHOW_DELAY ms from hovering over the source element.
5622
- const SHOW_DELAY = 200;
5623
- let timeout = null;
5663
+ const [tooltipPosition, setTooltipPosition] = hooks.useState(null);
5664
+ const [arrowOffset, setArrowOffset] = hooks.useState(null);
5665
+ const showTimeoutRef = hooks.useRef(null);
5666
+ const hideTimeoutRef = hooks.useRef(null);
5624
5667
  const wrapperRef = hooks.useRef(null);
5625
5668
  const tooltipRef = hooks.useRef(null);
5626
5669
  const show = (_, delay) => {
5670
+ clearTimeout(showTimeoutRef.current);
5671
+ clearTimeout(hideTimeoutRef.current);
5627
5672
  if (visible) return;
5628
5673
  if (delay) {
5629
- timeout = setTimeout(() => {
5674
+ showTimeoutRef.current = setTimeout(() => {
5630
5675
  setVisible(true);
5631
- }, SHOW_DELAY);
5676
+ }, showDelay);
5632
5677
  } else {
5633
5678
  setVisible(true);
5634
5679
  }
5635
5680
  };
5636
- const hide = () => {
5637
- clearTimeout(timeout);
5638
- setVisible(false);
5681
+ const handleWrapperMouseEnter = e => {
5682
+ show(e, true);
5639
5683
  };
5684
+ const hide = (delay = false) => {
5685
+ clearTimeout(showTimeoutRef.current);
5686
+ clearTimeout(hideTimeoutRef.current);
5687
+ if (delay) {
5688
+ hideTimeoutRef.current = setTimeout(() => {
5689
+ setVisible(false);
5690
+ }, hideDelay);
5691
+ } else {
5692
+ setVisible(false);
5693
+ }
5694
+ };
5695
+
5696
+ // Cleanup timeouts on unmount
5697
+ hooks.useEffect(() => {
5698
+ return () => {
5699
+ clearTimeout(showTimeoutRef.current);
5700
+ clearTimeout(hideTimeoutRef.current);
5701
+ };
5702
+ }, []);
5703
+
5704
+ // Handle click outside to close tooltip for non-focusable elements
5705
+ hooks.useEffect(() => {
5706
+ if (!visible) return;
5707
+ const handleClickOutside = e => {
5708
+ // If clicking outside both the wrapper and tooltip, hide it
5709
+ if (wrapperRef.current && !wrapperRef.current.contains(e.target) && tooltipRef.current && !tooltipRef.current.contains(e.target)) {
5710
+ hide(false);
5711
+ }
5712
+ };
5713
+ document.addEventListener('mousedown', handleClickOutside);
5714
+ return () => {
5715
+ document.removeEventListener('mousedown', handleClickOutside);
5716
+ };
5717
+ }, [visible, hide]);
5718
+ hooks.useLayoutEffect(() => {
5719
+ if (!visible || position) {
5720
+ setTooltipPosition(null);
5721
+ setArrowOffset(null);
5722
+ return;
5723
+ }
5724
+ if (!wrapperRef.current || !tooltipRef.current) return;
5725
+ const {
5726
+ tooltipPosition: newPosition,
5727
+ arrowOffset: newArrowOffset
5728
+ } = getTooltipPosition(wrapperRef.current, tooltipRef.current, direction);
5729
+ setTooltipPosition(newPosition);
5730
+ setArrowOffset(newArrowOffset);
5731
+ }, [visible, position]);
5640
5732
  const handleMouseLeave = ({
5641
5733
  relatedTarget
5642
5734
  }) => {
@@ -5644,39 +5736,52 @@ function Tooltip(props) {
5644
5736
  if (relatedTarget === wrapperRef.current || relatedTarget === tooltipRef.current || relatedTarget?.parentElement === tooltipRef.current) {
5645
5737
  return;
5646
5738
  }
5647
- hide();
5739
+ const selection = window.getSelection();
5740
+ if (selection && selection.toString().length > 0) {
5741
+ // Check if selection is within tooltip content
5742
+ const selectionRange = selection.getRangeAt(0);
5743
+ if (tooltipRef.current?.contains(selectionRange.commonAncestorContainer) || tooltipRef.current?.contains(selection.anchorNode) || tooltipRef.current?.contains(selection.focusNode)) {
5744
+ return; // Keep tooltip open during text selection
5745
+ }
5746
+ }
5747
+ hide(true);
5748
+ };
5749
+ const handleTooltipMouseEnter = () => {
5750
+ clearTimeout(hideTimeoutRef.current);
5648
5751
  };
5649
5752
  const handleFocusOut = e => {
5650
5753
  const {
5651
- target
5754
+ relatedTarget
5652
5755
  } = e;
5653
5756
 
5654
- // Don't hide the tooltip if the wrapper or the tooltip itself is clicked.
5655
- const isHovered = target.matches(':hover') || tooltipRef.current?.matches(':hover');
5656
- if (target === wrapperRef.current && isHovered) {
5657
- e.stopPropagation();
5757
+ // Don't hide if focus moved to the tooltip or another element within the wrapper
5758
+ if (tooltipRef.current?.contains(relatedTarget) || wrapperRef.current?.contains(relatedTarget)) {
5658
5759
  return;
5659
5760
  }
5660
- hide();
5761
+ hide(false);
5661
5762
  };
5662
5763
  const hideTooltipViaEscape = e => {
5663
- e.code === 'Escape' && hide();
5764
+ e.code === 'Escape' && hide(false);
5664
5765
  };
5665
5766
  const renderTooltip = () => {
5767
+ const tooltipStyle = position || (tooltipPosition ? `right: ${tooltipPosition.right}; top: ${tooltipPosition.top}px;` : undefined);
5768
+ const arrowStyle = arrowOffset != null ? `margin-top: ${arrowOffset}px;` : undefined;
5666
5769
  return jsxRuntime.jsxs("div", {
5667
5770
  class: `bio-properties-panel-tooltip ${direction}`,
5668
5771
  role: "tooltip",
5669
5772
  id: "bio-properties-panel-tooltip",
5670
5773
  "aria-labelledby": forId,
5671
- style: position || getTooltipPosition(wrapperRef.current),
5774
+ style: tooltipStyle,
5672
5775
  ref: tooltipRef,
5673
5776
  onClick: e => e.stopPropagation(),
5777
+ onMouseEnter: handleTooltipMouseEnter,
5674
5778
  onMouseLeave: handleMouseLeave,
5675
5779
  children: [jsxRuntime.jsx("div", {
5676
5780
  class: "bio-properties-panel-tooltip-content",
5677
5781
  children: value
5678
5782
  }), jsxRuntime.jsx("div", {
5679
- class: "bio-properties-panel-tooltip-arrow"
5783
+ class: "bio-properties-panel-tooltip-arrow",
5784
+ style: arrowStyle
5680
5785
  })]
5681
5786
  });
5682
5787
  };
@@ -5684,7 +5789,7 @@ function Tooltip(props) {
5684
5789
  class: "bio-properties-panel-tooltip-wrapper",
5685
5790
  tabIndex: "0",
5686
5791
  ref: wrapperRef,
5687
- onMouseEnter: e => show(e, true),
5792
+ onMouseEnter: handleWrapperMouseEnter,
5688
5793
  onMouseLeave: handleMouseLeave,
5689
5794
  onFocus: show,
5690
5795
  onBlur: handleFocusOut,
@@ -5695,11 +5800,47 @@ function Tooltip(props) {
5695
5800
 
5696
5801
  // helper
5697
5802
 
5698
- function getTooltipPosition(refElement) {
5803
+ function getTooltipPosition(refElement, tooltipElement, direction) {
5804
+ if (!refElement) {
5805
+ return {
5806
+ tooltipPosition: null,
5807
+ arrowOffset: null
5808
+ };
5809
+ }
5699
5810
  const refPosition = refElement.getBoundingClientRect();
5700
5811
  const right = `calc(100% - ${refPosition.x}px)`;
5701
- const top = `${refPosition.top - 10}px`;
5702
- return `right: ${right}; top: ${top};`;
5812
+ let top = refPosition.top - 10;
5813
+ let arrowOffset = null;
5814
+
5815
+ // Ensure that the tooltip is within the viewport, adjust the top position if needed.
5816
+ // This is only relevant for the 'right' direction for now
5817
+ if (tooltipElement && direction === 'right') {
5818
+ const tooltipRect = tooltipElement.getBoundingClientRect();
5819
+ const viewportHeight = window.innerHeight;
5820
+ const minTop = 0;
5821
+ const maxTop = viewportHeight - tooltipRect.height;
5822
+ const originalTop = top;
5823
+ if (top > maxTop) {
5824
+ top = maxTop;
5825
+ }
5826
+ if (top < minTop) {
5827
+ top = minTop;
5828
+ }
5829
+
5830
+ // Adjust the arrow position if the tooltip had to be moved to stay within viewport
5831
+ if (top !== originalTop) {
5832
+ const defaultMarginTop = 16;
5833
+ const topDiff = top - originalTop;
5834
+ arrowOffset = defaultMarginTop - topDiff;
5835
+ }
5836
+ }
5837
+ return {
5838
+ tooltipPosition: {
5839
+ right,
5840
+ top
5841
+ },
5842
+ arrowOffset
5843
+ };
5703
5844
  }
5704
5845
 
5705
5846
  /**
@@ -5937,12 +6078,17 @@ function useStickyIntersectionObserver(ref, scrollContainerSelector, setSticky)
5937
6078
  * The `callback` reference is static and can be safely used in external
5938
6079
  * libraries or as a prop that does not cause rerendering of children.
5939
6080
  *
6081
+ * The ref update is deferred to useLayoutEffect to prevent stale-closure
6082
+ * bugs when Chrome fires blur on elements removed during re-render.
6083
+ *
5940
6084
  * @param {Function} callback function with changing reference
5941
6085
  * @returns {Function} static function reference
5942
6086
  */
5943
6087
  function useStaticCallback(callback) {
5944
6088
  const callbackRef = hooks.useRef(callback);
5945
- callbackRef.current = callback;
6089
+ hooks.useLayoutEffect(() => {
6090
+ callbackRef.current = callback;
6091
+ });
5946
6092
  return hooks.useCallback((...args) => callbackRef.current(...args), []);
5947
6093
  }
5948
6094
  function useElementVisible(element) {
@@ -5962,6 +6108,10 @@ function useElementVisible(element) {
5962
6108
  }, [element, visible]);
5963
6109
  return visible;
5964
6110
  }
6111
+
6112
+ /**
6113
+ * @param {import('../PropertiesPanel').GroupDefinition} props
6114
+ */
5965
6115
  function Group(props) {
5966
6116
  const {
5967
6117
  element,
@@ -6016,8 +6166,6 @@ function Group(props) {
6016
6166
  class: classnames('bio-properties-panel-group-header', edited ? '' : 'empty', open ? 'open' : '', sticky && open ? 'sticky' : ''),
6017
6167
  onClick: toggleOpen,
6018
6168
  children: [jsxRuntime.jsx("div", {
6019
- title: props.tooltip ? null : label,
6020
- "data-title": label,
6021
6169
  class: "bio-properties-panel-group-header-title",
6022
6170
  children: jsxRuntime.jsx(TooltipWrapper, {
6023
6171
  value: props.tooltip,
@@ -6221,9 +6369,11 @@ function PropertiesPanel$1(props) {
6221
6369
  return minDash.get(layout, key, defaultValue);
6222
6370
  };
6223
6371
  const setLayoutForKey = (key, config) => {
6224
- const newLayout = minDash.assign({}, layout);
6225
- minDash.set(newLayout, key, config);
6226
- setLayout(newLayout);
6372
+ setLayout(prevLayout => {
6373
+ const newLayout = minDash.assign({}, prevLayout);
6374
+ minDash.set(newLayout, key, config);
6375
+ return newLayout;
6376
+ });
6227
6377
  };
6228
6378
  const layoutContext = {
6229
6379
  layout,
@@ -6422,7 +6572,6 @@ function CollapsibleEntry(props) {
6422
6572
  class: "bio-properties-panel-collapsible-entry-header",
6423
6573
  onClick: toggleOpen,
6424
6574
  children: [jsxRuntime.jsx("div", {
6425
- title: label || placeholderLabel,
6426
6575
  class: classnames('bio-properties-panel-collapsible-entry-header-title', !label && 'empty'),
6427
6576
  children: label || placeholderLabel
6428
6577
  }), jsxRuntime.jsx("button", {
@@ -6458,6 +6607,10 @@ function CollapsibleEntry(props) {
6458
6607
  })]
6459
6608
  });
6460
6609
  }
6610
+
6611
+ /**
6612
+ * @param {import('../PropertiesPanel').ListItemDefinition} props
6613
+ */
6461
6614
  function ListItem(props) {
6462
6615
  const {
6463
6616
  autoFocusEntry,
@@ -6559,8 +6712,6 @@ function ListGroup(props) {
6559
6712
  class: classnames('bio-properties-panel-group-header', hasItems ? '' : 'empty', hasItems && open ? 'open' : '', sticky && open ? 'sticky' : ''),
6560
6713
  onClick: hasItems ? toggleOpen : noop$6,
6561
6714
  children: [jsxRuntime.jsx("div", {
6562
- title: props.tooltip ? null : label,
6563
- "data-title": label,
6564
6715
  class: "bio-properties-panel-group-header-title",
6565
6716
  children: jsxRuntime.jsx(TooltipWrapper, {
6566
6717
  value: props.tooltip,
@@ -6629,6 +6780,13 @@ function getNewItemIds(newItems, oldItems) {
6629
6780
  const oldIds = oldItems.map(item => item.id);
6630
6781
  return newIds.filter(itemId => !oldIds.includes(itemId));
6631
6782
  }
6783
+
6784
+ /**
6785
+ * @param {Object} props
6786
+ * @param {Object} props.element
6787
+ * @param {String} props.forId - id of the entry the description is used for
6788
+ * @param {String} props.value
6789
+ */
6632
6790
  function Description$1(props) {
6633
6791
  const {
6634
6792
  element,
@@ -6758,6 +6916,16 @@ function isEdited$8(node) {
6758
6916
  function prefixId$8(id) {
6759
6917
  return `bio-properties-panel-${id}`;
6760
6918
  }
6919
+
6920
+ /**
6921
+ * Button to open popups.
6922
+ *
6923
+ * @param {Object} props
6924
+ * @param {Function} props.onClick - Callback to trigger when the button is clicked.
6925
+ * @param {string} [props.title] - Tooltip text for the button.
6926
+ * @param {boolean} [props.disabled] - Whether the button is disabled.
6927
+ * @param {string} [props.className] - Additional class names for the button.
6928
+ */
6761
6929
  function OpenPopupButton({
6762
6930
  onClick,
6763
6931
  title = 'Open pop-up editor'
@@ -6901,6 +7069,7 @@ const FeelEditor = React.forwardRef((props, ref) => {
6901
7069
  enableGutters,
6902
7070
  value,
6903
7071
  onInput,
7072
+ onKeyDown: onKeyDownProp = noop$4,
6904
7073
  onFeelToggle = noop$4,
6905
7074
  onLint = noop$4,
6906
7075
  onOpenPopup = noop$4,
@@ -6933,6 +7102,8 @@ const FeelEditor = React.forwardRef((props, ref) => {
6933
7102
  * - AND the cursor is at the beginning of the input
6934
7103
  */
6935
7104
  const onKeyDown = e => {
7105
+ // Call parent onKeyDown handler first
7106
+ onKeyDownProp(e);
6936
7107
  if (e.key !== 'Backspace' || !editor) {
6937
7108
  return;
6938
7109
  }
@@ -7049,6 +7220,22 @@ function FeelIcon(props) {
7049
7220
  children: jsxRuntime.jsx(FeelIcon$1, {})
7050
7221
  });
7051
7222
  }
7223
+
7224
+ /**
7225
+ * @param {KeyboardEvent} event
7226
+ * @return {boolean}
7227
+ */
7228
+ function isCmd(event) {
7229
+ // ensure we don't react to AltGr
7230
+ // (mapped to CTRL + ALT)
7231
+ if (event.altKey) {
7232
+ return false;
7233
+ }
7234
+ return event.ctrlKey || event.metaKey;
7235
+ }
7236
+ function isCmdWithChar(event) {
7237
+ return isCmd(event) && event.key.length === 1 && /^[a-zA-Z]$/.test(event.key);
7238
+ }
7052
7239
  function ToggleSwitch(props) {
7053
7240
  const {
7054
7241
  id,
@@ -7157,7 +7344,7 @@ function ToggleSwitchEntry(props) {
7157
7344
  inline: inline,
7158
7345
  tooltip: tooltip,
7159
7346
  element: element
7160
- }), jsxRuntime.jsx(Description$1, {
7347
+ }, element), jsxRuntime.jsx(Description$1, {
7161
7348
  forId: id,
7162
7349
  element: element,
7163
7350
  value: description
@@ -7330,7 +7517,7 @@ function prefixId$6(id) {
7330
7517
  const noop$2 = () => {};
7331
7518
 
7332
7519
  /**
7333
- * @typedef {'required'|'optional'|'static'} FeelType
7520
+ * @typedef {'required'|'optional'|'optional-default-enabled'|'static'} FeelType
7334
7521
  */
7335
7522
 
7336
7523
  /**
@@ -7360,7 +7547,7 @@ function FeelTextfield(props) {
7360
7547
  element,
7361
7548
  label,
7362
7549
  hostLanguage,
7363
- onInput,
7550
+ onInput: commitValue,
7364
7551
  onBlur,
7365
7552
  onError,
7366
7553
  placeholder,
@@ -7373,11 +7560,17 @@ function FeelTextfield(props) {
7373
7560
  OptionalComponent = OptionalFeelInput,
7374
7561
  tooltip
7375
7562
  } = props;
7376
- const [localValue, setLocalValue] = hooks.useState(value);
7563
+ const [localValue, setLocalValue] = hooks.useState(getInitialFeelLocalValue(feel, value));
7377
7564
  const editorRef = useShowEntryEvent(id);
7378
7565
  const containerRef = hooks.useRef();
7379
- const feelActive = minDash.isString(localValue) && localValue.startsWith('=') || feel === 'required';
7380
- const feelOnlyValue = minDash.isString(localValue) && localValue.startsWith('=') ? localValue.substring(1) : localValue;
7566
+ const onInput = hooks.useCallback(newValue => {
7567
+ // we don't commit empty FEEL expressions,
7568
+ // but instead serialize them as <undefined>
7569
+ const newModelValue = newValue === '' || newValue === '=' ? undefined : newValue;
7570
+ commitValue(newModelValue);
7571
+ }, [commitValue]);
7572
+ const feelActive = isFeelActive(feel, localValue);
7573
+ const feelOnlyValue = getFeelValue(localValue);
7381
7574
  const feelLanguageContext = hooks.useContext(FeelLanguageContext);
7382
7575
  const [focus, _setFocus] = hooks.useState(undefined);
7383
7576
  const {
@@ -7395,13 +7588,7 @@ function FeelTextfield(props) {
7395
7588
  /**
7396
7589
  * @type { import('min-dash').DebouncedFunction }
7397
7590
  */
7398
- const handleInputCallback = useDebounce(onInput, debounce);
7399
- const handleInput = newValue => {
7400
- // we don't commit empty FEEL expressions,
7401
- // but instead serialize them as <undefined>
7402
- const newModelValue = newValue === '' || newValue === '=' ? undefined : newValue;
7403
- handleInputCallback(newModelValue);
7404
- };
7591
+ const handleInput = useDebounce(onInput, debounce);
7405
7592
  const handleFeelToggle = useStaticCallback(() => {
7406
7593
  if (feel === 'required') {
7407
7594
  return;
@@ -7414,7 +7601,7 @@ function FeelTextfield(props) {
7414
7601
  handleInput(feelOnlyValue);
7415
7602
  }
7416
7603
  });
7417
- const handleLocalInput = newValue => {
7604
+ const handleLocalInput = (newValue, useDebounce = true) => {
7418
7605
  if (feelActive) {
7419
7606
  newValue = '=' + newValue;
7420
7607
  }
@@ -7422,23 +7609,33 @@ function FeelTextfield(props) {
7422
7609
  return;
7423
7610
  }
7424
7611
  setLocalValue(newValue);
7425
- handleInput(newValue);
7612
+ if (useDebounce) {
7613
+ handleInput(newValue);
7614
+ } else {
7615
+ onInput(newValue);
7616
+ }
7426
7617
  if (!feelActive && minDash.isString(newValue) && newValue.startsWith('=')) {
7427
7618
  // focus is behind `=` sign that will be removed
7428
7619
  setFocus(-1);
7429
7620
  }
7430
7621
  };
7431
7622
  const handleOnBlur = e => {
7623
+ handleInput.cancel?.();
7432
7624
  if (e.target.type === 'checkbox') {
7433
7625
  onInput(e.target.checked);
7434
7626
  } else {
7435
7627
  const trimmedValue = e.target.value.trim();
7436
- onInput(trimmedValue);
7628
+ handleLocalInput(trimmedValue, false);
7437
7629
  }
7438
7630
  if (onBlur) {
7439
7631
  onBlur(e);
7440
7632
  }
7441
7633
  };
7634
+ const handleOnKeyDown = e => {
7635
+ if (isCmdWithChar(e)) {
7636
+ handleInput.flush?.();
7637
+ }
7638
+ };
7442
7639
  const handleLint = useStaticCallback((lint = []) => {
7443
7640
  const syntaxError = lint.some(report => report.type === 'Syntax Error');
7444
7641
  if (syntaxError) {
@@ -7505,12 +7702,26 @@ function FeelTextfield(props) {
7505
7702
  if (feelActive || isPopupOpen) {
7506
7703
  return;
7507
7704
  }
7508
- const data = event.clipboardData.getData('application/FEEL');
7509
- if (data) {
7705
+ const feelData = event.clipboardData.getData('application/FEEL');
7706
+ if (feelData) {
7510
7707
  setTimeout(() => {
7511
7708
  handleFeelToggle();
7512
7709
  setFocus();
7513
7710
  });
7711
+ return;
7712
+ }
7713
+ const input = event.target;
7714
+ const isFieldEmpty = !input.value;
7715
+ const isAllSelected = input.selectionStart === 0 && input.selectionEnd === input.value.length;
7716
+ if (isFieldEmpty || isAllSelected) {
7717
+ const textData = event.clipboardData.getData('text');
7718
+ const trimmedValue = textData.trim();
7719
+ setLocalValue(trimmedValue);
7720
+ handleInput(trimmedValue);
7721
+ if (!feelActive && minDash.isString(trimmedValue) && trimmedValue.startsWith('=')) {
7722
+ setFocus(trimmedValue.length - 1);
7723
+ }
7724
+ event.preventDefault();
7514
7725
  }
7515
7726
  };
7516
7727
  containerRef.current.addEventListener('copy', copyHandler);
@@ -7546,11 +7757,12 @@ function FeelTextfield(props) {
7546
7757
  ref: containerRef,
7547
7758
  children: [jsxRuntime.jsx(FeelIndicator, {
7548
7759
  active: feelActive,
7549
- disabled: feel !== 'optional' || disabled,
7760
+ disabled: !isFeelOptional(feel) || disabled,
7550
7761
  onClick: handleFeelToggle
7551
7762
  }), feelActive ? jsxRuntime.jsx(FeelEditor, {
7552
7763
  name: id,
7553
7764
  onInput: handleLocalInput,
7765
+ onKeyDown: handleOnKeyDown,
7554
7766
  contentAttributes: {
7555
7767
  'id': prefixId$5(id),
7556
7768
  'aria-label': label
@@ -7573,6 +7785,7 @@ function FeelTextfield(props) {
7573
7785
  ...props,
7574
7786
  popupOpen: isPopupOpen,
7575
7787
  onInput: handleLocalInput,
7788
+ onKeyDown: handleOnKeyDown,
7576
7789
  onBlur: handleOnBlur,
7577
7790
  contentAttributes: {
7578
7791
  'id': prefixId$5(id),
@@ -7591,6 +7804,7 @@ const OptionalFeelInput = React.forwardRef((props, ref) => {
7591
7804
  id,
7592
7805
  disabled,
7593
7806
  onInput,
7807
+ onKeyDown,
7594
7808
  value,
7595
7809
  onFocus,
7596
7810
  onBlur,
@@ -7626,6 +7840,7 @@ const OptionalFeelInput = React.forwardRef((props, ref) => {
7626
7840
  class: "bio-properties-panel-input",
7627
7841
  onInput: e => onInput(e.target.value),
7628
7842
  onFocus: onFocus,
7843
+ onKeyDown: onKeyDown,
7629
7844
  onBlur: onBlur,
7630
7845
  placeholder: placeholder,
7631
7846
  value: value || ''
@@ -7999,6 +8214,87 @@ function isEdited$5(node) {
7999
8214
  function prefixId$5(id) {
8000
8215
  return `bio-properties-panel-${id}`;
8001
8216
  }
8217
+
8218
+ /**
8219
+ * Determine if FEEL is optional for the configured {@link FeelType}.
8220
+ *
8221
+ * @param {FeelType} feelType
8222
+ *
8223
+ * @return {boolean}
8224
+ */
8225
+ function isFeelOptional(feelType) {
8226
+ return feelType === 'optional' || feelType === 'optional-default-enabled';
8227
+ }
8228
+
8229
+ /**
8230
+ * Determine if FEEL editing is currently active.
8231
+ *
8232
+ * @param {FeelType} feelType
8233
+ * @param {string} localValue
8234
+ *
8235
+ * @return {boolean}
8236
+ */
8237
+ function isFeelActive(feelType, localValue) {
8238
+ if (feelType === 'required') {
8239
+ return true;
8240
+ }
8241
+ if (minDash.isString(localValue)) {
8242
+ if (localValue.startsWith('=')) {
8243
+ return true;
8244
+ }
8245
+ }
8246
+ return false;
8247
+ }
8248
+
8249
+ /**
8250
+ * @template T
8251
+ * @param {T} value
8252
+ *
8253
+ * @return {string|T}
8254
+ */
8255
+ function getFeelValue(value) {
8256
+ if (minDash.isString(value) && value.startsWith('=')) {
8257
+ return value.substring(1);
8258
+ }
8259
+ return value;
8260
+ }
8261
+
8262
+ /**
8263
+ * Initialize local FEEL value.
8264
+ *
8265
+ * `optional-default-enabled` starts in FEEL mode if no value or empty string is provided.
8266
+ *
8267
+ * @template T
8268
+ * @param {FeelType} feelType
8269
+ * @param {T} value
8270
+ *
8271
+ * @return {string|T}
8272
+ */
8273
+ function getInitialFeelLocalValue(feelType, value) {
8274
+ if (feelType === 'optional-default-enabled' && (value === undefined || value === '')) {
8275
+ return '=';
8276
+ }
8277
+ return value;
8278
+ }
8279
+
8280
+ /**
8281
+ * @typedef { { value: string, label: string, disabled: boolean, children: { value: string, label: string, disabled: boolean } } } Option
8282
+ */
8283
+
8284
+ /**
8285
+ * Provides basic select input.
8286
+ *
8287
+ * @param {object} props
8288
+ * @param {string} props.id
8289
+ * @param {string[]} props.path
8290
+ * @param {string} props.label
8291
+ * @param {Function} props.onChange
8292
+ * @param {Function} props.onFocus
8293
+ * @param {Function} props.onBlur
8294
+ * @param {Array<Option>} [props.options]
8295
+ * @param {string} props.value
8296
+ * @param {boolean} [props.disabled]
8297
+ */
8002
8298
  function Select(props) {
8003
8299
  const {
8004
8300
  id,
@@ -8164,12 +8460,13 @@ function TextArea(props) {
8164
8460
  id,
8165
8461
  label,
8166
8462
  debounce,
8167
- onInput,
8463
+ onInput: commitValue,
8168
8464
  value = '',
8169
8465
  disabled,
8170
8466
  monospace,
8171
8467
  onFocus,
8172
8468
  onBlur,
8469
+ onPaste,
8173
8470
  autoResize = true,
8174
8471
  placeholder,
8175
8472
  rows = autoResize ? 1 : 2,
@@ -8177,16 +8474,16 @@ function TextArea(props) {
8177
8474
  } = props;
8178
8475
  const [localValue, setLocalValue] = hooks.useState(value);
8179
8476
  const ref = useShowEntryEvent(id);
8477
+ const onInput = hooks.useCallback(newValue => {
8478
+ const newModelValue = newValue === '' ? undefined : newValue;
8479
+ commitValue(newModelValue);
8480
+ }, [commitValue]);
8180
8481
  const visible = useElementVisible(ref.current);
8181
8482
 
8182
8483
  /**
8183
8484
  * @type { import('min-dash').DebouncedFunction }
8184
8485
  */
8185
- const handleInputCallback = useDebounce(onInput, debounce);
8186
- const handleInput = newValue => {
8187
- const newModelValue = newValue === '' ? undefined : newValue;
8188
- handleInputCallback(newModelValue);
8189
- };
8486
+ const handleInput = useDebounce(onInput, debounce);
8190
8487
  const handleLocalInput = e => {
8191
8488
  autoResize && resizeToContents(e.target);
8192
8489
  if (e.target.value === localValue) {
@@ -8199,11 +8496,40 @@ function TextArea(props) {
8199
8496
  const trimmedValue = e.target.value.trim();
8200
8497
 
8201
8498
  // trim and commit on blur
8499
+ handleInput.cancel?.();
8202
8500
  onInput(trimmedValue);
8501
+ setLocalValue(trimmedValue);
8203
8502
  if (onBlur) {
8204
8503
  onBlur(e);
8205
8504
  }
8206
8505
  };
8506
+ const handleOnPaste = e => {
8507
+ const input = e.target;
8508
+ const isFieldEmpty = !input.value;
8509
+ const isAllSelected = input.selectionStart === 0 && input.selectionEnd === input.value.length;
8510
+
8511
+ // Trim and handle paste if field is empty or all content is selected
8512
+ if (isFieldEmpty || isAllSelected) {
8513
+ const trimmedValue = e.clipboardData.getData('text').trim();
8514
+ setLocalValue(trimmedValue);
8515
+ handleInput(trimmedValue);
8516
+ if (onPaste) {
8517
+ onPaste(e);
8518
+ }
8519
+ e.preventDefault();
8520
+ return;
8521
+ }
8522
+
8523
+ // Allow default paste behavior for normal text editing
8524
+ if (onPaste) {
8525
+ onPaste(e);
8526
+ }
8527
+ };
8528
+ const handleOnKeyDown = e => {
8529
+ if (isCmdWithChar(e)) {
8530
+ handleInput.flush?.();
8531
+ }
8532
+ };
8207
8533
  hooks.useLayoutEffect(() => {
8208
8534
  autoResize && resizeToContents(ref.current);
8209
8535
  }, []);
@@ -8235,7 +8561,9 @@ function TextArea(props) {
8235
8561
  class: classnames('bio-properties-panel-input', monospace ? 'bio-properties-panel-input-monospace' : '', autoResize ? 'auto-resize' : ''),
8236
8562
  onInput: handleLocalInput,
8237
8563
  onFocus: onFocus,
8564
+ onKeyDown: handleOnKeyDown,
8238
8565
  onBlur: handleOnBlur,
8566
+ onPaste: handleOnPaste,
8239
8567
  placeholder: placeholder,
8240
8568
  rows: rows,
8241
8569
  value: localValue,
@@ -8256,6 +8584,7 @@ function TextArea(props) {
8256
8584
  * @param {Function} props.setValue
8257
8585
  * @param {Function} props.onFocus
8258
8586
  * @param {Function} props.onBlur
8587
+ * @param {Function} props.onPaste
8259
8588
  * @param {number} props.rows
8260
8589
  * @param {boolean} props.monospace
8261
8590
  * @param {Function} [props.validate]
@@ -8276,6 +8605,7 @@ function TextAreaEntry(props) {
8276
8605
  validate,
8277
8606
  onFocus,
8278
8607
  onBlur,
8608
+ onPaste,
8279
8609
  placeholder,
8280
8610
  autoResize,
8281
8611
  tooltip
@@ -8311,6 +8641,7 @@ function TextAreaEntry(props) {
8311
8641
  onInput: onInput,
8312
8642
  onFocus: onFocus,
8313
8643
  onBlur: onBlur,
8644
+ onPaste: onPaste,
8314
8645
  rows: rows,
8315
8646
  debounce: debounce,
8316
8647
  monospace: monospace,
@@ -8344,32 +8675,57 @@ function Textfield(props) {
8344
8675
  disabled = false,
8345
8676
  id,
8346
8677
  label,
8347
- onInput,
8678
+ onInput: commitValue,
8348
8679
  onFocus,
8349
8680
  onBlur,
8681
+ onPaste,
8350
8682
  placeholder,
8351
8683
  value = '',
8352
8684
  tooltip
8353
8685
  } = props;
8354
8686
  const [localValue, setLocalValue] = hooks.useState(value || '');
8355
8687
  const ref = useShowEntryEvent(id);
8688
+ const onInput = hooks.useCallback(newValue => {
8689
+ const newModelValue = newValue === '' ? undefined : newValue;
8690
+ commitValue(newModelValue);
8691
+ }, [commitValue]);
8356
8692
 
8357
8693
  /**
8358
8694
  * @type { import('min-dash').DebouncedFunction }
8359
8695
  */
8360
- const handleInputCallback = useDebounce(onInput, debounce);
8696
+ const handleInput = useDebounce(onInput, debounce);
8361
8697
  const handleOnBlur = e => {
8362
8698
  const trimmedValue = e.target.value.trim();
8363
8699
 
8364
8700
  // trim and commit on blur
8701
+ handleInput.cancel?.();
8365
8702
  onInput(trimmedValue);
8703
+ setLocalValue(trimmedValue);
8366
8704
  if (onBlur) {
8367
8705
  onBlur(e);
8368
8706
  }
8369
8707
  };
8370
- const handleInput = newValue => {
8371
- const newModelValue = newValue === '' ? undefined : newValue;
8372
- handleInputCallback(newModelValue);
8708
+ const handleOnPaste = e => {
8709
+ const input = e.target;
8710
+ const isFieldEmpty = !input.value;
8711
+ const isAllSelected = input.selectionStart === 0 && input.selectionEnd === input.value.length;
8712
+
8713
+ // Trim and handle paste if field is empty or all content is selected (overwrite)
8714
+ if (isFieldEmpty || isAllSelected) {
8715
+ const trimmedValue = e.clipboardData.getData('text').trim();
8716
+ setLocalValue(trimmedValue);
8717
+ handleInput(trimmedValue);
8718
+ if (onPaste) {
8719
+ onPaste(e);
8720
+ }
8721
+ e.preventDefault();
8722
+ return;
8723
+ }
8724
+
8725
+ // Allow default paste behavior for normal text editing
8726
+ if (onPaste) {
8727
+ onPaste(e);
8728
+ }
8373
8729
  };
8374
8730
  const handleLocalInput = e => {
8375
8731
  if (e.target.value === localValue) {
@@ -8384,6 +8740,11 @@ function Textfield(props) {
8384
8740
  }
8385
8741
  setLocalValue(value);
8386
8742
  }, [value]);
8743
+ const handleOnKeyDown = e => {
8744
+ if (isCmdWithChar(e)) {
8745
+ handleInput.flush?.();
8746
+ }
8747
+ };
8387
8748
  return jsxRuntime.jsxs("div", {
8388
8749
  class: "bio-properties-panel-textfield",
8389
8750
  children: [jsxRuntime.jsx("label", {
@@ -8406,7 +8767,9 @@ function Textfield(props) {
8406
8767
  class: "bio-properties-panel-input",
8407
8768
  onInput: handleLocalInput,
8408
8769
  onFocus: onFocus,
8770
+ onKeyDown: handleOnKeyDown,
8409
8771
  onBlur: handleOnBlur,
8772
+ onPaste: handleOnPaste,
8410
8773
  placeholder: placeholder,
8411
8774
  value: localValue
8412
8775
  })]
@@ -8441,6 +8804,7 @@ function TextfieldEntry(props) {
8441
8804
  validate,
8442
8805
  onFocus,
8443
8806
  onBlur,
8807
+ onPaste,
8444
8808
  placeholder,
8445
8809
  tooltip
8446
8810
  } = props;
@@ -8476,6 +8840,7 @@ function TextfieldEntry(props) {
8476
8840
  onInput: onInput,
8477
8841
  onFocus: onFocus,
8478
8842
  onBlur: onBlur,
8843
+ onPaste: onPaste,
8479
8844
  placeholder: placeholder,
8480
8845
  value: value,
8481
8846
  tooltip: tooltip,
@@ -8745,6 +9110,7 @@ function Title(props) {
8745
9110
  class: "bio-properties-panel-popup__title",
8746
9111
  children: title
8747
9112
  }), children, showCloseButton && jsxRuntime.jsx("button", {
9113
+ type: "button",
8748
9114
  title: closeButtonTooltip,
8749
9115
  class: "bio-properties-panel-popup__close",
8750
9116
  onClick: onClose,
@@ -8786,6 +9152,25 @@ function cancel(event) {
8786
9152
  event.preventDefault();
8787
9153
  event.stopPropagation();
8788
9154
  }
9155
+
9156
+ /**
9157
+ * @typedef {Object} FeelPopupProps
9158
+ * @property {string} entryId
9159
+ * @property {Function} onInput
9160
+ * @property {Function} onClose
9161
+ * @property {string} title
9162
+ * @property {'feel'|'feelers'} type
9163
+ * @property {string} value
9164
+ * @property {Array} [links]
9165
+ * @property {Array|Object} [variables]
9166
+ * @property {Object} [position]
9167
+ * @property {string} [hostLanguage]
9168
+ * @property {boolean} [singleLine]
9169
+ * @property {HTMLElement} [sourceElement]
9170
+ * @property {HTMLElement|string} [tooltipContainer]
9171
+ * @property {Object} [eventBus]
9172
+ */
9173
+
8789
9174
  const FEEL_POPUP_WIDTH = 700;
8790
9175
  const FEEL_POPUP_HEIGHT = 250;
8791
9176