@bpmn-io/properties-panel 3.33.2 → 3.34.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.esm.js CHANGED
@@ -1,4 +1,4 @@
1
- import { useContext, useState, useRef, useCallback, useEffect, useMemo, useLayoutEffect } from '../preact/hooks';
1
+ import { useContext, useState, useRef, useEffect, useCallback, useMemo, useLayoutEffect } from '../preact/hooks';
2
2
  import { isFunction, isArray, get, assign, set, isString, isNumber, debounce } from 'min-dash';
3
3
  import { createPortal, forwardRef } from '../preact/compat';
4
4
  import { jsx, jsxs, Fragment } from '../preact/jsx-runtime';
@@ -153,6 +153,20 @@ OpenPopupIcon.defaultProps = {
153
153
  viewBox: "0 0 16 16"
154
154
  };
155
155
 
156
+ /**
157
+ * @typedef { {
158
+ * getElementLabel: (element: object) => string,
159
+ * getTypeLabel: (element: object) => string,
160
+ * getElementIcon: (element: object) => import('preact').Component,
161
+ * getDocumentationRef: (element: object) => string
162
+ * } } HeaderProvider
163
+ */
164
+
165
+ /**
166
+ * @param {Object} props
167
+ * @param {Object} props.element,
168
+ * @param {HeaderProvider} props.headerProvider
169
+ */
156
170
  function Header(props) {
157
171
  const {
158
172
  element,
@@ -180,11 +194,9 @@ function Header(props) {
180
194
  }), jsxs("div", {
181
195
  class: "bio-properties-panel-header-labels",
182
196
  children: [jsx("div", {
183
- title: type,
184
197
  class: "bio-properties-panel-header-type",
185
198
  children: type
186
199
  }), label ? jsx("div", {
187
- title: label,
188
200
  class: "bio-properties-panel-header-label",
189
201
  children: label
190
202
  }) : null]
@@ -277,6 +289,26 @@ function useTooltipContext(id, element) {
277
289
  return getTooltipForId(id, element);
278
290
  }
279
291
 
292
+ /**
293
+ * @typedef {Object} TooltipProps
294
+ * @property {Object} [parent] - Parent element ref for portal rendering
295
+ * @property {String} [direction='right'] - Tooltip direction ( 'right', 'top')
296
+ * @property {String} [position] - Custom CSS position override
297
+ * @property {Number} [showDelay=250] - Delay in ms before showing tooltip on hover
298
+ * @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
299
+ * @property {*} [children] - Child elements to render inside the tooltip wrapper
300
+ */
301
+
302
+ /**
303
+ * Tooltip wrapper that provides context-based tooltip content lookup.
304
+ * All props are forwarded to the underlying Tooltip component.
305
+ *
306
+ * @param {TooltipProps & {
307
+ * forId: String,
308
+ * value?: String|Object,
309
+ * element?: Object
310
+ * }} props - Shared tooltip props plus wrapper-specific ones
311
+ */
280
312
  function TooltipWrapper(props) {
281
313
  const {
282
314
  forId,
@@ -293,35 +325,77 @@ function TooltipWrapper(props) {
293
325
  forId: `bio-properties-panel-${forId}`
294
326
  });
295
327
  }
328
+
329
+ /**
330
+ * @param {TooltipProps & {
331
+ * forId: String,
332
+ * value: String|Object
333
+ * }} props
334
+ */
296
335
  function Tooltip(props) {
297
336
  const {
298
337
  forId,
299
338
  value,
300
339
  parent,
301
340
  direction = 'right',
302
- position
341
+ position,
342
+ showDelay = 250,
343
+ hideDelay = 250
303
344
  } = props;
304
345
  const [visible, setVisible] = useState(false);
305
-
306
- // Tooltip will be shown after SHOW_DELAY ms from hovering over the source element.
307
- const SHOW_DELAY = 200;
308
- let timeout = null;
346
+ const showTimeoutRef = useRef(null);
347
+ const hideTimeoutRef = useRef(null);
309
348
  const wrapperRef = useRef(null);
310
349
  const tooltipRef = useRef(null);
311
350
  const show = (_, delay) => {
351
+ clearTimeout(showTimeoutRef.current);
352
+ clearTimeout(hideTimeoutRef.current);
312
353
  if (visible) return;
313
354
  if (delay) {
314
- timeout = setTimeout(() => {
355
+ showTimeoutRef.current = setTimeout(() => {
315
356
  setVisible(true);
316
- }, SHOW_DELAY);
357
+ }, showDelay);
317
358
  } else {
318
359
  setVisible(true);
319
360
  }
320
361
  };
321
- const hide = () => {
322
- clearTimeout(timeout);
323
- setVisible(false);
362
+ const handleWrapperMouseEnter = e => {
363
+ show(e, true);
364
+ };
365
+ const hide = (delay = false) => {
366
+ clearTimeout(showTimeoutRef.current);
367
+ clearTimeout(hideTimeoutRef.current);
368
+ if (delay) {
369
+ hideTimeoutRef.current = setTimeout(() => {
370
+ setVisible(false);
371
+ }, hideDelay);
372
+ } else {
373
+ setVisible(false);
374
+ }
324
375
  };
376
+
377
+ // Cleanup timeouts on unmount
378
+ useEffect(() => {
379
+ return () => {
380
+ clearTimeout(showTimeoutRef.current);
381
+ clearTimeout(hideTimeoutRef.current);
382
+ };
383
+ }, []);
384
+
385
+ // Handle click outside to close tooltip for non-focusable elements
386
+ useEffect(() => {
387
+ if (!visible) return;
388
+ const handleClickOutside = e => {
389
+ // If clicking outside both the wrapper and tooltip, hide it
390
+ if (wrapperRef.current && !wrapperRef.current.contains(e.target) && tooltipRef.current && !tooltipRef.current.contains(e.target)) {
391
+ hide(false);
392
+ }
393
+ };
394
+ document.addEventListener('mousedown', handleClickOutside);
395
+ return () => {
396
+ document.removeEventListener('mousedown', handleClickOutside);
397
+ };
398
+ }, [visible, hide]);
325
399
  const handleMouseLeave = ({
326
400
  relatedTarget
327
401
  }) => {
@@ -329,23 +403,32 @@ function Tooltip(props) {
329
403
  if (relatedTarget === wrapperRef.current || relatedTarget === tooltipRef.current || relatedTarget?.parentElement === tooltipRef.current) {
330
404
  return;
331
405
  }
332
- hide();
406
+ const selection = window.getSelection();
407
+ if (selection && selection.toString().length > 0) {
408
+ // Check if selection is within tooltip content
409
+ const selectionRange = selection.getRangeAt(0);
410
+ if (tooltipRef.current?.contains(selectionRange.commonAncestorContainer) || tooltipRef.current?.contains(selection.anchorNode) || tooltipRef.current?.contains(selection.focusNode)) {
411
+ return; // Keep tooltip open during text selection
412
+ }
413
+ }
414
+ hide(true);
415
+ };
416
+ const handleTooltipMouseEnter = () => {
417
+ clearTimeout(hideTimeoutRef.current);
333
418
  };
334
419
  const handleFocusOut = e => {
335
420
  const {
336
- target
421
+ relatedTarget
337
422
  } = e;
338
423
 
339
- // Don't hide the tooltip if the wrapper or the tooltip itself is clicked.
340
- const isHovered = target.matches(':hover') || tooltipRef.current?.matches(':hover');
341
- if (target === wrapperRef.current && isHovered) {
342
- e.stopPropagation();
424
+ // Don't hide if focus moved to the tooltip or another element within the wrapper
425
+ if (tooltipRef.current?.contains(relatedTarget) || wrapperRef.current?.contains(relatedTarget)) {
343
426
  return;
344
427
  }
345
- hide();
428
+ hide(false);
346
429
  };
347
430
  const hideTooltipViaEscape = e => {
348
- e.code === 'Escape' && hide();
431
+ e.code === 'Escape' && hide(false);
349
432
  };
350
433
  const renderTooltip = () => {
351
434
  return jsxs("div", {
@@ -356,6 +439,7 @@ function Tooltip(props) {
356
439
  style: position || getTooltipPosition(wrapperRef.current),
357
440
  ref: tooltipRef,
358
441
  onClick: e => e.stopPropagation(),
442
+ onMouseEnter: handleTooltipMouseEnter,
359
443
  onMouseLeave: handleMouseLeave,
360
444
  children: [jsx("div", {
361
445
  class: "bio-properties-panel-tooltip-content",
@@ -369,7 +453,7 @@ function Tooltip(props) {
369
453
  class: "bio-properties-panel-tooltip-wrapper",
370
454
  tabIndex: "0",
371
455
  ref: wrapperRef,
372
- onMouseEnter: e => show(e, true),
456
+ onMouseEnter: handleWrapperMouseEnter,
373
457
  onMouseLeave: handleMouseLeave,
374
458
  onFocus: show,
375
459
  onBlur: handleFocusOut,
@@ -685,6 +769,9 @@ function useElementVisible(element) {
685
769
  return visible;
686
770
  }
687
771
 
772
+ /**
773
+ * @param {import('../PropertiesPanel').GroupDefinition} props
774
+ */
688
775
  function Group(props) {
689
776
  const {
690
777
  element,
@@ -739,8 +826,6 @@ function Group(props) {
739
826
  class: classnames('bio-properties-panel-group-header', edited ? '' : 'empty', open ? 'open' : '', sticky && open ? 'sticky' : ''),
740
827
  onClick: toggleOpen,
741
828
  children: [jsx("div", {
742
- title: props.tooltip ? null : label,
743
- "data-title": label,
744
829
  class: "bio-properties-panel-group-header-title",
745
830
  children: jsx(TooltipWrapper, {
746
831
  value: props.tooltip,
@@ -1093,6 +1178,13 @@ function useUpdateLayoutEffect(effect, deps) {
1093
1178
  }, deps);
1094
1179
  }
1095
1180
 
1181
+ /**
1182
+ *
1183
+ * @param {object} props
1184
+ * @param {string} [props.class]
1185
+ * @param {import('preact').Component[]} [props.menuItems]
1186
+ * @returns
1187
+ */
1096
1188
  function DropdownButton(props) {
1097
1189
  const {
1098
1190
  class: className,
@@ -1250,7 +1342,6 @@ function CollapsibleEntry(props) {
1250
1342
  class: "bio-properties-panel-collapsible-entry-header",
1251
1343
  onClick: toggleOpen,
1252
1344
  children: [jsx("div", {
1253
- title: label || placeholderLabel,
1254
1345
  class: classnames('bio-properties-panel-collapsible-entry-header-title', !label && 'empty'),
1255
1346
  children: label || placeholderLabel
1256
1347
  }), jsx("button", {
@@ -1287,6 +1378,9 @@ function CollapsibleEntry(props) {
1287
1378
  });
1288
1379
  }
1289
1380
 
1381
+ /**
1382
+ * @param {import('../PropertiesPanel').ListItemDefinition} props
1383
+ */
1290
1384
  function ListItem(props) {
1291
1385
  const {
1292
1386
  autoFocusEntry,
@@ -1389,8 +1483,6 @@ function ListGroup(props) {
1389
1483
  class: classnames('bio-properties-panel-group-header', hasItems ? '' : 'empty', hasItems && open ? 'open' : '', sticky && open ? 'sticky' : ''),
1390
1484
  onClick: hasItems ? toggleOpen : noop$6,
1391
1485
  children: [jsx("div", {
1392
- title: props.tooltip ? null : label,
1393
- "data-title": label,
1394
1486
  class: "bio-properties-panel-group-header-title",
1395
1487
  children: jsx(TooltipWrapper, {
1396
1488
  value: props.tooltip,
@@ -1460,6 +1552,12 @@ function getNewItemIds(newItems, oldItems) {
1460
1552
  return newIds.filter(itemId => !oldIds.includes(itemId));
1461
1553
  }
1462
1554
 
1555
+ /**
1556
+ * @param {Object} props
1557
+ * @param {Object} props.element
1558
+ * @param {String} props.forId - id of the entry the description is used for
1559
+ * @param {String} props.value
1560
+ */
1463
1561
  function Description(props) {
1464
1562
  const {
1465
1563
  element,
@@ -1591,6 +1689,15 @@ function prefixId$8(id) {
1591
1689
  return `bio-properties-panel-${id}`;
1592
1690
  }
1593
1691
 
1692
+ /**
1693
+ * Button to open popups.
1694
+ *
1695
+ * @param {Object} props
1696
+ * @param {Function} props.onClick - Callback to trigger when the button is clicked.
1697
+ * @param {string} [props.title] - Tooltip text for the button.
1698
+ * @param {boolean} [props.disabled] - Whether the button is disabled.
1699
+ * @param {string} [props.className] - Additional class names for the button.
1700
+ */
1594
1701
  function OpenPopupButton({
1595
1702
  onClick,
1596
1703
  title = 'Open pop-up editor'
@@ -1736,6 +1843,7 @@ const FeelEditor = forwardRef((props, ref) => {
1736
1843
  enableGutters,
1737
1844
  value,
1738
1845
  onInput,
1846
+ onKeyDown: onKeyDownProp = noop$4,
1739
1847
  onFeelToggle = noop$4,
1740
1848
  onLint = noop$4,
1741
1849
  onOpenPopup = noop$4,
@@ -1768,6 +1876,8 @@ const FeelEditor = forwardRef((props, ref) => {
1768
1876
  * - AND the cursor is at the beginning of the input
1769
1877
  */
1770
1878
  const onKeyDown = e => {
1879
+ // Call parent onKeyDown handler first
1880
+ onKeyDownProp(e);
1771
1881
  if (e.key !== 'Backspace' || !editor) {
1772
1882
  return;
1773
1883
  }
@@ -1887,6 +1997,22 @@ function FeelIcon(props) {
1887
1997
  });
1888
1998
  }
1889
1999
 
2000
+ /**
2001
+ * @param {KeyboardEvent} event
2002
+ * @return {boolean}
2003
+ */
2004
+ function isCmd(event) {
2005
+ // ensure we don't react to AltGr
2006
+ // (mapped to CTRL + ALT)
2007
+ if (event.altKey) {
2008
+ return false;
2009
+ }
2010
+ return event.ctrlKey || event.metaKey;
2011
+ }
2012
+ function isCmdWithChar(event) {
2013
+ return isCmd(event) && event.key.length === 1 && /^[a-zA-Z]$/.test(event.key);
2014
+ }
2015
+
1890
2016
  function ToggleSwitch(props) {
1891
2017
  const {
1892
2018
  id,
@@ -2254,7 +2380,7 @@ function FeelTextfield(props) {
2254
2380
  handleInput(feelOnlyValue);
2255
2381
  }
2256
2382
  });
2257
- const handleLocalInput = newValue => {
2383
+ const handleLocalInput = (newValue, useDebounce = true) => {
2258
2384
  if (feelActive) {
2259
2385
  newValue = '=' + newValue;
2260
2386
  }
@@ -2262,23 +2388,33 @@ function FeelTextfield(props) {
2262
2388
  return;
2263
2389
  }
2264
2390
  setLocalValue(newValue);
2265
- handleInput(newValue);
2391
+ if (useDebounce) {
2392
+ handleInput(newValue);
2393
+ } else {
2394
+ onInput(newValue);
2395
+ }
2266
2396
  if (!feelActive && isString(newValue) && newValue.startsWith('=')) {
2267
2397
  // focus is behind `=` sign that will be removed
2268
2398
  setFocus(-1);
2269
2399
  }
2270
2400
  };
2271
2401
  const handleOnBlur = e => {
2402
+ handleInput.cancel?.();
2272
2403
  if (e.target.type === 'checkbox') {
2273
2404
  onInput(e.target.checked);
2274
2405
  } else {
2275
2406
  const trimmedValue = e.target.value.trim();
2276
- onInput(trimmedValue);
2407
+ handleLocalInput(trimmedValue, false);
2277
2408
  }
2278
2409
  if (onBlur) {
2279
2410
  onBlur(e);
2280
2411
  }
2281
2412
  };
2413
+ const handleOnKeyDown = e => {
2414
+ if (isCmdWithChar(e)) {
2415
+ handleInput.flush();
2416
+ }
2417
+ };
2282
2418
  const handleLint = useStaticCallback((lint = []) => {
2283
2419
  const syntaxError = lint.some(report => report.type === 'Syntax Error');
2284
2420
  if (syntaxError) {
@@ -2345,12 +2481,23 @@ function FeelTextfield(props) {
2345
2481
  if (feelActive || isPopupOpen) {
2346
2482
  return;
2347
2483
  }
2348
- const data = event.clipboardData.getData('application/FEEL');
2349
- if (data) {
2484
+ const feelData = event.clipboardData.getData('application/FEEL');
2485
+ if (feelData) {
2350
2486
  setTimeout(() => {
2351
2487
  handleFeelToggle();
2352
2488
  setFocus();
2353
2489
  });
2490
+ return;
2491
+ }
2492
+ const input = event.target;
2493
+ const isFieldEmpty = !input.value;
2494
+ const isAllSelected = input.selectionStart === 0 && input.selectionEnd === input.value.length;
2495
+ if (isFieldEmpty || isAllSelected) {
2496
+ const textData = event.clipboardData.getData('text');
2497
+ const trimmedValue = textData.trim();
2498
+ setLocalValue(trimmedValue);
2499
+ handleInput(trimmedValue);
2500
+ event.preventDefault();
2354
2501
  }
2355
2502
  };
2356
2503
  containerRef.current.addEventListener('copy', copyHandler);
@@ -2391,6 +2538,7 @@ function FeelTextfield(props) {
2391
2538
  }), feelActive ? jsx(FeelEditor, {
2392
2539
  name: id,
2393
2540
  onInput: handleLocalInput,
2541
+ onKeyDown: handleOnKeyDown,
2394
2542
  contentAttributes: {
2395
2543
  'id': prefixId$5(id),
2396
2544
  'aria-label': label
@@ -2413,6 +2561,7 @@ function FeelTextfield(props) {
2413
2561
  ...props,
2414
2562
  popupOpen: isPopupOpen,
2415
2563
  onInput: handleLocalInput,
2564
+ onKeyDown: handleOnKeyDown,
2416
2565
  onBlur: handleOnBlur,
2417
2566
  contentAttributes: {
2418
2567
  'id': prefixId$5(id),
@@ -2431,6 +2580,7 @@ const OptionalFeelInput = forwardRef((props, ref) => {
2431
2580
  id,
2432
2581
  disabled,
2433
2582
  onInput,
2583
+ onKeyDown,
2434
2584
  value,
2435
2585
  onFocus,
2436
2586
  onBlur,
@@ -2466,6 +2616,7 @@ const OptionalFeelInput = forwardRef((props, ref) => {
2466
2616
  class: "bio-properties-panel-input",
2467
2617
  onInput: e => onInput(e.target.value),
2468
2618
  onFocus: onFocus,
2619
+ onKeyDown: onKeyDown,
2469
2620
  onBlur: onBlur,
2470
2621
  placeholder: placeholder,
2471
2622
  value: value || ''
@@ -3060,6 +3211,22 @@ function prefixIdLabel(id) {
3060
3211
  return `bio-properties-panel-feelers-${id}-label`;
3061
3212
  }
3062
3213
 
3214
+ /**
3215
+ * Entry for handling lists represented as nested entries.
3216
+ *
3217
+ * @template Item
3218
+ * @param {object} props
3219
+ * @param {string} props.id
3220
+ * @param {*} props.element
3221
+ * @param {Function} props.onAdd
3222
+ * @param {import('preact').Component} props.component
3223
+ * @param {string} [props.label='<empty>']
3224
+ * @param {Function} [props.onRemove]
3225
+ * @param {Item[]} [props.items]
3226
+ * @param {boolean} [props.open]
3227
+ * @param {string|boolean} [props.autoFocusEntry] either a custom selector string or true to focus the first input
3228
+ * @returns
3229
+ */
3063
3230
  function List(props) {
3064
3231
  const {
3065
3232
  id,
@@ -3107,7 +3274,6 @@ function List(props) {
3107
3274
  class: classnames('bio-properties-panel-list-entry-header', sticky && open ? 'sticky' : ''),
3108
3275
  onClick: toggleOpen,
3109
3276
  children: [jsx("div", {
3110
- title: label,
3111
3277
  class: classnames('bio-properties-panel-list-entry-header-title', open && 'open'),
3112
3278
  children: label
3113
3279
  }), jsxs("div", {
@@ -3212,6 +3378,24 @@ function useNewItems(items = [], shouldReset) {
3212
3378
  return previousItems ? items.filter(item => !previousItems.includes(item)) : [];
3213
3379
  }
3214
3380
 
3381
+ /**
3382
+ * @typedef { { value: string, label: string, disabled: boolean, children: { value: string, label: string, disabled: boolean } } } Option
3383
+ */
3384
+
3385
+ /**
3386
+ * Provides basic select input.
3387
+ *
3388
+ * @param {object} props
3389
+ * @param {string} props.id
3390
+ * @param {string[]} props.path
3391
+ * @param {string} props.label
3392
+ * @param {Function} props.onChange
3393
+ * @param {Function} props.onFocus
3394
+ * @param {Function} props.onBlur
3395
+ * @param {Array<Option>} [props.options]
3396
+ * @param {string} props.value
3397
+ * @param {boolean} [props.disabled]
3398
+ */
3215
3399
  function Select(props) {
3216
3400
  const {
3217
3401
  id,
@@ -3366,6 +3550,17 @@ function prefixId$4(id) {
3366
3550
  return `bio-properties-panel-${id}`;
3367
3551
  }
3368
3552
 
3553
+ /**
3554
+ * @param {Object} props
3555
+ * @param {Function} props.debounce
3556
+ * @param {Boolean} [props.disabled]
3557
+ * @param {Object} props.element
3558
+ * @param {Function} props.getValue
3559
+ * @param {String} props.id
3560
+ * @param {Function} [props.onBlur]
3561
+ * @param {Function} [props.onFocus]
3562
+ * @param {Function} props.setValue
3563
+ */
3369
3564
  function Simple(props) {
3370
3565
  const {
3371
3566
  debounce,
@@ -3438,6 +3633,7 @@ function TextArea(props) {
3438
3633
  monospace,
3439
3634
  onFocus,
3440
3635
  onBlur,
3636
+ onPaste,
3441
3637
  autoResize = true,
3442
3638
  placeholder,
3443
3639
  rows = autoResize ? 1 : 2,
@@ -3467,11 +3663,40 @@ function TextArea(props) {
3467
3663
  const trimmedValue = e.target.value.trim();
3468
3664
 
3469
3665
  // trim and commit on blur
3666
+ handleInput.cancel?.();
3470
3667
  onInput(trimmedValue);
3668
+ setLocalValue(trimmedValue);
3471
3669
  if (onBlur) {
3472
3670
  onBlur(e);
3473
3671
  }
3474
3672
  };
3673
+ const handleOnPaste = e => {
3674
+ const input = e.target;
3675
+ const isFieldEmpty = !input.value;
3676
+ const isAllSelected = input.selectionStart === 0 && input.selectionEnd === input.value.length;
3677
+
3678
+ // Trim and handle paste if field is empty or all content is selected
3679
+ if (isFieldEmpty || isAllSelected) {
3680
+ const trimmedValue = e.clipboardData.getData('text').trim();
3681
+ setLocalValue(trimmedValue);
3682
+ handleInput(trimmedValue);
3683
+ if (onPaste) {
3684
+ onPaste(e);
3685
+ }
3686
+ e.preventDefault();
3687
+ return;
3688
+ }
3689
+
3690
+ // Allow default paste behavior for normal text editing
3691
+ if (onPaste) {
3692
+ onPaste(e);
3693
+ }
3694
+ };
3695
+ const handleOnKeyDown = e => {
3696
+ if (isCmdWithChar(e)) {
3697
+ handleInput.flush();
3698
+ }
3699
+ };
3475
3700
  useLayoutEffect(() => {
3476
3701
  autoResize && resizeToContents(ref.current);
3477
3702
  }, []);
@@ -3503,7 +3728,9 @@ function TextArea(props) {
3503
3728
  class: classnames('bio-properties-panel-input', monospace ? 'bio-properties-panel-input-monospace' : '', autoResize ? 'auto-resize' : ''),
3504
3729
  onInput: handleLocalInput,
3505
3730
  onFocus: onFocus,
3731
+ onKeyDown: handleOnKeyDown,
3506
3732
  onBlur: handleOnBlur,
3733
+ onPaste: handleOnPaste,
3507
3734
  placeholder: placeholder,
3508
3735
  rows: rows,
3509
3736
  value: localValue,
@@ -3524,6 +3751,7 @@ function TextArea(props) {
3524
3751
  * @param {Function} props.setValue
3525
3752
  * @param {Function} props.onFocus
3526
3753
  * @param {Function} props.onBlur
3754
+ * @param {Function} props.onPaste
3527
3755
  * @param {number} props.rows
3528
3756
  * @param {boolean} props.monospace
3529
3757
  * @param {Function} [props.validate]
@@ -3544,6 +3772,7 @@ function TextAreaEntry(props) {
3544
3772
  validate,
3545
3773
  onFocus,
3546
3774
  onBlur,
3775
+ onPaste,
3547
3776
  placeholder,
3548
3777
  autoResize,
3549
3778
  tooltip
@@ -3579,6 +3808,7 @@ function TextAreaEntry(props) {
3579
3808
  onInput: onInput,
3580
3809
  onFocus: onFocus,
3581
3810
  onBlur: onBlur,
3811
+ onPaste: onPaste,
3582
3812
  rows: rows,
3583
3813
  debounce: debounce,
3584
3814
  monospace: monospace,
@@ -3616,6 +3846,7 @@ function Textfield(props) {
3616
3846
  onInput: commitValue,
3617
3847
  onFocus,
3618
3848
  onBlur,
3849
+ onPaste,
3619
3850
  placeholder,
3620
3851
  value = '',
3621
3852
  tooltip
@@ -3635,11 +3866,35 @@ function Textfield(props) {
3635
3866
  const trimmedValue = e.target.value.trim();
3636
3867
 
3637
3868
  // trim and commit on blur
3869
+ handleInput.cancel?.();
3638
3870
  onInput(trimmedValue);
3871
+ setLocalValue(trimmedValue);
3639
3872
  if (onBlur) {
3640
3873
  onBlur(e);
3641
3874
  }
3642
3875
  };
3876
+ const handleOnPaste = e => {
3877
+ const input = e.target;
3878
+ const isFieldEmpty = !input.value;
3879
+ const isAllSelected = input.selectionStart === 0 && input.selectionEnd === input.value.length;
3880
+
3881
+ // Trim and handle paste if field is empty or all content is selected (overwrite)
3882
+ if (isFieldEmpty || isAllSelected) {
3883
+ const trimmedValue = e.clipboardData.getData('text').trim();
3884
+ setLocalValue(trimmedValue);
3885
+ handleInput(trimmedValue);
3886
+ if (onPaste) {
3887
+ onPaste(e);
3888
+ }
3889
+ e.preventDefault();
3890
+ return;
3891
+ }
3892
+
3893
+ // Allow default paste behavior for normal text editing
3894
+ if (onPaste) {
3895
+ onPaste(e);
3896
+ }
3897
+ };
3643
3898
  const handleLocalInput = e => {
3644
3899
  if (e.target.value === localValue) {
3645
3900
  return;
@@ -3653,6 +3908,11 @@ function Textfield(props) {
3653
3908
  }
3654
3909
  setLocalValue(value);
3655
3910
  }, [value]);
3911
+ const handleOnKeyDown = e => {
3912
+ if (isCmdWithChar(e)) {
3913
+ handleInput.flush();
3914
+ }
3915
+ };
3656
3916
  return jsxs("div", {
3657
3917
  class: "bio-properties-panel-textfield",
3658
3918
  children: [jsx("label", {
@@ -3675,7 +3935,9 @@ function Textfield(props) {
3675
3935
  class: "bio-properties-panel-input",
3676
3936
  onInput: handleLocalInput,
3677
3937
  onFocus: onFocus,
3938
+ onKeyDown: handleOnKeyDown,
3678
3939
  onBlur: handleOnBlur,
3940
+ onPaste: handleOnPaste,
3679
3941
  placeholder: placeholder,
3680
3942
  value: localValue
3681
3943
  })]
@@ -3710,6 +3972,7 @@ function TextfieldEntry(props) {
3710
3972
  validate,
3711
3973
  onFocus,
3712
3974
  onBlur,
3975
+ onPaste,
3713
3976
  placeholder,
3714
3977
  tooltip
3715
3978
  } = props;
@@ -3745,6 +4008,7 @@ function TextfieldEntry(props) {
3745
4008
  onInput: onInput,
3746
4009
  onFocus: onFocus,
3747
4010
  onBlur: onBlur,
4011
+ onPaste: onPaste,
3748
4012
  placeholder: placeholder,
3749
4013
  value: value,
3750
4014
  tooltip: tooltip,
@@ -3777,7 +4041,7 @@ const DEFAULT_DEBOUNCE_TIME = 600;
3777
4041
  * - If `debounceDelay` is `false`, the function executes immediately without debouncing.
3778
4042
  * - If a number is provided, the function execution is delayed by the given time in milliseconds.
3779
4043
  *
3780
- * @param { Boolean | Number } [debounceDelay=300]
4044
+ * @param { Boolean | Number } [debounceDelay=600]
3781
4045
  *
3782
4046
  * @example
3783
4047
  * const debounce = debounceInput();
@@ -4097,6 +4361,24 @@ function cancel(event) {
4097
4361
  event.stopPropagation();
4098
4362
  }
4099
4363
 
4364
+ /**
4365
+ * @typedef {Object} FeelPopupProps
4366
+ * @property {string} entryId
4367
+ * @property {Function} onInput
4368
+ * @property {Function} onClose
4369
+ * @property {string} title
4370
+ * @property {'feel'|'feelers'} type
4371
+ * @property {string} value
4372
+ * @property {Array} [links]
4373
+ * @property {Array|Object} [variables]
4374
+ * @property {Object} [position]
4375
+ * @property {string} [hostLanguage]
4376
+ * @property {boolean} [singleLine]
4377
+ * @property {HTMLElement} [sourceElement]
4378
+ * @property {HTMLElement|string} [tooltipContainer]
4379
+ * @property {Object} [eventBus]
4380
+ */
4381
+
4100
4382
  const FEEL_POPUP_WIDTH = 700;
4101
4383
  const FEEL_POPUP_HEIGHT = 250;
4102
4384