@bpmn-io/form-js-editor 1.18.0 → 1.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- var Ids = require('ids');
3
+ var ids$1 = require('ids');
4
4
  var formJsViewer = require('@bpmn-io/form-js-viewer');
5
5
  var minDash = require('min-dash');
6
6
  var classnames = require('classnames');
@@ -2890,11 +2890,35 @@ EditorActions.prototype._registerDefaultActions = function (injector) {
2890
2890
  }
2891
2891
  });
2892
2892
  }
2893
+ if (copyPaste && selection) {
2894
+ this.register('duplicate', function () {
2895
+ var selectedElements = selection.get();
2896
+ if (selectedElements.length) {
2897
+ return copyPaste.duplicate(selectedElements);
2898
+ }
2899
+ });
2900
+ }
2893
2901
  if (copyPaste) {
2894
2902
  this.register('paste', function () {
2895
2903
  copyPaste.paste();
2896
2904
  });
2897
2905
  }
2906
+ if (copyPaste && selection && rules) {
2907
+ this.register('cut', function () {
2908
+ var selectedElements = selection.get();
2909
+ if (!selectedElements.length) {
2910
+ return;
2911
+ }
2912
+ var allowed = rules.allowed('elements.delete', {
2913
+ elements: selectedElements
2914
+ });
2915
+ if (allowed === false) {
2916
+ return;
2917
+ }
2918
+ var cuttableElements = minDash.isArray(allowed) ? allowed : selectedElements;
2919
+ return copyPaste.cut(cuttableElements.slice());
2920
+ });
2921
+ }
2898
2922
  if (zoomScroll) {
2899
2923
  this.register('stepZoom', function (opts) {
2900
2924
  zoomScroll.stepZoom(opts.value);
@@ -3113,6 +3137,8 @@ const EditorExpressionLanguageModule = {
3113
3137
 
3114
3138
  var KEYS_COPY = ['c', 'C'];
3115
3139
  var KEYS_PASTE = ['v', 'V'];
3140
+ var KEYS_DUPLICATE = ['d', 'D'];
3141
+ var KEYS_CUT = ['x', 'X'];
3116
3142
  var KEYS_REDO = ['y', 'Y'];
3117
3143
  var KEYS_UNDO = ['z', 'Z'];
3118
3144
 
@@ -3128,7 +3154,7 @@ function hasModifier(event) {
3128
3154
  * @param {KeyboardEvent} event
3129
3155
  * @return {boolean}
3130
3156
  */
3131
- function isCmd(event) {
3157
+ function isCmd$1(event) {
3132
3158
  // ensure we don't react to AltGr
3133
3159
  // (mapped to CTRL + ALT)
3134
3160
  if (event.altKey) {
@@ -3160,28 +3186,42 @@ function isShift(event) {
3160
3186
  * @param {KeyboardEvent} event
3161
3187
  */
3162
3188
  function isCopy(event) {
3163
- return isCmd(event) && isKey(KEYS_COPY, event);
3189
+ return isCmd$1(event) && isKey(KEYS_COPY, event);
3164
3190
  }
3165
3191
 
3166
3192
  /**
3167
3193
  * @param {KeyboardEvent} event
3168
3194
  */
3169
3195
  function isPaste(event) {
3170
- return isCmd(event) && isKey(KEYS_PASTE, event);
3196
+ return isCmd$1(event) && isKey(KEYS_PASTE, event);
3197
+ }
3198
+
3199
+ /**
3200
+ * @param {KeyboardEvent} event
3201
+ */
3202
+ function isDuplicate(event) {
3203
+ return isCmd$1(event) && isKey(KEYS_DUPLICATE, event);
3204
+ }
3205
+
3206
+ /**
3207
+ * @param {KeyboardEvent} event
3208
+ */
3209
+ function isCut(event) {
3210
+ return isCmd$1(event) && isKey(KEYS_CUT, event);
3171
3211
  }
3172
3212
 
3173
3213
  /**
3174
3214
  * @param {KeyboardEvent} event
3175
3215
  */
3176
3216
  function isUndo(event) {
3177
- return isCmd(event) && !isShift(event) && isKey(KEYS_UNDO, event);
3217
+ return isCmd$1(event) && !isShift(event) && isKey(KEYS_UNDO, event);
3178
3218
  }
3179
3219
 
3180
3220
  /**
3181
3221
  * @param {KeyboardEvent} event
3182
3222
  */
3183
3223
  function isRedo(event) {
3184
- return isCmd(event) && (isKey(KEYS_REDO, event) || isKey(KEYS_UNDO, event) && isShift(event));
3224
+ return isCmd$1(event) && (isKey(KEYS_REDO, event) || isKey(KEYS_UNDO, event) && isShift(event));
3185
3225
  }
3186
3226
 
3187
3227
  /**
@@ -3350,7 +3390,7 @@ Keyboard.prototype.removeListener = function (listener, type) {
3350
3390
  this._eventBus.off(type || KEYDOWN_EVENT, listener);
3351
3391
  };
3352
3392
  Keyboard.prototype.hasModifier = hasModifier;
3353
- Keyboard.prototype.isCmd = isCmd;
3393
+ Keyboard.prototype.isCmd = isCmd$1;
3354
3394
  Keyboard.prototype.isShift = isShift;
3355
3395
  Keyboard.prototype.isKey = isKey;
3356
3396
 
@@ -3435,6 +3475,26 @@ KeyboardBindings.prototype.registerBindings = function (keyboard, editorActions)
3435
3475
  }
3436
3476
  });
3437
3477
 
3478
+ // duplicate
3479
+ // CTRL/CMD + D
3480
+ addListener('duplicate', function (context) {
3481
+ var event = context.keyEvent;
3482
+ if (isDuplicate(event)) {
3483
+ editorActions.trigger('duplicate');
3484
+ return true;
3485
+ }
3486
+ });
3487
+
3488
+ // cut
3489
+ // CTRL/CMD + X
3490
+ addListener('cut', function (context) {
3491
+ var event = context.keyEvent;
3492
+ if (isCut(event)) {
3493
+ editorActions.trigger('cut');
3494
+ return true;
3495
+ }
3496
+ });
3497
+
3438
3498
  // zoom in one step
3439
3499
  // CTRL/CMD + +
3440
3500
  addListener('stepZoom', function (context) {
@@ -3442,7 +3502,7 @@ KeyboardBindings.prototype.registerBindings = function (keyboard, editorActions)
3442
3502
 
3443
3503
  // quirk: it has to be triggered by `=` as well to work on international keyboard layout
3444
3504
  // cf: https://github.com/bpmn-io/bpmn-js/issues/1362#issuecomment-722989754
3445
- if (isKey(['+', 'Add', '='], event) && isCmd(event)) {
3505
+ if (isKey(['+', 'Add', '='], event) && isCmd$1(event)) {
3446
3506
  editorActions.trigger('stepZoom', {
3447
3507
  value: 1
3448
3508
  });
@@ -3454,7 +3514,7 @@ KeyboardBindings.prototype.registerBindings = function (keyboard, editorActions)
3454
3514
  // CTRL + -
3455
3515
  addListener('stepZoom', function (context) {
3456
3516
  var event = context.keyEvent;
3457
- if (isKey(['-', 'Subtract'], event) && isCmd(event)) {
3517
+ if (isKey(['-', 'Subtract'], event) && isCmd$1(event)) {
3458
3518
  editorActions.trigger('stepZoom', {
3459
3519
  value: -1
3460
3520
  });
@@ -3466,7 +3526,7 @@ KeyboardBindings.prototype.registerBindings = function (keyboard, editorActions)
3466
3526
  // CTRL + 0
3467
3527
  addListener('zoom', function (context) {
3468
3528
  var event = context.keyEvent;
3469
- if (isKey('0', event) && isCmd(event)) {
3529
+ if (isKey('0', event) && isCmd$1(event)) {
3470
3530
  editorActions.trigger('zoom', {
3471
3531
  value: 1
3472
3532
  });
@@ -5473,6 +5533,21 @@ OpenPopupIcon.defaultProps = {
5473
5533
  xmlns: "http://www.w3.org/2000/svg",
5474
5534
  viewBox: "0 0 16 16"
5475
5535
  };
5536
+
5537
+ /**
5538
+ * @typedef { {
5539
+ * getElementLabel: (element: object) => string,
5540
+ * getTypeLabel: (element: object) => string,
5541
+ * getElementIcon: (element: object) => import('preact').Component,
5542
+ * getDocumentationRef: (element: object) => string
5543
+ * } } HeaderProvider
5544
+ */
5545
+
5546
+ /**
5547
+ * @param {Object} props
5548
+ * @param {Object} props.element,
5549
+ * @param {HeaderProvider} props.headerProvider
5550
+ */
5476
5551
  function Header(props) {
5477
5552
  const {
5478
5553
  element,
@@ -5500,11 +5575,9 @@ function Header(props) {
5500
5575
  }), jsxRuntime.jsxs("div", {
5501
5576
  class: "bio-properties-panel-header-labels",
5502
5577
  children: [jsxRuntime.jsx("div", {
5503
- title: type,
5504
5578
  class: "bio-properties-panel-header-type",
5505
5579
  children: type
5506
5580
  }), label ? jsxRuntime.jsx("div", {
5507
- title: label,
5508
5581
  class: "bio-properties-panel-header-label",
5509
5582
  children: label
5510
5583
  }) : null]
@@ -5592,6 +5665,27 @@ function useTooltipContext(id, element) {
5592
5665
  } = hooks.useContext(TooltipContext);
5593
5666
  return getTooltipForId(id, element);
5594
5667
  }
5668
+
5669
+ /**
5670
+ * @typedef {Object} TooltipProps
5671
+ * @property {Object} [parent] - Parent element ref for portal rendering
5672
+ * @property {String} [direction='right'] - Tooltip direction ( 'right', 'top')
5673
+ * @property {String} [position] - Custom CSS position override
5674
+ * @property {Number} [showDelay=250] - Delay in ms before showing tooltip on hover
5675
+ * @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
5676
+ * @property {*} [children] - Child elements to render inside the tooltip wrapper
5677
+ */
5678
+
5679
+ /**
5680
+ * Tooltip wrapper that provides context-based tooltip content lookup.
5681
+ * All props are forwarded to the underlying Tooltip component.
5682
+ *
5683
+ * @param {TooltipProps & {
5684
+ * forId: String,
5685
+ * value?: String|Object,
5686
+ * element?: Object
5687
+ * }} props - Shared tooltip props plus wrapper-specific ones
5688
+ */
5595
5689
  function TooltipWrapper(props) {
5596
5690
  const {
5597
5691
  forId,
@@ -5608,35 +5702,77 @@ function TooltipWrapper(props) {
5608
5702
  forId: `bio-properties-panel-${forId}`
5609
5703
  });
5610
5704
  }
5705
+
5706
+ /**
5707
+ * @param {TooltipProps & {
5708
+ * forId: String,
5709
+ * value: String|Object
5710
+ * }} props
5711
+ */
5611
5712
  function Tooltip(props) {
5612
5713
  const {
5613
5714
  forId,
5614
5715
  value,
5615
5716
  parent,
5616
5717
  direction = 'right',
5617
- position
5718
+ position,
5719
+ showDelay = 250,
5720
+ hideDelay = 250
5618
5721
  } = props;
5619
5722
  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;
5723
+ const showTimeoutRef = hooks.useRef(null);
5724
+ const hideTimeoutRef = hooks.useRef(null);
5624
5725
  const wrapperRef = hooks.useRef(null);
5625
5726
  const tooltipRef = hooks.useRef(null);
5626
5727
  const show = (_, delay) => {
5728
+ clearTimeout(showTimeoutRef.current);
5729
+ clearTimeout(hideTimeoutRef.current);
5627
5730
  if (visible) return;
5628
5731
  if (delay) {
5629
- timeout = setTimeout(() => {
5732
+ showTimeoutRef.current = setTimeout(() => {
5630
5733
  setVisible(true);
5631
- }, SHOW_DELAY);
5734
+ }, showDelay);
5632
5735
  } else {
5633
5736
  setVisible(true);
5634
5737
  }
5635
5738
  };
5636
- const hide = () => {
5637
- clearTimeout(timeout);
5638
- setVisible(false);
5739
+ const handleWrapperMouseEnter = e => {
5740
+ show(e, true);
5741
+ };
5742
+ const hide = (delay = false) => {
5743
+ clearTimeout(showTimeoutRef.current);
5744
+ clearTimeout(hideTimeoutRef.current);
5745
+ if (delay) {
5746
+ hideTimeoutRef.current = setTimeout(() => {
5747
+ setVisible(false);
5748
+ }, hideDelay);
5749
+ } else {
5750
+ setVisible(false);
5751
+ }
5639
5752
  };
5753
+
5754
+ // Cleanup timeouts on unmount
5755
+ hooks.useEffect(() => {
5756
+ return () => {
5757
+ clearTimeout(showTimeoutRef.current);
5758
+ clearTimeout(hideTimeoutRef.current);
5759
+ };
5760
+ }, []);
5761
+
5762
+ // Handle click outside to close tooltip for non-focusable elements
5763
+ hooks.useEffect(() => {
5764
+ if (!visible) return;
5765
+ const handleClickOutside = e => {
5766
+ // If clicking outside both the wrapper and tooltip, hide it
5767
+ if (wrapperRef.current && !wrapperRef.current.contains(e.target) && tooltipRef.current && !tooltipRef.current.contains(e.target)) {
5768
+ hide(false);
5769
+ }
5770
+ };
5771
+ document.addEventListener('mousedown', handleClickOutside);
5772
+ return () => {
5773
+ document.removeEventListener('mousedown', handleClickOutside);
5774
+ };
5775
+ }, [visible, hide]);
5640
5776
  const handleMouseLeave = ({
5641
5777
  relatedTarget
5642
5778
  }) => {
@@ -5644,23 +5780,32 @@ function Tooltip(props) {
5644
5780
  if (relatedTarget === wrapperRef.current || relatedTarget === tooltipRef.current || relatedTarget?.parentElement === tooltipRef.current) {
5645
5781
  return;
5646
5782
  }
5647
- hide();
5783
+ const selection = window.getSelection();
5784
+ if (selection && selection.toString().length > 0) {
5785
+ // Check if selection is within tooltip content
5786
+ const selectionRange = selection.getRangeAt(0);
5787
+ if (tooltipRef.current?.contains(selectionRange.commonAncestorContainer) || tooltipRef.current?.contains(selection.anchorNode) || tooltipRef.current?.contains(selection.focusNode)) {
5788
+ return; // Keep tooltip open during text selection
5789
+ }
5790
+ }
5791
+ hide(true);
5792
+ };
5793
+ const handleTooltipMouseEnter = () => {
5794
+ clearTimeout(hideTimeoutRef.current);
5648
5795
  };
5649
5796
  const handleFocusOut = e => {
5650
5797
  const {
5651
- target
5798
+ relatedTarget
5652
5799
  } = e;
5653
5800
 
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();
5801
+ // Don't hide if focus moved to the tooltip or another element within the wrapper
5802
+ if (tooltipRef.current?.contains(relatedTarget) || wrapperRef.current?.contains(relatedTarget)) {
5658
5803
  return;
5659
5804
  }
5660
- hide();
5805
+ hide(false);
5661
5806
  };
5662
5807
  const hideTooltipViaEscape = e => {
5663
- e.code === 'Escape' && hide();
5808
+ e.code === 'Escape' && hide(false);
5664
5809
  };
5665
5810
  const renderTooltip = () => {
5666
5811
  return jsxRuntime.jsxs("div", {
@@ -5671,6 +5816,7 @@ function Tooltip(props) {
5671
5816
  style: position || getTooltipPosition(wrapperRef.current),
5672
5817
  ref: tooltipRef,
5673
5818
  onClick: e => e.stopPropagation(),
5819
+ onMouseEnter: handleTooltipMouseEnter,
5674
5820
  onMouseLeave: handleMouseLeave,
5675
5821
  children: [jsxRuntime.jsx("div", {
5676
5822
  class: "bio-properties-panel-tooltip-content",
@@ -5684,7 +5830,7 @@ function Tooltip(props) {
5684
5830
  class: "bio-properties-panel-tooltip-wrapper",
5685
5831
  tabIndex: "0",
5686
5832
  ref: wrapperRef,
5687
- onMouseEnter: e => show(e, true),
5833
+ onMouseEnter: handleWrapperMouseEnter,
5688
5834
  onMouseLeave: handleMouseLeave,
5689
5835
  onFocus: show,
5690
5836
  onBlur: handleFocusOut,
@@ -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,
@@ -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,
@@ -7376,6 +7563,12 @@ function FeelTextfield(props) {
7376
7563
  const [localValue, setLocalValue] = hooks.useState(value);
7377
7564
  const editorRef = useShowEntryEvent(id);
7378
7565
  const containerRef = hooks.useRef();
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]);
7379
7572
  const feelActive = minDash.isString(localValue) && localValue.startsWith('=') || feel === 'required';
7380
7573
  const feelOnlyValue = minDash.isString(localValue) && localValue.startsWith('=') ? localValue.substring(1) : localValue;
7381
7574
  const feelLanguageContext = hooks.useContext(FeelLanguageContext);
@@ -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);
@@ -7551,6 +7762,7 @@ function FeelTextfield(props) {
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,25 @@ function isEdited$5(node) {
7999
8214
  function prefixId$5(id) {
8000
8215
  return `bio-properties-panel-${id}`;
8001
8216
  }
8217
+
8218
+ /**
8219
+ * @typedef { { value: string, label: string, disabled: boolean, children: { value: string, label: string, disabled: boolean } } } Option
8220
+ */
8221
+
8222
+ /**
8223
+ * Provides basic select input.
8224
+ *
8225
+ * @param {object} props
8226
+ * @param {string} props.id
8227
+ * @param {string[]} props.path
8228
+ * @param {string} props.label
8229
+ * @param {Function} props.onChange
8230
+ * @param {Function} props.onFocus
8231
+ * @param {Function} props.onBlur
8232
+ * @param {Array<Option>} [props.options]
8233
+ * @param {string} props.value
8234
+ * @param {boolean} [props.disabled]
8235
+ */
8002
8236
  function Select(props) {
8003
8237
  const {
8004
8238
  id,
@@ -8164,12 +8398,13 @@ function TextArea(props) {
8164
8398
  id,
8165
8399
  label,
8166
8400
  debounce,
8167
- onInput,
8401
+ onInput: commitValue,
8168
8402
  value = '',
8169
8403
  disabled,
8170
8404
  monospace,
8171
8405
  onFocus,
8172
8406
  onBlur,
8407
+ onPaste,
8173
8408
  autoResize = true,
8174
8409
  placeholder,
8175
8410
  rows = autoResize ? 1 : 2,
@@ -8177,16 +8412,16 @@ function TextArea(props) {
8177
8412
  } = props;
8178
8413
  const [localValue, setLocalValue] = hooks.useState(value);
8179
8414
  const ref = useShowEntryEvent(id);
8415
+ const onInput = hooks.useCallback(newValue => {
8416
+ const newModelValue = newValue === '' ? undefined : newValue;
8417
+ commitValue(newModelValue);
8418
+ }, [commitValue]);
8180
8419
  const visible = useElementVisible(ref.current);
8181
8420
 
8182
8421
  /**
8183
8422
  * @type { import('min-dash').DebouncedFunction }
8184
8423
  */
8185
- const handleInputCallback = useDebounce(onInput, debounce);
8186
- const handleInput = newValue => {
8187
- const newModelValue = newValue === '' ? undefined : newValue;
8188
- handleInputCallback(newModelValue);
8189
- };
8424
+ const handleInput = useDebounce(onInput, debounce);
8190
8425
  const handleLocalInput = e => {
8191
8426
  autoResize && resizeToContents(e.target);
8192
8427
  if (e.target.value === localValue) {
@@ -8199,11 +8434,40 @@ function TextArea(props) {
8199
8434
  const trimmedValue = e.target.value.trim();
8200
8435
 
8201
8436
  // trim and commit on blur
8437
+ handleInput.cancel?.();
8202
8438
  onInput(trimmedValue);
8439
+ setLocalValue(trimmedValue);
8203
8440
  if (onBlur) {
8204
8441
  onBlur(e);
8205
8442
  }
8206
8443
  };
8444
+ const handleOnPaste = e => {
8445
+ const input = e.target;
8446
+ const isFieldEmpty = !input.value;
8447
+ const isAllSelected = input.selectionStart === 0 && input.selectionEnd === input.value.length;
8448
+
8449
+ // Trim and handle paste if field is empty or all content is selected
8450
+ if (isFieldEmpty || isAllSelected) {
8451
+ const trimmedValue = e.clipboardData.getData('text').trim();
8452
+ setLocalValue(trimmedValue);
8453
+ handleInput(trimmedValue);
8454
+ if (onPaste) {
8455
+ onPaste(e);
8456
+ }
8457
+ e.preventDefault();
8458
+ return;
8459
+ }
8460
+
8461
+ // Allow default paste behavior for normal text editing
8462
+ if (onPaste) {
8463
+ onPaste(e);
8464
+ }
8465
+ };
8466
+ const handleOnKeyDown = e => {
8467
+ if (isCmdWithChar(e)) {
8468
+ handleInput.flush();
8469
+ }
8470
+ };
8207
8471
  hooks.useLayoutEffect(() => {
8208
8472
  autoResize && resizeToContents(ref.current);
8209
8473
  }, []);
@@ -8235,7 +8499,9 @@ function TextArea(props) {
8235
8499
  class: classnames('bio-properties-panel-input', monospace ? 'bio-properties-panel-input-monospace' : '', autoResize ? 'auto-resize' : ''),
8236
8500
  onInput: handleLocalInput,
8237
8501
  onFocus: onFocus,
8502
+ onKeyDown: handleOnKeyDown,
8238
8503
  onBlur: handleOnBlur,
8504
+ onPaste: handleOnPaste,
8239
8505
  placeholder: placeholder,
8240
8506
  rows: rows,
8241
8507
  value: localValue,
@@ -8256,6 +8522,7 @@ function TextArea(props) {
8256
8522
  * @param {Function} props.setValue
8257
8523
  * @param {Function} props.onFocus
8258
8524
  * @param {Function} props.onBlur
8525
+ * @param {Function} props.onPaste
8259
8526
  * @param {number} props.rows
8260
8527
  * @param {boolean} props.monospace
8261
8528
  * @param {Function} [props.validate]
@@ -8276,6 +8543,7 @@ function TextAreaEntry(props) {
8276
8543
  validate,
8277
8544
  onFocus,
8278
8545
  onBlur,
8546
+ onPaste,
8279
8547
  placeholder,
8280
8548
  autoResize,
8281
8549
  tooltip
@@ -8311,6 +8579,7 @@ function TextAreaEntry(props) {
8311
8579
  onInput: onInput,
8312
8580
  onFocus: onFocus,
8313
8581
  onBlur: onBlur,
8582
+ onPaste: onPaste,
8314
8583
  rows: rows,
8315
8584
  debounce: debounce,
8316
8585
  monospace: monospace,
@@ -8344,32 +8613,57 @@ function Textfield(props) {
8344
8613
  disabled = false,
8345
8614
  id,
8346
8615
  label,
8347
- onInput,
8616
+ onInput: commitValue,
8348
8617
  onFocus,
8349
8618
  onBlur,
8619
+ onPaste,
8350
8620
  placeholder,
8351
8621
  value = '',
8352
8622
  tooltip
8353
8623
  } = props;
8354
8624
  const [localValue, setLocalValue] = hooks.useState(value || '');
8355
8625
  const ref = useShowEntryEvent(id);
8626
+ const onInput = hooks.useCallback(newValue => {
8627
+ const newModelValue = newValue === '' ? undefined : newValue;
8628
+ commitValue(newModelValue);
8629
+ }, [commitValue]);
8356
8630
 
8357
8631
  /**
8358
8632
  * @type { import('min-dash').DebouncedFunction }
8359
8633
  */
8360
- const handleInputCallback = useDebounce(onInput, debounce);
8634
+ const handleInput = useDebounce(onInput, debounce);
8361
8635
  const handleOnBlur = e => {
8362
8636
  const trimmedValue = e.target.value.trim();
8363
8637
 
8364
8638
  // trim and commit on blur
8639
+ handleInput.cancel?.();
8365
8640
  onInput(trimmedValue);
8641
+ setLocalValue(trimmedValue);
8366
8642
  if (onBlur) {
8367
8643
  onBlur(e);
8368
8644
  }
8369
8645
  };
8370
- const handleInput = newValue => {
8371
- const newModelValue = newValue === '' ? undefined : newValue;
8372
- handleInputCallback(newModelValue);
8646
+ const handleOnPaste = e => {
8647
+ const input = e.target;
8648
+ const isFieldEmpty = !input.value;
8649
+ const isAllSelected = input.selectionStart === 0 && input.selectionEnd === input.value.length;
8650
+
8651
+ // Trim and handle paste if field is empty or all content is selected (overwrite)
8652
+ if (isFieldEmpty || isAllSelected) {
8653
+ const trimmedValue = e.clipboardData.getData('text').trim();
8654
+ setLocalValue(trimmedValue);
8655
+ handleInput(trimmedValue);
8656
+ if (onPaste) {
8657
+ onPaste(e);
8658
+ }
8659
+ e.preventDefault();
8660
+ return;
8661
+ }
8662
+
8663
+ // Allow default paste behavior for normal text editing
8664
+ if (onPaste) {
8665
+ onPaste(e);
8666
+ }
8373
8667
  };
8374
8668
  const handleLocalInput = e => {
8375
8669
  if (e.target.value === localValue) {
@@ -8384,6 +8678,11 @@ function Textfield(props) {
8384
8678
  }
8385
8679
  setLocalValue(value);
8386
8680
  }, [value]);
8681
+ const handleOnKeyDown = e => {
8682
+ if (isCmdWithChar(e)) {
8683
+ handleInput.flush();
8684
+ }
8685
+ };
8387
8686
  return jsxRuntime.jsxs("div", {
8388
8687
  class: "bio-properties-panel-textfield",
8389
8688
  children: [jsxRuntime.jsx("label", {
@@ -8406,7 +8705,9 @@ function Textfield(props) {
8406
8705
  class: "bio-properties-panel-input",
8407
8706
  onInput: handleLocalInput,
8408
8707
  onFocus: onFocus,
8708
+ onKeyDown: handleOnKeyDown,
8409
8709
  onBlur: handleOnBlur,
8710
+ onPaste: handleOnPaste,
8410
8711
  placeholder: placeholder,
8411
8712
  value: localValue
8412
8713
  })]
@@ -8441,6 +8742,7 @@ function TextfieldEntry(props) {
8441
8742
  validate,
8442
8743
  onFocus,
8443
8744
  onBlur,
8745
+ onPaste,
8444
8746
  placeholder,
8445
8747
  tooltip
8446
8748
  } = props;
@@ -8476,6 +8778,7 @@ function TextfieldEntry(props) {
8476
8778
  onInput: onInput,
8477
8779
  onFocus: onFocus,
8478
8780
  onBlur: onBlur,
8781
+ onPaste: onPaste,
8479
8782
  placeholder: placeholder,
8480
8783
  value: value,
8481
8784
  tooltip: tooltip,
@@ -8786,6 +9089,25 @@ function cancel(event) {
8786
9089
  event.preventDefault();
8787
9090
  event.stopPropagation();
8788
9091
  }
9092
+
9093
+ /**
9094
+ * @typedef {Object} FeelPopupProps
9095
+ * @property {string} entryId
9096
+ * @property {Function} onInput
9097
+ * @property {Function} onClose
9098
+ * @property {string} title
9099
+ * @property {'feel'|'feelers'} type
9100
+ * @property {string} value
9101
+ * @property {Array} [links]
9102
+ * @property {Array|Object} [variables]
9103
+ * @property {Object} [position]
9104
+ * @property {string} [hostLanguage]
9105
+ * @property {boolean} [singleLine]
9106
+ * @property {HTMLElement} [sourceElement]
9107
+ * @property {HTMLElement|string} [tooltipContainer]
9108
+ * @property {Object} [eventBus]
9109
+ */
9110
+
8789
9111
  const FEEL_POPUP_WIDTH = 700;
8790
9112
  const FEEL_POPUP_HEIGHT = 250;
8791
9113
 
@@ -13880,7 +14202,7 @@ const RepeatRenderModule = {
13880
14202
  repeatRenderManager: ['type', EditorRepeatRenderManager]
13881
14203
  };
13882
14204
 
13883
- const ids = new Ids([32, 36, 1]);
14205
+ const ids = new ids$1.Ids([32, 36, 1]);
13884
14206
 
13885
14207
  /**
13886
14208
  * @typedef { import('./types').Injector } Injector