@bpmn-io/properties-panel 3.33.1 → 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 +335 -53
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +334 -52
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -174,6 +174,20 @@ OpenPopupIcon.defaultProps = {
|
|
|
174
174
|
viewBox: "0 0 16 16"
|
|
175
175
|
};
|
|
176
176
|
|
|
177
|
+
/**
|
|
178
|
+
* @typedef { {
|
|
179
|
+
* getElementLabel: (element: object) => string,
|
|
180
|
+
* getTypeLabel: (element: object) => string,
|
|
181
|
+
* getElementIcon: (element: object) => import('preact').Component,
|
|
182
|
+
* getDocumentationRef: (element: object) => string
|
|
183
|
+
* } } HeaderProvider
|
|
184
|
+
*/
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* @param {Object} props
|
|
188
|
+
* @param {Object} props.element,
|
|
189
|
+
* @param {HeaderProvider} props.headerProvider
|
|
190
|
+
*/
|
|
177
191
|
function Header(props) {
|
|
178
192
|
const {
|
|
179
193
|
element,
|
|
@@ -201,11 +215,9 @@ function Header(props) {
|
|
|
201
215
|
}), jsxRuntime.jsxs("div", {
|
|
202
216
|
class: "bio-properties-panel-header-labels",
|
|
203
217
|
children: [jsxRuntime.jsx("div", {
|
|
204
|
-
title: type,
|
|
205
218
|
class: "bio-properties-panel-header-type",
|
|
206
219
|
children: type
|
|
207
220
|
}), label ? jsxRuntime.jsx("div", {
|
|
208
|
-
title: label,
|
|
209
221
|
class: "bio-properties-panel-header-label",
|
|
210
222
|
children: label
|
|
211
223
|
}) : null]
|
|
@@ -298,6 +310,26 @@ function useTooltipContext(id, element) {
|
|
|
298
310
|
return getTooltipForId(id, element);
|
|
299
311
|
}
|
|
300
312
|
|
|
313
|
+
/**
|
|
314
|
+
* @typedef {Object} TooltipProps
|
|
315
|
+
* @property {Object} [parent] - Parent element ref for portal rendering
|
|
316
|
+
* @property {String} [direction='right'] - Tooltip direction ( 'right', 'top')
|
|
317
|
+
* @property {String} [position] - Custom CSS position override
|
|
318
|
+
* @property {Number} [showDelay=250] - Delay in ms before showing tooltip on hover
|
|
319
|
+
* @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
|
|
320
|
+
* @property {*} [children] - Child elements to render inside the tooltip wrapper
|
|
321
|
+
*/
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* Tooltip wrapper that provides context-based tooltip content lookup.
|
|
325
|
+
* All props are forwarded to the underlying Tooltip component.
|
|
326
|
+
*
|
|
327
|
+
* @param {TooltipProps & {
|
|
328
|
+
* forId: String,
|
|
329
|
+
* value?: String|Object,
|
|
330
|
+
* element?: Object
|
|
331
|
+
* }} props - Shared tooltip props plus wrapper-specific ones
|
|
332
|
+
*/
|
|
301
333
|
function TooltipWrapper(props) {
|
|
302
334
|
const {
|
|
303
335
|
forId,
|
|
@@ -314,35 +346,77 @@ function TooltipWrapper(props) {
|
|
|
314
346
|
forId: `bio-properties-panel-${forId}`
|
|
315
347
|
});
|
|
316
348
|
}
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* @param {TooltipProps & {
|
|
352
|
+
* forId: String,
|
|
353
|
+
* value: String|Object
|
|
354
|
+
* }} props
|
|
355
|
+
*/
|
|
317
356
|
function Tooltip(props) {
|
|
318
357
|
const {
|
|
319
358
|
forId,
|
|
320
359
|
value,
|
|
321
360
|
parent,
|
|
322
361
|
direction = 'right',
|
|
323
|
-
position
|
|
362
|
+
position,
|
|
363
|
+
showDelay = 250,
|
|
364
|
+
hideDelay = 250
|
|
324
365
|
} = props;
|
|
325
366
|
const [visible, setVisible] = hooks.useState(false);
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
const SHOW_DELAY = 200;
|
|
329
|
-
let timeout = null;
|
|
367
|
+
const showTimeoutRef = hooks.useRef(null);
|
|
368
|
+
const hideTimeoutRef = hooks.useRef(null);
|
|
330
369
|
const wrapperRef = hooks.useRef(null);
|
|
331
370
|
const tooltipRef = hooks.useRef(null);
|
|
332
371
|
const show = (_, delay) => {
|
|
372
|
+
clearTimeout(showTimeoutRef.current);
|
|
373
|
+
clearTimeout(hideTimeoutRef.current);
|
|
333
374
|
if (visible) return;
|
|
334
375
|
if (delay) {
|
|
335
|
-
|
|
376
|
+
showTimeoutRef.current = setTimeout(() => {
|
|
336
377
|
setVisible(true);
|
|
337
|
-
},
|
|
378
|
+
}, showDelay);
|
|
338
379
|
} else {
|
|
339
380
|
setVisible(true);
|
|
340
381
|
}
|
|
341
382
|
};
|
|
342
|
-
const
|
|
343
|
-
|
|
344
|
-
|
|
383
|
+
const handleWrapperMouseEnter = e => {
|
|
384
|
+
show(e, true);
|
|
385
|
+
};
|
|
386
|
+
const hide = (delay = false) => {
|
|
387
|
+
clearTimeout(showTimeoutRef.current);
|
|
388
|
+
clearTimeout(hideTimeoutRef.current);
|
|
389
|
+
if (delay) {
|
|
390
|
+
hideTimeoutRef.current = setTimeout(() => {
|
|
391
|
+
setVisible(false);
|
|
392
|
+
}, hideDelay);
|
|
393
|
+
} else {
|
|
394
|
+
setVisible(false);
|
|
395
|
+
}
|
|
345
396
|
};
|
|
397
|
+
|
|
398
|
+
// Cleanup timeouts on unmount
|
|
399
|
+
hooks.useEffect(() => {
|
|
400
|
+
return () => {
|
|
401
|
+
clearTimeout(showTimeoutRef.current);
|
|
402
|
+
clearTimeout(hideTimeoutRef.current);
|
|
403
|
+
};
|
|
404
|
+
}, []);
|
|
405
|
+
|
|
406
|
+
// Handle click outside to close tooltip for non-focusable elements
|
|
407
|
+
hooks.useEffect(() => {
|
|
408
|
+
if (!visible) return;
|
|
409
|
+
const handleClickOutside = e => {
|
|
410
|
+
// If clicking outside both the wrapper and tooltip, hide it
|
|
411
|
+
if (wrapperRef.current && !wrapperRef.current.contains(e.target) && tooltipRef.current && !tooltipRef.current.contains(e.target)) {
|
|
412
|
+
hide(false);
|
|
413
|
+
}
|
|
414
|
+
};
|
|
415
|
+
document.addEventListener('mousedown', handleClickOutside);
|
|
416
|
+
return () => {
|
|
417
|
+
document.removeEventListener('mousedown', handleClickOutside);
|
|
418
|
+
};
|
|
419
|
+
}, [visible, hide]);
|
|
346
420
|
const handleMouseLeave = ({
|
|
347
421
|
relatedTarget
|
|
348
422
|
}) => {
|
|
@@ -350,23 +424,32 @@ function Tooltip(props) {
|
|
|
350
424
|
if (relatedTarget === wrapperRef.current || relatedTarget === tooltipRef.current || relatedTarget?.parentElement === tooltipRef.current) {
|
|
351
425
|
return;
|
|
352
426
|
}
|
|
353
|
-
|
|
427
|
+
const selection = window.getSelection();
|
|
428
|
+
if (selection && selection.toString().length > 0) {
|
|
429
|
+
// Check if selection is within tooltip content
|
|
430
|
+
const selectionRange = selection.getRangeAt(0);
|
|
431
|
+
if (tooltipRef.current?.contains(selectionRange.commonAncestorContainer) || tooltipRef.current?.contains(selection.anchorNode) || tooltipRef.current?.contains(selection.focusNode)) {
|
|
432
|
+
return; // Keep tooltip open during text selection
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
hide(true);
|
|
436
|
+
};
|
|
437
|
+
const handleTooltipMouseEnter = () => {
|
|
438
|
+
clearTimeout(hideTimeoutRef.current);
|
|
354
439
|
};
|
|
355
440
|
const handleFocusOut = e => {
|
|
356
441
|
const {
|
|
357
|
-
|
|
442
|
+
relatedTarget
|
|
358
443
|
} = e;
|
|
359
444
|
|
|
360
|
-
// Don't hide
|
|
361
|
-
|
|
362
|
-
if (target === wrapperRef.current && isHovered) {
|
|
363
|
-
e.stopPropagation();
|
|
445
|
+
// Don't hide if focus moved to the tooltip or another element within the wrapper
|
|
446
|
+
if (tooltipRef.current?.contains(relatedTarget) || wrapperRef.current?.contains(relatedTarget)) {
|
|
364
447
|
return;
|
|
365
448
|
}
|
|
366
|
-
hide();
|
|
449
|
+
hide(false);
|
|
367
450
|
};
|
|
368
451
|
const hideTooltipViaEscape = e => {
|
|
369
|
-
e.code === 'Escape' && hide();
|
|
452
|
+
e.code === 'Escape' && hide(false);
|
|
370
453
|
};
|
|
371
454
|
const renderTooltip = () => {
|
|
372
455
|
return jsxRuntime.jsxs("div", {
|
|
@@ -377,6 +460,7 @@ function Tooltip(props) {
|
|
|
377
460
|
style: position || getTooltipPosition(wrapperRef.current),
|
|
378
461
|
ref: tooltipRef,
|
|
379
462
|
onClick: e => e.stopPropagation(),
|
|
463
|
+
onMouseEnter: handleTooltipMouseEnter,
|
|
380
464
|
onMouseLeave: handleMouseLeave,
|
|
381
465
|
children: [jsxRuntime.jsx("div", {
|
|
382
466
|
class: "bio-properties-panel-tooltip-content",
|
|
@@ -390,7 +474,7 @@ function Tooltip(props) {
|
|
|
390
474
|
class: "bio-properties-panel-tooltip-wrapper",
|
|
391
475
|
tabIndex: "0",
|
|
392
476
|
ref: wrapperRef,
|
|
393
|
-
onMouseEnter:
|
|
477
|
+
onMouseEnter: handleWrapperMouseEnter,
|
|
394
478
|
onMouseLeave: handleMouseLeave,
|
|
395
479
|
onFocus: show,
|
|
396
480
|
onBlur: handleFocusOut,
|
|
@@ -706,6 +790,9 @@ function useElementVisible(element) {
|
|
|
706
790
|
return visible;
|
|
707
791
|
}
|
|
708
792
|
|
|
793
|
+
/**
|
|
794
|
+
* @param {import('../PropertiesPanel').GroupDefinition} props
|
|
795
|
+
*/
|
|
709
796
|
function Group(props) {
|
|
710
797
|
const {
|
|
711
798
|
element,
|
|
@@ -760,8 +847,6 @@ function Group(props) {
|
|
|
760
847
|
class: classnames('bio-properties-panel-group-header', edited ? '' : 'empty', open ? 'open' : '', sticky && open ? 'sticky' : ''),
|
|
761
848
|
onClick: toggleOpen,
|
|
762
849
|
children: [jsxRuntime.jsx("div", {
|
|
763
|
-
title: props.tooltip ? null : label,
|
|
764
|
-
"data-title": label,
|
|
765
850
|
class: "bio-properties-panel-group-header-title",
|
|
766
851
|
children: jsxRuntime.jsx(TooltipWrapper, {
|
|
767
852
|
value: props.tooltip,
|
|
@@ -1114,6 +1199,13 @@ function useUpdateLayoutEffect(effect, deps) {
|
|
|
1114
1199
|
}, deps);
|
|
1115
1200
|
}
|
|
1116
1201
|
|
|
1202
|
+
/**
|
|
1203
|
+
*
|
|
1204
|
+
* @param {object} props
|
|
1205
|
+
* @param {string} [props.class]
|
|
1206
|
+
* @param {import('preact').Component[]} [props.menuItems]
|
|
1207
|
+
* @returns
|
|
1208
|
+
*/
|
|
1117
1209
|
function DropdownButton(props) {
|
|
1118
1210
|
const {
|
|
1119
1211
|
class: className,
|
|
@@ -1271,7 +1363,6 @@ function CollapsibleEntry(props) {
|
|
|
1271
1363
|
class: "bio-properties-panel-collapsible-entry-header",
|
|
1272
1364
|
onClick: toggleOpen,
|
|
1273
1365
|
children: [jsxRuntime.jsx("div", {
|
|
1274
|
-
title: label || placeholderLabel,
|
|
1275
1366
|
class: classnames('bio-properties-panel-collapsible-entry-header-title', !label && 'empty'),
|
|
1276
1367
|
children: label || placeholderLabel
|
|
1277
1368
|
}), jsxRuntime.jsx("button", {
|
|
@@ -1308,6 +1399,9 @@ function CollapsibleEntry(props) {
|
|
|
1308
1399
|
});
|
|
1309
1400
|
}
|
|
1310
1401
|
|
|
1402
|
+
/**
|
|
1403
|
+
* @param {import('../PropertiesPanel').ListItemDefinition} props
|
|
1404
|
+
*/
|
|
1311
1405
|
function ListItem(props) {
|
|
1312
1406
|
const {
|
|
1313
1407
|
autoFocusEntry,
|
|
@@ -1410,8 +1504,6 @@ function ListGroup(props) {
|
|
|
1410
1504
|
class: classnames('bio-properties-panel-group-header', hasItems ? '' : 'empty', hasItems && open ? 'open' : '', sticky && open ? 'sticky' : ''),
|
|
1411
1505
|
onClick: hasItems ? toggleOpen : noop$6,
|
|
1412
1506
|
children: [jsxRuntime.jsx("div", {
|
|
1413
|
-
title: props.tooltip ? null : label,
|
|
1414
|
-
"data-title": label,
|
|
1415
1507
|
class: "bio-properties-panel-group-header-title",
|
|
1416
1508
|
children: jsxRuntime.jsx(TooltipWrapper, {
|
|
1417
1509
|
value: props.tooltip,
|
|
@@ -1481,6 +1573,12 @@ function getNewItemIds(newItems, oldItems) {
|
|
|
1481
1573
|
return newIds.filter(itemId => !oldIds.includes(itemId));
|
|
1482
1574
|
}
|
|
1483
1575
|
|
|
1576
|
+
/**
|
|
1577
|
+
* @param {Object} props
|
|
1578
|
+
* @param {Object} props.element
|
|
1579
|
+
* @param {String} props.forId - id of the entry the description is used for
|
|
1580
|
+
* @param {String} props.value
|
|
1581
|
+
*/
|
|
1484
1582
|
function Description(props) {
|
|
1485
1583
|
const {
|
|
1486
1584
|
element,
|
|
@@ -1612,6 +1710,15 @@ function prefixId$8(id) {
|
|
|
1612
1710
|
return `bio-properties-panel-${id}`;
|
|
1613
1711
|
}
|
|
1614
1712
|
|
|
1713
|
+
/**
|
|
1714
|
+
* Button to open popups.
|
|
1715
|
+
*
|
|
1716
|
+
* @param {Object} props
|
|
1717
|
+
* @param {Function} props.onClick - Callback to trigger when the button is clicked.
|
|
1718
|
+
* @param {string} [props.title] - Tooltip text for the button.
|
|
1719
|
+
* @param {boolean} [props.disabled] - Whether the button is disabled.
|
|
1720
|
+
* @param {string} [props.className] - Additional class names for the button.
|
|
1721
|
+
*/
|
|
1615
1722
|
function OpenPopupButton({
|
|
1616
1723
|
onClick,
|
|
1617
1724
|
title = 'Open pop-up editor'
|
|
@@ -1757,6 +1864,7 @@ const FeelEditor = compat.forwardRef((props, ref) => {
|
|
|
1757
1864
|
enableGutters,
|
|
1758
1865
|
value,
|
|
1759
1866
|
onInput,
|
|
1867
|
+
onKeyDown: onKeyDownProp = noop$4,
|
|
1760
1868
|
onFeelToggle = noop$4,
|
|
1761
1869
|
onLint = noop$4,
|
|
1762
1870
|
onOpenPopup = noop$4,
|
|
@@ -1789,6 +1897,8 @@ const FeelEditor = compat.forwardRef((props, ref) => {
|
|
|
1789
1897
|
* - AND the cursor is at the beginning of the input
|
|
1790
1898
|
*/
|
|
1791
1899
|
const onKeyDown = e => {
|
|
1900
|
+
// Call parent onKeyDown handler first
|
|
1901
|
+
onKeyDownProp(e);
|
|
1792
1902
|
if (e.key !== 'Backspace' || !editor) {
|
|
1793
1903
|
return;
|
|
1794
1904
|
}
|
|
@@ -1908,6 +2018,22 @@ function FeelIcon(props) {
|
|
|
1908
2018
|
});
|
|
1909
2019
|
}
|
|
1910
2020
|
|
|
2021
|
+
/**
|
|
2022
|
+
* @param {KeyboardEvent} event
|
|
2023
|
+
* @return {boolean}
|
|
2024
|
+
*/
|
|
2025
|
+
function isCmd(event) {
|
|
2026
|
+
// ensure we don't react to AltGr
|
|
2027
|
+
// (mapped to CTRL + ALT)
|
|
2028
|
+
if (event.altKey) {
|
|
2029
|
+
return false;
|
|
2030
|
+
}
|
|
2031
|
+
return event.ctrlKey || event.metaKey;
|
|
2032
|
+
}
|
|
2033
|
+
function isCmdWithChar(event) {
|
|
2034
|
+
return isCmd(event) && event.key.length === 1 && /^[a-zA-Z]$/.test(event.key);
|
|
2035
|
+
}
|
|
2036
|
+
|
|
1911
2037
|
function ToggleSwitch(props) {
|
|
1912
2038
|
const {
|
|
1913
2039
|
id,
|
|
@@ -2221,7 +2347,7 @@ function FeelTextfield(props) {
|
|
|
2221
2347
|
element,
|
|
2222
2348
|
label,
|
|
2223
2349
|
hostLanguage,
|
|
2224
|
-
onInput,
|
|
2350
|
+
onInput: commitValue,
|
|
2225
2351
|
onBlur,
|
|
2226
2352
|
onError,
|
|
2227
2353
|
placeholder,
|
|
@@ -2237,6 +2363,12 @@ function FeelTextfield(props) {
|
|
|
2237
2363
|
const [localValue, setLocalValue] = hooks.useState(value);
|
|
2238
2364
|
const editorRef = useShowEntryEvent(id);
|
|
2239
2365
|
const containerRef = hooks.useRef();
|
|
2366
|
+
const onInput = hooks.useCallback(newValue => {
|
|
2367
|
+
// we don't commit empty FEEL expressions,
|
|
2368
|
+
// but instead serialize them as <undefined>
|
|
2369
|
+
const newModelValue = newValue === '' || newValue === '=' ? undefined : newValue;
|
|
2370
|
+
commitValue(newModelValue);
|
|
2371
|
+
}, [commitValue]);
|
|
2240
2372
|
const feelActive = minDash.isString(localValue) && localValue.startsWith('=') || feel === 'required';
|
|
2241
2373
|
const feelOnlyValue = minDash.isString(localValue) && localValue.startsWith('=') ? localValue.substring(1) : localValue;
|
|
2242
2374
|
const feelLanguageContext = hooks.useContext(FeelLanguageContext);
|
|
@@ -2256,13 +2388,7 @@ function FeelTextfield(props) {
|
|
|
2256
2388
|
/**
|
|
2257
2389
|
* @type { import('min-dash').DebouncedFunction }
|
|
2258
2390
|
*/
|
|
2259
|
-
const
|
|
2260
|
-
const handleInput = newValue => {
|
|
2261
|
-
// we don't commit empty FEEL expressions,
|
|
2262
|
-
// but instead serialize them as <undefined>
|
|
2263
|
-
const newModelValue = newValue === '' || newValue === '=' ? undefined : newValue;
|
|
2264
|
-
handleInputCallback(newModelValue);
|
|
2265
|
-
};
|
|
2391
|
+
const handleInput = useDebounce(onInput, debounce);
|
|
2266
2392
|
const handleFeelToggle = useStaticCallback(() => {
|
|
2267
2393
|
if (feel === 'required') {
|
|
2268
2394
|
return;
|
|
@@ -2275,7 +2401,7 @@ function FeelTextfield(props) {
|
|
|
2275
2401
|
handleInput(feelOnlyValue);
|
|
2276
2402
|
}
|
|
2277
2403
|
});
|
|
2278
|
-
const handleLocalInput = newValue => {
|
|
2404
|
+
const handleLocalInput = (newValue, useDebounce = true) => {
|
|
2279
2405
|
if (feelActive) {
|
|
2280
2406
|
newValue = '=' + newValue;
|
|
2281
2407
|
}
|
|
@@ -2283,23 +2409,33 @@ function FeelTextfield(props) {
|
|
|
2283
2409
|
return;
|
|
2284
2410
|
}
|
|
2285
2411
|
setLocalValue(newValue);
|
|
2286
|
-
|
|
2412
|
+
if (useDebounce) {
|
|
2413
|
+
handleInput(newValue);
|
|
2414
|
+
} else {
|
|
2415
|
+
onInput(newValue);
|
|
2416
|
+
}
|
|
2287
2417
|
if (!feelActive && minDash.isString(newValue) && newValue.startsWith('=')) {
|
|
2288
2418
|
// focus is behind `=` sign that will be removed
|
|
2289
2419
|
setFocus(-1);
|
|
2290
2420
|
}
|
|
2291
2421
|
};
|
|
2292
2422
|
const handleOnBlur = e => {
|
|
2423
|
+
handleInput.cancel?.();
|
|
2293
2424
|
if (e.target.type === 'checkbox') {
|
|
2294
2425
|
onInput(e.target.checked);
|
|
2295
2426
|
} else {
|
|
2296
2427
|
const trimmedValue = e.target.value.trim();
|
|
2297
|
-
|
|
2428
|
+
handleLocalInput(trimmedValue, false);
|
|
2298
2429
|
}
|
|
2299
2430
|
if (onBlur) {
|
|
2300
2431
|
onBlur(e);
|
|
2301
2432
|
}
|
|
2302
2433
|
};
|
|
2434
|
+
const handleOnKeyDown = e => {
|
|
2435
|
+
if (isCmdWithChar(e)) {
|
|
2436
|
+
handleInput.flush();
|
|
2437
|
+
}
|
|
2438
|
+
};
|
|
2303
2439
|
const handleLint = useStaticCallback((lint = []) => {
|
|
2304
2440
|
const syntaxError = lint.some(report => report.type === 'Syntax Error');
|
|
2305
2441
|
if (syntaxError) {
|
|
@@ -2366,12 +2502,23 @@ function FeelTextfield(props) {
|
|
|
2366
2502
|
if (feelActive || isPopupOpen) {
|
|
2367
2503
|
return;
|
|
2368
2504
|
}
|
|
2369
|
-
const
|
|
2370
|
-
if (
|
|
2505
|
+
const feelData = event.clipboardData.getData('application/FEEL');
|
|
2506
|
+
if (feelData) {
|
|
2371
2507
|
setTimeout(() => {
|
|
2372
2508
|
handleFeelToggle();
|
|
2373
2509
|
setFocus();
|
|
2374
2510
|
});
|
|
2511
|
+
return;
|
|
2512
|
+
}
|
|
2513
|
+
const input = event.target;
|
|
2514
|
+
const isFieldEmpty = !input.value;
|
|
2515
|
+
const isAllSelected = input.selectionStart === 0 && input.selectionEnd === input.value.length;
|
|
2516
|
+
if (isFieldEmpty || isAllSelected) {
|
|
2517
|
+
const textData = event.clipboardData.getData('text');
|
|
2518
|
+
const trimmedValue = textData.trim();
|
|
2519
|
+
setLocalValue(trimmedValue);
|
|
2520
|
+
handleInput(trimmedValue);
|
|
2521
|
+
event.preventDefault();
|
|
2375
2522
|
}
|
|
2376
2523
|
};
|
|
2377
2524
|
containerRef.current.addEventListener('copy', copyHandler);
|
|
@@ -2412,6 +2559,7 @@ function FeelTextfield(props) {
|
|
|
2412
2559
|
}), feelActive ? jsxRuntime.jsx(FeelEditor, {
|
|
2413
2560
|
name: id,
|
|
2414
2561
|
onInput: handleLocalInput,
|
|
2562
|
+
onKeyDown: handleOnKeyDown,
|
|
2415
2563
|
contentAttributes: {
|
|
2416
2564
|
'id': prefixId$5(id),
|
|
2417
2565
|
'aria-label': label
|
|
@@ -2434,6 +2582,7 @@ function FeelTextfield(props) {
|
|
|
2434
2582
|
...props,
|
|
2435
2583
|
popupOpen: isPopupOpen,
|
|
2436
2584
|
onInput: handleLocalInput,
|
|
2585
|
+
onKeyDown: handleOnKeyDown,
|
|
2437
2586
|
onBlur: handleOnBlur,
|
|
2438
2587
|
contentAttributes: {
|
|
2439
2588
|
'id': prefixId$5(id),
|
|
@@ -2452,6 +2601,7 @@ const OptionalFeelInput = compat.forwardRef((props, ref) => {
|
|
|
2452
2601
|
id,
|
|
2453
2602
|
disabled,
|
|
2454
2603
|
onInput,
|
|
2604
|
+
onKeyDown,
|
|
2455
2605
|
value,
|
|
2456
2606
|
onFocus,
|
|
2457
2607
|
onBlur,
|
|
@@ -2487,6 +2637,7 @@ const OptionalFeelInput = compat.forwardRef((props, ref) => {
|
|
|
2487
2637
|
class: "bio-properties-panel-input",
|
|
2488
2638
|
onInput: e => onInput(e.target.value),
|
|
2489
2639
|
onFocus: onFocus,
|
|
2640
|
+
onKeyDown: onKeyDown,
|
|
2490
2641
|
onBlur: onBlur,
|
|
2491
2642
|
placeholder: placeholder,
|
|
2492
2643
|
value: value || ''
|
|
@@ -3081,6 +3232,22 @@ function prefixIdLabel(id) {
|
|
|
3081
3232
|
return `bio-properties-panel-feelers-${id}-label`;
|
|
3082
3233
|
}
|
|
3083
3234
|
|
|
3235
|
+
/**
|
|
3236
|
+
* Entry for handling lists represented as nested entries.
|
|
3237
|
+
*
|
|
3238
|
+
* @template Item
|
|
3239
|
+
* @param {object} props
|
|
3240
|
+
* @param {string} props.id
|
|
3241
|
+
* @param {*} props.element
|
|
3242
|
+
* @param {Function} props.onAdd
|
|
3243
|
+
* @param {import('preact').Component} props.component
|
|
3244
|
+
* @param {string} [props.label='<empty>']
|
|
3245
|
+
* @param {Function} [props.onRemove]
|
|
3246
|
+
* @param {Item[]} [props.items]
|
|
3247
|
+
* @param {boolean} [props.open]
|
|
3248
|
+
* @param {string|boolean} [props.autoFocusEntry] either a custom selector string or true to focus the first input
|
|
3249
|
+
* @returns
|
|
3250
|
+
*/
|
|
3084
3251
|
function List(props) {
|
|
3085
3252
|
const {
|
|
3086
3253
|
id,
|
|
@@ -3128,7 +3295,6 @@ function List(props) {
|
|
|
3128
3295
|
class: classnames('bio-properties-panel-list-entry-header', sticky && open ? 'sticky' : ''),
|
|
3129
3296
|
onClick: toggleOpen,
|
|
3130
3297
|
children: [jsxRuntime.jsx("div", {
|
|
3131
|
-
title: label,
|
|
3132
3298
|
class: classnames('bio-properties-panel-list-entry-header-title', open && 'open'),
|
|
3133
3299
|
children: label
|
|
3134
3300
|
}), jsxRuntime.jsxs("div", {
|
|
@@ -3233,6 +3399,24 @@ function useNewItems(items = [], shouldReset) {
|
|
|
3233
3399
|
return previousItems ? items.filter(item => !previousItems.includes(item)) : [];
|
|
3234
3400
|
}
|
|
3235
3401
|
|
|
3402
|
+
/**
|
|
3403
|
+
* @typedef { { value: string, label: string, disabled: boolean, children: { value: string, label: string, disabled: boolean } } } Option
|
|
3404
|
+
*/
|
|
3405
|
+
|
|
3406
|
+
/**
|
|
3407
|
+
* Provides basic select input.
|
|
3408
|
+
*
|
|
3409
|
+
* @param {object} props
|
|
3410
|
+
* @param {string} props.id
|
|
3411
|
+
* @param {string[]} props.path
|
|
3412
|
+
* @param {string} props.label
|
|
3413
|
+
* @param {Function} props.onChange
|
|
3414
|
+
* @param {Function} props.onFocus
|
|
3415
|
+
* @param {Function} props.onBlur
|
|
3416
|
+
* @param {Array<Option>} [props.options]
|
|
3417
|
+
* @param {string} props.value
|
|
3418
|
+
* @param {boolean} [props.disabled]
|
|
3419
|
+
*/
|
|
3236
3420
|
function Select(props) {
|
|
3237
3421
|
const {
|
|
3238
3422
|
id,
|
|
@@ -3387,6 +3571,17 @@ function prefixId$4(id) {
|
|
|
3387
3571
|
return `bio-properties-panel-${id}`;
|
|
3388
3572
|
}
|
|
3389
3573
|
|
|
3574
|
+
/**
|
|
3575
|
+
* @param {Object} props
|
|
3576
|
+
* @param {Function} props.debounce
|
|
3577
|
+
* @param {Boolean} [props.disabled]
|
|
3578
|
+
* @param {Object} props.element
|
|
3579
|
+
* @param {Function} props.getValue
|
|
3580
|
+
* @param {String} props.id
|
|
3581
|
+
* @param {Function} [props.onBlur]
|
|
3582
|
+
* @param {Function} [props.onFocus]
|
|
3583
|
+
* @param {Function} props.setValue
|
|
3584
|
+
*/
|
|
3390
3585
|
function Simple(props) {
|
|
3391
3586
|
const {
|
|
3392
3587
|
debounce,
|
|
@@ -3453,12 +3648,13 @@ function TextArea(props) {
|
|
|
3453
3648
|
id,
|
|
3454
3649
|
label,
|
|
3455
3650
|
debounce,
|
|
3456
|
-
onInput,
|
|
3651
|
+
onInput: commitValue,
|
|
3457
3652
|
value = '',
|
|
3458
3653
|
disabled,
|
|
3459
3654
|
monospace,
|
|
3460
3655
|
onFocus,
|
|
3461
3656
|
onBlur,
|
|
3657
|
+
onPaste,
|
|
3462
3658
|
autoResize = true,
|
|
3463
3659
|
placeholder,
|
|
3464
3660
|
rows = autoResize ? 1 : 2,
|
|
@@ -3466,16 +3662,16 @@ function TextArea(props) {
|
|
|
3466
3662
|
} = props;
|
|
3467
3663
|
const [localValue, setLocalValue] = hooks.useState(value);
|
|
3468
3664
|
const ref = useShowEntryEvent(id);
|
|
3665
|
+
const onInput = hooks.useCallback(newValue => {
|
|
3666
|
+
const newModelValue = newValue === '' ? undefined : newValue;
|
|
3667
|
+
commitValue(newModelValue);
|
|
3668
|
+
}, [commitValue]);
|
|
3469
3669
|
const visible = useElementVisible(ref.current);
|
|
3470
3670
|
|
|
3471
3671
|
/**
|
|
3472
3672
|
* @type { import('min-dash').DebouncedFunction }
|
|
3473
3673
|
*/
|
|
3474
|
-
const
|
|
3475
|
-
const handleInput = newValue => {
|
|
3476
|
-
const newModelValue = newValue === '' ? undefined : newValue;
|
|
3477
|
-
handleInputCallback(newModelValue);
|
|
3478
|
-
};
|
|
3674
|
+
const handleInput = useDebounce(onInput, debounce);
|
|
3479
3675
|
const handleLocalInput = e => {
|
|
3480
3676
|
autoResize && resizeToContents(e.target);
|
|
3481
3677
|
if (e.target.value === localValue) {
|
|
@@ -3488,11 +3684,40 @@ function TextArea(props) {
|
|
|
3488
3684
|
const trimmedValue = e.target.value.trim();
|
|
3489
3685
|
|
|
3490
3686
|
// trim and commit on blur
|
|
3687
|
+
handleInput.cancel?.();
|
|
3491
3688
|
onInput(trimmedValue);
|
|
3689
|
+
setLocalValue(trimmedValue);
|
|
3492
3690
|
if (onBlur) {
|
|
3493
3691
|
onBlur(e);
|
|
3494
3692
|
}
|
|
3495
3693
|
};
|
|
3694
|
+
const handleOnPaste = e => {
|
|
3695
|
+
const input = e.target;
|
|
3696
|
+
const isFieldEmpty = !input.value;
|
|
3697
|
+
const isAllSelected = input.selectionStart === 0 && input.selectionEnd === input.value.length;
|
|
3698
|
+
|
|
3699
|
+
// Trim and handle paste if field is empty or all content is selected
|
|
3700
|
+
if (isFieldEmpty || isAllSelected) {
|
|
3701
|
+
const trimmedValue = e.clipboardData.getData('text').trim();
|
|
3702
|
+
setLocalValue(trimmedValue);
|
|
3703
|
+
handleInput(trimmedValue);
|
|
3704
|
+
if (onPaste) {
|
|
3705
|
+
onPaste(e);
|
|
3706
|
+
}
|
|
3707
|
+
e.preventDefault();
|
|
3708
|
+
return;
|
|
3709
|
+
}
|
|
3710
|
+
|
|
3711
|
+
// Allow default paste behavior for normal text editing
|
|
3712
|
+
if (onPaste) {
|
|
3713
|
+
onPaste(e);
|
|
3714
|
+
}
|
|
3715
|
+
};
|
|
3716
|
+
const handleOnKeyDown = e => {
|
|
3717
|
+
if (isCmdWithChar(e)) {
|
|
3718
|
+
handleInput.flush();
|
|
3719
|
+
}
|
|
3720
|
+
};
|
|
3496
3721
|
hooks.useLayoutEffect(() => {
|
|
3497
3722
|
autoResize && resizeToContents(ref.current);
|
|
3498
3723
|
}, []);
|
|
@@ -3524,7 +3749,9 @@ function TextArea(props) {
|
|
|
3524
3749
|
class: classnames('bio-properties-panel-input', monospace ? 'bio-properties-panel-input-monospace' : '', autoResize ? 'auto-resize' : ''),
|
|
3525
3750
|
onInput: handleLocalInput,
|
|
3526
3751
|
onFocus: onFocus,
|
|
3752
|
+
onKeyDown: handleOnKeyDown,
|
|
3527
3753
|
onBlur: handleOnBlur,
|
|
3754
|
+
onPaste: handleOnPaste,
|
|
3528
3755
|
placeholder: placeholder,
|
|
3529
3756
|
rows: rows,
|
|
3530
3757
|
value: localValue,
|
|
@@ -3545,6 +3772,7 @@ function TextArea(props) {
|
|
|
3545
3772
|
* @param {Function} props.setValue
|
|
3546
3773
|
* @param {Function} props.onFocus
|
|
3547
3774
|
* @param {Function} props.onBlur
|
|
3775
|
+
* @param {Function} props.onPaste
|
|
3548
3776
|
* @param {number} props.rows
|
|
3549
3777
|
* @param {boolean} props.monospace
|
|
3550
3778
|
* @param {Function} [props.validate]
|
|
@@ -3565,6 +3793,7 @@ function TextAreaEntry(props) {
|
|
|
3565
3793
|
validate,
|
|
3566
3794
|
onFocus,
|
|
3567
3795
|
onBlur,
|
|
3796
|
+
onPaste,
|
|
3568
3797
|
placeholder,
|
|
3569
3798
|
autoResize,
|
|
3570
3799
|
tooltip
|
|
@@ -3600,6 +3829,7 @@ function TextAreaEntry(props) {
|
|
|
3600
3829
|
onInput: onInput,
|
|
3601
3830
|
onFocus: onFocus,
|
|
3602
3831
|
onBlur: onBlur,
|
|
3832
|
+
onPaste: onPaste,
|
|
3603
3833
|
rows: rows,
|
|
3604
3834
|
debounce: debounce,
|
|
3605
3835
|
monospace: monospace,
|
|
@@ -3634,32 +3864,57 @@ function Textfield(props) {
|
|
|
3634
3864
|
disabled = false,
|
|
3635
3865
|
id,
|
|
3636
3866
|
label,
|
|
3637
|
-
onInput,
|
|
3867
|
+
onInput: commitValue,
|
|
3638
3868
|
onFocus,
|
|
3639
3869
|
onBlur,
|
|
3870
|
+
onPaste,
|
|
3640
3871
|
placeholder,
|
|
3641
3872
|
value = '',
|
|
3642
3873
|
tooltip
|
|
3643
3874
|
} = props;
|
|
3644
3875
|
const [localValue, setLocalValue] = hooks.useState(value || '');
|
|
3645
3876
|
const ref = useShowEntryEvent(id);
|
|
3877
|
+
const onInput = hooks.useCallback(newValue => {
|
|
3878
|
+
const newModelValue = newValue === '' ? undefined : newValue;
|
|
3879
|
+
commitValue(newModelValue);
|
|
3880
|
+
}, [commitValue]);
|
|
3646
3881
|
|
|
3647
3882
|
/**
|
|
3648
3883
|
* @type { import('min-dash').DebouncedFunction }
|
|
3649
3884
|
*/
|
|
3650
|
-
const
|
|
3885
|
+
const handleInput = useDebounce(onInput, debounce);
|
|
3651
3886
|
const handleOnBlur = e => {
|
|
3652
3887
|
const trimmedValue = e.target.value.trim();
|
|
3653
3888
|
|
|
3654
3889
|
// trim and commit on blur
|
|
3890
|
+
handleInput.cancel?.();
|
|
3655
3891
|
onInput(trimmedValue);
|
|
3892
|
+
setLocalValue(trimmedValue);
|
|
3656
3893
|
if (onBlur) {
|
|
3657
3894
|
onBlur(e);
|
|
3658
3895
|
}
|
|
3659
3896
|
};
|
|
3660
|
-
const
|
|
3661
|
-
const
|
|
3662
|
-
|
|
3897
|
+
const handleOnPaste = e => {
|
|
3898
|
+
const input = e.target;
|
|
3899
|
+
const isFieldEmpty = !input.value;
|
|
3900
|
+
const isAllSelected = input.selectionStart === 0 && input.selectionEnd === input.value.length;
|
|
3901
|
+
|
|
3902
|
+
// Trim and handle paste if field is empty or all content is selected (overwrite)
|
|
3903
|
+
if (isFieldEmpty || isAllSelected) {
|
|
3904
|
+
const trimmedValue = e.clipboardData.getData('text').trim();
|
|
3905
|
+
setLocalValue(trimmedValue);
|
|
3906
|
+
handleInput(trimmedValue);
|
|
3907
|
+
if (onPaste) {
|
|
3908
|
+
onPaste(e);
|
|
3909
|
+
}
|
|
3910
|
+
e.preventDefault();
|
|
3911
|
+
return;
|
|
3912
|
+
}
|
|
3913
|
+
|
|
3914
|
+
// Allow default paste behavior for normal text editing
|
|
3915
|
+
if (onPaste) {
|
|
3916
|
+
onPaste(e);
|
|
3917
|
+
}
|
|
3663
3918
|
};
|
|
3664
3919
|
const handleLocalInput = e => {
|
|
3665
3920
|
if (e.target.value === localValue) {
|
|
@@ -3674,6 +3929,11 @@ function Textfield(props) {
|
|
|
3674
3929
|
}
|
|
3675
3930
|
setLocalValue(value);
|
|
3676
3931
|
}, [value]);
|
|
3932
|
+
const handleOnKeyDown = e => {
|
|
3933
|
+
if (isCmdWithChar(e)) {
|
|
3934
|
+
handleInput.flush();
|
|
3935
|
+
}
|
|
3936
|
+
};
|
|
3677
3937
|
return jsxRuntime.jsxs("div", {
|
|
3678
3938
|
class: "bio-properties-panel-textfield",
|
|
3679
3939
|
children: [jsxRuntime.jsx("label", {
|
|
@@ -3696,7 +3956,9 @@ function Textfield(props) {
|
|
|
3696
3956
|
class: "bio-properties-panel-input",
|
|
3697
3957
|
onInput: handleLocalInput,
|
|
3698
3958
|
onFocus: onFocus,
|
|
3959
|
+
onKeyDown: handleOnKeyDown,
|
|
3699
3960
|
onBlur: handleOnBlur,
|
|
3961
|
+
onPaste: handleOnPaste,
|
|
3700
3962
|
placeholder: placeholder,
|
|
3701
3963
|
value: localValue
|
|
3702
3964
|
})]
|
|
@@ -3731,6 +3993,7 @@ function TextfieldEntry(props) {
|
|
|
3731
3993
|
validate,
|
|
3732
3994
|
onFocus,
|
|
3733
3995
|
onBlur,
|
|
3996
|
+
onPaste,
|
|
3734
3997
|
placeholder,
|
|
3735
3998
|
tooltip
|
|
3736
3999
|
} = props;
|
|
@@ -3766,6 +4029,7 @@ function TextfieldEntry(props) {
|
|
|
3766
4029
|
onInput: onInput,
|
|
3767
4030
|
onFocus: onFocus,
|
|
3768
4031
|
onBlur: onBlur,
|
|
4032
|
+
onPaste: onPaste,
|
|
3769
4033
|
placeholder: placeholder,
|
|
3770
4034
|
value: value,
|
|
3771
4035
|
tooltip: tooltip,
|
|
@@ -3798,7 +4062,7 @@ const DEFAULT_DEBOUNCE_TIME = 600;
|
|
|
3798
4062
|
* - If `debounceDelay` is `false`, the function executes immediately without debouncing.
|
|
3799
4063
|
* - If a number is provided, the function execution is delayed by the given time in milliseconds.
|
|
3800
4064
|
*
|
|
3801
|
-
* @param { Boolean | Number } [debounceDelay=
|
|
4065
|
+
* @param { Boolean | Number } [debounceDelay=600]
|
|
3802
4066
|
*
|
|
3803
4067
|
* @example
|
|
3804
4068
|
* const debounce = debounceInput();
|
|
@@ -4118,6 +4382,24 @@ function cancel(event) {
|
|
|
4118
4382
|
event.stopPropagation();
|
|
4119
4383
|
}
|
|
4120
4384
|
|
|
4385
|
+
/**
|
|
4386
|
+
* @typedef {Object} FeelPopupProps
|
|
4387
|
+
* @property {string} entryId
|
|
4388
|
+
* @property {Function} onInput
|
|
4389
|
+
* @property {Function} onClose
|
|
4390
|
+
* @property {string} title
|
|
4391
|
+
* @property {'feel'|'feelers'} type
|
|
4392
|
+
* @property {string} value
|
|
4393
|
+
* @property {Array} [links]
|
|
4394
|
+
* @property {Array|Object} [variables]
|
|
4395
|
+
* @property {Object} [position]
|
|
4396
|
+
* @property {string} [hostLanguage]
|
|
4397
|
+
* @property {boolean} [singleLine]
|
|
4398
|
+
* @property {HTMLElement} [sourceElement]
|
|
4399
|
+
* @property {HTMLElement|string} [tooltipContainer]
|
|
4400
|
+
* @property {Object} [eventBus]
|
|
4401
|
+
*/
|
|
4402
|
+
|
|
4121
4403
|
const FEEL_POPUP_WIDTH = 700;
|
|
4122
4404
|
const FEEL_POPUP_HEIGHT = 250;
|
|
4123
4405
|
|