@bpmn-io/properties-panel 3.18.2 → 3.20.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 +69 -134
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +68 -133
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.esm.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useContext, useState, useRef, useEffect, useMemo, useCallback, useLayoutEffect } from '../preact/hooks';
|
|
2
|
-
import { isFunction, isString, isArray, get, assign, set,
|
|
2
|
+
import { isFunction, isString, isArray, get, assign, set, isNumber, debounce } from 'min-dash';
|
|
3
3
|
import { createPortal, forwardRef } from '../preact/compat';
|
|
4
4
|
import { jsx, jsxs, Fragment } from '../preact/jsx-runtime';
|
|
5
5
|
import { createContext, createElement } from '../preact';
|
|
@@ -974,6 +974,7 @@ const CodeEditor = forwardRef((props, ref) => {
|
|
|
974
974
|
onFeelToggle = noop$5,
|
|
975
975
|
onLint = noop$5,
|
|
976
976
|
onPopupOpen = noop$5,
|
|
977
|
+
placeholder,
|
|
977
978
|
popupOpen,
|
|
978
979
|
disabled,
|
|
979
980
|
tooltipContainer,
|
|
@@ -1010,6 +1011,7 @@ const CodeEditor = forwardRef((props, ref) => {
|
|
|
1010
1011
|
onChange: handleInput,
|
|
1011
1012
|
onKeyDown: onKeyDown,
|
|
1012
1013
|
onLint: onLint,
|
|
1014
|
+
placeholder: placeholder,
|
|
1013
1015
|
tooltipContainer: tooltipContainer,
|
|
1014
1016
|
value: localValue,
|
|
1015
1017
|
variables: variables,
|
|
@@ -1039,6 +1041,12 @@ const CodeEditor = forwardRef((props, ref) => {
|
|
|
1039
1041
|
}
|
|
1040
1042
|
editor.setVariables(variables);
|
|
1041
1043
|
}, [variables]);
|
|
1044
|
+
useEffect(() => {
|
|
1045
|
+
if (!editor) {
|
|
1046
|
+
return;
|
|
1047
|
+
}
|
|
1048
|
+
editor.setPlaceholder(placeholder);
|
|
1049
|
+
}, [placeholder]);
|
|
1042
1050
|
const handleClick = () => {
|
|
1043
1051
|
ref.current.focus();
|
|
1044
1052
|
};
|
|
@@ -1944,6 +1952,7 @@ function FeelTextfieldComponent(props) {
|
|
|
1944
1952
|
hostLanguage,
|
|
1945
1953
|
onInput,
|
|
1946
1954
|
onError,
|
|
1955
|
+
placeholder,
|
|
1947
1956
|
feel,
|
|
1948
1957
|
value = '',
|
|
1949
1958
|
disabled = false,
|
|
@@ -2118,6 +2127,7 @@ function FeelTextfieldComponent(props) {
|
|
|
2118
2127
|
},
|
|
2119
2128
|
onLint: handleLint,
|
|
2120
2129
|
onPopupOpen: handlePopupOpen,
|
|
2130
|
+
placeholder: placeholder,
|
|
2121
2131
|
value: feelOnlyValue,
|
|
2122
2132
|
variables: variables,
|
|
2123
2133
|
ref: editorRef,
|
|
@@ -2146,7 +2156,8 @@ const OptionalFeelInput = forwardRef((props, ref) => {
|
|
|
2146
2156
|
onInput,
|
|
2147
2157
|
value,
|
|
2148
2158
|
onFocus,
|
|
2149
|
-
onBlur
|
|
2159
|
+
onBlur,
|
|
2160
|
+
placeholder
|
|
2150
2161
|
} = props;
|
|
2151
2162
|
const inputRef = useRef();
|
|
2152
2163
|
|
|
@@ -2179,6 +2190,7 @@ const OptionalFeelInput = forwardRef((props, ref) => {
|
|
|
2179
2190
|
onInput: e => onInput(e.target.value),
|
|
2180
2191
|
onFocus: onFocus,
|
|
2181
2192
|
onBlur: onBlur,
|
|
2193
|
+
placeholder: placeholder,
|
|
2182
2194
|
value: value || ''
|
|
2183
2195
|
});
|
|
2184
2196
|
});
|
|
@@ -2236,7 +2248,8 @@ const OptionalFeelTextArea = forwardRef((props, ref) => {
|
|
|
2236
2248
|
onInput,
|
|
2237
2249
|
value,
|
|
2238
2250
|
onFocus,
|
|
2239
|
-
onBlur
|
|
2251
|
+
onBlur,
|
|
2252
|
+
placeholder
|
|
2240
2253
|
} = props;
|
|
2241
2254
|
const inputRef = useRef();
|
|
2242
2255
|
|
|
@@ -2264,6 +2277,7 @@ const OptionalFeelTextArea = forwardRef((props, ref) => {
|
|
|
2264
2277
|
onInput: e => onInput(e.target.value),
|
|
2265
2278
|
onFocus: onFocus,
|
|
2266
2279
|
onBlur: onBlur,
|
|
2280
|
+
placeholder: placeholder,
|
|
2267
2281
|
value: value || '',
|
|
2268
2282
|
"data-gramm": "false"
|
|
2269
2283
|
});
|
|
@@ -2359,6 +2373,7 @@ const OptionalFeelCheckbox = forwardRef((props, ref) => {
|
|
|
2359
2373
|
* @param {Function} props.variables
|
|
2360
2374
|
* @param {Function} props.onFocus
|
|
2361
2375
|
* @param {Function} props.onBlur
|
|
2376
|
+
* @param {string} [props.placeholder]
|
|
2362
2377
|
* @param {string|import('preact').Component} props.tooltip
|
|
2363
2378
|
*/
|
|
2364
2379
|
function FeelEntry(props) {
|
|
@@ -2381,6 +2396,7 @@ function FeelEntry(props) {
|
|
|
2381
2396
|
variables,
|
|
2382
2397
|
onFocus,
|
|
2383
2398
|
onBlur,
|
|
2399
|
+
placeholder,
|
|
2384
2400
|
tooltip
|
|
2385
2401
|
} = props;
|
|
2386
2402
|
const [validationError, setValidationError] = useState(null);
|
|
@@ -2424,6 +2440,7 @@ function FeelEntry(props) {
|
|
|
2424
2440
|
onError: onError,
|
|
2425
2441
|
onFocus: onFocus,
|
|
2426
2442
|
onBlur: onBlur,
|
|
2443
|
+
placeholder: placeholder,
|
|
2427
2444
|
example: example,
|
|
2428
2445
|
hostLanguage: hostLanguage,
|
|
2429
2446
|
singleLine: singleLine,
|
|
@@ -2492,6 +2509,7 @@ function FeelNumberEntry(props) {
|
|
|
2492
2509
|
* @param {Function} props.variables
|
|
2493
2510
|
* @param {Function} props.onFocus
|
|
2494
2511
|
* @param {Function} props.onBlur
|
|
2512
|
+
* @param {string} [props.placeholder]
|
|
2495
2513
|
*/
|
|
2496
2514
|
function FeelTextAreaEntry(props) {
|
|
2497
2515
|
return jsx(FeelEntry, {
|
|
@@ -2666,7 +2684,6 @@ const DEFAULT_TOOLTIP = {};
|
|
|
2666
2684
|
* id: String,
|
|
2667
2685
|
* items: Array<ListItemDefinition>,
|
|
2668
2686
|
* label: String,
|
|
2669
|
-
* shouldSort?: Boolean,
|
|
2670
2687
|
* shouldOpen?: Boolean
|
|
2671
2688
|
* } } ListGroupDefinition
|
|
2672
2689
|
*
|
|
@@ -3099,6 +3116,7 @@ function ListItem(props) {
|
|
|
3099
3116
|
} else if (isFunction(focusableInput.focus)) {
|
|
3100
3117
|
focusableInput.focus();
|
|
3101
3118
|
}
|
|
3119
|
+
focusableInput.scrollIntoView();
|
|
3102
3120
|
}
|
|
3103
3121
|
}
|
|
3104
3122
|
}, [autoOpen, autoFocusEntry]);
|
|
@@ -3123,97 +3141,61 @@ function ListGroup(props) {
|
|
|
3123
3141
|
id,
|
|
3124
3142
|
items,
|
|
3125
3143
|
label,
|
|
3126
|
-
shouldOpen = true
|
|
3127
|
-
shouldSort = true
|
|
3144
|
+
shouldOpen = true
|
|
3128
3145
|
} = props;
|
|
3146
|
+
useEffect(() => {
|
|
3147
|
+
if (props.shouldSort != undefined) {
|
|
3148
|
+
console.warn('the property \'shouldSort\' is no longer supported');
|
|
3149
|
+
}
|
|
3150
|
+
}, [props.shouldSort]);
|
|
3129
3151
|
const groupRef = useRef(null);
|
|
3130
3152
|
const [open, setOpen] = useLayoutState(['groups', id, 'open'], false);
|
|
3131
3153
|
const [sticky, setSticky] = useState(false);
|
|
3132
3154
|
const onShow = useCallback(() => setOpen(true), [setOpen]);
|
|
3133
|
-
const [
|
|
3134
|
-
const [
|
|
3155
|
+
const [localItems, setLocalItems] = useState([]);
|
|
3156
|
+
const [newlyAddedItemIds, setNewlyAddedItemIds] = useState([]);
|
|
3135
3157
|
|
|
3136
3158
|
// Flag to mark that add button was clicked in the last render cycle
|
|
3137
3159
|
const [addTriggered, setAddTriggered] = useState(false);
|
|
3138
|
-
const prevItems = usePrevious(items);
|
|
3139
3160
|
const prevElement = usePrevious(element);
|
|
3140
3161
|
const elementChanged = element !== prevElement;
|
|
3141
|
-
const shouldHandleEffects = !elementChanged &&
|
|
3142
|
-
|
|
3143
|
-
//
|
|
3144
|
-
|
|
3145
|
-
|
|
3146
|
-
|
|
3147
|
-
|
|
3148
|
-
//
|
|
3149
|
-
|
|
3150
|
-
// (0) set initial ordering from given items
|
|
3162
|
+
const shouldHandleEffects = !elementChanged && shouldOpen;
|
|
3163
|
+
|
|
3164
|
+
// (0) delay setting items
|
|
3165
|
+
//
|
|
3166
|
+
// We need to this to align the render cycles of items
|
|
3167
|
+
// with the detection of newly added items.
|
|
3168
|
+
// This is important, because the autoOpen property can
|
|
3169
|
+
// only set per list item on its very first render.
|
|
3151
3170
|
useEffect(() => {
|
|
3152
|
-
|
|
3153
|
-
|
|
3154
|
-
}
|
|
3155
|
-
}, [items, element]);
|
|
3171
|
+
setLocalItems(items);
|
|
3172
|
+
}, [items]);
|
|
3156
3173
|
|
|
3157
|
-
// (1) items were added
|
|
3174
|
+
// (1) handle auto opening when items were added
|
|
3158
3175
|
useEffect(() => {
|
|
3159
3176
|
// reset addTriggered flag
|
|
3160
3177
|
setAddTriggered(false);
|
|
3161
|
-
if (shouldHandleEffects &&
|
|
3162
|
-
|
|
3163
|
-
|
|
3164
|
-
|
|
3165
|
-
|
|
3178
|
+
if (shouldHandleEffects && localItems) {
|
|
3179
|
+
if (addTriggered) {
|
|
3180
|
+
const previousItemIds = localItems.map(item => item.id);
|
|
3181
|
+
const currentItemsIds = items.map(item => item.id);
|
|
3182
|
+
const newItemIds = currentItemsIds.filter(itemId => !previousItemIds.includes(itemId));
|
|
3183
|
+
|
|
3184
|
+
// open if not open, configured and triggered by add button
|
|
3185
|
+
//
|
|
3186
|
+
// TODO(marstamm): remove once we refactor layout handling for listGroups.
|
|
3187
|
+
// Ideally, opening should be handled as part of the `add` callback and
|
|
3188
|
+
// not be a concern for the ListGroup component.
|
|
3189
|
+
if (!open && shouldOpen && newItemIds.length > 0) {
|
|
3190
|
+
toggleOpen();
|
|
3166
3191
|
}
|
|
3167
|
-
|
|
3168
|
-
let newOrdering = ordering;
|
|
3169
|
-
|
|
3170
|
-
// open if not open, configured and triggered by add button
|
|
3171
|
-
//
|
|
3172
|
-
// TODO(marstamm): remove once we refactor layout handling for listGroups.
|
|
3173
|
-
// Ideally, opening should be handled as part of the `add` callback and
|
|
3174
|
-
// not be a concern for the ListGroup component.
|
|
3175
|
-
if (addTriggered && !open && shouldOpen) {
|
|
3176
|
-
toggleOpen();
|
|
3177
|
-
}
|
|
3178
|
-
|
|
3179
|
-
// filter when not open and configured
|
|
3180
|
-
if (!open && shouldSort) {
|
|
3181
|
-
newOrdering = createOrdering(sortItems(items));
|
|
3182
|
-
}
|
|
3183
|
-
|
|
3184
|
-
// add new items on top or bottom depending on sorting behavior
|
|
3185
|
-
newOrdering = newOrdering.filter(item => !add.includes(item));
|
|
3186
|
-
if (shouldSort) {
|
|
3187
|
-
newOrdering.unshift(...add);
|
|
3192
|
+
setNewlyAddedItemIds(newItemIds);
|
|
3188
3193
|
} else {
|
|
3189
|
-
|
|
3194
|
+
// ignore newly added items that do not result from a triggered add
|
|
3195
|
+
setNewlyAddedItemIds([]);
|
|
3190
3196
|
}
|
|
3191
|
-
setOrdering(newOrdering);
|
|
3192
|
-
setNewItemAdded(addTriggered);
|
|
3193
|
-
} else {
|
|
3194
|
-
setNewItemAdded(false);
|
|
3195
|
-
}
|
|
3196
|
-
}, [items, open, shouldHandleEffects, addTriggered]);
|
|
3197
|
-
|
|
3198
|
-
// (2) sort items on open if shouldSort is set
|
|
3199
|
-
useEffect(() => {
|
|
3200
|
-
if (shouldSort && open && !newItemAdded) {
|
|
3201
|
-
setOrdering(createOrdering(sortItems(items)));
|
|
3202
3197
|
}
|
|
3203
|
-
}, [open,
|
|
3204
|
-
|
|
3205
|
-
// (3) items were deleted
|
|
3206
|
-
useEffect(() => {
|
|
3207
|
-
if (shouldHandleEffects && prevItems && items.length < prevItems.length) {
|
|
3208
|
-
let keep = [];
|
|
3209
|
-
ordering.forEach(o => {
|
|
3210
|
-
if (getItem(items, o)) {
|
|
3211
|
-
keep.push(o);
|
|
3212
|
-
}
|
|
3213
|
-
});
|
|
3214
|
-
setOrdering(keep);
|
|
3215
|
-
}
|
|
3216
|
-
}, [items, shouldHandleEffects]);
|
|
3198
|
+
}, [items, open, shouldHandleEffects, addTriggered, localItems]);
|
|
3217
3199
|
|
|
3218
3200
|
// set css class when group is sticky to top
|
|
3219
3201
|
useStickyIntersectionObserver(groupRef, 'div.bio-properties-panel-scroll-container', setSticky);
|
|
@@ -3285,8 +3267,7 @@ function ListGroup(props) {
|
|
|
3285
3267
|
class: classnames('bio-properties-panel-list', open && hasItems ? 'open' : ''),
|
|
3286
3268
|
children: jsx(LayoutContext.Provider, {
|
|
3287
3269
|
value: propertiesPanelContext,
|
|
3288
|
-
children:
|
|
3289
|
-
const item = getItem(items, o);
|
|
3270
|
+
children: localItems.map((item, index) => {
|
|
3290
3271
|
if (!item) {
|
|
3291
3272
|
return;
|
|
3292
3273
|
}
|
|
@@ -3296,7 +3277,7 @@ function ListGroup(props) {
|
|
|
3296
3277
|
|
|
3297
3278
|
// if item was added, open it
|
|
3298
3279
|
// Existing items will not be affected as autoOpen is only applied on first render
|
|
3299
|
-
const autoOpen =
|
|
3280
|
+
const autoOpen = newlyAddedItemIds.includes(item.id);
|
|
3300
3281
|
return createElement(ListItem, {
|
|
3301
3282
|
...item,
|
|
3302
3283
|
autoOpen: autoOpen,
|
|
@@ -3310,21 +3291,6 @@ function ListGroup(props) {
|
|
|
3310
3291
|
});
|
|
3311
3292
|
}
|
|
3312
3293
|
|
|
3313
|
-
// helpers ////////////////////
|
|
3314
|
-
|
|
3315
|
-
/**
|
|
3316
|
-
* Sorts given items alphanumeric by label
|
|
3317
|
-
*/
|
|
3318
|
-
function sortItems(items) {
|
|
3319
|
-
return sortBy(items, i => i.label.toLowerCase());
|
|
3320
|
-
}
|
|
3321
|
-
function getItem(items, id) {
|
|
3322
|
-
return find(items, i => i.id === id);
|
|
3323
|
-
}
|
|
3324
|
-
function createOrdering(items) {
|
|
3325
|
-
return items.map(i => i.id);
|
|
3326
|
-
}
|
|
3327
|
-
|
|
3328
3294
|
function Checkbox(props) {
|
|
3329
3295
|
const {
|
|
3330
3296
|
id,
|
|
@@ -3616,16 +3582,12 @@ function List(props) {
|
|
|
3616
3582
|
onAdd,
|
|
3617
3583
|
onRemove,
|
|
3618
3584
|
autoFocusEntry,
|
|
3619
|
-
compareFn,
|
|
3620
3585
|
...restProps
|
|
3621
3586
|
} = props;
|
|
3622
3587
|
const [open, setOpen] = useState(!!shouldOpen);
|
|
3623
3588
|
const hasItems = !!items.length;
|
|
3624
3589
|
const toggleOpen = () => hasItems && setOpen(!open);
|
|
3625
|
-
const opening = !usePrevious(open) && open;
|
|
3626
3590
|
const elementChanged = usePrevious(element) !== element;
|
|
3627
|
-
const shouldReset = opening || elementChanged;
|
|
3628
|
-
const sortedItems = useSortedItems(items, compareFn, shouldReset);
|
|
3629
3591
|
const newItems = useNewItems(items, elementChanged);
|
|
3630
3592
|
useEffect(() => {
|
|
3631
3593
|
if (open && !hasItems) {
|
|
@@ -3683,7 +3645,7 @@ function List(props) {
|
|
|
3683
3645
|
component: component,
|
|
3684
3646
|
element: element,
|
|
3685
3647
|
id: id,
|
|
3686
|
-
items:
|
|
3648
|
+
items: items,
|
|
3687
3649
|
newItems: newItems,
|
|
3688
3650
|
onRemove: onRemove,
|
|
3689
3651
|
open: open
|
|
@@ -3747,41 +3709,6 @@ function ItemsList(props) {
|
|
|
3747
3709
|
})
|
|
3748
3710
|
});
|
|
3749
3711
|
}
|
|
3750
|
-
|
|
3751
|
-
/**
|
|
3752
|
-
* Place new items in the beginning of the list and sort the rest with provided function.
|
|
3753
|
-
*
|
|
3754
|
-
* @template Item
|
|
3755
|
-
* @param {Item[]} currentItems
|
|
3756
|
-
* @param {(a: Item, b: Item) => 0 | 1 | -1} [compareFn] function used to sort items
|
|
3757
|
-
* @param {boolean} [shouldReset=false] set to `true` to reset state of the hook
|
|
3758
|
-
* @returns {Item[]}
|
|
3759
|
-
*/
|
|
3760
|
-
function useSortedItems(currentItems, compareFn, shouldReset = false) {
|
|
3761
|
-
const itemsRef = useRef(currentItems.slice());
|
|
3762
|
-
|
|
3763
|
-
// (1) Reset and optionally sort.
|
|
3764
|
-
if (shouldReset) {
|
|
3765
|
-
itemsRef.current = currentItems.slice();
|
|
3766
|
-
if (compareFn) {
|
|
3767
|
-
itemsRef.current.sort(compareFn);
|
|
3768
|
-
}
|
|
3769
|
-
} else {
|
|
3770
|
-
const items = itemsRef.current;
|
|
3771
|
-
|
|
3772
|
-
// (2) Add new item to the list.
|
|
3773
|
-
for (const item of currentItems) {
|
|
3774
|
-
if (!items.includes(item)) {
|
|
3775
|
-
// Unshift or push depending on whether we have a compareFn
|
|
3776
|
-
compareFn ? items.unshift(item) : items.push(item);
|
|
3777
|
-
}
|
|
3778
|
-
}
|
|
3779
|
-
|
|
3780
|
-
// (3) Filter out removed items.
|
|
3781
|
-
itemsRef.current = items.filter(item => currentItems.includes(item));
|
|
3782
|
-
}
|
|
3783
|
-
return itemsRef.current;
|
|
3784
|
-
}
|
|
3785
3712
|
function useNewItems(items = [], shouldReset) {
|
|
3786
3713
|
const previousItems = usePrevious(items.slice()) || [];
|
|
3787
3714
|
if (shouldReset) {
|
|
@@ -4017,6 +3944,7 @@ function TextArea(props) {
|
|
|
4017
3944
|
onFocus,
|
|
4018
3945
|
onBlur,
|
|
4019
3946
|
autoResize,
|
|
3947
|
+
placeholder,
|
|
4020
3948
|
rows = autoResize ? 1 : 2,
|
|
4021
3949
|
tooltip
|
|
4022
3950
|
} = props;
|
|
@@ -4059,6 +3987,7 @@ function TextArea(props) {
|
|
|
4059
3987
|
onInput: handleInput,
|
|
4060
3988
|
onFocus: onFocus,
|
|
4061
3989
|
onBlur: onBlur,
|
|
3990
|
+
placeholder: placeholder,
|
|
4062
3991
|
rows: rows,
|
|
4063
3992
|
value: localValue,
|
|
4064
3993
|
disabled: disabled,
|
|
@@ -4098,6 +4027,7 @@ function TextAreaEntry(props) {
|
|
|
4098
4027
|
validate,
|
|
4099
4028
|
onFocus,
|
|
4100
4029
|
onBlur,
|
|
4030
|
+
placeholder,
|
|
4101
4031
|
autoResize,
|
|
4102
4032
|
tooltip
|
|
4103
4033
|
} = props;
|
|
@@ -4133,6 +4063,7 @@ function TextAreaEntry(props) {
|
|
|
4133
4063
|
debounce: debounce,
|
|
4134
4064
|
monospace: monospace,
|
|
4135
4065
|
disabled: disabled,
|
|
4066
|
+
placeholder: placeholder,
|
|
4136
4067
|
autoResize: autoResize,
|
|
4137
4068
|
tooltip: tooltip,
|
|
4138
4069
|
element: element
|
|
@@ -4165,6 +4096,7 @@ function Textfield(props) {
|
|
|
4165
4096
|
onInput,
|
|
4166
4097
|
onFocus,
|
|
4167
4098
|
onBlur,
|
|
4099
|
+
placeholder,
|
|
4168
4100
|
value = '',
|
|
4169
4101
|
tooltip
|
|
4170
4102
|
} = props;
|
|
@@ -4206,6 +4138,7 @@ function Textfield(props) {
|
|
|
4206
4138
|
onInput: handleInput,
|
|
4207
4139
|
onFocus: onFocus,
|
|
4208
4140
|
onBlur: onBlur,
|
|
4141
|
+
placeholder: placeholder,
|
|
4209
4142
|
value: localValue
|
|
4210
4143
|
})]
|
|
4211
4144
|
});
|
|
@@ -4239,6 +4172,7 @@ function TextfieldEntry(props) {
|
|
|
4239
4172
|
validate,
|
|
4240
4173
|
onFocus,
|
|
4241
4174
|
onBlur,
|
|
4175
|
+
placeholder,
|
|
4242
4176
|
tooltip
|
|
4243
4177
|
} = props;
|
|
4244
4178
|
const globalError = useError(id);
|
|
@@ -4270,6 +4204,7 @@ function TextfieldEntry(props) {
|
|
|
4270
4204
|
onInput: onInput,
|
|
4271
4205
|
onFocus: onFocus,
|
|
4272
4206
|
onBlur: onBlur,
|
|
4207
|
+
placeholder: placeholder,
|
|
4273
4208
|
value: value,
|
|
4274
4209
|
tooltip: tooltip,
|
|
4275
4210
|
element: element
|