@bpmn-io/form-js-editor 1.17.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.es.js CHANGED
@@ -1,7 +1,7 @@
1
- import Ids from 'ids';
1
+ import { Ids } from 'ids';
2
2
  import { FormFieldRegistry as FormFieldRegistry$1, iconsByType, Label as Label$3, IFrame, Text as Text$1, Html, Table, ExpressionField, DocumentPreview, FormFields, sanitizeImageSource, getAncestryList, FormContext, FormRenderContext, FormComponent, getScrollContainer, FieldFactory, FormLayouter, PathRegistry, Importer, FeelExpressionLanguage, OPTIONS_SOURCES, OPTIONS_SOURCES_PATHS, clone, runRecursively, getSchemaVariables, DATETIME_SUBTYPES, DATE_LABEL_PATH, TIME_LABEL_PATH, TEXT_VIEW_DEFAULT_TEXT, TIME_USE24H_PATH, DATETIME_SUBTYPE_PATH, DATETIME_SUBTYPES_LABELS, TIME_INTERVAL_PATH, TIME_SERIALISING_FORMAT_PATH, DATE_DISALLOW_PAST_PATH, TIME_SERIALISING_FORMATS, TIME_SERIALISINGFORMAT_LABELS, getOptionsSource, OPTIONS_SOURCES_DEFAULTS, OPTIONS_SOURCES_LABELS, SECURITY_ATTRIBUTES_DEFINITIONS, createFormContainer, createInjector, MarkdownRendererModule, schemaVersion } from '@bpmn-io/form-js-viewer';
3
3
  export { schemaVersion } from '@bpmn-io/form-js-viewer';
4
- import { isArray, isFunction, isNumber, bind, assign, debounce, forEach, isString, uniqueBy, isObject, get, isDefined, set as set$1, reduce, without, isNil, has } from 'min-dash';
4
+ import { isArray, isFunction, isNumber, bind, assign, debounce, forEach, isString, uniqueBy, isObject, get, isDefined, set as set$1, reduce, isNil, without, has } from 'min-dash';
5
5
  import classnames from 'classnames';
6
6
  import { jsxs, jsx, Fragment as Fragment$1 } from 'preact/jsx-runtime';
7
7
  import { useContext, useRef, useEffect, useMemo, useState, useCallback, useLayoutEffect } from 'preact/hooks';
@@ -13,7 +13,7 @@ import { classes, query, event, domify } from 'min-dom';
13
13
  import { arrayMoveMutable } from 'array-move';
14
14
  import { FeelersEditor } from 'feelers';
15
15
  import Editor from '@bpmn-io/feel-editor';
16
- import { lineNumbers, EditorView } from '@codemirror/view';
16
+ import { EditorView, lineNumbers } from '@codemirror/view';
17
17
  import * as focusTrap from 'focus-trap';
18
18
  import Big from 'big.js';
19
19
 
@@ -2870,11 +2870,35 @@ EditorActions.prototype._registerDefaultActions = function (injector) {
2870
2870
  }
2871
2871
  });
2872
2872
  }
2873
+ if (copyPaste && selection) {
2874
+ this.register('duplicate', function () {
2875
+ var selectedElements = selection.get();
2876
+ if (selectedElements.length) {
2877
+ return copyPaste.duplicate(selectedElements);
2878
+ }
2879
+ });
2880
+ }
2873
2881
  if (copyPaste) {
2874
2882
  this.register('paste', function () {
2875
2883
  copyPaste.paste();
2876
2884
  });
2877
2885
  }
2886
+ if (copyPaste && selection && rules) {
2887
+ this.register('cut', function () {
2888
+ var selectedElements = selection.get();
2889
+ if (!selectedElements.length) {
2890
+ return;
2891
+ }
2892
+ var allowed = rules.allowed('elements.delete', {
2893
+ elements: selectedElements
2894
+ });
2895
+ if (allowed === false) {
2896
+ return;
2897
+ }
2898
+ var cuttableElements = isArray(allowed) ? allowed : selectedElements;
2899
+ return copyPaste.cut(cuttableElements.slice());
2900
+ });
2901
+ }
2878
2902
  if (zoomScroll) {
2879
2903
  this.register('stepZoom', function (opts) {
2880
2904
  zoomScroll.stepZoom(opts.value);
@@ -3093,6 +3117,8 @@ const EditorExpressionLanguageModule = {
3093
3117
 
3094
3118
  var KEYS_COPY = ['c', 'C'];
3095
3119
  var KEYS_PASTE = ['v', 'V'];
3120
+ var KEYS_DUPLICATE = ['d', 'D'];
3121
+ var KEYS_CUT = ['x', 'X'];
3096
3122
  var KEYS_REDO = ['y', 'Y'];
3097
3123
  var KEYS_UNDO = ['z', 'Z'];
3098
3124
 
@@ -3108,7 +3134,7 @@ function hasModifier(event) {
3108
3134
  * @param {KeyboardEvent} event
3109
3135
  * @return {boolean}
3110
3136
  */
3111
- function isCmd(event) {
3137
+ function isCmd$1(event) {
3112
3138
  // ensure we don't react to AltGr
3113
3139
  // (mapped to CTRL + ALT)
3114
3140
  if (event.altKey) {
@@ -3140,28 +3166,42 @@ function isShift(event) {
3140
3166
  * @param {KeyboardEvent} event
3141
3167
  */
3142
3168
  function isCopy(event) {
3143
- return isCmd(event) && isKey(KEYS_COPY, event);
3169
+ return isCmd$1(event) && isKey(KEYS_COPY, event);
3144
3170
  }
3145
3171
 
3146
3172
  /**
3147
3173
  * @param {KeyboardEvent} event
3148
3174
  */
3149
3175
  function isPaste(event) {
3150
- return isCmd(event) && isKey(KEYS_PASTE, event);
3176
+ return isCmd$1(event) && isKey(KEYS_PASTE, event);
3177
+ }
3178
+
3179
+ /**
3180
+ * @param {KeyboardEvent} event
3181
+ */
3182
+ function isDuplicate(event) {
3183
+ return isCmd$1(event) && isKey(KEYS_DUPLICATE, event);
3184
+ }
3185
+
3186
+ /**
3187
+ * @param {KeyboardEvent} event
3188
+ */
3189
+ function isCut(event) {
3190
+ return isCmd$1(event) && isKey(KEYS_CUT, event);
3151
3191
  }
3152
3192
 
3153
3193
  /**
3154
3194
  * @param {KeyboardEvent} event
3155
3195
  */
3156
3196
  function isUndo(event) {
3157
- return isCmd(event) && !isShift(event) && isKey(KEYS_UNDO, event);
3197
+ return isCmd$1(event) && !isShift(event) && isKey(KEYS_UNDO, event);
3158
3198
  }
3159
3199
 
3160
3200
  /**
3161
3201
  * @param {KeyboardEvent} event
3162
3202
  */
3163
3203
  function isRedo(event) {
3164
- return isCmd(event) && (isKey(KEYS_REDO, event) || isKey(KEYS_UNDO, event) && isShift(event));
3204
+ return isCmd$1(event) && (isKey(KEYS_REDO, event) || isKey(KEYS_UNDO, event) && isShift(event));
3165
3205
  }
3166
3206
 
3167
3207
  /**
@@ -3330,7 +3370,7 @@ Keyboard.prototype.removeListener = function (listener, type) {
3330
3370
  this._eventBus.off(type || KEYDOWN_EVENT, listener);
3331
3371
  };
3332
3372
  Keyboard.prototype.hasModifier = hasModifier;
3333
- Keyboard.prototype.isCmd = isCmd;
3373
+ Keyboard.prototype.isCmd = isCmd$1;
3334
3374
  Keyboard.prototype.isShift = isShift;
3335
3375
  Keyboard.prototype.isKey = isKey;
3336
3376
 
@@ -3415,6 +3455,26 @@ KeyboardBindings.prototype.registerBindings = function (keyboard, editorActions)
3415
3455
  }
3416
3456
  });
3417
3457
 
3458
+ // duplicate
3459
+ // CTRL/CMD + D
3460
+ addListener('duplicate', function (context) {
3461
+ var event = context.keyEvent;
3462
+ if (isDuplicate(event)) {
3463
+ editorActions.trigger('duplicate');
3464
+ return true;
3465
+ }
3466
+ });
3467
+
3468
+ // cut
3469
+ // CTRL/CMD + X
3470
+ addListener('cut', function (context) {
3471
+ var event = context.keyEvent;
3472
+ if (isCut(event)) {
3473
+ editorActions.trigger('cut');
3474
+ return true;
3475
+ }
3476
+ });
3477
+
3418
3478
  // zoom in one step
3419
3479
  // CTRL/CMD + +
3420
3480
  addListener('stepZoom', function (context) {
@@ -3422,7 +3482,7 @@ KeyboardBindings.prototype.registerBindings = function (keyboard, editorActions)
3422
3482
 
3423
3483
  // quirk: it has to be triggered by `=` as well to work on international keyboard layout
3424
3484
  // cf: https://github.com/bpmn-io/bpmn-js/issues/1362#issuecomment-722989754
3425
- if (isKey(['+', 'Add', '='], event) && isCmd(event)) {
3485
+ if (isKey(['+', 'Add', '='], event) && isCmd$1(event)) {
3426
3486
  editorActions.trigger('stepZoom', {
3427
3487
  value: 1
3428
3488
  });
@@ -3434,7 +3494,7 @@ KeyboardBindings.prototype.registerBindings = function (keyboard, editorActions)
3434
3494
  // CTRL + -
3435
3495
  addListener('stepZoom', function (context) {
3436
3496
  var event = context.keyEvent;
3437
- if (isKey(['-', 'Subtract'], event) && isCmd(event)) {
3497
+ if (isKey(['-', 'Subtract'], event) && isCmd$1(event)) {
3438
3498
  editorActions.trigger('stepZoom', {
3439
3499
  value: -1
3440
3500
  });
@@ -3446,7 +3506,7 @@ KeyboardBindings.prototype.registerBindings = function (keyboard, editorActions)
3446
3506
  // CTRL + 0
3447
3507
  addListener('zoom', function (context) {
3448
3508
  var event = context.keyEvent;
3449
- if (isKey('0', event) && isCmd(event)) {
3509
+ if (isKey('0', event) && isCmd$1(event)) {
3450
3510
  editorActions.trigger('zoom', {
3451
3511
  value: 1
3452
3512
  });
@@ -5442,23 +5502,32 @@ LaunchIcon.defaultProps = {
5442
5502
  viewBox: "0 0 32 32"
5443
5503
  };
5444
5504
  var OpenPopupIcon = function OpenPopupIcon(props) {
5445
- return jsxs("svg", {
5505
+ return jsx("svg", {
5446
5506
  ...props,
5447
- children: [jsx("path", {
5448
- fill: "currentColor",
5449
- d: "M28 4H10a2.006 2.006 0 0 0-2 2v14a2.006 2.006 0 0 0 2 2h18a2.006 2.006 0 0 0 2-2V6a2.006 2.006 0 0 0-2-2Zm0 16H10V6h18Z"
5450
- }), jsx("path", {
5451
- fill: "currentColor",
5452
- d: "M18 26H4V16h2v-2H4a2.006 2.006 0 0 0-2 2v10a2.006 2.006 0 0 0 2 2h14a2.006 2.006 0 0 0 2-2v-2h-2Z"
5453
- })]
5507
+ children: jsx("path", {
5508
+ d: "M6 15v-1H2.7L7 9.7 6.3 9 2 13.3V10H1v5zm4-14v1h3.3L9 6.3l.7.7L14 2.7V6h1V1z"
5509
+ })
5454
5510
  });
5455
5511
  };
5456
5512
  OpenPopupIcon.defaultProps = {
5457
5513
  xmlns: "http://www.w3.org/2000/svg",
5458
- width: "16",
5459
- height: "16",
5460
- viewBox: "0 0 32 32"
5514
+ viewBox: "0 0 16 16"
5461
5515
  };
5516
+
5517
+ /**
5518
+ * @typedef { {
5519
+ * getElementLabel: (element: object) => string,
5520
+ * getTypeLabel: (element: object) => string,
5521
+ * getElementIcon: (element: object) => import('preact').Component,
5522
+ * getDocumentationRef: (element: object) => string
5523
+ * } } HeaderProvider
5524
+ */
5525
+
5526
+ /**
5527
+ * @param {Object} props
5528
+ * @param {Object} props.element,
5529
+ * @param {HeaderProvider} props.headerProvider
5530
+ */
5462
5531
  function Header(props) {
5463
5532
  const {
5464
5533
  element,
@@ -5486,11 +5555,9 @@ function Header(props) {
5486
5555
  }), jsxs("div", {
5487
5556
  class: "bio-properties-panel-header-labels",
5488
5557
  children: [jsx("div", {
5489
- title: type,
5490
5558
  class: "bio-properties-panel-header-type",
5491
5559
  children: type
5492
5560
  }), label ? jsx("div", {
5493
- title: label,
5494
5561
  class: "bio-properties-panel-header-label",
5495
5562
  children: label
5496
5563
  }) : null]
@@ -5578,6 +5645,27 @@ function useTooltipContext(id, element) {
5578
5645
  } = useContext(TooltipContext);
5579
5646
  return getTooltipForId(id, element);
5580
5647
  }
5648
+
5649
+ /**
5650
+ * @typedef {Object} TooltipProps
5651
+ * @property {Object} [parent] - Parent element ref for portal rendering
5652
+ * @property {String} [direction='right'] - Tooltip direction ( 'right', 'top')
5653
+ * @property {String} [position] - Custom CSS position override
5654
+ * @property {Number} [showDelay=250] - Delay in ms before showing tooltip on hover
5655
+ * @property {Number} [hideDelay=250] - Delay in ms before hiding tooltip when mouse leaves, to avoid multiple tooltips from being opened, this should be the same as showDelay
5656
+ * @property {*} [children] - Child elements to render inside the tooltip wrapper
5657
+ */
5658
+
5659
+ /**
5660
+ * Tooltip wrapper that provides context-based tooltip content lookup.
5661
+ * All props are forwarded to the underlying Tooltip component.
5662
+ *
5663
+ * @param {TooltipProps & {
5664
+ * forId: String,
5665
+ * value?: String|Object,
5666
+ * element?: Object
5667
+ * }} props - Shared tooltip props plus wrapper-specific ones
5668
+ */
5581
5669
  function TooltipWrapper(props) {
5582
5670
  const {
5583
5671
  forId,
@@ -5594,35 +5682,77 @@ function TooltipWrapper(props) {
5594
5682
  forId: `bio-properties-panel-${forId}`
5595
5683
  });
5596
5684
  }
5685
+
5686
+ /**
5687
+ * @param {TooltipProps & {
5688
+ * forId: String,
5689
+ * value: String|Object
5690
+ * }} props
5691
+ */
5597
5692
  function Tooltip(props) {
5598
5693
  const {
5599
5694
  forId,
5600
5695
  value,
5601
5696
  parent,
5602
5697
  direction = 'right',
5603
- position
5698
+ position,
5699
+ showDelay = 250,
5700
+ hideDelay = 250
5604
5701
  } = props;
5605
5702
  const [visible, setVisible] = useState(false);
5606
-
5607
- // Tooltip will be shown after SHOW_DELAY ms from hovering over the source element.
5608
- const SHOW_DELAY = 200;
5609
- let timeout = null;
5703
+ const showTimeoutRef = useRef(null);
5704
+ const hideTimeoutRef = useRef(null);
5610
5705
  const wrapperRef = useRef(null);
5611
5706
  const tooltipRef = useRef(null);
5612
5707
  const show = (_, delay) => {
5708
+ clearTimeout(showTimeoutRef.current);
5709
+ clearTimeout(hideTimeoutRef.current);
5613
5710
  if (visible) return;
5614
5711
  if (delay) {
5615
- timeout = setTimeout(() => {
5712
+ showTimeoutRef.current = setTimeout(() => {
5616
5713
  setVisible(true);
5617
- }, SHOW_DELAY);
5714
+ }, showDelay);
5618
5715
  } else {
5619
5716
  setVisible(true);
5620
5717
  }
5621
5718
  };
5622
- const hide = () => {
5623
- clearTimeout(timeout);
5624
- setVisible(false);
5719
+ const handleWrapperMouseEnter = e => {
5720
+ show(e, true);
5721
+ };
5722
+ const hide = (delay = false) => {
5723
+ clearTimeout(showTimeoutRef.current);
5724
+ clearTimeout(hideTimeoutRef.current);
5725
+ if (delay) {
5726
+ hideTimeoutRef.current = setTimeout(() => {
5727
+ setVisible(false);
5728
+ }, hideDelay);
5729
+ } else {
5730
+ setVisible(false);
5731
+ }
5625
5732
  };
5733
+
5734
+ // Cleanup timeouts on unmount
5735
+ useEffect(() => {
5736
+ return () => {
5737
+ clearTimeout(showTimeoutRef.current);
5738
+ clearTimeout(hideTimeoutRef.current);
5739
+ };
5740
+ }, []);
5741
+
5742
+ // Handle click outside to close tooltip for non-focusable elements
5743
+ useEffect(() => {
5744
+ if (!visible) return;
5745
+ const handleClickOutside = e => {
5746
+ // If clicking outside both the wrapper and tooltip, hide it
5747
+ if (wrapperRef.current && !wrapperRef.current.contains(e.target) && tooltipRef.current && !tooltipRef.current.contains(e.target)) {
5748
+ hide(false);
5749
+ }
5750
+ };
5751
+ document.addEventListener('mousedown', handleClickOutside);
5752
+ return () => {
5753
+ document.removeEventListener('mousedown', handleClickOutside);
5754
+ };
5755
+ }, [visible, hide]);
5626
5756
  const handleMouseLeave = ({
5627
5757
  relatedTarget
5628
5758
  }) => {
@@ -5630,23 +5760,32 @@ function Tooltip(props) {
5630
5760
  if (relatedTarget === wrapperRef.current || relatedTarget === tooltipRef.current || relatedTarget?.parentElement === tooltipRef.current) {
5631
5761
  return;
5632
5762
  }
5633
- hide();
5763
+ const selection = window.getSelection();
5764
+ if (selection && selection.toString().length > 0) {
5765
+ // Check if selection is within tooltip content
5766
+ const selectionRange = selection.getRangeAt(0);
5767
+ if (tooltipRef.current?.contains(selectionRange.commonAncestorContainer) || tooltipRef.current?.contains(selection.anchorNode) || tooltipRef.current?.contains(selection.focusNode)) {
5768
+ return; // Keep tooltip open during text selection
5769
+ }
5770
+ }
5771
+ hide(true);
5772
+ };
5773
+ const handleTooltipMouseEnter = () => {
5774
+ clearTimeout(hideTimeoutRef.current);
5634
5775
  };
5635
5776
  const handleFocusOut = e => {
5636
5777
  const {
5637
- target
5778
+ relatedTarget
5638
5779
  } = e;
5639
5780
 
5640
- // Don't hide the tooltip if the wrapper or the tooltip itself is clicked.
5641
- const isHovered = target.matches(':hover') || tooltipRef.current?.matches(':hover');
5642
- if (target === wrapperRef.current && isHovered) {
5643
- e.stopPropagation();
5781
+ // Don't hide if focus moved to the tooltip or another element within the wrapper
5782
+ if (tooltipRef.current?.contains(relatedTarget) || wrapperRef.current?.contains(relatedTarget)) {
5644
5783
  return;
5645
5784
  }
5646
- hide();
5785
+ hide(false);
5647
5786
  };
5648
5787
  const hideTooltipViaEscape = e => {
5649
- e.code === 'Escape' && hide();
5788
+ e.code === 'Escape' && hide(false);
5650
5789
  };
5651
5790
  const renderTooltip = () => {
5652
5791
  return jsxs("div", {
@@ -5657,6 +5796,7 @@ function Tooltip(props) {
5657
5796
  style: position || getTooltipPosition(wrapperRef.current),
5658
5797
  ref: tooltipRef,
5659
5798
  onClick: e => e.stopPropagation(),
5799
+ onMouseEnter: handleTooltipMouseEnter,
5660
5800
  onMouseLeave: handleMouseLeave,
5661
5801
  children: [jsx("div", {
5662
5802
  class: "bio-properties-panel-tooltip-content",
@@ -5670,7 +5810,7 @@ function Tooltip(props) {
5670
5810
  class: "bio-properties-panel-tooltip-wrapper",
5671
5811
  tabIndex: "0",
5672
5812
  ref: wrapperRef,
5673
- onMouseEnter: e => show(e, true),
5813
+ onMouseEnter: handleWrapperMouseEnter,
5674
5814
  onMouseLeave: handleMouseLeave,
5675
5815
  onFocus: show,
5676
5816
  onBlur: handleFocusOut,
@@ -5948,6 +6088,10 @@ function useElementVisible(element) {
5948
6088
  }, [element, visible]);
5949
6089
  return visible;
5950
6090
  }
6091
+
6092
+ /**
6093
+ * @param {import('../PropertiesPanel').GroupDefinition} props
6094
+ */
5951
6095
  function Group(props) {
5952
6096
  const {
5953
6097
  element,
@@ -6002,8 +6146,6 @@ function Group(props) {
6002
6146
  class: classnames('bio-properties-panel-group-header', edited ? '' : 'empty', open ? 'open' : '', sticky && open ? 'sticky' : ''),
6003
6147
  onClick: toggleOpen,
6004
6148
  children: [jsx("div", {
6005
- title: props.tooltip ? null : label,
6006
- "data-title": label,
6007
6149
  class: "bio-properties-panel-group-header-title",
6008
6150
  children: jsx(TooltipWrapper, {
6009
6151
  value: props.tooltip,
@@ -6207,9 +6349,11 @@ function PropertiesPanel$1(props) {
6207
6349
  return get(layout, key, defaultValue);
6208
6350
  };
6209
6351
  const setLayoutForKey = (key, config) => {
6210
- const newLayout = assign({}, layout);
6211
- set$1(newLayout, key, config);
6212
- setLayout(newLayout);
6352
+ setLayout(prevLayout => {
6353
+ const newLayout = assign({}, prevLayout);
6354
+ set$1(newLayout, key, config);
6355
+ return newLayout;
6356
+ });
6213
6357
  };
6214
6358
  const layoutContext = {
6215
6359
  layout,
@@ -6408,7 +6552,6 @@ function CollapsibleEntry(props) {
6408
6552
  class: "bio-properties-panel-collapsible-entry-header",
6409
6553
  onClick: toggleOpen,
6410
6554
  children: [jsx("div", {
6411
- title: label || placeholderLabel,
6412
6555
  class: classnames('bio-properties-panel-collapsible-entry-header-title', !label && 'empty'),
6413
6556
  children: label || placeholderLabel
6414
6557
  }), jsx("button", {
@@ -6444,6 +6587,10 @@ function CollapsibleEntry(props) {
6444
6587
  })]
6445
6588
  });
6446
6589
  }
6590
+
6591
+ /**
6592
+ * @param {import('../PropertiesPanel').ListItemDefinition} props
6593
+ */
6447
6594
  function ListItem(props) {
6448
6595
  const {
6449
6596
  autoFocusEntry,
@@ -6545,8 +6692,6 @@ function ListGroup(props) {
6545
6692
  class: classnames('bio-properties-panel-group-header', hasItems ? '' : 'empty', hasItems && open ? 'open' : '', sticky && open ? 'sticky' : ''),
6546
6693
  onClick: hasItems ? toggleOpen : noop$6,
6547
6694
  children: [jsx("div", {
6548
- title: props.tooltip ? null : label,
6549
- "data-title": label,
6550
6695
  class: "bio-properties-panel-group-header-title",
6551
6696
  children: jsx(TooltipWrapper, {
6552
6697
  value: props.tooltip,
@@ -6615,6 +6760,13 @@ function getNewItemIds(newItems, oldItems) {
6615
6760
  const oldIds = oldItems.map(item => item.id);
6616
6761
  return newIds.filter(itemId => !oldIds.includes(itemId));
6617
6762
  }
6763
+
6764
+ /**
6765
+ * @param {Object} props
6766
+ * @param {Object} props.element
6767
+ * @param {String} props.forId - id of the entry the description is used for
6768
+ * @param {String} props.value
6769
+ */
6618
6770
  function Description$1(props) {
6619
6771
  const {
6620
6772
  element,
@@ -6744,6 +6896,16 @@ function isEdited$8(node) {
6744
6896
  function prefixId$8(id) {
6745
6897
  return `bio-properties-panel-${id}`;
6746
6898
  }
6899
+
6900
+ /**
6901
+ * Button to open popups.
6902
+ *
6903
+ * @param {Object} props
6904
+ * @param {Function} props.onClick - Callback to trigger when the button is clicked.
6905
+ * @param {string} [props.title] - Tooltip text for the button.
6906
+ * @param {boolean} [props.disabled] - Whether the button is disabled.
6907
+ * @param {string} [props.className] - Additional class names for the button.
6908
+ */
6747
6909
  function OpenPopupButton({
6748
6910
  onClick,
6749
6911
  title = 'Open pop-up editor'
@@ -6887,6 +7049,7 @@ const FeelEditor = forwardRef((props, ref) => {
6887
7049
  enableGutters,
6888
7050
  value,
6889
7051
  onInput,
7052
+ onKeyDown: onKeyDownProp = noop$4,
6890
7053
  onFeelToggle = noop$4,
6891
7054
  onLint = noop$4,
6892
7055
  onOpenPopup = noop$4,
@@ -6919,6 +7082,8 @@ const FeelEditor = forwardRef((props, ref) => {
6919
7082
  * - AND the cursor is at the beginning of the input
6920
7083
  */
6921
7084
  const onKeyDown = e => {
7085
+ // Call parent onKeyDown handler first
7086
+ onKeyDownProp(e);
6922
7087
  if (e.key !== 'Backspace' || !editor) {
6923
7088
  return;
6924
7089
  }
@@ -7035,6 +7200,22 @@ function FeelIcon(props) {
7035
7200
  children: jsx(FeelIcon$1, {})
7036
7201
  });
7037
7202
  }
7203
+
7204
+ /**
7205
+ * @param {KeyboardEvent} event
7206
+ * @return {boolean}
7207
+ */
7208
+ function isCmd(event) {
7209
+ // ensure we don't react to AltGr
7210
+ // (mapped to CTRL + ALT)
7211
+ if (event.altKey) {
7212
+ return false;
7213
+ }
7214
+ return event.ctrlKey || event.metaKey;
7215
+ }
7216
+ function isCmdWithChar(event) {
7217
+ return isCmd(event) && event.key.length === 1 && /^[a-zA-Z]$/.test(event.key);
7218
+ }
7038
7219
  function ToggleSwitch(props) {
7039
7220
  const {
7040
7221
  id,
@@ -7346,7 +7527,7 @@ function FeelTextfield(props) {
7346
7527
  element,
7347
7528
  label,
7348
7529
  hostLanguage,
7349
- onInput,
7530
+ onInput: commitValue,
7350
7531
  onBlur,
7351
7532
  onError,
7352
7533
  placeholder,
@@ -7362,6 +7543,12 @@ function FeelTextfield(props) {
7362
7543
  const [localValue, setLocalValue] = useState(value);
7363
7544
  const editorRef = useShowEntryEvent(id);
7364
7545
  const containerRef = useRef();
7546
+ const onInput = useCallback(newValue => {
7547
+ // we don't commit empty FEEL expressions,
7548
+ // but instead serialize them as <undefined>
7549
+ const newModelValue = newValue === '' || newValue === '=' ? undefined : newValue;
7550
+ commitValue(newModelValue);
7551
+ }, [commitValue]);
7365
7552
  const feelActive = isString(localValue) && localValue.startsWith('=') || feel === 'required';
7366
7553
  const feelOnlyValue = isString(localValue) && localValue.startsWith('=') ? localValue.substring(1) : localValue;
7367
7554
  const feelLanguageContext = useContext(FeelLanguageContext);
@@ -7381,13 +7568,7 @@ function FeelTextfield(props) {
7381
7568
  /**
7382
7569
  * @type { import('min-dash').DebouncedFunction }
7383
7570
  */
7384
- const handleInputCallback = useDebounce(onInput, debounce);
7385
- const handleInput = newValue => {
7386
- // we don't commit empty FEEL expressions,
7387
- // but instead serialize them as <undefined>
7388
- const newModelValue = newValue === '' || newValue === '=' ? undefined : newValue;
7389
- handleInputCallback(newModelValue);
7390
- };
7571
+ const handleInput = useDebounce(onInput, debounce);
7391
7572
  const handleFeelToggle = useStaticCallback(() => {
7392
7573
  if (feel === 'required') {
7393
7574
  return;
@@ -7400,7 +7581,7 @@ function FeelTextfield(props) {
7400
7581
  handleInput(feelOnlyValue);
7401
7582
  }
7402
7583
  });
7403
- const handleLocalInput = newValue => {
7584
+ const handleLocalInput = (newValue, useDebounce = true) => {
7404
7585
  if (feelActive) {
7405
7586
  newValue = '=' + newValue;
7406
7587
  }
@@ -7408,23 +7589,33 @@ function FeelTextfield(props) {
7408
7589
  return;
7409
7590
  }
7410
7591
  setLocalValue(newValue);
7411
- handleInput(newValue);
7592
+ if (useDebounce) {
7593
+ handleInput(newValue);
7594
+ } else {
7595
+ onInput(newValue);
7596
+ }
7412
7597
  if (!feelActive && isString(newValue) && newValue.startsWith('=')) {
7413
7598
  // focus is behind `=` sign that will be removed
7414
7599
  setFocus(-1);
7415
7600
  }
7416
7601
  };
7417
7602
  const handleOnBlur = e => {
7603
+ handleInput.cancel?.();
7418
7604
  if (e.target.type === 'checkbox') {
7419
7605
  onInput(e.target.checked);
7420
7606
  } else {
7421
7607
  const trimmedValue = e.target.value.trim();
7422
- onInput(trimmedValue);
7608
+ handleLocalInput(trimmedValue, false);
7423
7609
  }
7424
7610
  if (onBlur) {
7425
7611
  onBlur(e);
7426
7612
  }
7427
7613
  };
7614
+ const handleOnKeyDown = e => {
7615
+ if (isCmdWithChar(e)) {
7616
+ handleInput.flush();
7617
+ }
7618
+ };
7428
7619
  const handleLint = useStaticCallback((lint = []) => {
7429
7620
  const syntaxError = lint.some(report => report.type === 'Syntax Error');
7430
7621
  if (syntaxError) {
@@ -7491,12 +7682,26 @@ function FeelTextfield(props) {
7491
7682
  if (feelActive || isPopupOpen) {
7492
7683
  return;
7493
7684
  }
7494
- const data = event.clipboardData.getData('application/FEEL');
7495
- if (data) {
7685
+ const feelData = event.clipboardData.getData('application/FEEL');
7686
+ if (feelData) {
7496
7687
  setTimeout(() => {
7497
7688
  handleFeelToggle();
7498
7689
  setFocus();
7499
7690
  });
7691
+ return;
7692
+ }
7693
+ const input = event.target;
7694
+ const isFieldEmpty = !input.value;
7695
+ const isAllSelected = input.selectionStart === 0 && input.selectionEnd === input.value.length;
7696
+ if (isFieldEmpty || isAllSelected) {
7697
+ const textData = event.clipboardData.getData('text');
7698
+ const trimmedValue = textData.trim();
7699
+ setLocalValue(trimmedValue);
7700
+ handleInput(trimmedValue);
7701
+ if (!feelActive && isString(trimmedValue) && trimmedValue.startsWith('=')) {
7702
+ setFocus(trimmedValue.length - 1);
7703
+ }
7704
+ event.preventDefault();
7500
7705
  }
7501
7706
  };
7502
7707
  containerRef.current.addEventListener('copy', copyHandler);
@@ -7537,6 +7742,7 @@ function FeelTextfield(props) {
7537
7742
  }), feelActive ? jsx(FeelEditor, {
7538
7743
  name: id,
7539
7744
  onInput: handleLocalInput,
7745
+ onKeyDown: handleOnKeyDown,
7540
7746
  contentAttributes: {
7541
7747
  'id': prefixId$5(id),
7542
7748
  'aria-label': label
@@ -7559,6 +7765,7 @@ function FeelTextfield(props) {
7559
7765
  ...props,
7560
7766
  popupOpen: isPopupOpen,
7561
7767
  onInput: handleLocalInput,
7768
+ onKeyDown: handleOnKeyDown,
7562
7769
  onBlur: handleOnBlur,
7563
7770
  contentAttributes: {
7564
7771
  'id': prefixId$5(id),
@@ -7577,6 +7784,7 @@ const OptionalFeelInput = forwardRef((props, ref) => {
7577
7784
  id,
7578
7785
  disabled,
7579
7786
  onInput,
7787
+ onKeyDown,
7580
7788
  value,
7581
7789
  onFocus,
7582
7790
  onBlur,
@@ -7612,6 +7820,7 @@ const OptionalFeelInput = forwardRef((props, ref) => {
7612
7820
  class: "bio-properties-panel-input",
7613
7821
  onInput: e => onInput(e.target.value),
7614
7822
  onFocus: onFocus,
7823
+ onKeyDown: onKeyDown,
7615
7824
  onBlur: onBlur,
7616
7825
  placeholder: placeholder,
7617
7826
  value: value || ''
@@ -7985,6 +8194,25 @@ function isEdited$5(node) {
7985
8194
  function prefixId$5(id) {
7986
8195
  return `bio-properties-panel-${id}`;
7987
8196
  }
8197
+
8198
+ /**
8199
+ * @typedef { { value: string, label: string, disabled: boolean, children: { value: string, label: string, disabled: boolean } } } Option
8200
+ */
8201
+
8202
+ /**
8203
+ * Provides basic select input.
8204
+ *
8205
+ * @param {object} props
8206
+ * @param {string} props.id
8207
+ * @param {string[]} props.path
8208
+ * @param {string} props.label
8209
+ * @param {Function} props.onChange
8210
+ * @param {Function} props.onFocus
8211
+ * @param {Function} props.onBlur
8212
+ * @param {Array<Option>} [props.options]
8213
+ * @param {string} props.value
8214
+ * @param {boolean} [props.disabled]
8215
+ */
7988
8216
  function Select(props) {
7989
8217
  const {
7990
8218
  id,
@@ -8150,12 +8378,13 @@ function TextArea(props) {
8150
8378
  id,
8151
8379
  label,
8152
8380
  debounce,
8153
- onInput,
8381
+ onInput: commitValue,
8154
8382
  value = '',
8155
8383
  disabled,
8156
8384
  monospace,
8157
8385
  onFocus,
8158
8386
  onBlur,
8387
+ onPaste,
8159
8388
  autoResize = true,
8160
8389
  placeholder,
8161
8390
  rows = autoResize ? 1 : 2,
@@ -8163,16 +8392,16 @@ function TextArea(props) {
8163
8392
  } = props;
8164
8393
  const [localValue, setLocalValue] = useState(value);
8165
8394
  const ref = useShowEntryEvent(id);
8395
+ const onInput = useCallback(newValue => {
8396
+ const newModelValue = newValue === '' ? undefined : newValue;
8397
+ commitValue(newModelValue);
8398
+ }, [commitValue]);
8166
8399
  const visible = useElementVisible(ref.current);
8167
8400
 
8168
8401
  /**
8169
8402
  * @type { import('min-dash').DebouncedFunction }
8170
8403
  */
8171
- const handleInputCallback = useDebounce(onInput, debounce);
8172
- const handleInput = newValue => {
8173
- const newModelValue = newValue === '' ? undefined : newValue;
8174
- handleInputCallback(newModelValue);
8175
- };
8404
+ const handleInput = useDebounce(onInput, debounce);
8176
8405
  const handleLocalInput = e => {
8177
8406
  autoResize && resizeToContents(e.target);
8178
8407
  if (e.target.value === localValue) {
@@ -8185,11 +8414,40 @@ function TextArea(props) {
8185
8414
  const trimmedValue = e.target.value.trim();
8186
8415
 
8187
8416
  // trim and commit on blur
8417
+ handleInput.cancel?.();
8188
8418
  onInput(trimmedValue);
8419
+ setLocalValue(trimmedValue);
8189
8420
  if (onBlur) {
8190
8421
  onBlur(e);
8191
8422
  }
8192
8423
  };
8424
+ const handleOnPaste = e => {
8425
+ const input = e.target;
8426
+ const isFieldEmpty = !input.value;
8427
+ const isAllSelected = input.selectionStart === 0 && input.selectionEnd === input.value.length;
8428
+
8429
+ // Trim and handle paste if field is empty or all content is selected
8430
+ if (isFieldEmpty || isAllSelected) {
8431
+ const trimmedValue = e.clipboardData.getData('text').trim();
8432
+ setLocalValue(trimmedValue);
8433
+ handleInput(trimmedValue);
8434
+ if (onPaste) {
8435
+ onPaste(e);
8436
+ }
8437
+ e.preventDefault();
8438
+ return;
8439
+ }
8440
+
8441
+ // Allow default paste behavior for normal text editing
8442
+ if (onPaste) {
8443
+ onPaste(e);
8444
+ }
8445
+ };
8446
+ const handleOnKeyDown = e => {
8447
+ if (isCmdWithChar(e)) {
8448
+ handleInput.flush();
8449
+ }
8450
+ };
8193
8451
  useLayoutEffect(() => {
8194
8452
  autoResize && resizeToContents(ref.current);
8195
8453
  }, []);
@@ -8221,7 +8479,9 @@ function TextArea(props) {
8221
8479
  class: classnames('bio-properties-panel-input', monospace ? 'bio-properties-panel-input-monospace' : '', autoResize ? 'auto-resize' : ''),
8222
8480
  onInput: handleLocalInput,
8223
8481
  onFocus: onFocus,
8482
+ onKeyDown: handleOnKeyDown,
8224
8483
  onBlur: handleOnBlur,
8484
+ onPaste: handleOnPaste,
8225
8485
  placeholder: placeholder,
8226
8486
  rows: rows,
8227
8487
  value: localValue,
@@ -8242,6 +8502,7 @@ function TextArea(props) {
8242
8502
  * @param {Function} props.setValue
8243
8503
  * @param {Function} props.onFocus
8244
8504
  * @param {Function} props.onBlur
8505
+ * @param {Function} props.onPaste
8245
8506
  * @param {number} props.rows
8246
8507
  * @param {boolean} props.monospace
8247
8508
  * @param {Function} [props.validate]
@@ -8262,6 +8523,7 @@ function TextAreaEntry(props) {
8262
8523
  validate,
8263
8524
  onFocus,
8264
8525
  onBlur,
8526
+ onPaste,
8265
8527
  placeholder,
8266
8528
  autoResize,
8267
8529
  tooltip
@@ -8297,6 +8559,7 @@ function TextAreaEntry(props) {
8297
8559
  onInput: onInput,
8298
8560
  onFocus: onFocus,
8299
8561
  onBlur: onBlur,
8562
+ onPaste: onPaste,
8300
8563
  rows: rows,
8301
8564
  debounce: debounce,
8302
8565
  monospace: monospace,
@@ -8330,32 +8593,57 @@ function Textfield(props) {
8330
8593
  disabled = false,
8331
8594
  id,
8332
8595
  label,
8333
- onInput,
8596
+ onInput: commitValue,
8334
8597
  onFocus,
8335
8598
  onBlur,
8599
+ onPaste,
8336
8600
  placeholder,
8337
8601
  value = '',
8338
8602
  tooltip
8339
8603
  } = props;
8340
8604
  const [localValue, setLocalValue] = useState(value || '');
8341
8605
  const ref = useShowEntryEvent(id);
8606
+ const onInput = useCallback(newValue => {
8607
+ const newModelValue = newValue === '' ? undefined : newValue;
8608
+ commitValue(newModelValue);
8609
+ }, [commitValue]);
8342
8610
 
8343
8611
  /**
8344
8612
  * @type { import('min-dash').DebouncedFunction }
8345
8613
  */
8346
- const handleInputCallback = useDebounce(onInput, debounce);
8614
+ const handleInput = useDebounce(onInput, debounce);
8347
8615
  const handleOnBlur = e => {
8348
8616
  const trimmedValue = e.target.value.trim();
8349
8617
 
8350
8618
  // trim and commit on blur
8619
+ handleInput.cancel?.();
8351
8620
  onInput(trimmedValue);
8621
+ setLocalValue(trimmedValue);
8352
8622
  if (onBlur) {
8353
8623
  onBlur(e);
8354
8624
  }
8355
8625
  };
8356
- const handleInput = newValue => {
8357
- const newModelValue = newValue === '' ? undefined : newValue;
8358
- handleInputCallback(newModelValue);
8626
+ const handleOnPaste = e => {
8627
+ const input = e.target;
8628
+ const isFieldEmpty = !input.value;
8629
+ const isAllSelected = input.selectionStart === 0 && input.selectionEnd === input.value.length;
8630
+
8631
+ // Trim and handle paste if field is empty or all content is selected (overwrite)
8632
+ if (isFieldEmpty || isAllSelected) {
8633
+ const trimmedValue = e.clipboardData.getData('text').trim();
8634
+ setLocalValue(trimmedValue);
8635
+ handleInput(trimmedValue);
8636
+ if (onPaste) {
8637
+ onPaste(e);
8638
+ }
8639
+ e.preventDefault();
8640
+ return;
8641
+ }
8642
+
8643
+ // Allow default paste behavior for normal text editing
8644
+ if (onPaste) {
8645
+ onPaste(e);
8646
+ }
8359
8647
  };
8360
8648
  const handleLocalInput = e => {
8361
8649
  if (e.target.value === localValue) {
@@ -8370,6 +8658,11 @@ function Textfield(props) {
8370
8658
  }
8371
8659
  setLocalValue(value);
8372
8660
  }, [value]);
8661
+ const handleOnKeyDown = e => {
8662
+ if (isCmdWithChar(e)) {
8663
+ handleInput.flush();
8664
+ }
8665
+ };
8373
8666
  return jsxs("div", {
8374
8667
  class: "bio-properties-panel-textfield",
8375
8668
  children: [jsx("label", {
@@ -8392,7 +8685,9 @@ function Textfield(props) {
8392
8685
  class: "bio-properties-panel-input",
8393
8686
  onInput: handleLocalInput,
8394
8687
  onFocus: onFocus,
8688
+ onKeyDown: handleOnKeyDown,
8395
8689
  onBlur: handleOnBlur,
8690
+ onPaste: handleOnPaste,
8396
8691
  placeholder: placeholder,
8397
8692
  value: localValue
8398
8693
  })]
@@ -8427,6 +8722,7 @@ function TextfieldEntry(props) {
8427
8722
  validate,
8428
8723
  onFocus,
8429
8724
  onBlur,
8725
+ onPaste,
8430
8726
  placeholder,
8431
8727
  tooltip
8432
8728
  } = props;
@@ -8462,6 +8758,7 @@ function TextfieldEntry(props) {
8462
8758
  onInput: onInput,
8463
8759
  onFocus: onFocus,
8464
8760
  onBlur: onBlur,
8761
+ onPaste: onPaste,
8465
8762
  placeholder: placeholder,
8466
8763
  value: value,
8467
8764
  tooltip: tooltip,
@@ -8772,6 +9069,25 @@ function cancel(event) {
8772
9069
  event.preventDefault();
8773
9070
  event.stopPropagation();
8774
9071
  }
9072
+
9073
+ /**
9074
+ * @typedef {Object} FeelPopupProps
9075
+ * @property {string} entryId
9076
+ * @property {Function} onInput
9077
+ * @property {Function} onClose
9078
+ * @property {string} title
9079
+ * @property {'feel'|'feelers'} type
9080
+ * @property {string} value
9081
+ * @property {Array} [links]
9082
+ * @property {Array|Object} [variables]
9083
+ * @property {Object} [position]
9084
+ * @property {string} [hostLanguage]
9085
+ * @property {boolean} [singleLine]
9086
+ * @property {HTMLElement} [sourceElement]
9087
+ * @property {HTMLElement|string} [tooltipContainer]
9088
+ * @property {Object} [eventBus]
9089
+ */
9090
+
8775
9091
  const FEEL_POPUP_WIDTH = 700;
8776
9092
  const FEEL_POPUP_HEIGHT = 250;
8777
9093
 
@@ -11455,7 +11771,14 @@ function Value$1(props) {
11455
11771
  if (error) {
11456
11772
  return;
11457
11773
  }
11774
+ const {
11775
+ defaultValue
11776
+ } = field;
11458
11777
  const values = get(field, ['values']);
11778
+ const previousValue = get(field, ['values', index, 'value']);
11779
+ if (!isNil(defaultValue) && defaultValue === previousValue) {
11780
+ set$1(field, ['defaultValue'], value);
11781
+ }
11459
11782
  return editField(field, 'values', set$1(values, [index, 'value'], value));
11460
11783
  };
11461
11784
  const getValue = () => {
@@ -12865,7 +13188,7 @@ function DocumentsDataSource(props) {
12865
13188
  children: "When using Camunda Tasklist UI, additional document reference attributes are automatically handled. Modifying the document reference may affect the document preview functionality."
12866
13189
  }), jsxs("p", {
12867
13190
  children: ["Learn more in our", ' ', jsx("a", {
12868
- href: "https://docs.camunda.io/docs/8.7/components/modeler/forms/form-element-library/forms-element-library-document-preview/",
13191
+ href: "https://docs.camunda.io/docs/components/modeler/forms/form-element-library/forms-element-library-document-preview/",
12869
13192
  target: "_blank",
12870
13193
  rel: "noopener noreferrer",
12871
13194
  children: "documentation"
@@ -13477,7 +13800,7 @@ function CustomPropertiesGroup(field, editField) {
13477
13800
  event.stopPropagation();
13478
13801
  return editField(field, ['properties'], removeKey(properties, key));
13479
13802
  };
13480
- const id = `property-${index}`;
13803
+ const id = `property-${field.id}-${index}`;
13481
13804
  return {
13482
13805
  autoFocusEntry: id + '-key',
13483
13806
  entries: CustomValueEntry({