@bpmn-io/properties-panel 3.33.2 → 3.35.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 +327 -40
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +326 -39
- package/dist/index.js.map +1 -1
- package/package.json +23 -24
package/dist/index.esm.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useContext, useState, useRef,
|
|
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
|
-
|
|
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
|
-
|
|
355
|
+
showTimeoutRef.current = setTimeout(() => {
|
|
315
356
|
setVisible(true);
|
|
316
|
-
},
|
|
357
|
+
}, showDelay);
|
|
317
358
|
} else {
|
|
318
359
|
setVisible(true);
|
|
319
360
|
}
|
|
320
361
|
};
|
|
321
|
-
const
|
|
322
|
-
|
|
323
|
-
|
|
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
|
-
|
|
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
|
-
|
|
421
|
+
relatedTarget
|
|
337
422
|
} = e;
|
|
338
423
|
|
|
339
|
-
// Don't hide
|
|
340
|
-
|
|
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:
|
|
456
|
+
onMouseEnter: handleWrapperMouseEnter,
|
|
373
457
|
onMouseLeave: handleMouseLeave,
|
|
374
458
|
onFocus: show,
|
|
375
459
|
onBlur: handleFocusOut,
|
|
@@ -468,8 +552,6 @@ function useEvent(event, callback, eventBus) {
|
|
|
468
552
|
}, [callback, event, eventBus]);
|
|
469
553
|
}
|
|
470
554
|
|
|
471
|
-
const KEY_LENGTH = 6;
|
|
472
|
-
|
|
473
555
|
/**
|
|
474
556
|
* Create a persistent key factory for plain objects without id.
|
|
475
557
|
*
|
|
@@ -494,7 +576,7 @@ function useKeyFactory(dependencies = []) {
|
|
|
494
576
|
const getKey = el => {
|
|
495
577
|
let key = map.get(el);
|
|
496
578
|
if (!key) {
|
|
497
|
-
key = Math.random().toString().slice(-
|
|
579
|
+
key = Math.random().toString().slice(-6);
|
|
498
580
|
map.set(el, key);
|
|
499
581
|
}
|
|
500
582
|
return key;
|
|
@@ -685,6 +767,9 @@ function useElementVisible(element) {
|
|
|
685
767
|
return visible;
|
|
686
768
|
}
|
|
687
769
|
|
|
770
|
+
/**
|
|
771
|
+
* @param {import('../PropertiesPanel').GroupDefinition} props
|
|
772
|
+
*/
|
|
688
773
|
function Group(props) {
|
|
689
774
|
const {
|
|
690
775
|
element,
|
|
@@ -739,8 +824,6 @@ function Group(props) {
|
|
|
739
824
|
class: classnames('bio-properties-panel-group-header', edited ? '' : 'empty', open ? 'open' : '', sticky && open ? 'sticky' : ''),
|
|
740
825
|
onClick: toggleOpen,
|
|
741
826
|
children: [jsx("div", {
|
|
742
|
-
title: props.tooltip ? null : label,
|
|
743
|
-
"data-title": label,
|
|
744
827
|
class: "bio-properties-panel-group-header-title",
|
|
745
828
|
children: jsx(TooltipWrapper, {
|
|
746
829
|
value: props.tooltip,
|
|
@@ -1093,6 +1176,13 @@ function useUpdateLayoutEffect(effect, deps) {
|
|
|
1093
1176
|
}, deps);
|
|
1094
1177
|
}
|
|
1095
1178
|
|
|
1179
|
+
/**
|
|
1180
|
+
*
|
|
1181
|
+
* @param {object} props
|
|
1182
|
+
* @param {string} [props.class]
|
|
1183
|
+
* @param {import('preact').Component[]} [props.menuItems]
|
|
1184
|
+
* @returns
|
|
1185
|
+
*/
|
|
1096
1186
|
function DropdownButton(props) {
|
|
1097
1187
|
const {
|
|
1098
1188
|
class: className,
|
|
@@ -1250,7 +1340,6 @@ function CollapsibleEntry(props) {
|
|
|
1250
1340
|
class: "bio-properties-panel-collapsible-entry-header",
|
|
1251
1341
|
onClick: toggleOpen,
|
|
1252
1342
|
children: [jsx("div", {
|
|
1253
|
-
title: label || placeholderLabel,
|
|
1254
1343
|
class: classnames('bio-properties-panel-collapsible-entry-header-title', !label && 'empty'),
|
|
1255
1344
|
children: label || placeholderLabel
|
|
1256
1345
|
}), jsx("button", {
|
|
@@ -1287,6 +1376,9 @@ function CollapsibleEntry(props) {
|
|
|
1287
1376
|
});
|
|
1288
1377
|
}
|
|
1289
1378
|
|
|
1379
|
+
/**
|
|
1380
|
+
* @param {import('../PropertiesPanel').ListItemDefinition} props
|
|
1381
|
+
*/
|
|
1290
1382
|
function ListItem(props) {
|
|
1291
1383
|
const {
|
|
1292
1384
|
autoFocusEntry,
|
|
@@ -1389,8 +1481,6 @@ function ListGroup(props) {
|
|
|
1389
1481
|
class: classnames('bio-properties-panel-group-header', hasItems ? '' : 'empty', hasItems && open ? 'open' : '', sticky && open ? 'sticky' : ''),
|
|
1390
1482
|
onClick: hasItems ? toggleOpen : noop$6,
|
|
1391
1483
|
children: [jsx("div", {
|
|
1392
|
-
title: props.tooltip ? null : label,
|
|
1393
|
-
"data-title": label,
|
|
1394
1484
|
class: "bio-properties-panel-group-header-title",
|
|
1395
1485
|
children: jsx(TooltipWrapper, {
|
|
1396
1486
|
value: props.tooltip,
|
|
@@ -1460,6 +1550,12 @@ function getNewItemIds(newItems, oldItems) {
|
|
|
1460
1550
|
return newIds.filter(itemId => !oldIds.includes(itemId));
|
|
1461
1551
|
}
|
|
1462
1552
|
|
|
1553
|
+
/**
|
|
1554
|
+
* @param {Object} props
|
|
1555
|
+
* @param {Object} props.element
|
|
1556
|
+
* @param {String} props.forId - id of the entry the description is used for
|
|
1557
|
+
* @param {String} props.value
|
|
1558
|
+
*/
|
|
1463
1559
|
function Description(props) {
|
|
1464
1560
|
const {
|
|
1465
1561
|
element,
|
|
@@ -1591,6 +1687,15 @@ function prefixId$8(id) {
|
|
|
1591
1687
|
return `bio-properties-panel-${id}`;
|
|
1592
1688
|
}
|
|
1593
1689
|
|
|
1690
|
+
/**
|
|
1691
|
+
* Button to open popups.
|
|
1692
|
+
*
|
|
1693
|
+
* @param {Object} props
|
|
1694
|
+
* @param {Function} props.onClick - Callback to trigger when the button is clicked.
|
|
1695
|
+
* @param {string} [props.title] - Tooltip text for the button.
|
|
1696
|
+
* @param {boolean} [props.disabled] - Whether the button is disabled.
|
|
1697
|
+
* @param {string} [props.className] - Additional class names for the button.
|
|
1698
|
+
*/
|
|
1594
1699
|
function OpenPopupButton({
|
|
1595
1700
|
onClick,
|
|
1596
1701
|
title = 'Open pop-up editor'
|
|
@@ -1736,6 +1841,7 @@ const FeelEditor = forwardRef((props, ref) => {
|
|
|
1736
1841
|
enableGutters,
|
|
1737
1842
|
value,
|
|
1738
1843
|
onInput,
|
|
1844
|
+
onKeyDown: onKeyDownProp = noop$4,
|
|
1739
1845
|
onFeelToggle = noop$4,
|
|
1740
1846
|
onLint = noop$4,
|
|
1741
1847
|
onOpenPopup = noop$4,
|
|
@@ -1768,6 +1874,8 @@ const FeelEditor = forwardRef((props, ref) => {
|
|
|
1768
1874
|
* - AND the cursor is at the beginning of the input
|
|
1769
1875
|
*/
|
|
1770
1876
|
const onKeyDown = e => {
|
|
1877
|
+
// Call parent onKeyDown handler first
|
|
1878
|
+
onKeyDownProp(e);
|
|
1771
1879
|
if (e.key !== 'Backspace' || !editor) {
|
|
1772
1880
|
return;
|
|
1773
1881
|
}
|
|
@@ -1887,6 +1995,22 @@ function FeelIcon(props) {
|
|
|
1887
1995
|
});
|
|
1888
1996
|
}
|
|
1889
1997
|
|
|
1998
|
+
/**
|
|
1999
|
+
* @param {KeyboardEvent} event
|
|
2000
|
+
* @return {boolean}
|
|
2001
|
+
*/
|
|
2002
|
+
function isCmd(event) {
|
|
2003
|
+
// ensure we don't react to AltGr
|
|
2004
|
+
// (mapped to CTRL + ALT)
|
|
2005
|
+
if (event.altKey) {
|
|
2006
|
+
return false;
|
|
2007
|
+
}
|
|
2008
|
+
return event.ctrlKey || event.metaKey;
|
|
2009
|
+
}
|
|
2010
|
+
function isCmdWithChar(event) {
|
|
2011
|
+
return isCmd(event) && event.key.length === 1 && /^[a-zA-Z]$/.test(event.key);
|
|
2012
|
+
}
|
|
2013
|
+
|
|
1890
2014
|
function ToggleSwitch(props) {
|
|
1891
2015
|
const {
|
|
1892
2016
|
id,
|
|
@@ -2216,6 +2340,7 @@ function FeelTextfield(props) {
|
|
|
2216
2340
|
const [localValue, setLocalValue] = useState(value);
|
|
2217
2341
|
const editorRef = useShowEntryEvent(id);
|
|
2218
2342
|
const containerRef = useRef();
|
|
2343
|
+
const isTogglingFromPasteRef = useRef(false);
|
|
2219
2344
|
const onInput = useCallback(newValue => {
|
|
2220
2345
|
// we don't commit empty FEEL expressions,
|
|
2221
2346
|
// but instead serialize them as <undefined>
|
|
@@ -2254,31 +2379,43 @@ function FeelTextfield(props) {
|
|
|
2254
2379
|
handleInput(feelOnlyValue);
|
|
2255
2380
|
}
|
|
2256
2381
|
});
|
|
2257
|
-
const handleLocalInput = newValue => {
|
|
2382
|
+
const handleLocalInput = (newValue, useDebounce = true) => {
|
|
2258
2383
|
if (feelActive) {
|
|
2259
2384
|
newValue = '=' + newValue;
|
|
2260
2385
|
}
|
|
2261
|
-
if (newValue === localValue) {
|
|
2262
|
-
return;
|
|
2263
|
-
}
|
|
2264
2386
|
setLocalValue(newValue);
|
|
2265
|
-
|
|
2387
|
+
if (useDebounce) {
|
|
2388
|
+
handleInput(newValue);
|
|
2389
|
+
} else {
|
|
2390
|
+
onInput(newValue);
|
|
2391
|
+
}
|
|
2266
2392
|
if (!feelActive && isString(newValue) && newValue.startsWith('=')) {
|
|
2267
2393
|
// focus is behind `=` sign that will be removed
|
|
2268
2394
|
setFocus(-1);
|
|
2269
2395
|
}
|
|
2270
2396
|
};
|
|
2271
2397
|
const handleOnBlur = e => {
|
|
2398
|
+
// Ignore blur when toggling from paste to avoid interference
|
|
2399
|
+
if (isTogglingFromPasteRef.current) {
|
|
2400
|
+
isTogglingFromPasteRef.current = false;
|
|
2401
|
+
return;
|
|
2402
|
+
}
|
|
2403
|
+
handleInput.cancel?.();
|
|
2272
2404
|
if (e.target.type === 'checkbox') {
|
|
2273
2405
|
onInput(e.target.checked);
|
|
2274
2406
|
} else {
|
|
2275
2407
|
const trimmedValue = e.target.value.trim();
|
|
2276
|
-
|
|
2408
|
+
handleLocalInput(trimmedValue, false);
|
|
2277
2409
|
}
|
|
2278
2410
|
if (onBlur) {
|
|
2279
2411
|
onBlur(e);
|
|
2280
2412
|
}
|
|
2281
2413
|
};
|
|
2414
|
+
const handleOnKeyDown = e => {
|
|
2415
|
+
if (isCmdWithChar(e)) {
|
|
2416
|
+
handleInput.flush();
|
|
2417
|
+
}
|
|
2418
|
+
};
|
|
2282
2419
|
const handleLint = useStaticCallback((lint = []) => {
|
|
2283
2420
|
const syntaxError = lint.some(report => report.type === 'Syntax Error');
|
|
2284
2421
|
if (syntaxError) {
|
|
@@ -2345,12 +2482,27 @@ function FeelTextfield(props) {
|
|
|
2345
2482
|
if (feelActive || isPopupOpen) {
|
|
2346
2483
|
return;
|
|
2347
2484
|
}
|
|
2348
|
-
const
|
|
2349
|
-
if (
|
|
2485
|
+
const feelData = event.clipboardData.getData('application/FEEL');
|
|
2486
|
+
if (feelData) {
|
|
2350
2487
|
setTimeout(() => {
|
|
2351
2488
|
handleFeelToggle();
|
|
2352
2489
|
setFocus();
|
|
2353
2490
|
});
|
|
2491
|
+
return;
|
|
2492
|
+
}
|
|
2493
|
+
const input = event.target;
|
|
2494
|
+
const isFieldEmpty = !input.value;
|
|
2495
|
+
const isAllSelected = input.selectionStart === 0 && input.selectionEnd === input.value.length;
|
|
2496
|
+
if (isFieldEmpty || isAllSelected) {
|
|
2497
|
+
const textData = event.clipboardData.getData('text');
|
|
2498
|
+
const trimmedValue = textData.trim();
|
|
2499
|
+
setLocalValue(trimmedValue);
|
|
2500
|
+
handleInput(trimmedValue);
|
|
2501
|
+
if (!feelActive && isString(trimmedValue) && trimmedValue.startsWith('=')) {
|
|
2502
|
+
isTogglingFromPasteRef.current = true;
|
|
2503
|
+
setFocus(trimmedValue.length - 1);
|
|
2504
|
+
}
|
|
2505
|
+
event.preventDefault();
|
|
2354
2506
|
}
|
|
2355
2507
|
};
|
|
2356
2508
|
containerRef.current.addEventListener('copy', copyHandler);
|
|
@@ -2391,6 +2543,7 @@ function FeelTextfield(props) {
|
|
|
2391
2543
|
}), feelActive ? jsx(FeelEditor, {
|
|
2392
2544
|
name: id,
|
|
2393
2545
|
onInput: handleLocalInput,
|
|
2546
|
+
onKeyDown: handleOnKeyDown,
|
|
2394
2547
|
contentAttributes: {
|
|
2395
2548
|
'id': prefixId$5(id),
|
|
2396
2549
|
'aria-label': label
|
|
@@ -2413,6 +2566,7 @@ function FeelTextfield(props) {
|
|
|
2413
2566
|
...props,
|
|
2414
2567
|
popupOpen: isPopupOpen,
|
|
2415
2568
|
onInput: handleLocalInput,
|
|
2569
|
+
onKeyDown: handleOnKeyDown,
|
|
2416
2570
|
onBlur: handleOnBlur,
|
|
2417
2571
|
contentAttributes: {
|
|
2418
2572
|
'id': prefixId$5(id),
|
|
@@ -2431,6 +2585,7 @@ const OptionalFeelInput = forwardRef((props, ref) => {
|
|
|
2431
2585
|
id,
|
|
2432
2586
|
disabled,
|
|
2433
2587
|
onInput,
|
|
2588
|
+
onKeyDown,
|
|
2434
2589
|
value,
|
|
2435
2590
|
onFocus,
|
|
2436
2591
|
onBlur,
|
|
@@ -2466,6 +2621,7 @@ const OptionalFeelInput = forwardRef((props, ref) => {
|
|
|
2466
2621
|
class: "bio-properties-panel-input",
|
|
2467
2622
|
onInput: e => onInput(e.target.value),
|
|
2468
2623
|
onFocus: onFocus,
|
|
2624
|
+
onKeyDown: onKeyDown,
|
|
2469
2625
|
onBlur: onBlur,
|
|
2470
2626
|
placeholder: placeholder,
|
|
2471
2627
|
value: value || ''
|
|
@@ -3060,6 +3216,22 @@ function prefixIdLabel(id) {
|
|
|
3060
3216
|
return `bio-properties-panel-feelers-${id}-label`;
|
|
3061
3217
|
}
|
|
3062
3218
|
|
|
3219
|
+
/**
|
|
3220
|
+
* Entry for handling lists represented as nested entries.
|
|
3221
|
+
*
|
|
3222
|
+
* @template Item
|
|
3223
|
+
* @param {object} props
|
|
3224
|
+
* @param {string} props.id
|
|
3225
|
+
* @param {*} props.element
|
|
3226
|
+
* @param {Function} props.onAdd
|
|
3227
|
+
* @param {import('preact').Component} props.component
|
|
3228
|
+
* @param {string} [props.label='<empty>']
|
|
3229
|
+
* @param {Function} [props.onRemove]
|
|
3230
|
+
* @param {Item[]} [props.items]
|
|
3231
|
+
* @param {boolean} [props.open]
|
|
3232
|
+
* @param {string|boolean} [props.autoFocusEntry] either a custom selector string or true to focus the first input
|
|
3233
|
+
* @returns
|
|
3234
|
+
*/
|
|
3063
3235
|
function List(props) {
|
|
3064
3236
|
const {
|
|
3065
3237
|
id,
|
|
@@ -3107,7 +3279,6 @@ function List(props) {
|
|
|
3107
3279
|
class: classnames('bio-properties-panel-list-entry-header', sticky && open ? 'sticky' : ''),
|
|
3108
3280
|
onClick: toggleOpen,
|
|
3109
3281
|
children: [jsx("div", {
|
|
3110
|
-
title: label,
|
|
3111
3282
|
class: classnames('bio-properties-panel-list-entry-header-title', open && 'open'),
|
|
3112
3283
|
children: label
|
|
3113
3284
|
}), jsxs("div", {
|
|
@@ -3212,6 +3383,24 @@ function useNewItems(items = [], shouldReset) {
|
|
|
3212
3383
|
return previousItems ? items.filter(item => !previousItems.includes(item)) : [];
|
|
3213
3384
|
}
|
|
3214
3385
|
|
|
3386
|
+
/**
|
|
3387
|
+
* @typedef { { value: string, label: string, disabled: boolean, children: { value: string, label: string, disabled: boolean } } } Option
|
|
3388
|
+
*/
|
|
3389
|
+
|
|
3390
|
+
/**
|
|
3391
|
+
* Provides basic select input.
|
|
3392
|
+
*
|
|
3393
|
+
* @param {object} props
|
|
3394
|
+
* @param {string} props.id
|
|
3395
|
+
* @param {string[]} props.path
|
|
3396
|
+
* @param {string} props.label
|
|
3397
|
+
* @param {Function} props.onChange
|
|
3398
|
+
* @param {Function} props.onFocus
|
|
3399
|
+
* @param {Function} props.onBlur
|
|
3400
|
+
* @param {Array<Option>} [props.options]
|
|
3401
|
+
* @param {string} props.value
|
|
3402
|
+
* @param {boolean} [props.disabled]
|
|
3403
|
+
*/
|
|
3215
3404
|
function Select(props) {
|
|
3216
3405
|
const {
|
|
3217
3406
|
id,
|
|
@@ -3366,6 +3555,17 @@ function prefixId$4(id) {
|
|
|
3366
3555
|
return `bio-properties-panel-${id}`;
|
|
3367
3556
|
}
|
|
3368
3557
|
|
|
3558
|
+
/**
|
|
3559
|
+
* @param {Object} props
|
|
3560
|
+
* @param {Function} props.debounce
|
|
3561
|
+
* @param {Boolean} [props.disabled]
|
|
3562
|
+
* @param {Object} props.element
|
|
3563
|
+
* @param {Function} props.getValue
|
|
3564
|
+
* @param {String} props.id
|
|
3565
|
+
* @param {Function} [props.onBlur]
|
|
3566
|
+
* @param {Function} [props.onFocus]
|
|
3567
|
+
* @param {Function} props.setValue
|
|
3568
|
+
*/
|
|
3369
3569
|
function Simple(props) {
|
|
3370
3570
|
const {
|
|
3371
3571
|
debounce,
|
|
@@ -3438,6 +3638,7 @@ function TextArea(props) {
|
|
|
3438
3638
|
monospace,
|
|
3439
3639
|
onFocus,
|
|
3440
3640
|
onBlur,
|
|
3641
|
+
onPaste,
|
|
3441
3642
|
autoResize = true,
|
|
3442
3643
|
placeholder,
|
|
3443
3644
|
rows = autoResize ? 1 : 2,
|
|
@@ -3467,11 +3668,40 @@ function TextArea(props) {
|
|
|
3467
3668
|
const trimmedValue = e.target.value.trim();
|
|
3468
3669
|
|
|
3469
3670
|
// trim and commit on blur
|
|
3671
|
+
handleInput.cancel?.();
|
|
3470
3672
|
onInput(trimmedValue);
|
|
3673
|
+
setLocalValue(trimmedValue);
|
|
3471
3674
|
if (onBlur) {
|
|
3472
3675
|
onBlur(e);
|
|
3473
3676
|
}
|
|
3474
3677
|
};
|
|
3678
|
+
const handleOnPaste = e => {
|
|
3679
|
+
const input = e.target;
|
|
3680
|
+
const isFieldEmpty = !input.value;
|
|
3681
|
+
const isAllSelected = input.selectionStart === 0 && input.selectionEnd === input.value.length;
|
|
3682
|
+
|
|
3683
|
+
// Trim and handle paste if field is empty or all content is selected
|
|
3684
|
+
if (isFieldEmpty || isAllSelected) {
|
|
3685
|
+
const trimmedValue = e.clipboardData.getData('text').trim();
|
|
3686
|
+
setLocalValue(trimmedValue);
|
|
3687
|
+
handleInput(trimmedValue);
|
|
3688
|
+
if (onPaste) {
|
|
3689
|
+
onPaste(e);
|
|
3690
|
+
}
|
|
3691
|
+
e.preventDefault();
|
|
3692
|
+
return;
|
|
3693
|
+
}
|
|
3694
|
+
|
|
3695
|
+
// Allow default paste behavior for normal text editing
|
|
3696
|
+
if (onPaste) {
|
|
3697
|
+
onPaste(e);
|
|
3698
|
+
}
|
|
3699
|
+
};
|
|
3700
|
+
const handleOnKeyDown = e => {
|
|
3701
|
+
if (isCmdWithChar(e)) {
|
|
3702
|
+
handleInput.flush();
|
|
3703
|
+
}
|
|
3704
|
+
};
|
|
3475
3705
|
useLayoutEffect(() => {
|
|
3476
3706
|
autoResize && resizeToContents(ref.current);
|
|
3477
3707
|
}, []);
|
|
@@ -3503,7 +3733,9 @@ function TextArea(props) {
|
|
|
3503
3733
|
class: classnames('bio-properties-panel-input', monospace ? 'bio-properties-panel-input-monospace' : '', autoResize ? 'auto-resize' : ''),
|
|
3504
3734
|
onInput: handleLocalInput,
|
|
3505
3735
|
onFocus: onFocus,
|
|
3736
|
+
onKeyDown: handleOnKeyDown,
|
|
3506
3737
|
onBlur: handleOnBlur,
|
|
3738
|
+
onPaste: handleOnPaste,
|
|
3507
3739
|
placeholder: placeholder,
|
|
3508
3740
|
rows: rows,
|
|
3509
3741
|
value: localValue,
|
|
@@ -3524,6 +3756,7 @@ function TextArea(props) {
|
|
|
3524
3756
|
* @param {Function} props.setValue
|
|
3525
3757
|
* @param {Function} props.onFocus
|
|
3526
3758
|
* @param {Function} props.onBlur
|
|
3759
|
+
* @param {Function} props.onPaste
|
|
3527
3760
|
* @param {number} props.rows
|
|
3528
3761
|
* @param {boolean} props.monospace
|
|
3529
3762
|
* @param {Function} [props.validate]
|
|
@@ -3544,6 +3777,7 @@ function TextAreaEntry(props) {
|
|
|
3544
3777
|
validate,
|
|
3545
3778
|
onFocus,
|
|
3546
3779
|
onBlur,
|
|
3780
|
+
onPaste,
|
|
3547
3781
|
placeholder,
|
|
3548
3782
|
autoResize,
|
|
3549
3783
|
tooltip
|
|
@@ -3579,6 +3813,7 @@ function TextAreaEntry(props) {
|
|
|
3579
3813
|
onInput: onInput,
|
|
3580
3814
|
onFocus: onFocus,
|
|
3581
3815
|
onBlur: onBlur,
|
|
3816
|
+
onPaste: onPaste,
|
|
3582
3817
|
rows: rows,
|
|
3583
3818
|
debounce: debounce,
|
|
3584
3819
|
monospace: monospace,
|
|
@@ -3616,6 +3851,7 @@ function Textfield(props) {
|
|
|
3616
3851
|
onInput: commitValue,
|
|
3617
3852
|
onFocus,
|
|
3618
3853
|
onBlur,
|
|
3854
|
+
onPaste,
|
|
3619
3855
|
placeholder,
|
|
3620
3856
|
value = '',
|
|
3621
3857
|
tooltip
|
|
@@ -3635,11 +3871,35 @@ function Textfield(props) {
|
|
|
3635
3871
|
const trimmedValue = e.target.value.trim();
|
|
3636
3872
|
|
|
3637
3873
|
// trim and commit on blur
|
|
3874
|
+
handleInput.cancel?.();
|
|
3638
3875
|
onInput(trimmedValue);
|
|
3876
|
+
setLocalValue(trimmedValue);
|
|
3639
3877
|
if (onBlur) {
|
|
3640
3878
|
onBlur(e);
|
|
3641
3879
|
}
|
|
3642
3880
|
};
|
|
3881
|
+
const handleOnPaste = e => {
|
|
3882
|
+
const input = e.target;
|
|
3883
|
+
const isFieldEmpty = !input.value;
|
|
3884
|
+
const isAllSelected = input.selectionStart === 0 && input.selectionEnd === input.value.length;
|
|
3885
|
+
|
|
3886
|
+
// Trim and handle paste if field is empty or all content is selected (overwrite)
|
|
3887
|
+
if (isFieldEmpty || isAllSelected) {
|
|
3888
|
+
const trimmedValue = e.clipboardData.getData('text').trim();
|
|
3889
|
+
setLocalValue(trimmedValue);
|
|
3890
|
+
handleInput(trimmedValue);
|
|
3891
|
+
if (onPaste) {
|
|
3892
|
+
onPaste(e);
|
|
3893
|
+
}
|
|
3894
|
+
e.preventDefault();
|
|
3895
|
+
return;
|
|
3896
|
+
}
|
|
3897
|
+
|
|
3898
|
+
// Allow default paste behavior for normal text editing
|
|
3899
|
+
if (onPaste) {
|
|
3900
|
+
onPaste(e);
|
|
3901
|
+
}
|
|
3902
|
+
};
|
|
3643
3903
|
const handleLocalInput = e => {
|
|
3644
3904
|
if (e.target.value === localValue) {
|
|
3645
3905
|
return;
|
|
@@ -3653,6 +3913,11 @@ function Textfield(props) {
|
|
|
3653
3913
|
}
|
|
3654
3914
|
setLocalValue(value);
|
|
3655
3915
|
}, [value]);
|
|
3916
|
+
const handleOnKeyDown = e => {
|
|
3917
|
+
if (isCmdWithChar(e)) {
|
|
3918
|
+
handleInput.flush();
|
|
3919
|
+
}
|
|
3920
|
+
};
|
|
3656
3921
|
return jsxs("div", {
|
|
3657
3922
|
class: "bio-properties-panel-textfield",
|
|
3658
3923
|
children: [jsx("label", {
|
|
@@ -3675,7 +3940,9 @@ function Textfield(props) {
|
|
|
3675
3940
|
class: "bio-properties-panel-input",
|
|
3676
3941
|
onInput: handleLocalInput,
|
|
3677
3942
|
onFocus: onFocus,
|
|
3943
|
+
onKeyDown: handleOnKeyDown,
|
|
3678
3944
|
onBlur: handleOnBlur,
|
|
3945
|
+
onPaste: handleOnPaste,
|
|
3679
3946
|
placeholder: placeholder,
|
|
3680
3947
|
value: localValue
|
|
3681
3948
|
})]
|
|
@@ -3710,6 +3977,7 @@ function TextfieldEntry(props) {
|
|
|
3710
3977
|
validate,
|
|
3711
3978
|
onFocus,
|
|
3712
3979
|
onBlur,
|
|
3980
|
+
onPaste,
|
|
3713
3981
|
placeholder,
|
|
3714
3982
|
tooltip
|
|
3715
3983
|
} = props;
|
|
@@ -3745,6 +4013,7 @@ function TextfieldEntry(props) {
|
|
|
3745
4013
|
onInput: onInput,
|
|
3746
4014
|
onFocus: onFocus,
|
|
3747
4015
|
onBlur: onBlur,
|
|
4016
|
+
onPaste: onPaste,
|
|
3748
4017
|
placeholder: placeholder,
|
|
3749
4018
|
value: value,
|
|
3750
4019
|
tooltip: tooltip,
|
|
@@ -3777,7 +4046,7 @@ const DEFAULT_DEBOUNCE_TIME = 600;
|
|
|
3777
4046
|
* - If `debounceDelay` is `false`, the function executes immediately without debouncing.
|
|
3778
4047
|
* - If a number is provided, the function execution is delayed by the given time in milliseconds.
|
|
3779
4048
|
*
|
|
3780
|
-
* @param { Boolean | Number } [debounceDelay=
|
|
4049
|
+
* @param { Boolean | Number } [debounceDelay=600]
|
|
3781
4050
|
*
|
|
3782
4051
|
* @example
|
|
3783
4052
|
* const debounce = debounceInput();
|
|
@@ -4097,6 +4366,24 @@ function cancel(event) {
|
|
|
4097
4366
|
event.stopPropagation();
|
|
4098
4367
|
}
|
|
4099
4368
|
|
|
4369
|
+
/**
|
|
4370
|
+
* @typedef {Object} FeelPopupProps
|
|
4371
|
+
* @property {string} entryId
|
|
4372
|
+
* @property {Function} onInput
|
|
4373
|
+
* @property {Function} onClose
|
|
4374
|
+
* @property {string} title
|
|
4375
|
+
* @property {'feel'|'feelers'} type
|
|
4376
|
+
* @property {string} value
|
|
4377
|
+
* @property {Array} [links]
|
|
4378
|
+
* @property {Array|Object} [variables]
|
|
4379
|
+
* @property {Object} [position]
|
|
4380
|
+
* @property {string} [hostLanguage]
|
|
4381
|
+
* @property {boolean} [singleLine]
|
|
4382
|
+
* @property {HTMLElement} [sourceElement]
|
|
4383
|
+
* @property {HTMLElement|string} [tooltipContainer]
|
|
4384
|
+
* @property {Object} [eventBus]
|
|
4385
|
+
*/
|
|
4386
|
+
|
|
4100
4387
|
const FEEL_POPUP_WIDTH = 700;
|
|
4101
4388
|
const FEEL_POPUP_HEIGHT = 250;
|
|
4102
4389
|
|