@bpmn-io/properties-panel 3.10.0 → 3.10.1

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.
@@ -0,0 +1,4259 @@
1
+ import { useContext, useState, useRef, useEffect, useMemo, useCallback, useLayoutEffect } from '../preact/hooks';
2
+ import { isFunction, isString, isArray, get, assign, set, sortBy, find, isNumber, debounce } from 'min-dash';
3
+ import { createPortal, forwardRef } from '../preact/compat';
4
+ import { jsx, jsxs, Fragment } from '../preact/jsx-runtime';
5
+ import { createContext, createElement } from '../preact';
6
+ import classnames from 'classnames';
7
+ import { query, domify } from 'min-dom';
8
+ import { FeelersEditor } from 'feelers';
9
+ import FeelEditor from '@bpmn-io/feel-editor';
10
+ import { lineNumbers } from '@codemirror/view';
11
+ import * as focusTrap from 'focus-trap';
12
+
13
+ var ArrowIcon = function ArrowIcon(props) {
14
+ return jsx("svg", {
15
+ ...props,
16
+ children: jsx("path", {
17
+ fillRule: "evenodd",
18
+ d: "m11.657 8-4.95 4.95a1 1 0 0 1-1.414-1.414L8.828 8 5.293 4.464A1 1 0 1 1 6.707 3.05L11.657 8Z"
19
+ })
20
+ });
21
+ };
22
+ ArrowIcon.defaultProps = {
23
+ xmlns: "http://www.w3.org/2000/svg",
24
+ width: "16",
25
+ height: "16"
26
+ };
27
+ var CreateIcon = function CreateIcon(props) {
28
+ return jsx("svg", {
29
+ ...props,
30
+ children: jsx("path", {
31
+ fillRule: "evenodd",
32
+ d: "M9 13V9h4a1 1 0 0 0 0-2H9V3a1 1 0 1 0-2 0v4H3a1 1 0 1 0 0 2h4v4a1 1 0 0 0 2 0Z"
33
+ })
34
+ });
35
+ };
36
+ CreateIcon.defaultProps = {
37
+ xmlns: "http://www.w3.org/2000/svg",
38
+ width: "16",
39
+ height: "16"
40
+ };
41
+ var DeleteIcon = function DeleteIcon(props) {
42
+ return jsx("svg", {
43
+ ...props,
44
+ children: jsx("path", {
45
+ fillRule: "evenodd",
46
+ d: "M12 6v7c0 1.1-.4 1.55-1.5 1.55h-5C4.4 14.55 4 14.1 4 13V6h8Zm-1.5 1.5h-5v4.3c0 .66.5 1.2 1.111 1.2H9.39c.611 0 1.111-.54 1.111-1.2V7.5ZM13 3h-2l-1-1H6L5 3H3v1.5h10V3Z"
47
+ })
48
+ });
49
+ };
50
+ DeleteIcon.defaultProps = {
51
+ xmlns: "http://www.w3.org/2000/svg",
52
+ width: "16",
53
+ height: "16"
54
+ };
55
+ var DragIcon = function DragIcon(props) {
56
+ return jsxs("svg", {
57
+ ...props,
58
+ children: [jsx("path", {
59
+ fill: "#fff",
60
+ style: {
61
+ mixBlendMode: "multiply"
62
+ },
63
+ d: "M0 0h16v16H0z"
64
+ }), jsx("path", {
65
+ fill: "#fff",
66
+ style: {
67
+ mixBlendMode: "multiply"
68
+ },
69
+ d: "M0 0h16v16H0z"
70
+ }), jsx("path", {
71
+ d: "M7 3H5v2h2V3zm4 0H9v2h2V3zM7 7H5v2h2V7zm4 0H9v2h2V7zm-4 4H5v2h2v-2zm4 0H9v2h2v-2z",
72
+ fill: "#161616"
73
+ })]
74
+ });
75
+ };
76
+ DragIcon.defaultProps = {
77
+ width: "16",
78
+ height: "16",
79
+ fill: "none",
80
+ xmlns: "http://www.w3.org/2000/svg"
81
+ };
82
+ var ExternalLinkIcon = function ExternalLinkIcon(props) {
83
+ return jsx("svg", {
84
+ ...props,
85
+ children: jsx("path", {
86
+ fillRule: "evenodd",
87
+ clipRule: "evenodd",
88
+ d: "M12.637 12.637v-4.72h1.362v4.721c0 .36-.137.676-.411.95-.275.275-.591.412-.95.412H3.362c-.38 0-.703-.132-.967-.396A1.315 1.315 0 0 1 2 12.638V3.362c0-.38.132-.703.396-.967S2.982 2 3.363 2h4.553v1.363H3.363v9.274h9.274ZM14 2H9.28l-.001 1.362h2.408L5.065 9.984l.95.95 6.622-6.622v2.409H14V2Z",
89
+ fill: "currentcolor"
90
+ })
91
+ });
92
+ };
93
+ ExternalLinkIcon.defaultProps = {
94
+ width: "16",
95
+ height: "16",
96
+ fill: "none",
97
+ xmlns: "http://www.w3.org/2000/svg"
98
+ };
99
+ var FeelIcon$1 = function FeelIcon(props) {
100
+ return jsx("svg", {
101
+ ...props,
102
+ children: jsx("path", {
103
+ d: "M3.617 11.99c-.137.684-.392 1.19-.765 1.518-.362.328-.882.492-1.558.492H0l.309-1.579h1.264l1.515-7.64h-.912l.309-1.579h.911l.236-1.191c.137-.685.387-1.192.75-1.52C4.753.164 5.277 0 5.953 0h1.294L6.94 1.579H5.675l-.323 1.623h1.264l-.309 1.579H5.043l-1.426 7.208ZM5.605 11.021l3.029-4.155L7.28 3.202h2.073l.706 2.547h.176l1.691-2.547H14l-3.014 4.051 1.338 3.768H10.25l-.706-2.606H9.37L7.678 11.02H5.605Z",
104
+ fill: "currentcolor"
105
+ })
106
+ });
107
+ };
108
+ FeelIcon$1.defaultProps = {
109
+ width: "14",
110
+ height: "14",
111
+ fill: "none",
112
+ xmlns: "http://www.w3.org/2000/svg"
113
+ };
114
+
115
+ function Header(props) {
116
+ const {
117
+ element,
118
+ headerProvider
119
+ } = props;
120
+ const {
121
+ getElementIcon,
122
+ getDocumentationRef,
123
+ getElementLabel,
124
+ getTypeLabel
125
+ } = headerProvider;
126
+ const label = getElementLabel(element);
127
+ const type = getTypeLabel(element);
128
+ const documentationRef = getDocumentationRef && getDocumentationRef(element);
129
+ const ElementIcon = getElementIcon(element);
130
+ return jsxs("div", {
131
+ class: "bio-properties-panel-header",
132
+ children: [jsx("div", {
133
+ class: "bio-properties-panel-header-icon",
134
+ children: ElementIcon && jsx(ElementIcon, {
135
+ width: "32",
136
+ height: "32",
137
+ viewBox: "0 0 32 32"
138
+ })
139
+ }), jsxs("div", {
140
+ class: "bio-properties-panel-header-labels",
141
+ children: [jsx("div", {
142
+ title: type,
143
+ class: "bio-properties-panel-header-type",
144
+ children: type
145
+ }), label ? jsx("div", {
146
+ title: label,
147
+ class: "bio-properties-panel-header-label",
148
+ children: label
149
+ }) : null]
150
+ }), jsx("div", {
151
+ class: "bio-properties-panel-header-actions",
152
+ children: documentationRef ? jsx("a", {
153
+ rel: "noopener",
154
+ class: "bio-properties-panel-header-link",
155
+ href: documentationRef,
156
+ title: "Open documentation",
157
+ target: "_blank",
158
+ children: jsx(ExternalLinkIcon, {})
159
+ }) : null
160
+ })]
161
+ });
162
+ }
163
+
164
+ const DescriptionContext = createContext({
165
+ description: {},
166
+ getDescriptionForId: () => {}
167
+ });
168
+
169
+ const ErrorsContext = createContext({
170
+ errors: {}
171
+ });
172
+
173
+ /**
174
+ * @typedef {Function} <propertiesPanel.showEntry> callback
175
+ *
176
+ * @example
177
+ *
178
+ * useEvent('propertiesPanel.showEntry', ({ focus = false, ...rest }) => {
179
+ * // ...
180
+ * });
181
+ *
182
+ * @param {Object} context
183
+ * @param {boolean} [context.focus]
184
+ *
185
+ * @returns void
186
+ */
187
+ const EventContext = createContext({
188
+ eventBus: null
189
+ });
190
+
191
+ const LayoutContext = createContext({
192
+ layout: {},
193
+ setLayout: () => {},
194
+ getLayoutForKey: () => {},
195
+ setLayoutForKey: () => {}
196
+ });
197
+
198
+ const TooltipContext = createContext({
199
+ tooltip: {},
200
+ getTooltipForId: () => {}
201
+ });
202
+
203
+ /**
204
+ * Accesses the global TooltipContext and returns a tooltip for a given id and element.
205
+ *
206
+ * @example
207
+ * ```jsx
208
+ * function TextField(props) {
209
+ * const tooltip = useTooltipContext('input1', element);
210
+ * }
211
+ * ```
212
+ *
213
+ * @param {string} id
214
+ * @param {object} element
215
+ *
216
+ * @returns {string}
217
+ */
218
+ function useTooltipContext(id, element) {
219
+ const {
220
+ getTooltipForId
221
+ } = useContext(TooltipContext);
222
+ return getTooltipForId(id, element);
223
+ }
224
+
225
+ function TooltipWrapper(props) {
226
+ const {
227
+ forId,
228
+ element
229
+ } = props;
230
+ const contextDescription = useTooltipContext(forId, element);
231
+ const value = props.value || contextDescription;
232
+ if (!value) {
233
+ return props.children;
234
+ }
235
+ return jsx(Tooltip, {
236
+ ...props,
237
+ value: value,
238
+ forId: prefixId$9(forId)
239
+ });
240
+ }
241
+ function Tooltip(props) {
242
+ const {
243
+ forId,
244
+ value,
245
+ parent
246
+ } = props;
247
+ const [visible, setShow] = useState(false);
248
+ const [focusedViaKeyboard, setFocusedViaKeyboard] = useState(false);
249
+ let timeout = null;
250
+ const wrapperRef = useRef(null);
251
+ const tooltipRef = useRef(null);
252
+ const showTooltip = async event => {
253
+ const show = () => setShow(true);
254
+ if (!visible && !timeout) {
255
+ if (event instanceof MouseEvent) {
256
+ timeout = setTimeout(show, 200);
257
+ } else {
258
+ show();
259
+ setFocusedViaKeyboard(true);
260
+ }
261
+ }
262
+ };
263
+ const hideTooltip = () => {
264
+ setShow(false);
265
+ setFocusedViaKeyboard(false);
266
+ };
267
+ const hideTooltipViaEscape = e => {
268
+ e.code === 'Escape' && hideTooltip();
269
+ };
270
+ const isTooltipHovered = ({
271
+ x,
272
+ y
273
+ }) => {
274
+ const tooltip = tooltipRef.current;
275
+ const wrapper = wrapperRef.current;
276
+ return tooltip && (inBounds(x, y, wrapper.getBoundingClientRect()) || inBounds(x, y, tooltip.getBoundingClientRect()));
277
+ };
278
+ useEffect(() => {
279
+ const {
280
+ current
281
+ } = wrapperRef;
282
+ if (!current) {
283
+ return;
284
+ }
285
+ const hideHoveredTooltip = e => {
286
+ const isFocused = document.activeElement === wrapperRef.current || document.activeElement.closest('.bio-properties-panel-tooltip');
287
+ if (visible && !isTooltipHovered({
288
+ x: e.x,
289
+ y: e.y
290
+ }) && !(isFocused && focusedViaKeyboard)) {
291
+ hideTooltip();
292
+ }
293
+ };
294
+ const hideFocusedTooltip = e => {
295
+ const {
296
+ relatedTarget
297
+ } = e;
298
+ const isTooltipChild = el => !!el.closest('.bio-properties-panel-tooltip');
299
+ if (visible && !isHovered(wrapperRef.current) && relatedTarget && !isTooltipChild(relatedTarget)) {
300
+ hideTooltip();
301
+ }
302
+ };
303
+ document.addEventListener('wheel', hideHoveredTooltip);
304
+ document.addEventListener('focusout', hideFocusedTooltip);
305
+ document.addEventListener('mousemove', hideHoveredTooltip);
306
+ return () => {
307
+ document.removeEventListener('wheel', hideHoveredTooltip);
308
+ document.removeEventListener('mousemove', hideHoveredTooltip);
309
+ document.removeEventListener('focusout', hideFocusedTooltip);
310
+ };
311
+ }, [wrapperRef.current, visible, focusedViaKeyboard]);
312
+ const renderTooltip = () => {
313
+ return jsxs("div", {
314
+ class: "bio-properties-panel-tooltip",
315
+ role: "tooltip",
316
+ id: "bio-properties-panel-tooltip",
317
+ "aria-labelledby": forId,
318
+ style: getTooltipPosition(wrapperRef.current),
319
+ ref: tooltipRef,
320
+ onClick: e => e.stopPropagation(),
321
+ children: [jsx("div", {
322
+ class: "bio-properties-panel-tooltip-content",
323
+ children: value
324
+ }), jsx("div", {
325
+ class: "bio-properties-panel-tooltip-arrow"
326
+ })]
327
+ });
328
+ };
329
+ return jsxs("div", {
330
+ class: "bio-properties-panel-tooltip-wrapper",
331
+ tabIndex: "0",
332
+ ref: wrapperRef,
333
+ onMouseEnter: showTooltip,
334
+ onMouseLeave: () => {
335
+ clearTimeout(timeout);
336
+ timeout = null;
337
+ },
338
+ onFocus: showTooltip,
339
+ onKeyDown: hideTooltipViaEscape,
340
+ children: [props.children, visible ? parent ? createPortal(renderTooltip(), parent.current) : renderTooltip() : null]
341
+ });
342
+ }
343
+
344
+ // helper
345
+ function inBounds(x, y, bounds) {
346
+ const {
347
+ top,
348
+ right,
349
+ bottom,
350
+ left
351
+ } = bounds;
352
+ return x >= left && x <= right && y >= top && y <= bottom;
353
+ }
354
+ function getTooltipPosition(refElement) {
355
+ const refPosition = refElement.getBoundingClientRect();
356
+ const right = `calc(100% - ${refPosition.x}px)`;
357
+ const top = `${refPosition.top - 10}px`;
358
+ return `right: ${right}; top: ${top};`;
359
+ }
360
+ function isHovered(element) {
361
+ return element.matches(':hover');
362
+ }
363
+ function prefixId$9(id) {
364
+ return `bio-properties-panel-${id}`;
365
+ }
366
+
367
+ /**
368
+ * Accesses the global DescriptionContext and returns a description for a given id and element.
369
+ *
370
+ * @example
371
+ * ```jsx
372
+ * function TextField(props) {
373
+ * const description = useDescriptionContext('input1', element);
374
+ * }
375
+ * ```
376
+ *
377
+ * @param {string} id
378
+ * @param {object} element
379
+ *
380
+ * @returns {string}
381
+ */
382
+ function useDescriptionContext(id, element) {
383
+ const {
384
+ getDescriptionForId
385
+ } = useContext(DescriptionContext);
386
+ return getDescriptionForId(id, element);
387
+ }
388
+
389
+ function useError(id) {
390
+ const {
391
+ errors
392
+ } = useContext(ErrorsContext);
393
+ return errors[id];
394
+ }
395
+ function useErrors() {
396
+ const {
397
+ errors
398
+ } = useContext(ErrorsContext);
399
+ return errors;
400
+ }
401
+
402
+ /**
403
+ * Subscribe to an event immediately. Update subscription after inputs changed.
404
+ *
405
+ * @param {string} event
406
+ * @param {Function} callback
407
+ */
408
+ function useEvent(event, callback, eventBus) {
409
+ const eventContext = useContext(EventContext);
410
+ if (!eventBus) {
411
+ ({
412
+ eventBus
413
+ } = eventContext);
414
+ }
415
+ const didMount = useRef(false);
416
+
417
+ // (1) subscribe immediately
418
+ if (eventBus && !didMount.current) {
419
+ eventBus.on(event, callback);
420
+ }
421
+
422
+ // (2) update subscription after inputs changed
423
+ useEffect(() => {
424
+ if (eventBus && didMount.current) {
425
+ eventBus.on(event, callback);
426
+ }
427
+ didMount.current = true;
428
+ return () => {
429
+ if (eventBus) {
430
+ eventBus.off(event, callback);
431
+ }
432
+ };
433
+ }, [callback, event, eventBus]);
434
+ }
435
+
436
+ const KEY_LENGTH = 6;
437
+
438
+ /**
439
+ * Create a persistent key factory for plain objects without id.
440
+ *
441
+ * @example
442
+ * ```jsx
443
+ * function List({ objects }) {
444
+ * const getKey = useKeyFactory();
445
+ * return (<ol>{
446
+ * objects.map(obj => {
447
+ * const key = getKey(obj);
448
+ * return <li key={key}>obj.name</li>
449
+ * })
450
+ * }</ol>);
451
+ * }
452
+ * ```
453
+ *
454
+ * @param {any[]} dependencies
455
+ * @returns {(element: object) => string}
456
+ */
457
+ function useKeyFactory(dependencies = []) {
458
+ const map = useMemo(() => new Map(), dependencies);
459
+ const getKey = el => {
460
+ let key = map.get(el);
461
+ if (!key) {
462
+ key = Math.random().toString().slice(-KEY_LENGTH);
463
+ map.set(el, key);
464
+ }
465
+ return key;
466
+ };
467
+ return getKey;
468
+ }
469
+
470
+ /**
471
+ * Creates a state that persists in the global LayoutContext.
472
+ *
473
+ * @example
474
+ * ```jsx
475
+ * function Group(props) {
476
+ * const [ open, setOpen ] = useLayoutState([ 'groups', 'foo', 'open' ], false);
477
+ * }
478
+ * ```
479
+ *
480
+ * @param {(string|number)[]} path
481
+ * @param {any} [defaultValue]
482
+ *
483
+ * @returns {[ any, Function ]}
484
+ */
485
+ function useLayoutState(path, defaultValue) {
486
+ const {
487
+ getLayoutForKey,
488
+ setLayoutForKey
489
+ } = useContext(LayoutContext);
490
+ const layoutForKey = getLayoutForKey(path, defaultValue);
491
+ const setState = useCallback(newValue => {
492
+ setLayoutForKey(path, newValue);
493
+ }, [setLayoutForKey]);
494
+ return [layoutForKey, setState];
495
+ }
496
+
497
+ /**
498
+ * @pinussilvestrus: we need to introduce our own hook to persist the previous
499
+ * state on updates.
500
+ *
501
+ * cf. https://reactjs.org/docs/hooks-faq.html#how-to-get-the-previous-props-or-state
502
+ */
503
+
504
+ function usePrevious(value) {
505
+ const ref = useRef();
506
+ useEffect(() => {
507
+ ref.current = value;
508
+ });
509
+ return ref.current;
510
+ }
511
+
512
+ /**
513
+ * Subscribe to `propertiesPanel.showEntry`.
514
+ *
515
+ * @param {string} id
516
+ *
517
+ * @returns {import('preact').Ref}
518
+ */
519
+ function useShowEntryEvent(id) {
520
+ const {
521
+ onShow
522
+ } = useContext(LayoutContext);
523
+ const ref = useRef();
524
+ const focus = useRef(false);
525
+ const onShowEntry = useCallback(event => {
526
+ if (event.id === id) {
527
+ onShow();
528
+ if (!focus.current) {
529
+ focus.current = true;
530
+ }
531
+ }
532
+ }, [id]);
533
+ useEffect(() => {
534
+ if (focus.current && ref.current) {
535
+ if (isFunction(ref.current.focus)) {
536
+ ref.current.focus();
537
+ }
538
+ if (isFunction(ref.current.select)) {
539
+ ref.current.select();
540
+ }
541
+ focus.current = false;
542
+ }
543
+ });
544
+ useEvent('propertiesPanel.showEntry', onShowEntry);
545
+ return ref;
546
+ }
547
+
548
+ /**
549
+ * @callback setSticky
550
+ * @param {boolean} value
551
+ */
552
+
553
+ /**
554
+ * Use IntersectionObserver to identify when DOM element is in sticky mode.
555
+ * If sticky is observered setSticky(true) will be called.
556
+ * If sticky mode is left, setSticky(false) will be called.
557
+ *
558
+ *
559
+ * @param {Object} ref
560
+ * @param {string} scrollContainerSelector
561
+ * @param {setSticky} setSticky
562
+ */
563
+ function useStickyIntersectionObserver(ref, scrollContainerSelector, setSticky) {
564
+ const [scrollContainer, setScrollContainer] = useState(query(scrollContainerSelector));
565
+ const updateScrollContainer = useCallback(() => {
566
+ const newScrollContainer = query(scrollContainerSelector);
567
+ if (newScrollContainer !== scrollContainer) {
568
+ setScrollContainer(newScrollContainer);
569
+ }
570
+ }, [scrollContainerSelector, scrollContainer]);
571
+ useEffect(() => {
572
+ updateScrollContainer();
573
+ }, [updateScrollContainer]);
574
+ useEvent('propertiesPanel.attach', updateScrollContainer);
575
+ useEvent('propertiesPanel.detach', updateScrollContainer);
576
+ useEffect(() => {
577
+ const Observer = IntersectionObserver;
578
+
579
+ // return early if IntersectionObserver is not available
580
+ if (!Observer) {
581
+ return;
582
+ }
583
+
584
+ // TODO(@barmac): test this
585
+ if (!ref.current || !scrollContainer) {
586
+ return;
587
+ }
588
+ const observer = new Observer(entries => {
589
+ // scroll container is unmounted, do not update sticky state
590
+ if (scrollContainer.scrollHeight === 0) {
591
+ return;
592
+ }
593
+ entries.forEach(entry => {
594
+ if (entry.intersectionRatio < 1) {
595
+ setSticky(true);
596
+ } else if (entry.intersectionRatio === 1) {
597
+ setSticky(false);
598
+ }
599
+ });
600
+ }, {
601
+ root: scrollContainer,
602
+ rootMargin: '0px 0px 999999% 0px',
603
+ // Use bottom margin to avoid stickyness when scrolling out to bottom
604
+ threshold: [1]
605
+ });
606
+ observer.observe(ref.current);
607
+
608
+ // Unobserve if unmounted
609
+ return () => {
610
+ observer.unobserve(ref.current);
611
+ };
612
+ }, [ref.current, scrollContainer, setSticky]);
613
+ }
614
+
615
+ /**
616
+ * Creates a static function reference with changing body.
617
+ * This is necessary when external libraries require a callback function
618
+ * that has references to state variables.
619
+ *
620
+ * Usage:
621
+ * const callback = useStaticCallback((val) => {val === currentState});
622
+ *
623
+ * The `callback` reference is static and can be safely used in external
624
+ * libraries or as a prop that does not cause rerendering of children.
625
+ *
626
+ * @param {Function} callback function with changing reference
627
+ * @returns {Function} static function reference
628
+ */
629
+ function useStaticCallback(callback) {
630
+ const callbackRef = useRef(callback);
631
+ callbackRef.current = callback;
632
+ return useCallback((...args) => callbackRef.current(...args), []);
633
+ }
634
+
635
+ function Group(props) {
636
+ const {
637
+ element,
638
+ entries = [],
639
+ id,
640
+ label,
641
+ shouldOpen = false
642
+ } = props;
643
+ const groupRef = useRef(null);
644
+ const [open, setOpen] = useLayoutState(['groups', id, 'open'], shouldOpen);
645
+ const onShow = useCallback(() => setOpen(true), [setOpen]);
646
+ const toggleOpen = () => setOpen(!open);
647
+ const [edited, setEdited] = useState(false);
648
+ const [sticky, setSticky] = useState(false);
649
+
650
+ // set edited state depending on all entries
651
+ useEffect(() => {
652
+ // TODO(@barmac): replace with CSS when `:has()` is supported in all major browsers, or rewrite as in https://github.com/camunda/camunda-modeler/issues/3815#issuecomment-1733038161
653
+ const scheduled = requestAnimationFrame(() => {
654
+ const hasOneEditedEntry = entries.find(entry => {
655
+ const {
656
+ id,
657
+ isEdited
658
+ } = entry;
659
+ const entryNode = query(`[data-entry-id="${id}"]`);
660
+ if (!isFunction(isEdited) || !entryNode) {
661
+ return false;
662
+ }
663
+ const inputNode = query('.bio-properties-panel-input', entryNode);
664
+ return isEdited(inputNode);
665
+ });
666
+ setEdited(hasOneEditedEntry);
667
+ });
668
+ return () => cancelAnimationFrame(scheduled);
669
+ }, [entries, setEdited]);
670
+
671
+ // set error state depending on all entries
672
+ const allErrors = useErrors();
673
+ const hasErrors = entries.some(entry => allErrors[entry.id]);
674
+
675
+ // set css class when group is sticky to top
676
+ useStickyIntersectionObserver(groupRef, 'div.bio-properties-panel-scroll-container', setSticky);
677
+ const propertiesPanelContext = {
678
+ ...useContext(LayoutContext),
679
+ onShow
680
+ };
681
+ return jsxs("div", {
682
+ class: "bio-properties-panel-group",
683
+ "data-group-id": 'group-' + id,
684
+ ref: groupRef,
685
+ children: [jsxs("div", {
686
+ class: classnames('bio-properties-panel-group-header', edited ? '' : 'empty', open ? 'open' : '', sticky && open ? 'sticky' : ''),
687
+ onClick: toggleOpen,
688
+ children: [jsx("div", {
689
+ title: props.tooltip ? null : label,
690
+ "data-title": label,
691
+ class: "bio-properties-panel-group-header-title",
692
+ children: jsx(TooltipWrapper, {
693
+ value: props.tooltip,
694
+ forId: 'group-' + id,
695
+ element: element,
696
+ parent: groupRef,
697
+ children: label
698
+ })
699
+ }), jsxs("div", {
700
+ class: "bio-properties-panel-group-header-buttons",
701
+ children: [jsx(DataMarker, {
702
+ edited: edited,
703
+ hasErrors: hasErrors
704
+ }), jsx("button", {
705
+ title: "Toggle section",
706
+ class: "bio-properties-panel-group-header-button bio-properties-panel-arrow",
707
+ children: jsx(ArrowIcon, {
708
+ class: open ? 'bio-properties-panel-arrow-down' : 'bio-properties-panel-arrow-right'
709
+ })
710
+ })]
711
+ })]
712
+ }), jsx("div", {
713
+ class: classnames('bio-properties-panel-group-entries', open ? 'open' : ''),
714
+ children: jsx(LayoutContext.Provider, {
715
+ value: propertiesPanelContext,
716
+ children: entries.map(entry => {
717
+ const {
718
+ component: Component,
719
+ id
720
+ } = entry;
721
+ return createElement(Component, {
722
+ ...entry,
723
+ element: element,
724
+ key: id
725
+ });
726
+ })
727
+ })
728
+ })]
729
+ });
730
+ }
731
+ function DataMarker(props) {
732
+ const {
733
+ edited,
734
+ hasErrors
735
+ } = props;
736
+ if (hasErrors) {
737
+ return jsx("div", {
738
+ title: "Section contains an error",
739
+ class: "bio-properties-panel-dot bio-properties-panel-dot--error"
740
+ });
741
+ }
742
+ if (edited) {
743
+ return jsx("div", {
744
+ title: "Section contains data",
745
+ class: "bio-properties-panel-dot"
746
+ });
747
+ }
748
+ return null;
749
+ }
750
+
751
+ /**
752
+ * @typedef { {
753
+ * text: (element: object) => string,
754
+ * icon?: (element: Object) => import('preact').Component
755
+ * } } PlaceholderDefinition
756
+ *
757
+ * @param { PlaceholderDefinition } props
758
+ */
759
+ function Placeholder(props) {
760
+ const {
761
+ text,
762
+ icon: Icon
763
+ } = props;
764
+ return jsx("div", {
765
+ class: "bio-properties-panel open",
766
+ children: jsxs("section", {
767
+ class: "bio-properties-panel-placeholder",
768
+ children: [Icon && jsx(Icon, {
769
+ class: "bio-properties-panel-placeholder-icon"
770
+ }), jsx("p", {
771
+ class: "bio-properties-panel-placeholder-text",
772
+ children: text
773
+ })]
774
+ })
775
+ });
776
+ }
777
+
778
+ function Description(props) {
779
+ const {
780
+ element,
781
+ forId,
782
+ value
783
+ } = props;
784
+ const contextDescription = useDescriptionContext(forId, element);
785
+ const description = value || contextDescription;
786
+ if (description) {
787
+ return jsx("div", {
788
+ class: "bio-properties-panel-description",
789
+ children: description
790
+ });
791
+ }
792
+ }
793
+
794
+ const noop$6 = () => {};
795
+
796
+ /**
797
+ * Buffer `.focus()` calls while the editor is not initialized.
798
+ * Set Focus inside when the editor is ready.
799
+ */
800
+ const useBufferedFocus$1 = function (editor, ref) {
801
+ const [buffer, setBuffer] = useState(undefined);
802
+ ref.current = useMemo(() => ({
803
+ focus: offset => {
804
+ if (editor) {
805
+ editor.focus(offset);
806
+ } else {
807
+ if (typeof offset === 'undefined') {
808
+ offset = Infinity;
809
+ }
810
+ setBuffer(offset);
811
+ }
812
+ }
813
+ }), [editor]);
814
+ useEffect(() => {
815
+ if (typeof buffer !== 'undefined' && editor) {
816
+ editor.focus(buffer);
817
+ setBuffer(false);
818
+ }
819
+ }, [editor, buffer]);
820
+ };
821
+ const CodeEditor$1 = forwardRef((props, ref) => {
822
+ const {
823
+ onInput,
824
+ disabled,
825
+ tooltipContainer,
826
+ enableGutters,
827
+ value,
828
+ onLint = noop$6,
829
+ onPopupOpen = noop$6,
830
+ popupOpen,
831
+ contentAttributes = {},
832
+ hostLanguage = null,
833
+ singleLine = false
834
+ } = props;
835
+ const inputRef = useRef();
836
+ const [editor, setEditor] = useState();
837
+ const [localValue, setLocalValue] = useState(value || '');
838
+ useBufferedFocus$1(editor, ref);
839
+ const handleInput = useStaticCallback(newValue => {
840
+ onInput(newValue);
841
+ setLocalValue(newValue);
842
+ });
843
+ useEffect(() => {
844
+ let editor;
845
+ editor = new FeelersEditor({
846
+ container: inputRef.current,
847
+ onChange: handleInput,
848
+ value: localValue,
849
+ onLint,
850
+ contentAttributes,
851
+ tooltipContainer,
852
+ enableGutters,
853
+ hostLanguage,
854
+ singleLine
855
+ });
856
+ setEditor(editor);
857
+ return () => {
858
+ onLint([]);
859
+ inputRef.current.innerHTML = '';
860
+ setEditor(null);
861
+ };
862
+ }, []);
863
+ useEffect(() => {
864
+ if (!editor) {
865
+ return;
866
+ }
867
+ if (value === localValue) {
868
+ return;
869
+ }
870
+ editor.setValue(value);
871
+ setLocalValue(value);
872
+ }, [value]);
873
+ const handleClick = () => {
874
+ ref.current.focus();
875
+ };
876
+ return jsxs("div", {
877
+ class: classnames('bio-properties-panel-feelers-editor-container', popupOpen ? 'popupOpen' : null),
878
+ children: [jsx("div", {
879
+ class: "bio-properties-panel-feelers-editor__open-popup-placeholder",
880
+ children: "Opened in editor"
881
+ }), jsx("div", {
882
+ name: props.name,
883
+ class: classnames('bio-properties-panel-feelers-editor bio-properties-panel-input', localValue ? 'edited' : null, disabled ? 'disabled' : null),
884
+ ref: inputRef,
885
+ onClick: handleClick
886
+ }), jsx("button", {
887
+ title: "Open pop-up editor",
888
+ class: "bio-properties-panel-open-feel-popup",
889
+ onClick: () => onPopupOpen('feelers'),
890
+ children: jsx(ExternalLinkIcon, {})
891
+ })]
892
+ });
893
+ });
894
+
895
+ const noop$5 = () => {};
896
+
897
+ /**
898
+ * Buffer `.focus()` calls while the editor is not initialized.
899
+ * Set Focus inside when the editor is ready.
900
+ */
901
+ const useBufferedFocus = function (editor, ref) {
902
+ const [buffer, setBuffer] = useState(undefined);
903
+ ref.current = useMemo(() => ({
904
+ focus: offset => {
905
+ if (editor) {
906
+ editor.focus(offset);
907
+ } else {
908
+ if (typeof offset === 'undefined') {
909
+ offset = Infinity;
910
+ }
911
+ setBuffer(offset);
912
+ }
913
+ }
914
+ }), [editor]);
915
+ useEffect(() => {
916
+ if (typeof buffer !== 'undefined' && editor) {
917
+ editor.focus(buffer);
918
+ setBuffer(false);
919
+ }
920
+ }, [editor, buffer]);
921
+ };
922
+ const CodeEditor = forwardRef((props, ref) => {
923
+ const {
924
+ enableGutters,
925
+ value,
926
+ onInput,
927
+ onFeelToggle = noop$5,
928
+ onLint = noop$5,
929
+ onPopupOpen = noop$5,
930
+ popupOpen,
931
+ disabled,
932
+ tooltipContainer,
933
+ variables
934
+ } = props;
935
+ const inputRef = useRef();
936
+ const [editor, setEditor] = useState();
937
+ const [localValue, setLocalValue] = useState(value || '');
938
+ useBufferedFocus(editor, ref);
939
+ const handleInput = useStaticCallback(newValue => {
940
+ onInput(newValue);
941
+ setLocalValue(newValue);
942
+ });
943
+ useEffect(() => {
944
+ let editor;
945
+
946
+ /* Trigger FEEL toggle when
947
+ *
948
+ * - `backspace` is pressed
949
+ * - AND the cursor is at the beginning of the input
950
+ */
951
+ const onKeyDown = e => {
952
+ if (e.key !== 'Backspace' || !editor) {
953
+ return;
954
+ }
955
+ const selection = editor.getSelection();
956
+ const range = selection.ranges[selection.mainIndex];
957
+ if (range.from === 0 && range.to === 0) {
958
+ onFeelToggle();
959
+ }
960
+ };
961
+ editor = new FeelEditor({
962
+ container: inputRef.current,
963
+ onChange: handleInput,
964
+ onKeyDown: onKeyDown,
965
+ onLint: onLint,
966
+ tooltipContainer: tooltipContainer,
967
+ value: localValue,
968
+ variables: variables,
969
+ extensions: [...(enableGutters ? [lineNumbers()] : [])]
970
+ });
971
+ setEditor(editor);
972
+ return () => {
973
+ onLint([]);
974
+ inputRef.current.innerHTML = '';
975
+ setEditor(null);
976
+ };
977
+ }, []);
978
+ useEffect(() => {
979
+ if (!editor) {
980
+ return;
981
+ }
982
+ if (value === localValue) {
983
+ return;
984
+ }
985
+ editor.setValue(value);
986
+ setLocalValue(value);
987
+ }, [value]);
988
+ useEffect(() => {
989
+ if (!editor) {
990
+ return;
991
+ }
992
+ editor.setVariables(variables);
993
+ }, [variables]);
994
+ const handleClick = () => {
995
+ ref.current.focus();
996
+ };
997
+ return jsxs("div", {
998
+ class: classnames('bio-properties-panel-feel-editor-container', disabled ? 'disabled' : null, popupOpen ? 'popupOpen' : null),
999
+ children: [jsx("div", {
1000
+ class: "bio-properties-panel-feel-editor__open-popup-placeholder",
1001
+ children: "Opened in editor"
1002
+ }), jsx("div", {
1003
+ name: props.name,
1004
+ class: classnames('bio-properties-panel-input', localValue ? 'edited' : null),
1005
+ ref: inputRef,
1006
+ onClick: handleClick
1007
+ }), jsx("button", {
1008
+ title: "Open pop-up editor",
1009
+ class: "bio-properties-panel-open-feel-popup",
1010
+ onClick: () => onPopupOpen(),
1011
+ children: jsx(ExternalLinkIcon, {})
1012
+ })]
1013
+ });
1014
+ });
1015
+
1016
+ function FeelIndicator(props) {
1017
+ const {
1018
+ active
1019
+ } = props;
1020
+ if (!active) {
1021
+ return null;
1022
+ }
1023
+ return jsx("span", {
1024
+ class: "bio-properties-panel-feel-indicator",
1025
+ children: "="
1026
+ });
1027
+ }
1028
+
1029
+ const noop$4 = () => {};
1030
+
1031
+ /**
1032
+ * @param {Object} props
1033
+ * @param {Object} props.label
1034
+ * @param {String} props.feel
1035
+ */
1036
+ function FeelIcon(props) {
1037
+ const {
1038
+ feel = false,
1039
+ active,
1040
+ disabled = false,
1041
+ onClick = noop$4
1042
+ } = props;
1043
+ const feelRequiredLabel = 'FEEL expression is mandatory';
1044
+ const feelOptionalLabel = `Click to ${active ? 'remove' : 'set a'} dynamic value with FEEL expression`;
1045
+ const handleClick = e => {
1046
+ onClick(e);
1047
+
1048
+ // when pointer event was created from keyboard, keep focus on button
1049
+ if (!e.pointerType) {
1050
+ e.stopPropagation();
1051
+ }
1052
+ };
1053
+ return jsx("button", {
1054
+ class: classnames('bio-properties-panel-feel-icon', active ? 'active' : null, feel === 'required' ? 'required' : 'optional'),
1055
+ onClick: handleClick,
1056
+ disabled: feel === 'required' || disabled,
1057
+ title: feel === 'required' ? feelRequiredLabel : feelOptionalLabel,
1058
+ children: jsx(FeelIcon$1, {})
1059
+ });
1060
+ }
1061
+
1062
+ const FeelPopupContext = createContext({
1063
+ open: () => {},
1064
+ close: () => {},
1065
+ source: null
1066
+ });
1067
+
1068
+ /**
1069
+ * Add a dragger that calls back the passed function with
1070
+ * { event, delta } on drag.
1071
+ *
1072
+ * @example
1073
+ *
1074
+ * function dragMove(event, delta) {
1075
+ * // we are dragging (!!)
1076
+ * }
1077
+ *
1078
+ * domElement.addEventListener('dragstart', dragger(dragMove));
1079
+ *
1080
+ * @param {Function} fn
1081
+ * @param {Element} [dragPreview]
1082
+ *
1083
+ * @return {Function} drag start callback function
1084
+ */
1085
+ function createDragger(fn, dragPreview) {
1086
+ let self;
1087
+ let startX, startY;
1088
+
1089
+ /** drag start */
1090
+ function onDragStart(event) {
1091
+ self = this;
1092
+ startX = event.clientX;
1093
+ startY = event.clientY;
1094
+
1095
+ // (1) prevent preview image
1096
+ if (event.dataTransfer) {
1097
+ event.dataTransfer.setDragImage(dragPreview || emptyCanvas(), 0, 0);
1098
+ }
1099
+
1100
+ // (2) setup drag listeners
1101
+
1102
+ // attach drag + cleanup event
1103
+ // we need to do this to make sure we track cursor
1104
+ // movements before we reach other drag event handlers,
1105
+ // e.g. in child containers.
1106
+ document.addEventListener('dragover', onDrag, true);
1107
+ document.addEventListener('dragenter', preventDefault, true);
1108
+ document.addEventListener('dragend', onEnd);
1109
+ document.addEventListener('drop', preventDefault);
1110
+ }
1111
+ function onDrag(event) {
1112
+ const delta = {
1113
+ x: event.clientX - startX,
1114
+ y: event.clientY - startY
1115
+ };
1116
+
1117
+ // call provided fn with event, delta
1118
+ return fn.call(self, event, delta);
1119
+ }
1120
+ function onEnd() {
1121
+ document.removeEventListener('dragover', onDrag, true);
1122
+ document.removeEventListener('dragenter', preventDefault, true);
1123
+ document.removeEventListener('dragend', onEnd);
1124
+ document.removeEventListener('drop', preventDefault);
1125
+ }
1126
+ return onDragStart;
1127
+ }
1128
+ function preventDefault(event) {
1129
+ event.preventDefault();
1130
+ event.stopPropagation();
1131
+ }
1132
+ function emptyCanvas() {
1133
+ return domify('<canvas width="0" height="0" />');
1134
+ }
1135
+
1136
+ const noop$3 = () => {};
1137
+
1138
+ /**
1139
+ * A generic popup component.
1140
+ *
1141
+ * @param {Object} props
1142
+ * @param {HTMLElement} [props.container]
1143
+ * @param {string} [props.className]
1144
+ * @param {boolean} [props.delayInitialFocus]
1145
+ * @param {{x: number, y: number}} [props.position]
1146
+ * @param {number} [props.width]
1147
+ * @param {number} [props.height]
1148
+ * @param {Function} props.onClose
1149
+ * @param {Function} [props.onPostActivate]
1150
+ * @param {Function} [props.onPostDeactivate]
1151
+ * @param {boolean} [props.returnFocus]
1152
+ * @param {boolean} [props.closeOnEscape]
1153
+ * @param {string} props.title
1154
+ * @param {Ref} [ref]
1155
+ */
1156
+ function PopupComponent(props, globalRef) {
1157
+ const {
1158
+ container,
1159
+ className,
1160
+ delayInitialFocus,
1161
+ position,
1162
+ width,
1163
+ height,
1164
+ onClose,
1165
+ onPostActivate = noop$3,
1166
+ onPostDeactivate = noop$3,
1167
+ returnFocus = true,
1168
+ closeOnEscape = true,
1169
+ title
1170
+ } = props;
1171
+ const focusTrapRef = useRef(null);
1172
+ const localRef = useRef(null);
1173
+ const popupRef = globalRef || localRef;
1174
+ const containerNode = useMemo(() => getContainerNode(container), [container]);
1175
+ const handleKeydown = event => {
1176
+ // do not allow keyboard events to bubble
1177
+ event.stopPropagation();
1178
+ if (closeOnEscape && event.key === 'Escape') {
1179
+ onClose();
1180
+ }
1181
+ };
1182
+
1183
+ // re-activate focus trap on focus
1184
+ const handleFocus = () => {
1185
+ if (focusTrapRef.current) {
1186
+ focusTrapRef.current.activate();
1187
+ }
1188
+ };
1189
+ let style = {};
1190
+ if (position) {
1191
+ style = {
1192
+ ...style,
1193
+ top: position.top + 'px',
1194
+ left: position.left + 'px'
1195
+ };
1196
+ }
1197
+ if (width) {
1198
+ style.width = width + 'px';
1199
+ }
1200
+ if (height) {
1201
+ style.height = height + 'px';
1202
+ }
1203
+ useEffect(() => {
1204
+ if (popupRef.current) {
1205
+ popupRef.current.addEventListener('focusin', handleFocus);
1206
+ }
1207
+ return () => {
1208
+ popupRef.current.removeEventListener('focusin', handleFocus);
1209
+ };
1210
+ }, [popupRef]);
1211
+ useEffect(() => {
1212
+ if (popupRef.current) {
1213
+ focusTrapRef.current = focusTrap.createFocusTrap(popupRef.current, {
1214
+ clickOutsideDeactivates: true,
1215
+ delayInitialFocus,
1216
+ fallbackFocus: popupRef.current,
1217
+ onPostActivate,
1218
+ onPostDeactivate,
1219
+ returnFocusOnDeactivate: returnFocus
1220
+ });
1221
+ focusTrapRef.current.activate();
1222
+ }
1223
+ return () => focusTrapRef.current && focusTrapRef.current.deactivate();
1224
+ }, [popupRef]);
1225
+ return createPortal(jsx("div", {
1226
+ "aria-label": title,
1227
+ tabIndex: -1,
1228
+ ref: popupRef,
1229
+ onKeyDown: handleKeydown,
1230
+ role: "dialog",
1231
+ class: classnames('bio-properties-panel-popup', className),
1232
+ style: style,
1233
+ children: props.children
1234
+ }), containerNode || document.body);
1235
+ }
1236
+ const Popup = forwardRef(PopupComponent);
1237
+ Popup.Title = Title;
1238
+ Popup.Body = Body;
1239
+ Popup.Footer = Footer;
1240
+ function Title(props) {
1241
+ const {
1242
+ children,
1243
+ className,
1244
+ draggable,
1245
+ emit = () => {},
1246
+ title,
1247
+ ...rest
1248
+ } = props;
1249
+
1250
+ // we can't use state as we need to
1251
+ // manipulate this inside dragging events
1252
+ const context = useRef({
1253
+ startPosition: null,
1254
+ newPosition: null
1255
+ });
1256
+ const dragPreviewRef = useRef();
1257
+ const titleRef = useRef();
1258
+ const onMove = (event, delta) => {
1259
+ cancel(event);
1260
+ const {
1261
+ x: dx,
1262
+ y: dy
1263
+ } = delta;
1264
+ const newPosition = {
1265
+ x: context.current.startPosition.x + dx,
1266
+ y: context.current.startPosition.y + dy
1267
+ };
1268
+ const popupParent = getPopupParent(titleRef.current);
1269
+ popupParent.style.top = newPosition.y + 'px';
1270
+ popupParent.style.left = newPosition.x + 'px';
1271
+
1272
+ // notify interested parties
1273
+ emit('dragover', {
1274
+ newPosition,
1275
+ delta
1276
+ });
1277
+ };
1278
+ const onMoveStart = event => {
1279
+ // initialize drag handler
1280
+ const onDragStart = createDragger(onMove, dragPreviewRef.current);
1281
+ onDragStart(event);
1282
+ event.stopPropagation();
1283
+ const popupParent = getPopupParent(titleRef.current);
1284
+ const bounds = popupParent.getBoundingClientRect();
1285
+ context.current.startPosition = {
1286
+ x: bounds.left,
1287
+ y: bounds.top
1288
+ };
1289
+
1290
+ // notify interested parties
1291
+ emit('dragstart');
1292
+ };
1293
+ const onMoveEnd = () => {
1294
+ context.current.newPosition = null;
1295
+
1296
+ // notify interested parties
1297
+ emit('dragend');
1298
+ };
1299
+ return jsxs("div", {
1300
+ class: classnames('bio-properties-panel-popup__header', draggable && 'draggable', className),
1301
+ ref: titleRef,
1302
+ draggable: draggable,
1303
+ onDragStart: onMoveStart,
1304
+ onDragEnd: onMoveEnd,
1305
+ ...rest,
1306
+ children: [draggable && jsxs(Fragment, {
1307
+ children: [jsx("div", {
1308
+ ref: dragPreviewRef,
1309
+ class: "bio-properties-panel-popup__drag-preview"
1310
+ }), jsx("div", {
1311
+ class: "bio-properties-panel-popup__drag-handle",
1312
+ children: jsx(DragIcon, {})
1313
+ })]
1314
+ }), jsx("div", {
1315
+ class: "bio-properties-panel-popup__title",
1316
+ children: title
1317
+ }), children]
1318
+ });
1319
+ }
1320
+ function Body(props) {
1321
+ const {
1322
+ children,
1323
+ className,
1324
+ ...rest
1325
+ } = props;
1326
+ return jsx("div", {
1327
+ class: classnames('bio-properties-panel-popup__body', className),
1328
+ ...rest,
1329
+ children: children
1330
+ });
1331
+ }
1332
+ function Footer(props) {
1333
+ const {
1334
+ children,
1335
+ className,
1336
+ ...rest
1337
+ } = props;
1338
+ return jsx("div", {
1339
+ class: classnames('bio-properties-panel-popup__footer', className),
1340
+ ...rest,
1341
+ children: props.children
1342
+ });
1343
+ }
1344
+
1345
+ // helpers //////////////////////
1346
+
1347
+ function getPopupParent(node) {
1348
+ return node.closest('.bio-properties-panel-popup');
1349
+ }
1350
+ function cancel(event) {
1351
+ event.preventDefault();
1352
+ event.stopPropagation();
1353
+ }
1354
+ function getContainerNode(node) {
1355
+ if (typeof node === 'string') {
1356
+ return query(node);
1357
+ }
1358
+ return node;
1359
+ }
1360
+
1361
+ const FEEL_POPUP_WIDTH = 700;
1362
+ const FEEL_POPUP_HEIGHT = 250;
1363
+
1364
+ /**
1365
+ * FEEL popup component, built as a singleton. Emits lifecycle events as follows:
1366
+ * - `feelPopup.open` - fired before the popup is mounted
1367
+ * - `feelPopup.opened` - fired after the popup is mounted. Event context contains the DOM node of the popup
1368
+ * - `feelPopup.close` - fired before the popup is unmounted. Event context contains the DOM node of the popup
1369
+ * - `feelPopup.closed` - fired after the popup is unmounted
1370
+ */
1371
+ function FEELPopupRoot(props) {
1372
+ const {
1373
+ element,
1374
+ eventBus = {
1375
+ fire() {},
1376
+ on() {},
1377
+ off() {}
1378
+ },
1379
+ popupContainer
1380
+ } = props;
1381
+ const prevElement = usePrevious(element);
1382
+ const [popupConfig, setPopupConfig] = useState({});
1383
+ const [open, setOpen] = useState(false);
1384
+ const [source, setSource] = useState(null);
1385
+ const [sourceElement, setSourceElement] = useState(null);
1386
+ const emit = (type, context) => {
1387
+ eventBus.fire('feelPopup.' + type, context);
1388
+ };
1389
+ const isOpen = useCallback(() => {
1390
+ return !!open;
1391
+ }, [open]);
1392
+ useUpdateEffect(() => {
1393
+ if (!open) {
1394
+ emit('closed');
1395
+ }
1396
+ }, [open]);
1397
+ const handleOpen = (entryId, config, _sourceElement) => {
1398
+ setSource(entryId);
1399
+ setPopupConfig(config);
1400
+ setOpen(true);
1401
+ setSourceElement(_sourceElement);
1402
+ emit('open');
1403
+ };
1404
+ const handleClose = () => {
1405
+ setOpen(false);
1406
+ setSource(null);
1407
+ };
1408
+ const feelPopupContext = {
1409
+ open: handleOpen,
1410
+ close: handleClose,
1411
+ source
1412
+ };
1413
+
1414
+ // close popup on element change, cf. https://github.com/bpmn-io/properties-panel/issues/270
1415
+ useEffect(() => {
1416
+ if (element && prevElement && element !== prevElement) {
1417
+ handleClose();
1418
+ }
1419
+ }, [element]);
1420
+
1421
+ // allow close and open via events
1422
+ useEffect(() => {
1423
+ const handlePopupOpen = context => {
1424
+ const {
1425
+ entryId,
1426
+ popupConfig,
1427
+ sourceElement
1428
+ } = context;
1429
+ handleOpen(entryId, popupConfig, sourceElement);
1430
+ };
1431
+ const handleIsOpen = () => {
1432
+ return isOpen();
1433
+ };
1434
+ eventBus.on('feelPopup._close', handleClose);
1435
+ eventBus.on('feelPopup._open', handlePopupOpen);
1436
+ eventBus.on('feelPopup._isOpen', handleIsOpen);
1437
+ return () => {
1438
+ eventBus.off('feelPopup._close', handleClose);
1439
+ eventBus.off('feelPopup._open', handleOpen);
1440
+ eventBus.off('feelPopup._isOpen', handleIsOpen);
1441
+ };
1442
+ }, [eventBus, isOpen]);
1443
+ return jsxs(FeelPopupContext.Provider, {
1444
+ value: feelPopupContext,
1445
+ children: [open && jsx(FeelPopupComponent, {
1446
+ onClose: handleClose,
1447
+ container: popupContainer,
1448
+ sourceElement: sourceElement,
1449
+ emit: emit,
1450
+ ...popupConfig
1451
+ }), props.children]
1452
+ });
1453
+ }
1454
+ function FeelPopupComponent(props) {
1455
+ const {
1456
+ container,
1457
+ id,
1458
+ hostLanguage,
1459
+ onInput,
1460
+ onClose,
1461
+ position,
1462
+ singleLine,
1463
+ sourceElement,
1464
+ title,
1465
+ tooltipContainer,
1466
+ type,
1467
+ value,
1468
+ variables,
1469
+ emit
1470
+ } = props;
1471
+ const editorRef = useRef();
1472
+ const popupRef = useRef();
1473
+ const isAutoCompletionOpen = useRef(false);
1474
+ const handleSetReturnFocus = () => {
1475
+ sourceElement && sourceElement.focus();
1476
+ };
1477
+ const onKeyDownCapture = event => {
1478
+ // we use capture here to make sure we handle the event before the editor does
1479
+ if (event.key === 'Escape') {
1480
+ isAutoCompletionOpen.current = autoCompletionOpen(event.target);
1481
+ }
1482
+ };
1483
+ const onKeyDown = event => {
1484
+ if (event.key === 'Escape') {
1485
+ // close popup only if auto completion is not open
1486
+ // we need to do check this because the editor is not
1487
+ // stop propagating the keydown event
1488
+ // cf. https://discuss.codemirror.net/t/how-can-i-replace-the-default-autocompletion-keymap-v6/3322/5
1489
+ if (!isAutoCompletionOpen.current) {
1490
+ onClose();
1491
+ isAutoCompletionOpen.current = false;
1492
+ }
1493
+ }
1494
+ };
1495
+ useEffect(() => {
1496
+ emit('opened', {
1497
+ domNode: popupRef.current
1498
+ });
1499
+ return () => emit('close', {
1500
+ domNode: popupRef.current
1501
+ });
1502
+ }, []);
1503
+ return jsxs(Popup, {
1504
+ container: container,
1505
+ className: "bio-properties-panel-feel-popup",
1506
+ emit: emit,
1507
+ position: position,
1508
+ title: title,
1509
+ onClose: onClose
1510
+
1511
+ // handle focus manually on deactivate
1512
+ ,
1513
+ returnFocus: false,
1514
+ closeOnEscape: false,
1515
+ delayInitialFocus: false,
1516
+ onPostDeactivate: handleSetReturnFocus,
1517
+ height: FEEL_POPUP_HEIGHT,
1518
+ width: FEEL_POPUP_WIDTH,
1519
+ ref: popupRef,
1520
+ children: [jsx(Popup.Title, {
1521
+ title: title,
1522
+ emit: emit,
1523
+ draggable: true
1524
+ }), jsx(Popup.Body, {
1525
+ children: jsxs("div", {
1526
+ onKeyDownCapture: onKeyDownCapture,
1527
+ onKeyDown: onKeyDown,
1528
+ class: "bio-properties-panel-feel-popup__body",
1529
+ children: [type === 'feel' && jsx(CodeEditor, {
1530
+ enableGutters: true,
1531
+ id: prefixId$8(id),
1532
+ name: id,
1533
+ onInput: onInput,
1534
+ value: value,
1535
+ variables: variables,
1536
+ ref: editorRef,
1537
+ tooltipContainer: tooltipContainer
1538
+ }), type === 'feelers' && jsx(CodeEditor$1, {
1539
+ id: prefixId$8(id),
1540
+ contentAttributes: {
1541
+ 'aria-label': title
1542
+ },
1543
+ enableGutters: true,
1544
+ hostLanguage: hostLanguage,
1545
+ name: id,
1546
+ onInput: onInput,
1547
+ value: value,
1548
+ ref: editorRef,
1549
+ singleLine: singleLine,
1550
+ tooltipContainer: tooltipContainer
1551
+ })]
1552
+ })
1553
+ }), jsx(Popup.Footer, {
1554
+ children: jsx("button", {
1555
+ onClick: onClose,
1556
+ title: "Close pop-up editor",
1557
+ class: "bio-properties-panel-feel-popup__close-btn",
1558
+ children: "Close"
1559
+ })
1560
+ })]
1561
+ });
1562
+ }
1563
+
1564
+ // helpers /////////////////
1565
+
1566
+ function prefixId$8(id) {
1567
+ return `bio-properties-panel-${id}`;
1568
+ }
1569
+ function autoCompletionOpen(element) {
1570
+ return element.closest('.cm-editor').querySelector('.cm-tooltip-autocomplete');
1571
+ }
1572
+
1573
+ /**
1574
+ * This hook behaves like useEffect, but does not trigger on the first render.
1575
+ *
1576
+ * @param {Function} effect
1577
+ * @param {Array} deps
1578
+ */
1579
+ function useUpdateEffect(effect, deps) {
1580
+ const isMounted = useRef(false);
1581
+ useEffect(() => {
1582
+ if (isMounted.current) {
1583
+ return effect();
1584
+ } else {
1585
+ isMounted.current = true;
1586
+ }
1587
+ }, deps);
1588
+ }
1589
+
1590
+ function ToggleSwitch(props) {
1591
+ const {
1592
+ id,
1593
+ label,
1594
+ onInput,
1595
+ value,
1596
+ switcherLabel,
1597
+ inline,
1598
+ onFocus,
1599
+ onBlur,
1600
+ inputRef,
1601
+ tooltip
1602
+ } = props;
1603
+ const [localValue, setLocalValue] = useState(value);
1604
+ const handleInputCallback = async () => {
1605
+ onInput(!value);
1606
+ };
1607
+ const handleInput = e => {
1608
+ handleInputCallback();
1609
+ setLocalValue(e.target.value);
1610
+ };
1611
+ useEffect(() => {
1612
+ if (value === localValue) {
1613
+ return;
1614
+ }
1615
+ setLocalValue(value);
1616
+ }, [value]);
1617
+ return jsxs("div", {
1618
+ class: classnames('bio-properties-panel-toggle-switch', {
1619
+ inline
1620
+ }),
1621
+ children: [jsx("label", {
1622
+ class: "bio-properties-panel-label",
1623
+ for: prefixId$7(id),
1624
+ children: jsx(TooltipWrapper, {
1625
+ value: tooltip,
1626
+ forId: id,
1627
+ element: props.element,
1628
+ children: label
1629
+ })
1630
+ }), jsxs("div", {
1631
+ class: "bio-properties-panel-field-wrapper",
1632
+ children: [jsxs("label", {
1633
+ class: "bio-properties-panel-toggle-switch__switcher",
1634
+ children: [jsx("input", {
1635
+ ref: inputRef,
1636
+ id: prefixId$7(id),
1637
+ class: "bio-properties-panel-input",
1638
+ type: "checkbox",
1639
+ onFocus: onFocus,
1640
+ onBlur: onBlur,
1641
+ name: id,
1642
+ onInput: handleInput,
1643
+ checked: !!localValue
1644
+ }), jsx("span", {
1645
+ class: "bio-properties-panel-toggle-switch__slider"
1646
+ })]
1647
+ }), switcherLabel && jsx("p", {
1648
+ class: "bio-properties-panel-toggle-switch__label",
1649
+ children: switcherLabel
1650
+ })]
1651
+ })]
1652
+ });
1653
+ }
1654
+
1655
+ /**
1656
+ * @param {Object} props
1657
+ * @param {Object} props.element
1658
+ * @param {String} props.id
1659
+ * @param {String} props.description
1660
+ * @param {String} props.label
1661
+ * @param {String} props.switcherLabel
1662
+ * @param {Boolean} props.inline
1663
+ * @param {Function} props.getValue
1664
+ * @param {Function} props.setValue
1665
+ * @param {Function} props.onFocus
1666
+ * @param {Function} props.onBlur
1667
+ * @param {string|import('preact').Component} props.tooltip
1668
+ */
1669
+ function ToggleSwitchEntry(props) {
1670
+ const {
1671
+ element,
1672
+ id,
1673
+ description,
1674
+ label,
1675
+ switcherLabel,
1676
+ inline,
1677
+ getValue,
1678
+ setValue,
1679
+ onFocus,
1680
+ onBlur,
1681
+ tooltip
1682
+ } = props;
1683
+ const value = getValue(element);
1684
+ return jsxs("div", {
1685
+ class: "bio-properties-panel-entry bio-properties-panel-toggle-switch-entry",
1686
+ "data-entry-id": id,
1687
+ children: [jsx(ToggleSwitch, {
1688
+ id: id,
1689
+ label: label,
1690
+ value: value,
1691
+ onInput: setValue,
1692
+ onFocus: onFocus,
1693
+ onBlur: onBlur,
1694
+ switcherLabel: switcherLabel,
1695
+ inline: inline,
1696
+ tooltip: tooltip,
1697
+ element: element
1698
+ }), jsx(Description, {
1699
+ forId: id,
1700
+ element: element,
1701
+ value: description
1702
+ })]
1703
+ });
1704
+ }
1705
+ function isEdited$8(node) {
1706
+ return node && !!node.checked;
1707
+ }
1708
+
1709
+ // helpers /////////////////
1710
+
1711
+ function prefixId$7(id) {
1712
+ return `bio-properties-panel-${id}`;
1713
+ }
1714
+
1715
+ function NumberField(props) {
1716
+ const {
1717
+ debounce,
1718
+ disabled,
1719
+ displayLabel = true,
1720
+ id,
1721
+ inputRef,
1722
+ label,
1723
+ max,
1724
+ min,
1725
+ onInput,
1726
+ step,
1727
+ value = '',
1728
+ onFocus,
1729
+ onBlur
1730
+ } = props;
1731
+ const [localValue, setLocalValue] = useState(value);
1732
+ const handleInputCallback = useMemo(() => {
1733
+ return debounce(event => {
1734
+ const {
1735
+ validity,
1736
+ value
1737
+ } = event.target;
1738
+ if (validity.valid) {
1739
+ onInput(value ? parseFloat(value) : undefined);
1740
+ }
1741
+ });
1742
+ }, [onInput, debounce]);
1743
+ const handleInput = e => {
1744
+ handleInputCallback(e);
1745
+ setLocalValue(e.target.value);
1746
+ };
1747
+ useEffect(() => {
1748
+ if (value === localValue) {
1749
+ return;
1750
+ }
1751
+ setLocalValue(value);
1752
+ }, [value]);
1753
+ return jsxs("div", {
1754
+ class: "bio-properties-panel-numberfield",
1755
+ children: [displayLabel && jsx("label", {
1756
+ for: prefixId$6(id),
1757
+ class: "bio-properties-panel-label",
1758
+ children: label
1759
+ }), jsx("input", {
1760
+ id: prefixId$6(id),
1761
+ ref: inputRef,
1762
+ type: "number",
1763
+ name: id,
1764
+ spellCheck: "false",
1765
+ autoComplete: "off",
1766
+ disabled: disabled,
1767
+ class: "bio-properties-panel-input",
1768
+ max: max,
1769
+ min: min,
1770
+ onInput: handleInput,
1771
+ onFocus: onFocus,
1772
+ onBlur: onBlur,
1773
+ step: step,
1774
+ value: localValue
1775
+ })]
1776
+ });
1777
+ }
1778
+
1779
+ /**
1780
+ * @param {Object} props
1781
+ * @param {Boolean} props.debounce
1782
+ * @param {String} props.description
1783
+ * @param {Boolean} props.disabled
1784
+ * @param {Object} props.element
1785
+ * @param {Function} props.getValue
1786
+ * @param {String} props.id
1787
+ * @param {String} props.label
1788
+ * @param {String} props.max
1789
+ * @param {String} props.min
1790
+ * @param {Function} props.setValue
1791
+ * @param {Function} props.onFocus
1792
+ * @param {Function} props.onBlur
1793
+ * @param {String} props.step
1794
+ * @param {Function} props.validate
1795
+ */
1796
+ function NumberFieldEntry(props) {
1797
+ const {
1798
+ debounce,
1799
+ description,
1800
+ disabled,
1801
+ element,
1802
+ getValue,
1803
+ id,
1804
+ label,
1805
+ max,
1806
+ min,
1807
+ setValue,
1808
+ step,
1809
+ onFocus,
1810
+ onBlur,
1811
+ validate
1812
+ } = props;
1813
+ const globalError = useError(id);
1814
+ const [localError, setLocalError] = useState(null);
1815
+ let value = getValue(element);
1816
+ useEffect(() => {
1817
+ if (isFunction(validate)) {
1818
+ const newValidationError = validate(value) || null;
1819
+ setLocalError(newValidationError);
1820
+ }
1821
+ }, [value]);
1822
+ const onInput = newValue => {
1823
+ let newValidationError = null;
1824
+ if (isFunction(validate)) {
1825
+ newValidationError = validate(newValue) || null;
1826
+ }
1827
+ setValue(newValue, newValidationError);
1828
+ setLocalError(newValidationError);
1829
+ };
1830
+ const error = globalError || localError;
1831
+ return jsxs("div", {
1832
+ class: classnames('bio-properties-panel-entry', error ? 'has-error' : ''),
1833
+ "data-entry-id": id,
1834
+ children: [jsx(NumberField, {
1835
+ debounce: debounce,
1836
+ disabled: disabled,
1837
+ id: id,
1838
+ label: label,
1839
+ onFocus: onFocus,
1840
+ onBlur: onBlur,
1841
+ onInput: onInput,
1842
+ max: max,
1843
+ min: min,
1844
+ step: step,
1845
+ value: value
1846
+ }, element), error && jsx("div", {
1847
+ class: "bio-properties-panel-error",
1848
+ children: error
1849
+ }), jsx(Description, {
1850
+ forId: id,
1851
+ element: element,
1852
+ value: description
1853
+ })]
1854
+ });
1855
+ }
1856
+ function isEdited$7(node) {
1857
+ return node && !!node.value;
1858
+ }
1859
+
1860
+ // helpers /////////////////
1861
+
1862
+ function prefixId$6(id) {
1863
+ return `bio-properties-panel-${id}`;
1864
+ }
1865
+
1866
+ const noop$2 = () => {};
1867
+ function FeelTextfield(props) {
1868
+ const {
1869
+ debounce,
1870
+ id,
1871
+ element,
1872
+ label,
1873
+ hostLanguage,
1874
+ onInput,
1875
+ onError,
1876
+ feel,
1877
+ value = '',
1878
+ disabled = false,
1879
+ variables,
1880
+ singleLine,
1881
+ tooltipContainer,
1882
+ OptionalComponent = OptionalFeelInput,
1883
+ tooltip
1884
+ } = props;
1885
+ const [localValue, _setLocalValue] = useState(value);
1886
+ const editorRef = useShowEntryEvent(id);
1887
+ const containerRef = useRef();
1888
+ const feelActive = isString(localValue) && localValue.startsWith('=') || feel === 'required';
1889
+ const feelOnlyValue = isString(localValue) && localValue.startsWith('=') ? localValue.substring(1) : localValue;
1890
+ const [focus, _setFocus] = useState(undefined);
1891
+ const {
1892
+ open: openPopup,
1893
+ source: popupSource
1894
+ } = useContext(FeelPopupContext);
1895
+ const popuOpen = popupSource === id;
1896
+ const setFocus = (offset = 0) => {
1897
+ const hasFocus = containerRef.current.contains(document.activeElement);
1898
+
1899
+ // Keep caret position if it is already focused, otherwise focus at the end
1900
+ const position = hasFocus ? document.activeElement.selectionStart : Infinity;
1901
+ _setFocus(position + offset);
1902
+ };
1903
+ const handleInputCallback = useMemo(() => {
1904
+ return debounce(newValue => {
1905
+ onInput(newValue);
1906
+ });
1907
+ }, [onInput, debounce]);
1908
+ const setLocalValue = newValue => {
1909
+ _setLocalValue(newValue);
1910
+ if (typeof newValue === 'undefined' || newValue === '' || newValue === '=') {
1911
+ handleInputCallback(undefined);
1912
+ } else {
1913
+ handleInputCallback(newValue);
1914
+ }
1915
+ };
1916
+ const handleFeelToggle = useStaticCallback(() => {
1917
+ if (feel === 'required') {
1918
+ return;
1919
+ }
1920
+ if (!feelActive) {
1921
+ setLocalValue('=' + localValue);
1922
+ } else {
1923
+ setLocalValue(feelOnlyValue);
1924
+ }
1925
+ });
1926
+ const handleLocalInput = newValue => {
1927
+ if (feelActive) {
1928
+ newValue = '=' + newValue;
1929
+ }
1930
+ if (newValue === localValue) {
1931
+ return;
1932
+ }
1933
+ setLocalValue(newValue);
1934
+ if (!feelActive && isString(newValue) && newValue.startsWith('=')) {
1935
+ // focus is behind `=` sign that will be removed
1936
+ setFocus(-1);
1937
+ }
1938
+ };
1939
+ const handleLint = useStaticCallback(lint => {
1940
+ if (!(lint && lint.length)) {
1941
+ onError(undefined);
1942
+ return;
1943
+ }
1944
+ const error = lint[0];
1945
+ const message = `${error.source}: ${error.message}`;
1946
+ onError(message);
1947
+ });
1948
+ const handlePopupOpen = (type = 'feel') => {
1949
+ const popupOptions = {
1950
+ id,
1951
+ hostLanguage,
1952
+ onInput: handleLocalInput,
1953
+ position: calculatePopupPosition(containerRef.current),
1954
+ singleLine,
1955
+ title: getPopupTitle(element, label),
1956
+ tooltipContainer,
1957
+ type,
1958
+ value: feelOnlyValue,
1959
+ variables
1960
+ };
1961
+ openPopup(id, popupOptions, editorRef.current);
1962
+ };
1963
+ useEffect(() => {
1964
+ if (typeof focus !== 'undefined') {
1965
+ editorRef.current.focus(focus);
1966
+ _setFocus(undefined);
1967
+ }
1968
+ }, [focus]);
1969
+ useEffect(() => {
1970
+ if (value === localValue) {
1971
+ return;
1972
+ }
1973
+
1974
+ // External value change removed content => keep FEEL configuration
1975
+ if (!value) {
1976
+ setLocalValue(feelActive ? '=' : '');
1977
+ return;
1978
+ }
1979
+ setLocalValue(value);
1980
+ }, [value]);
1981
+
1982
+ // copy-paste integration
1983
+ useEffect(() => {
1984
+ const copyHandler = event => {
1985
+ if (!feelActive) {
1986
+ return;
1987
+ }
1988
+ event.clipboardData.setData('application/FEEL', event.clipboardData.getData('text'));
1989
+ };
1990
+ const pasteHandler = event => {
1991
+ if (feelActive || popuOpen) {
1992
+ return;
1993
+ }
1994
+ const data = event.clipboardData.getData('application/FEEL');
1995
+ if (data) {
1996
+ setTimeout(() => {
1997
+ handleFeelToggle();
1998
+ setFocus();
1999
+ });
2000
+ }
2001
+ };
2002
+ containerRef.current.addEventListener('copy', copyHandler);
2003
+ containerRef.current.addEventListener('cut', copyHandler);
2004
+ containerRef.current.addEventListener('paste', pasteHandler);
2005
+ return () => {
2006
+ containerRef.current.removeEventListener('copy', copyHandler);
2007
+ containerRef.current.removeEventListener('cut', copyHandler);
2008
+ containerRef.current.removeEventListener('paste', pasteHandler);
2009
+ };
2010
+ }, [containerRef, feelActive, handleFeelToggle, setFocus]);
2011
+ return jsxs("div", {
2012
+ class: classnames('bio-properties-panel-feel-entry', {
2013
+ 'feel-active': feelActive
2014
+ }),
2015
+ children: [jsxs("label", {
2016
+ for: prefixId$5(id),
2017
+ class: "bio-properties-panel-label",
2018
+ onClick: () => setFocus(),
2019
+ children: [jsx(TooltipWrapper, {
2020
+ value: tooltip,
2021
+ forId: id,
2022
+ element: props.element,
2023
+ children: label
2024
+ }), jsx(FeelIcon, {
2025
+ label: label,
2026
+ feel: feel,
2027
+ onClick: handleFeelToggle,
2028
+ active: feelActive
2029
+ })]
2030
+ }), jsxs("div", {
2031
+ class: "bio-properties-panel-feel-container",
2032
+ ref: containerRef,
2033
+ children: [jsx(FeelIndicator, {
2034
+ active: feelActive,
2035
+ disabled: feel !== 'optional' || disabled,
2036
+ onClick: handleFeelToggle
2037
+ }), feelActive ? jsx(CodeEditor, {
2038
+ id: prefixId$5(id),
2039
+ name: id,
2040
+ onInput: handleLocalInput,
2041
+ disabled: disabled,
2042
+ popupOpen: popuOpen,
2043
+ onFeelToggle: () => {
2044
+ handleFeelToggle();
2045
+ setFocus(true);
2046
+ },
2047
+ onLint: handleLint,
2048
+ onPopupOpen: handlePopupOpen,
2049
+ value: feelOnlyValue,
2050
+ variables: variables,
2051
+ ref: editorRef,
2052
+ tooltipContainer: tooltipContainer
2053
+ }) : jsx(OptionalComponent, {
2054
+ ...props,
2055
+ popupOpen: popuOpen,
2056
+ onInput: handleLocalInput,
2057
+ contentAttributes: {
2058
+ 'id': prefixId$5(id),
2059
+ 'aria-label': label
2060
+ },
2061
+ value: localValue,
2062
+ ref: editorRef,
2063
+ onPopupOpen: handlePopupOpen,
2064
+ containerRef: containerRef
2065
+ })]
2066
+ })]
2067
+ });
2068
+ }
2069
+ const OptionalFeelInput = forwardRef((props, ref) => {
2070
+ const {
2071
+ id,
2072
+ disabled,
2073
+ onInput,
2074
+ value,
2075
+ onFocus,
2076
+ onBlur
2077
+ } = props;
2078
+ const inputRef = useRef();
2079
+
2080
+ // To be consistent with the FEEL editor, set focus at start of input
2081
+ // this ensures clean editing experience when switching with the keyboard
2082
+ ref.current = {
2083
+ focus: position => {
2084
+ const input = inputRef.current;
2085
+ if (!input) {
2086
+ return;
2087
+ }
2088
+ input.focus();
2089
+ if (typeof position === 'number') {
2090
+ if (position > value.length) {
2091
+ position = value.length;
2092
+ }
2093
+ input.setSelectionRange(position, position);
2094
+ }
2095
+ }
2096
+ };
2097
+ return jsx("input", {
2098
+ id: prefixId$5(id),
2099
+ type: "text",
2100
+ ref: inputRef,
2101
+ name: id,
2102
+ spellCheck: "false",
2103
+ autoComplete: "off",
2104
+ disabled: disabled,
2105
+ class: "bio-properties-panel-input",
2106
+ onInput: e => onInput(e.target.value),
2107
+ onFocus: onFocus,
2108
+ onBlur: onBlur,
2109
+ value: value || ''
2110
+ });
2111
+ });
2112
+ const OptionalFeelNumberField = forwardRef((props, ref) => {
2113
+ const {
2114
+ id,
2115
+ debounce,
2116
+ disabled,
2117
+ onInput,
2118
+ value,
2119
+ min,
2120
+ max,
2121
+ step,
2122
+ onFocus,
2123
+ onBlur
2124
+ } = props;
2125
+ const inputRef = useRef();
2126
+
2127
+ // To be consistent with the FEEL editor, set focus at start of input
2128
+ // this ensures clean editing experience when switching with the keyboard
2129
+ ref.current = {
2130
+ focus: position => {
2131
+ const input = inputRef.current;
2132
+ if (!input) {
2133
+ return;
2134
+ }
2135
+ input.focus();
2136
+ if (typeof position === 'number' && position !== Infinity) {
2137
+ if (position > value.length) {
2138
+ position = value.length;
2139
+ }
2140
+ input.setSelectionRange(position, position);
2141
+ }
2142
+ }
2143
+ };
2144
+ return jsx(NumberField, {
2145
+ id: id,
2146
+ debounce: debounce,
2147
+ disabled: disabled,
2148
+ displayLabel: false,
2149
+ inputRef: inputRef,
2150
+ max: max,
2151
+ min: min,
2152
+ onInput: onInput,
2153
+ step: step,
2154
+ value: value,
2155
+ onFocus: onFocus,
2156
+ onBlur: onBlur
2157
+ });
2158
+ });
2159
+ const OptionalFeelTextArea = forwardRef((props, ref) => {
2160
+ const {
2161
+ id,
2162
+ disabled,
2163
+ onInput,
2164
+ value,
2165
+ onFocus,
2166
+ onBlur
2167
+ } = props;
2168
+ const inputRef = useRef();
2169
+
2170
+ // To be consistent with the FEEL editor, set focus at start of input
2171
+ // this ensures clean editing experience when switching with the keyboard
2172
+ ref.current = {
2173
+ focus: () => {
2174
+ const input = inputRef.current;
2175
+ if (!input) {
2176
+ return;
2177
+ }
2178
+ input.focus();
2179
+ input.setSelectionRange(0, 0);
2180
+ }
2181
+ };
2182
+ return jsx("textarea", {
2183
+ id: prefixId$5(id),
2184
+ type: "text",
2185
+ ref: inputRef,
2186
+ name: id,
2187
+ spellCheck: "false",
2188
+ autoComplete: "off",
2189
+ disabled: disabled,
2190
+ class: "bio-properties-panel-input",
2191
+ onInput: e => onInput(e.target.value),
2192
+ onFocus: onFocus,
2193
+ onBlur: onBlur,
2194
+ value: value || '',
2195
+ "data-gramm": "false"
2196
+ });
2197
+ });
2198
+ const OptionalFeelToggleSwitch = forwardRef((props, ref) => {
2199
+ const {
2200
+ id,
2201
+ onInput,
2202
+ value,
2203
+ onFocus,
2204
+ onBlur,
2205
+ switcherLabel
2206
+ } = props;
2207
+ const inputRef = useRef();
2208
+
2209
+ // To be consistent with the FEEL editor, set focus at start of input
2210
+ // this ensures clean editing experience when switching with the keyboard
2211
+ ref.current = {
2212
+ focus: () => {
2213
+ const input = inputRef.current;
2214
+ if (!input) {
2215
+ return;
2216
+ }
2217
+ input.focus();
2218
+ }
2219
+ };
2220
+ return jsx(ToggleSwitch, {
2221
+ id: id,
2222
+ value: value,
2223
+ inputRef: inputRef,
2224
+ onInput: onInput,
2225
+ onFocus: onFocus,
2226
+ onBlur: onBlur,
2227
+ switcherLabel: switcherLabel
2228
+ });
2229
+ });
2230
+ const OptionalFeelCheckbox = forwardRef((props, ref) => {
2231
+ const {
2232
+ id,
2233
+ disabled,
2234
+ onInput,
2235
+ value,
2236
+ onFocus,
2237
+ onBlur
2238
+ } = props;
2239
+ const inputRef = useRef();
2240
+ const handleChange = ({
2241
+ target
2242
+ }) => {
2243
+ onInput(target.checked);
2244
+ };
2245
+
2246
+ // To be consistent with the FEEL editor, set focus at start of input
2247
+ // this ensures clean editing experience when switching with the keyboard
2248
+ ref.current = {
2249
+ focus: () => {
2250
+ const input = inputRef.current;
2251
+ if (!input) {
2252
+ return;
2253
+ }
2254
+ input.focus();
2255
+ }
2256
+ };
2257
+ return jsx("input", {
2258
+ ref: inputRef,
2259
+ id: prefixId$5(id),
2260
+ name: id,
2261
+ onFocus: onFocus,
2262
+ onBlur: onBlur,
2263
+ type: "checkbox",
2264
+ class: "bio-properties-panel-input",
2265
+ onChange: handleChange,
2266
+ checked: value,
2267
+ disabled: disabled
2268
+ });
2269
+ });
2270
+
2271
+ /**
2272
+ * @param {Object} props
2273
+ * @param {Object} props.element
2274
+ * @param {String} props.id
2275
+ * @param {String} props.description
2276
+ * @param {Boolean} props.debounce
2277
+ * @param {Boolean} props.disabled
2278
+ * @param {Boolean} props.feel
2279
+ * @param {String} props.label
2280
+ * @param {Function} props.getValue
2281
+ * @param {Function} props.setValue
2282
+ * @param {Function} props.tooltipContainer
2283
+ * @param {Function} props.validate
2284
+ * @param {Function} props.show
2285
+ * @param {Function} props.example
2286
+ * @param {Function} props.variables
2287
+ * @param {Function} props.onFocus
2288
+ * @param {Function} props.onBlur
2289
+ * @param {string|import('preact').Component} props.tooltip
2290
+ */
2291
+ function FeelEntry(props) {
2292
+ const {
2293
+ element,
2294
+ id,
2295
+ description,
2296
+ debounce,
2297
+ disabled,
2298
+ feel,
2299
+ label,
2300
+ getValue,
2301
+ setValue,
2302
+ tooltipContainer,
2303
+ hostLanguage,
2304
+ singleLine,
2305
+ validate,
2306
+ show = noop$2,
2307
+ example,
2308
+ variables,
2309
+ onFocus,
2310
+ onBlur,
2311
+ tooltip
2312
+ } = props;
2313
+ const [validationError, setValidationError] = useState(null);
2314
+ const [localError, setLocalError] = useState(null);
2315
+ let value = getValue(element);
2316
+ useEffect(() => {
2317
+ if (isFunction(validate)) {
2318
+ const newValidationError = validate(value) || null;
2319
+ setValidationError(newValidationError);
2320
+ }
2321
+ }, [value]);
2322
+ const onInput = useStaticCallback(newValue => {
2323
+ let newValidationError = null;
2324
+ if (isFunction(validate)) {
2325
+ newValidationError = validate(newValue) || null;
2326
+ }
2327
+
2328
+ // don't create multiple commandStack entries for the same value
2329
+ if (newValue !== value) {
2330
+ setValue(newValue, newValidationError);
2331
+ }
2332
+ setValidationError(newValidationError);
2333
+ });
2334
+ const onError = useCallback(err => {
2335
+ setLocalError(err);
2336
+ }, []);
2337
+ const temporaryError = useError(id);
2338
+ const error = temporaryError || localError || validationError;
2339
+ return jsxs("div", {
2340
+ class: classnames(props.class, 'bio-properties-panel-entry', error ? 'has-error' : ''),
2341
+ "data-entry-id": id,
2342
+ children: [createElement(FeelTextfield, {
2343
+ ...props,
2344
+ debounce: debounce,
2345
+ disabled: disabled,
2346
+ feel: feel,
2347
+ id: id,
2348
+ key: element,
2349
+ label: label,
2350
+ onInput: onInput,
2351
+ onError: onError,
2352
+ onFocus: onFocus,
2353
+ onBlur: onBlur,
2354
+ example: example,
2355
+ hostLanguage: hostLanguage,
2356
+ singleLine: singleLine,
2357
+ show: show,
2358
+ value: value,
2359
+ variables: variables,
2360
+ tooltipContainer: tooltipContainer,
2361
+ OptionalComponent: props.OptionalComponent,
2362
+ tooltip: tooltip
2363
+ }), error && jsx("div", {
2364
+ class: "bio-properties-panel-error",
2365
+ children: error
2366
+ }), jsx(Description, {
2367
+ forId: id,
2368
+ element: element,
2369
+ value: description
2370
+ })]
2371
+ });
2372
+ }
2373
+
2374
+ /**
2375
+ * @param {Object} props
2376
+ * @param {Object} props.element
2377
+ * @param {String} props.id
2378
+ * @param {String} props.description
2379
+ * @param {Boolean} props.debounce
2380
+ * @param {Boolean} props.disabled
2381
+ * @param {String} props.max
2382
+ * @param {String} props.min
2383
+ * @param {String} props.step
2384
+ * @param {Boolean} props.feel
2385
+ * @param {String} props.label
2386
+ * @param {Function} props.getValue
2387
+ * @param {Function} props.setValue
2388
+ * @param {Function} props.tooltipContainer
2389
+ * @param {Function} props.validate
2390
+ * @param {Function} props.show
2391
+ * @param {Function} props.example
2392
+ * @param {Function} props.variables
2393
+ * @param {Function} props.onFocus
2394
+ * @param {Function} props.onBlur
2395
+ */
2396
+ function FeelNumberEntry(props) {
2397
+ return jsx(FeelEntry, {
2398
+ class: "bio-properties-panel-feel-number",
2399
+ OptionalComponent: OptionalFeelNumberField,
2400
+ ...props
2401
+ });
2402
+ }
2403
+
2404
+ /**
2405
+ * @param {Object} props
2406
+ * @param {Object} props.element
2407
+ * @param {String} props.id
2408
+ * @param {String} props.description
2409
+ * @param {Boolean} props.debounce
2410
+ * @param {Boolean} props.disabled
2411
+ * @param {Boolean} props.feel
2412
+ * @param {String} props.label
2413
+ * @param {Function} props.getValue
2414
+ * @param {Function} props.setValue
2415
+ * @param {Function} props.tooltipContainer
2416
+ * @param {Function} props.validate
2417
+ * @param {Function} props.show
2418
+ * @param {Function} props.example
2419
+ * @param {Function} props.variables
2420
+ * @param {Function} props.onFocus
2421
+ * @param {Function} props.onBlur
2422
+ */
2423
+ function FeelTextAreaEntry(props) {
2424
+ return jsx(FeelEntry, {
2425
+ class: "bio-properties-panel-feel-textarea",
2426
+ OptionalComponent: OptionalFeelTextArea,
2427
+ ...props
2428
+ });
2429
+ }
2430
+
2431
+ /**
2432
+ * @param {Object} props
2433
+ * @param {Object} props.element
2434
+ * @param {String} props.id
2435
+ * @param {String} props.description
2436
+ * @param {Boolean} props.debounce
2437
+ * @param {Boolean} props.disabled
2438
+ * @param {Boolean} props.feel
2439
+ * @param {String} props.label
2440
+ * @param {Function} props.getValue
2441
+ * @param {Function} props.setValue
2442
+ * @param {Function} props.tooltipContainer
2443
+ * @param {Function} props.validate
2444
+ * @param {Function} props.show
2445
+ * @param {Function} props.example
2446
+ * @param {Function} props.variables
2447
+ * @param {Function} props.onFocus
2448
+ * @param {Function} props.onBlur
2449
+ */
2450
+ function FeelToggleSwitchEntry(props) {
2451
+ return jsx(FeelEntry, {
2452
+ class: "bio-properties-panel-feel-toggle-switch",
2453
+ OptionalComponent: OptionalFeelToggleSwitch,
2454
+ ...props
2455
+ });
2456
+ }
2457
+
2458
+ /**
2459
+ * @param {Object} props
2460
+ * @param {Object} props.element
2461
+ * @param {String} props.id
2462
+ * @param {String} props.description
2463
+ * @param {Boolean} props.debounce
2464
+ * @param {Boolean} props.disabled
2465
+ * @param {Boolean} props.feel
2466
+ * @param {String} props.label
2467
+ * @param {Function} props.getValue
2468
+ * @param {Function} props.setValue
2469
+ * @param {Function} props.tooltipContainer
2470
+ * @param {Function} props.validate
2471
+ * @param {Function} props.show
2472
+ * @param {Function} props.example
2473
+ * @param {Function} props.variables
2474
+ * @param {Function} props.onFocus
2475
+ * @param {Function} props.onBlur
2476
+ */
2477
+ function FeelCheckboxEntry(props) {
2478
+ return jsx(FeelEntry, {
2479
+ class: "bio-properties-panel-feel-checkbox",
2480
+ OptionalComponent: OptionalFeelCheckbox,
2481
+ ...props
2482
+ });
2483
+ }
2484
+
2485
+ /**
2486
+ * @param {Object} props
2487
+ * @param {Object} props.element
2488
+ * @param {String} props.id
2489
+ * @param {String} props.description
2490
+ * @param {String} props.hostLanguage
2491
+ * @param {Boolean} props.singleLine
2492
+ * @param {Boolean} props.debounce
2493
+ * @param {Boolean} props.disabled
2494
+ * @param {Boolean} props.feel
2495
+ * @param {String} props.label
2496
+ * @param {Function} props.getValue
2497
+ * @param {Function} props.setValue
2498
+ * @param {Function} props.tooltipContainer
2499
+ * @param {Function} props.validate
2500
+ * @param {Function} props.show
2501
+ * @param {Function} props.example
2502
+ * @param {Function} props.variables
2503
+ * @param {Function} props.onFocus
2504
+ * @param {Function} props.onBlur
2505
+ */
2506
+ function FeelTemplatingEntry(props) {
2507
+ return jsx(FeelEntry, {
2508
+ class: "bio-properties-panel-feel-templating",
2509
+ OptionalComponent: CodeEditor$1,
2510
+ ...props
2511
+ });
2512
+ }
2513
+ function isEdited$6(node) {
2514
+ if (!node) {
2515
+ return false;
2516
+ }
2517
+ if (node.type === 'checkbox') {
2518
+ return !!node.checked || node.classList.contains('edited');
2519
+ }
2520
+ return !!node.value || node.classList.contains('edited');
2521
+ }
2522
+
2523
+ // helpers /////////////////
2524
+
2525
+ function prefixId$5(id) {
2526
+ return `bio-properties-panel-${id}`;
2527
+ }
2528
+ function calculatePopupPosition(element) {
2529
+ const {
2530
+ top,
2531
+ left
2532
+ } = element.getBoundingClientRect();
2533
+ return {
2534
+ left: left - FEEL_POPUP_WIDTH - 20,
2535
+ top: top
2536
+ };
2537
+ }
2538
+
2539
+ // todo(pinussilvestrus): make this configurable in the future
2540
+ function getPopupTitle(element, label) {
2541
+ let popupTitle = '';
2542
+ if (element && element.type) {
2543
+ popupTitle = `${element.type} / `;
2544
+ }
2545
+ return `${popupTitle}${label}`;
2546
+ }
2547
+
2548
+ const DEFAULT_LAYOUT = {};
2549
+ const DEFAULT_DESCRIPTION = {};
2550
+ const DEFAULT_TOOLTIP = {};
2551
+
2552
+ /**
2553
+ * @typedef { {
2554
+ * component: import('preact').Component,
2555
+ * id: String,
2556
+ * isEdited?: Function
2557
+ * } } EntryDefinition
2558
+ *
2559
+ * @typedef { {
2560
+ * autoFocusEntry: String,
2561
+ * autoOpen?: Boolean,
2562
+ * entries: Array<EntryDefinition>,
2563
+ * id: String,
2564
+ * label: String,
2565
+ * remove: (event: MouseEvent) => void
2566
+ * } } ListItemDefinition
2567
+ *
2568
+ * @typedef { {
2569
+ * add: (event: MouseEvent) => void,
2570
+ * component: import('preact').Component,
2571
+ * element: Object,
2572
+ * id: String,
2573
+ * items: Array<ListItemDefinition>,
2574
+ * label: String,
2575
+ * shouldSort?: Boolean,
2576
+ * shouldOpen?: Boolean
2577
+ * } } ListGroupDefinition
2578
+ *
2579
+ * @typedef { {
2580
+ * component?: import('preact').Component,
2581
+ * entries: Array<EntryDefinition>,
2582
+ * id: String,
2583
+ * label: String,
2584
+ * shouldOpen?: Boolean
2585
+ * } } GroupDefinition
2586
+ *
2587
+ * @typedef { {
2588
+ * [id: String]: GetDescriptionFunction
2589
+ * } } DescriptionConfig
2590
+ *
2591
+ * @typedef { {
2592
+ * [id: String]: GetTooltipFunction
2593
+ * } } TooltipConfig
2594
+ *
2595
+ * @callback { {
2596
+ * @param {string} id
2597
+ * @param {Object} element
2598
+ * @returns {string}
2599
+ * } } GetDescriptionFunction
2600
+ *
2601
+ * @callback { {
2602
+ * @param {string} id
2603
+ * @param {Object} element
2604
+ * @returns {string}
2605
+ * } } GetTooltipFunction
2606
+ *
2607
+ * @typedef { {
2608
+ * getEmpty: (element: object) => import('./components/Placeholder').PlaceholderDefinition,
2609
+ * getMultiple: (element: Object) => import('./components/Placeholder').PlaceholderDefinition
2610
+ * } } PlaceholderProvider
2611
+ *
2612
+ */
2613
+
2614
+ /**
2615
+ * A basic properties panel component. Describes *how* content will be rendered, accepts
2616
+ * data from implementor to describe *what* will be rendered.
2617
+ *
2618
+ * @param {Object} props
2619
+ * @param {Object|Array} props.element
2620
+ * @param {import('./components/Header').HeaderProvider} props.headerProvider
2621
+ * @param {PlaceholderProvider} [props.placeholderProvider]
2622
+ * @param {Array<GroupDefinition|ListGroupDefinition>} props.groups
2623
+ * @param {Object} [props.layoutConfig]
2624
+ * @param {Function} [props.layoutChanged]
2625
+ * @param {DescriptionConfig} [props.descriptionConfig]
2626
+ * @param {Function} [props.descriptionLoaded]
2627
+ * @param {TooltipConfig} [props.tooltipConfig]
2628
+ * @param {Function} [props.tooltipLoaded]
2629
+ * @param {HTMLElement} [props.feelPopupContainer]
2630
+ * @param {Object} [props.eventBus]
2631
+ */
2632
+ function PropertiesPanel(props) {
2633
+ const {
2634
+ element,
2635
+ headerProvider,
2636
+ placeholderProvider,
2637
+ groups,
2638
+ layoutConfig,
2639
+ layoutChanged,
2640
+ descriptionConfig,
2641
+ descriptionLoaded,
2642
+ tooltipConfig,
2643
+ tooltipLoaded,
2644
+ feelPopupContainer,
2645
+ eventBus
2646
+ } = props;
2647
+
2648
+ // set-up layout context
2649
+ const [layout, setLayout] = useState(createLayout(layoutConfig));
2650
+
2651
+ // react to external changes in the layout config
2652
+ useUpdateLayoutEffect(() => {
2653
+ const newLayout = createLayout(layoutConfig);
2654
+ setLayout(newLayout);
2655
+ }, [layoutConfig]);
2656
+ useEffect(() => {
2657
+ if (typeof layoutChanged === 'function') {
2658
+ layoutChanged(layout);
2659
+ }
2660
+ }, [layout, layoutChanged]);
2661
+ const getLayoutForKey = (key, defaultValue) => {
2662
+ return get(layout, key, defaultValue);
2663
+ };
2664
+ const setLayoutForKey = (key, config) => {
2665
+ const newLayout = assign({}, layout);
2666
+ set(newLayout, key, config);
2667
+ setLayout(newLayout);
2668
+ };
2669
+ const layoutContext = {
2670
+ layout,
2671
+ setLayout,
2672
+ getLayoutForKey,
2673
+ setLayoutForKey
2674
+ };
2675
+
2676
+ // set-up description context
2677
+ const description = useMemo(() => createDescriptionContext(descriptionConfig), [descriptionConfig]);
2678
+ useEffect(() => {
2679
+ if (typeof descriptionLoaded === 'function') {
2680
+ descriptionLoaded(description);
2681
+ }
2682
+ }, [description, descriptionLoaded]);
2683
+ const getDescriptionForId = (id, element) => {
2684
+ return description[id] && description[id](element);
2685
+ };
2686
+ const descriptionContext = {
2687
+ description,
2688
+ getDescriptionForId
2689
+ };
2690
+
2691
+ // set-up tooltip context
2692
+ const tooltip = useMemo(() => createTooltipContext(tooltipConfig), [tooltipConfig]);
2693
+ useEffect(() => {
2694
+ if (typeof tooltipLoaded === 'function') {
2695
+ tooltipLoaded(tooltip);
2696
+ }
2697
+ }, [tooltip, tooltipLoaded]);
2698
+ const getTooltipForId = (id, element) => {
2699
+ return tooltip[id] && tooltip[id](element);
2700
+ };
2701
+ const tooltipContext = {
2702
+ tooltip,
2703
+ getTooltipForId
2704
+ };
2705
+ const [errors, setErrors] = useState({});
2706
+ const onSetErrors = ({
2707
+ errors
2708
+ }) => setErrors(errors);
2709
+ useEvent('propertiesPanel.setErrors', onSetErrors, eventBus);
2710
+ const errorsContext = {
2711
+ errors
2712
+ };
2713
+ const eventContext = {
2714
+ eventBus
2715
+ };
2716
+ const propertiesPanelContext = {
2717
+ element
2718
+ };
2719
+
2720
+ // empty state
2721
+ if (placeholderProvider && !element) {
2722
+ return jsx(Placeholder, {
2723
+ ...placeholderProvider.getEmpty()
2724
+ });
2725
+ }
2726
+
2727
+ // multiple state
2728
+ if (placeholderProvider && isArray(element)) {
2729
+ return jsx(Placeholder, {
2730
+ ...placeholderProvider.getMultiple()
2731
+ });
2732
+ }
2733
+ return jsx(LayoutContext.Provider, {
2734
+ value: propertiesPanelContext,
2735
+ children: jsx(ErrorsContext.Provider, {
2736
+ value: errorsContext,
2737
+ children: jsx(DescriptionContext.Provider, {
2738
+ value: descriptionContext,
2739
+ children: jsx(TooltipContext.Provider, {
2740
+ value: tooltipContext,
2741
+ children: jsx(LayoutContext.Provider, {
2742
+ value: layoutContext,
2743
+ children: jsx(EventContext.Provider, {
2744
+ value: eventContext,
2745
+ children: jsx(FEELPopupRoot, {
2746
+ element: element,
2747
+ eventBus: eventBus,
2748
+ popupContainer: feelPopupContainer,
2749
+ children: jsxs("div", {
2750
+ class: "bio-properties-panel",
2751
+ children: [jsx(Header, {
2752
+ element: element,
2753
+ headerProvider: headerProvider
2754
+ }), jsx("div", {
2755
+ class: "bio-properties-panel-scroll-container",
2756
+ children: groups.map(group => {
2757
+ const {
2758
+ component: Component = Group,
2759
+ id
2760
+ } = group;
2761
+ return createElement(Component, {
2762
+ ...group,
2763
+ key: id,
2764
+ element: element
2765
+ });
2766
+ })
2767
+ })]
2768
+ })
2769
+ })
2770
+ })
2771
+ })
2772
+ })
2773
+ })
2774
+ })
2775
+ });
2776
+ }
2777
+
2778
+ // helpers //////////////////
2779
+
2780
+ function createLayout(overrides = {}, defaults = DEFAULT_LAYOUT) {
2781
+ return {
2782
+ ...defaults,
2783
+ ...overrides
2784
+ };
2785
+ }
2786
+ function createDescriptionContext(overrides = {}) {
2787
+ return {
2788
+ ...DEFAULT_DESCRIPTION,
2789
+ ...overrides
2790
+ };
2791
+ }
2792
+ function createTooltipContext(overrides = {}) {
2793
+ return {
2794
+ ...DEFAULT_TOOLTIP,
2795
+ ...overrides
2796
+ };
2797
+ }
2798
+
2799
+ // hooks //////////////////
2800
+
2801
+ /**
2802
+ * This hook behaves like useLayoutEffect, but does not trigger on the first render.
2803
+ *
2804
+ * @param {Function} effect
2805
+ * @param {Array} deps
2806
+ */
2807
+ function useUpdateLayoutEffect(effect, deps) {
2808
+ const isMounted = useRef(false);
2809
+ useLayoutEffect(() => {
2810
+ if (isMounted.current) {
2811
+ return effect();
2812
+ } else {
2813
+ isMounted.current = true;
2814
+ }
2815
+ }, deps);
2816
+ }
2817
+
2818
+ function DropdownButton(props) {
2819
+ const {
2820
+ class: className,
2821
+ children,
2822
+ menuItems = []
2823
+ } = props;
2824
+ const dropdownRef = useRef(null);
2825
+ const menuRef = useRef(null);
2826
+ const [open, setOpen] = useState(false);
2827
+ const close = () => setOpen(false);
2828
+ function onDropdownToggle(event) {
2829
+ if (menuRef.current && menuRef.current.contains(event.target)) {
2830
+ return;
2831
+ }
2832
+ event.stopPropagation();
2833
+ setOpen(open => !open);
2834
+ }
2835
+ function onActionClick(event, action) {
2836
+ event.stopPropagation();
2837
+ close();
2838
+ action();
2839
+ }
2840
+ useGlobalClick([dropdownRef.current], () => close());
2841
+ return jsxs("div", {
2842
+ class: classnames('bio-properties-panel-dropdown-button', {
2843
+ open
2844
+ }, className),
2845
+ onClick: onDropdownToggle,
2846
+ ref: dropdownRef,
2847
+ children: [children, jsx("div", {
2848
+ class: "bio-properties-panel-dropdown-button__menu",
2849
+ ref: menuRef,
2850
+ children: menuItems.map((item, index) => jsx(MenuItem, {
2851
+ onClick: onActionClick,
2852
+ item: item
2853
+ }, index))
2854
+ })]
2855
+ });
2856
+ }
2857
+ function MenuItem({
2858
+ item,
2859
+ onClick
2860
+ }) {
2861
+ if (item.separator) {
2862
+ return jsx("div", {
2863
+ class: "bio-properties-panel-dropdown-button__menu-item bio-properties-panel-dropdown-button__menu-item--separator"
2864
+ });
2865
+ }
2866
+ if (item.action) {
2867
+ return jsx("button", {
2868
+ class: "bio-properties-panel-dropdown-button__menu-item bio-properties-panel-dropdown-button__menu-item--actionable",
2869
+ onClick: event => onClick(event, item.action),
2870
+ children: item.entry
2871
+ });
2872
+ }
2873
+ return jsx("div", {
2874
+ class: "bio-properties-panel-dropdown-button__menu-item",
2875
+ children: item.entry
2876
+ });
2877
+ }
2878
+
2879
+ /**
2880
+ *
2881
+ * @param {Array<null | Element>} ignoredElements
2882
+ * @param {Function} callback
2883
+ */
2884
+ function useGlobalClick(ignoredElements, callback) {
2885
+ useEffect(() => {
2886
+ /**
2887
+ * @param {MouseEvent} event
2888
+ */
2889
+ function listener(event) {
2890
+ if (ignoredElements.some(element => element && element.contains(event.target))) {
2891
+ return;
2892
+ }
2893
+ callback();
2894
+ }
2895
+ document.addEventListener('click', listener, {
2896
+ capture: true
2897
+ });
2898
+ return () => document.removeEventListener('click', listener, {
2899
+ capture: true
2900
+ });
2901
+ }, [...ignoredElements, callback]);
2902
+ }
2903
+
2904
+ function HeaderButton(props) {
2905
+ const {
2906
+ children = null,
2907
+ class: classname,
2908
+ onClick = () => {},
2909
+ ...otherProps
2910
+ } = props;
2911
+ return jsx("button", {
2912
+ ...otherProps,
2913
+ onClick: onClick,
2914
+ class: classnames('bio-properties-panel-group-header-button', classname),
2915
+ children: children
2916
+ });
2917
+ }
2918
+
2919
+ function CollapsibleEntry(props) {
2920
+ const {
2921
+ element,
2922
+ entries = [],
2923
+ id,
2924
+ label,
2925
+ open: shouldOpen,
2926
+ remove
2927
+ } = props;
2928
+ const [open, setOpen] = useState(shouldOpen);
2929
+ const toggleOpen = () => setOpen(!open);
2930
+ const {
2931
+ onShow
2932
+ } = useContext(LayoutContext);
2933
+ const propertiesPanelContext = {
2934
+ ...useContext(LayoutContext),
2935
+ onShow: useCallback(() => {
2936
+ setOpen(true);
2937
+ if (isFunction(onShow)) {
2938
+ onShow();
2939
+ }
2940
+ }, [onShow, setOpen])
2941
+ };
2942
+
2943
+ // todo(pinussilvestrus): translate once we have a translate mechanism for the core
2944
+ const placeholderLabel = '<empty>';
2945
+ return jsxs("div", {
2946
+ "data-entry-id": id,
2947
+ class: classnames('bio-properties-panel-collapsible-entry', open ? 'open' : ''),
2948
+ children: [jsxs("div", {
2949
+ class: "bio-properties-panel-collapsible-entry-header",
2950
+ onClick: toggleOpen,
2951
+ children: [jsx("div", {
2952
+ title: label || placeholderLabel,
2953
+ class: classnames('bio-properties-panel-collapsible-entry-header-title', !label && 'empty'),
2954
+ children: label || placeholderLabel
2955
+ }), jsx("button", {
2956
+ title: "Toggle list item",
2957
+ class: "bio-properties-panel-arrow bio-properties-panel-collapsible-entry-arrow",
2958
+ children: jsx(ArrowIcon, {
2959
+ class: open ? 'bio-properties-panel-arrow-down' : 'bio-properties-panel-arrow-right'
2960
+ })
2961
+ }), remove ? jsx("button", {
2962
+ title: "Delete item",
2963
+ class: "bio-properties-panel-remove-entry",
2964
+ onClick: remove,
2965
+ children: jsx(DeleteIcon, {})
2966
+ }) : null]
2967
+ }), jsx("div", {
2968
+ class: classnames('bio-properties-panel-collapsible-entry-entries', open ? 'open' : ''),
2969
+ children: jsx(LayoutContext.Provider, {
2970
+ value: propertiesPanelContext,
2971
+ children: entries.map(entry => {
2972
+ const {
2973
+ component: Component,
2974
+ id
2975
+ } = entry;
2976
+ return createElement(Component, {
2977
+ ...entry,
2978
+ element: element,
2979
+ key: id
2980
+ });
2981
+ })
2982
+ })
2983
+ })]
2984
+ });
2985
+ }
2986
+
2987
+ function ListItem(props) {
2988
+ const {
2989
+ autoFocusEntry,
2990
+ autoOpen
2991
+ } = props;
2992
+
2993
+ // focus specified entry on auto open
2994
+ useEffect(() => {
2995
+ if (autoOpen && autoFocusEntry) {
2996
+ const entry = query(`[data-entry-id="${autoFocusEntry}"]`);
2997
+ const focusableInput = query('.bio-properties-panel-input', entry);
2998
+ if (focusableInput) {
2999
+ if (isFunction(focusableInput.select)) {
3000
+ focusableInput.select();
3001
+ } else if (isFunction(focusableInput.focus)) {
3002
+ focusableInput.focus();
3003
+ }
3004
+ }
3005
+ }
3006
+ }, [autoOpen, autoFocusEntry]);
3007
+ return jsx("div", {
3008
+ class: "bio-properties-panel-list-item",
3009
+ children: jsx(CollapsibleEntry, {
3010
+ ...props,
3011
+ open: autoOpen
3012
+ })
3013
+ });
3014
+ }
3015
+
3016
+ const noop$1 = () => {};
3017
+
3018
+ /**
3019
+ * @param {import('../PropertiesPanel').ListGroupDefinition} props
3020
+ */
3021
+ function ListGroup(props) {
3022
+ const {
3023
+ add,
3024
+ element,
3025
+ id,
3026
+ items,
3027
+ label,
3028
+ shouldOpen = true,
3029
+ shouldSort = true
3030
+ } = props;
3031
+ const groupRef = useRef(null);
3032
+ const [open, setOpen] = useLayoutState(['groups', id, 'open'], false);
3033
+ const [sticky, setSticky] = useState(false);
3034
+ const onShow = useCallback(() => setOpen(true), [setOpen]);
3035
+ const [ordering, setOrdering] = useState([]);
3036
+ const [newItemAdded, setNewItemAdded] = useState(false);
3037
+
3038
+ // Flag to mark that add button was clicked in the last render cycle
3039
+ const [addTriggered, setAddTriggered] = useState(false);
3040
+ const prevItems = usePrevious(items);
3041
+ const prevElement = usePrevious(element);
3042
+ const elementChanged = element !== prevElement;
3043
+ const shouldHandleEffects = !elementChanged && (shouldSort || shouldOpen);
3044
+
3045
+ // reset initial ordering when element changes (before first render)
3046
+ if (elementChanged) {
3047
+ setOrdering(createOrdering(shouldSort ? sortItems(items) : items));
3048
+ }
3049
+
3050
+ // keep ordering in sync to items - and open changes
3051
+
3052
+ // (0) set initial ordering from given items
3053
+ useEffect(() => {
3054
+ if (!prevItems || !shouldSort) {
3055
+ setOrdering(createOrdering(items));
3056
+ }
3057
+ }, [items, element]);
3058
+
3059
+ // (1) items were added
3060
+ useEffect(() => {
3061
+ // reset addTriggered flag
3062
+ setAddTriggered(false);
3063
+ if (shouldHandleEffects && prevItems && items.length > prevItems.length) {
3064
+ let add = [];
3065
+ items.forEach(item => {
3066
+ if (!ordering.includes(item.id)) {
3067
+ add.push(item.id);
3068
+ }
3069
+ });
3070
+ let newOrdering = ordering;
3071
+
3072
+ // open if not open, configured and triggered by add button
3073
+ //
3074
+ // TODO(marstamm): remove once we refactor layout handling for listGroups.
3075
+ // Ideally, opening should be handled as part of the `add` callback and
3076
+ // not be a concern for the ListGroup component.
3077
+ if (addTriggered && !open && shouldOpen) {
3078
+ toggleOpen();
3079
+ }
3080
+
3081
+ // filter when not open and configured
3082
+ if (!open && shouldSort) {
3083
+ newOrdering = createOrdering(sortItems(items));
3084
+ }
3085
+
3086
+ // add new items on top or bottom depending on sorting behavior
3087
+ newOrdering = newOrdering.filter(item => !add.includes(item));
3088
+ if (shouldSort) {
3089
+ newOrdering.unshift(...add);
3090
+ } else {
3091
+ newOrdering.push(...add);
3092
+ }
3093
+ setOrdering(newOrdering);
3094
+ setNewItemAdded(addTriggered);
3095
+ } else {
3096
+ setNewItemAdded(false);
3097
+ }
3098
+ }, [items, open, shouldHandleEffects, addTriggered]);
3099
+
3100
+ // (2) sort items on open if shouldSort is set
3101
+ useEffect(() => {
3102
+ if (shouldSort && open && !newItemAdded) {
3103
+ setOrdering(createOrdering(sortItems(items)));
3104
+ }
3105
+ }, [open, shouldSort]);
3106
+
3107
+ // (3) items were deleted
3108
+ useEffect(() => {
3109
+ if (shouldHandleEffects && prevItems && items.length < prevItems.length) {
3110
+ let keep = [];
3111
+ ordering.forEach(o => {
3112
+ if (getItem(items, o)) {
3113
+ keep.push(o);
3114
+ }
3115
+ });
3116
+ setOrdering(keep);
3117
+ }
3118
+ }, [items, shouldHandleEffects]);
3119
+
3120
+ // set css class when group is sticky to top
3121
+ useStickyIntersectionObserver(groupRef, 'div.bio-properties-panel-scroll-container', setSticky);
3122
+ const toggleOpen = () => setOpen(!open);
3123
+ const hasItems = !!items.length;
3124
+ const propertiesPanelContext = {
3125
+ ...useContext(LayoutContext),
3126
+ onShow
3127
+ };
3128
+ const handleAddClick = e => {
3129
+ setAddTriggered(true);
3130
+ add(e);
3131
+ };
3132
+ const allErrors = useErrors();
3133
+ const hasError = items.some(item => {
3134
+ if (allErrors[item.id]) {
3135
+ return true;
3136
+ }
3137
+ if (!item.entries) {
3138
+ return;
3139
+ }
3140
+
3141
+ // also check if the error is nested, e.g. for name-value entries
3142
+ return item.entries.some(entry => allErrors[entry.id]);
3143
+ });
3144
+ return jsxs("div", {
3145
+ class: "bio-properties-panel-group",
3146
+ "data-group-id": 'group-' + id,
3147
+ ref: groupRef,
3148
+ children: [jsxs("div", {
3149
+ class: classnames('bio-properties-panel-group-header', hasItems ? '' : 'empty', hasItems && open ? 'open' : '', sticky && open ? 'sticky' : ''),
3150
+ onClick: hasItems ? toggleOpen : noop$1,
3151
+ children: [jsx("div", {
3152
+ title: props.tooltip ? null : label,
3153
+ "data-title": label,
3154
+ class: "bio-properties-panel-group-header-title",
3155
+ children: jsx(TooltipWrapper, {
3156
+ value: props.tooltip,
3157
+ forId: 'group-' + id,
3158
+ element: element,
3159
+ parent: groupRef,
3160
+ children: label
3161
+ })
3162
+ }), jsxs("div", {
3163
+ class: "bio-properties-panel-group-header-buttons",
3164
+ children: [add ? jsxs("button", {
3165
+ title: "Create new list item",
3166
+ class: "bio-properties-panel-group-header-button bio-properties-panel-add-entry",
3167
+ onClick: handleAddClick,
3168
+ children: [jsx(CreateIcon, {}), !hasItems ? jsx("span", {
3169
+ class: "bio-properties-panel-add-entry-label",
3170
+ children: "Create"
3171
+ }) : null]
3172
+ }) : null, hasItems ? jsx("div", {
3173
+ title: `List contains ${items.length} item${items.length != 1 ? 's' : ''}`,
3174
+ class: classnames('bio-properties-panel-list-badge', hasError ? 'bio-properties-panel-list-badge--error' : ''),
3175
+ children: items.length
3176
+ }) : null, hasItems ? jsx("button", {
3177
+ title: "Toggle section",
3178
+ class: "bio-properties-panel-group-header-button bio-properties-panel-arrow",
3179
+ children: jsx(ArrowIcon, {
3180
+ class: open ? 'bio-properties-panel-arrow-down' : 'bio-properties-panel-arrow-right'
3181
+ })
3182
+ }) : null]
3183
+ })]
3184
+ }), jsx("div", {
3185
+ class: classnames('bio-properties-panel-list', open && hasItems ? 'open' : ''),
3186
+ children: jsx(LayoutContext.Provider, {
3187
+ value: propertiesPanelContext,
3188
+ children: ordering.map((o, index) => {
3189
+ const item = getItem(items, o);
3190
+ if (!item) {
3191
+ return;
3192
+ }
3193
+ const {
3194
+ id
3195
+ } = item;
3196
+
3197
+ // if item was added, open it
3198
+ // Existing items will not be affected as autoOpen is only applied on first render
3199
+ const autoOpen = newItemAdded;
3200
+ return createElement(ListItem, {
3201
+ ...item,
3202
+ autoOpen: autoOpen,
3203
+ element: element,
3204
+ index: index,
3205
+ key: id
3206
+ });
3207
+ })
3208
+ })
3209
+ })]
3210
+ });
3211
+ }
3212
+
3213
+ // helpers ////////////////////
3214
+
3215
+ /**
3216
+ * Sorts given items alphanumeric by label
3217
+ */
3218
+ function sortItems(items) {
3219
+ return sortBy(items, i => i.label.toLowerCase());
3220
+ }
3221
+ function getItem(items, id) {
3222
+ return find(items, i => i.id === id);
3223
+ }
3224
+ function createOrdering(items) {
3225
+ return items.map(i => i.id);
3226
+ }
3227
+
3228
+ function Checkbox(props) {
3229
+ const {
3230
+ id,
3231
+ label,
3232
+ onChange,
3233
+ disabled,
3234
+ value = false,
3235
+ onFocus,
3236
+ onBlur,
3237
+ tooltip
3238
+ } = props;
3239
+ const [localValue, setLocalValue] = useState(value);
3240
+ const handleChangeCallback = ({
3241
+ target
3242
+ }) => {
3243
+ onChange(target.checked);
3244
+ };
3245
+ const handleChange = e => {
3246
+ handleChangeCallback(e);
3247
+ setLocalValue(e.target.value);
3248
+ };
3249
+ useEffect(() => {
3250
+ if (value === localValue) {
3251
+ return;
3252
+ }
3253
+ setLocalValue(value);
3254
+ }, [value]);
3255
+ const ref = useShowEntryEvent(id);
3256
+ return jsxs("div", {
3257
+ class: "bio-properties-panel-checkbox",
3258
+ children: [jsx("input", {
3259
+ ref: ref,
3260
+ id: prefixId$4(id),
3261
+ name: id,
3262
+ onFocus: onFocus,
3263
+ onBlur: onBlur,
3264
+ type: "checkbox",
3265
+ class: "bio-properties-panel-input",
3266
+ onChange: handleChange,
3267
+ checked: localValue,
3268
+ disabled: disabled
3269
+ }), jsx("label", {
3270
+ for: prefixId$4(id),
3271
+ class: "bio-properties-panel-label",
3272
+ children: jsx(TooltipWrapper, {
3273
+ value: tooltip,
3274
+ forId: id,
3275
+ element: props.element,
3276
+ children: label
3277
+ })
3278
+ })]
3279
+ });
3280
+ }
3281
+
3282
+ /**
3283
+ * @param {Object} props
3284
+ * @param {Object} props.element
3285
+ * @param {String} props.id
3286
+ * @param {String} props.description
3287
+ * @param {String} props.label
3288
+ * @param {Function} props.getValue
3289
+ * @param {Function} props.setValue
3290
+ * @param {Function} props.onFocus
3291
+ * @param {Function} props.onBlur
3292
+ * @param {string|import('preact').Component} props.tooltip
3293
+ * @param {boolean} [props.disabled]
3294
+ */
3295
+ function CheckboxEntry(props) {
3296
+ const {
3297
+ element,
3298
+ id,
3299
+ description,
3300
+ label,
3301
+ getValue,
3302
+ setValue,
3303
+ disabled,
3304
+ onFocus,
3305
+ onBlur,
3306
+ tooltip
3307
+ } = props;
3308
+ const value = getValue(element);
3309
+ const error = useError(id);
3310
+ return jsxs("div", {
3311
+ class: "bio-properties-panel-entry bio-properties-panel-checkbox-entry",
3312
+ "data-entry-id": id,
3313
+ children: [jsx(Checkbox, {
3314
+ disabled: disabled,
3315
+ id: id,
3316
+ label: label,
3317
+ onChange: setValue,
3318
+ onFocus: onFocus,
3319
+ onBlur: onBlur,
3320
+ value: value,
3321
+ tooltip: tooltip,
3322
+ element: element
3323
+ }, element), error && jsx("div", {
3324
+ class: "bio-properties-panel-error",
3325
+ children: error
3326
+ }), jsx(Description, {
3327
+ forId: id,
3328
+ element: element,
3329
+ value: description
3330
+ })]
3331
+ });
3332
+ }
3333
+ function isEdited$5(node) {
3334
+ return node && !!node.checked;
3335
+ }
3336
+
3337
+ // helpers /////////////////
3338
+
3339
+ function prefixId$4(id) {
3340
+ return `bio-properties-panel-${id}`;
3341
+ }
3342
+
3343
+ const noop = () => {};
3344
+
3345
+ /**
3346
+ * @param {Object} props
3347
+ * @param {Object} props.element
3348
+ * @param {String} props.id
3349
+ * @param {String} props.description
3350
+ * @param {Boolean} props.debounce
3351
+ * @param {Boolean} props.disabled
3352
+ * @param {String} props.label
3353
+ * @param {Function} props.getValue
3354
+ * @param {Function} props.setValue
3355
+ * @param {Function} props.tooltipContainer
3356
+ * @param {Function} props.validate
3357
+ * @param {Function} props.show
3358
+ */
3359
+ function TemplatingEntry(props) {
3360
+ const {
3361
+ element,
3362
+ id,
3363
+ description,
3364
+ debounce,
3365
+ disabled,
3366
+ label,
3367
+ getValue,
3368
+ setValue,
3369
+ tooltipContainer,
3370
+ validate,
3371
+ show = noop
3372
+ } = props;
3373
+ const [validationError, setValidationError] = useState(null);
3374
+ const [localError, setLocalError] = useState(null);
3375
+ let value = getValue(element);
3376
+ useEffect(() => {
3377
+ if (isFunction(validate)) {
3378
+ const newValidationError = validate(value) || null;
3379
+ setValidationError(newValidationError);
3380
+ }
3381
+ }, [value]);
3382
+ const onInput = useStaticCallback(newValue => {
3383
+ let newValidationError = null;
3384
+ if (isFunction(validate)) {
3385
+ newValidationError = validate(newValue) || null;
3386
+ }
3387
+
3388
+ // don't create multiple commandStack entries for the same value
3389
+ if (newValue !== value) {
3390
+ setValue(newValue, newValidationError);
3391
+ }
3392
+ setValidationError(newValidationError);
3393
+ });
3394
+ const onError = useCallback(err => {
3395
+ setLocalError(err);
3396
+ }, []);
3397
+ const temporaryError = useError(id);
3398
+ const error = localError || temporaryError || validationError;
3399
+ return jsxs("div", {
3400
+ class: classnames('bio-properties-panel-entry', error ? 'has-error' : ''),
3401
+ "data-entry-id": id,
3402
+ children: [jsx(Templating, {
3403
+ debounce: debounce,
3404
+ disabled: disabled,
3405
+ id: id,
3406
+ label: label,
3407
+ onInput: onInput,
3408
+ onError: onError,
3409
+ show: show,
3410
+ value: value,
3411
+ tooltipContainer: tooltipContainer
3412
+ }, element), error && jsx("div", {
3413
+ class: "bio-properties-panel-error",
3414
+ children: error
3415
+ }), jsx(Description, {
3416
+ forId: id,
3417
+ element: element,
3418
+ value: description
3419
+ })]
3420
+ });
3421
+ }
3422
+ function Templating(props) {
3423
+ const {
3424
+ debounce,
3425
+ id,
3426
+ label,
3427
+ onInput,
3428
+ onError,
3429
+ value = '',
3430
+ disabled = false,
3431
+ tooltipContainer
3432
+ } = props;
3433
+ const [localValue, setLocalValue] = useState(value);
3434
+ const editorRef = useShowEntryEvent(id);
3435
+ const containerRef = useRef();
3436
+ const [focus, _setFocus] = useState(undefined);
3437
+ const setFocus = (offset = 0) => {
3438
+ const hasFocus = containerRef.current.contains(document.activeElement);
3439
+
3440
+ // Keep caret position if it is already focused, otherwise focus at the end
3441
+ const position = hasFocus ? document.activeElement.selectionStart : Infinity;
3442
+ _setFocus(position + offset);
3443
+ };
3444
+ const handleInputCallback = useMemo(() => {
3445
+ return debounce(newValue => onInput(newValue.length ? newValue : undefined));
3446
+ }, [onInput, debounce]);
3447
+ const handleInput = newValue => {
3448
+ handleInputCallback(newValue);
3449
+ setLocalValue(newValue);
3450
+ };
3451
+ const handleLint = useStaticCallback(lint => {
3452
+ const errors = lint && lint.length && lint.filter(e => e.severity === 'error') || [];
3453
+ if (!errors.length) {
3454
+ onError(undefined);
3455
+ return;
3456
+ }
3457
+ const error = lint[0];
3458
+ const message = `${error.source}: ${error.message}`;
3459
+ onError(message);
3460
+ });
3461
+ useEffect(() => {
3462
+ if (typeof focus !== 'undefined') {
3463
+ editorRef.current.focus(focus);
3464
+ _setFocus(undefined);
3465
+ }
3466
+ }, [focus]);
3467
+ useEffect(() => {
3468
+ if (value === localValue) {
3469
+ return;
3470
+ }
3471
+ setLocalValue(value ? value : '');
3472
+ }, [value]);
3473
+ return jsxs("div", {
3474
+ class: "bio-properties-panel-feelers",
3475
+ children: [jsx("label", {
3476
+ id: prefixIdLabel(id),
3477
+ class: "bio-properties-panel-label",
3478
+ onClick: () => setFocus(),
3479
+ children: label
3480
+ }), jsx("div", {
3481
+ class: "bio-properties-panel-feelers-input",
3482
+ ref: containerRef,
3483
+ children: jsx(CodeEditor$1, {
3484
+ name: id,
3485
+ onInput: handleInput,
3486
+ contentAttributes: {
3487
+ 'aria-labelledby': prefixIdLabel(id)
3488
+ },
3489
+ disabled: disabled,
3490
+ onLint: handleLint,
3491
+ value: localValue,
3492
+ ref: editorRef,
3493
+ tooltipContainer: tooltipContainer
3494
+ })
3495
+ })]
3496
+ });
3497
+ }
3498
+ function isEdited$4(node) {
3499
+ return node && (!!node.value || node.classList.contains('edited'));
3500
+ }
3501
+
3502
+ // helpers /////////////////
3503
+
3504
+ function prefixIdLabel(id) {
3505
+ return `bio-properties-panel-feelers-${id}-label`;
3506
+ }
3507
+
3508
+ function List(props) {
3509
+ const {
3510
+ id,
3511
+ element,
3512
+ items = [],
3513
+ component,
3514
+ label = '<empty>',
3515
+ open: shouldOpen,
3516
+ onAdd,
3517
+ onRemove,
3518
+ autoFocusEntry,
3519
+ compareFn,
3520
+ ...restProps
3521
+ } = props;
3522
+ const [open, setOpen] = useState(!!shouldOpen);
3523
+ const hasItems = !!items.length;
3524
+ const toggleOpen = () => hasItems && setOpen(!open);
3525
+ const opening = !usePrevious(open) && open;
3526
+ const elementChanged = usePrevious(element) !== element;
3527
+ const shouldReset = opening || elementChanged;
3528
+ const sortedItems = useSortedItems(items, compareFn, shouldReset);
3529
+ const newItems = useNewItems(items, elementChanged);
3530
+ useEffect(() => {
3531
+ if (open && !hasItems) {
3532
+ setOpen(false);
3533
+ }
3534
+ }, [open, hasItems]);
3535
+
3536
+ /**
3537
+ * @param {MouseEvent} event
3538
+ */
3539
+ function addItem(event) {
3540
+ event.stopPropagation();
3541
+ onAdd();
3542
+ if (!open) {
3543
+ setOpen(true);
3544
+ }
3545
+ }
3546
+ return jsxs("div", {
3547
+ "data-entry-id": id,
3548
+ class: classnames('bio-properties-panel-entry', 'bio-properties-panel-list-entry', hasItems ? '' : 'empty', open ? 'open' : ''),
3549
+ children: [jsxs("div", {
3550
+ class: "bio-properties-panel-list-entry-header",
3551
+ onClick: toggleOpen,
3552
+ children: [jsx("div", {
3553
+ title: label,
3554
+ class: classnames('bio-properties-panel-list-entry-header-title', open && 'open'),
3555
+ children: label
3556
+ }), jsxs("div", {
3557
+ class: "bio-properties-panel-list-entry-header-buttons",
3558
+ children: [jsxs("button", {
3559
+ title: "Create new list item",
3560
+ onClick: addItem,
3561
+ class: "bio-properties-panel-add-entry",
3562
+ children: [jsx(CreateIcon, {}), !hasItems ? jsx("span", {
3563
+ class: "bio-properties-panel-add-entry-label",
3564
+ children: "Create"
3565
+ }) : null]
3566
+ }), hasItems && jsx("div", {
3567
+ title: `List contains ${items.length} item${items.length != 1 ? 's' : ''}`,
3568
+ class: "bio-properties-panel-list-badge",
3569
+ children: items.length
3570
+ }), hasItems && jsx("button", {
3571
+ title: "Toggle list item",
3572
+ class: "bio-properties-panel-arrow",
3573
+ children: jsx(ArrowIcon, {
3574
+ class: open ? 'bio-properties-panel-arrow-down' : 'bio-properties-panel-arrow-right'
3575
+ })
3576
+ })]
3577
+ })]
3578
+ }), hasItems && jsx(ItemsList, {
3579
+ ...restProps,
3580
+ autoFocusEntry: autoFocusEntry,
3581
+ component: component,
3582
+ element: element,
3583
+ id: id,
3584
+ items: sortedItems,
3585
+ newItems: newItems,
3586
+ onRemove: onRemove,
3587
+ open: open
3588
+ })]
3589
+ });
3590
+ }
3591
+ function ItemsList(props) {
3592
+ const {
3593
+ autoFocusEntry,
3594
+ component: Component,
3595
+ element,
3596
+ id,
3597
+ items,
3598
+ newItems,
3599
+ onRemove,
3600
+ open,
3601
+ ...restProps
3602
+ } = props;
3603
+ const getKey = useKeyFactory();
3604
+ const newItem = newItems[0];
3605
+ useEffect(() => {
3606
+ if (newItem && autoFocusEntry) {
3607
+ // (0) select the parent entry (containing all list items)
3608
+ const entry = query(`[data-entry-id="${id}"]`);
3609
+
3610
+ // (1) select the first input or a custom element to be focussed
3611
+ const selector = typeof autoFocusEntry === 'boolean' ? '.bio-properties-panel-input' : autoFocusEntry;
3612
+ const focusableInput = query(selector, entry);
3613
+
3614
+ // (2) set focus
3615
+ if (focusableInput) {
3616
+ if (isFunction(focusableInput.select)) {
3617
+ focusableInput.select();
3618
+ } else if (isFunction(focusableInput.focus)) {
3619
+ focusableInput.focus();
3620
+ }
3621
+ }
3622
+ }
3623
+ }, [newItem, autoFocusEntry, id]);
3624
+ return jsx("ol", {
3625
+ class: classnames('bio-properties-panel-list-entry-items', open ? 'open' : ''),
3626
+ children: items.map((item, index) => {
3627
+ const key = getKey(item);
3628
+ return jsxs("li", {
3629
+ class: "bio-properties-panel-list-entry-item",
3630
+ children: [jsx(Component, {
3631
+ ...restProps,
3632
+ element: element,
3633
+ id: id,
3634
+ index: index,
3635
+ item: item,
3636
+ open: item === newItem
3637
+ }), onRemove && jsx("button", {
3638
+ type: "button",
3639
+ title: "Delete item",
3640
+ class: "bio-properties-panel-remove-entry bio-properties-panel-remove-list-entry",
3641
+ onClick: () => onRemove && onRemove(item),
3642
+ children: jsx(DeleteIcon, {})
3643
+ })]
3644
+ }, key);
3645
+ })
3646
+ });
3647
+ }
3648
+
3649
+ /**
3650
+ * Place new items in the beginning of the list and sort the rest with provided function.
3651
+ *
3652
+ * @template Item
3653
+ * @param {Item[]} currentItems
3654
+ * @param {(a: Item, b: Item) => 0 | 1 | -1} [compareFn] function used to sort items
3655
+ * @param {boolean} [shouldReset=false] set to `true` to reset state of the hook
3656
+ * @returns {Item[]}
3657
+ */
3658
+ function useSortedItems(currentItems, compareFn, shouldReset = false) {
3659
+ const itemsRef = useRef(currentItems.slice());
3660
+
3661
+ // (1) Reset and optionally sort.
3662
+ if (shouldReset) {
3663
+ itemsRef.current = currentItems.slice();
3664
+ if (compareFn) {
3665
+ itemsRef.current.sort(compareFn);
3666
+ }
3667
+ } else {
3668
+ const items = itemsRef.current;
3669
+
3670
+ // (2) Add new item to the list.
3671
+ for (const item of currentItems) {
3672
+ if (!items.includes(item)) {
3673
+ // Unshift or push depending on whether we have a compareFn
3674
+ compareFn ? items.unshift(item) : items.push(item);
3675
+ }
3676
+ }
3677
+
3678
+ // (3) Filter out removed items.
3679
+ itemsRef.current = items.filter(item => currentItems.includes(item));
3680
+ }
3681
+ return itemsRef.current;
3682
+ }
3683
+ function useNewItems(items = [], shouldReset) {
3684
+ const previousItems = usePrevious(items.slice()) || [];
3685
+ if (shouldReset) {
3686
+ return [];
3687
+ }
3688
+ return previousItems ? items.filter(item => !previousItems.includes(item)) : [];
3689
+ }
3690
+
3691
+ function Select(props) {
3692
+ const {
3693
+ id,
3694
+ label,
3695
+ onChange,
3696
+ options = [],
3697
+ value = '',
3698
+ disabled,
3699
+ onFocus,
3700
+ onBlur,
3701
+ tooltip
3702
+ } = props;
3703
+ const ref = useShowEntryEvent(id);
3704
+ const [localValue, setLocalValue] = useState(value);
3705
+ const handleChangeCallback = ({
3706
+ target
3707
+ }) => {
3708
+ onChange(target.value);
3709
+ };
3710
+ const handleChange = e => {
3711
+ handleChangeCallback(e);
3712
+ setLocalValue(e.target.value);
3713
+ };
3714
+ useEffect(() => {
3715
+ if (value === localValue) {
3716
+ return;
3717
+ }
3718
+ setLocalValue(value);
3719
+ }, [value]);
3720
+ return jsxs("div", {
3721
+ class: "bio-properties-panel-select",
3722
+ children: [jsx("label", {
3723
+ for: prefixId$3(id),
3724
+ class: "bio-properties-panel-label",
3725
+ children: jsx(TooltipWrapper, {
3726
+ value: tooltip,
3727
+ forId: id,
3728
+ element: props.element,
3729
+ children: label
3730
+ })
3731
+ }), jsx("select", {
3732
+ ref: ref,
3733
+ id: prefixId$3(id),
3734
+ name: id,
3735
+ class: "bio-properties-panel-input",
3736
+ onInput: handleChange,
3737
+ onFocus: onFocus,
3738
+ onBlur: onBlur,
3739
+ value: localValue,
3740
+ disabled: disabled,
3741
+ children: options.map((option, idx) => {
3742
+ if (option.children) {
3743
+ return jsx("optgroup", {
3744
+ label: option.label,
3745
+ children: option.children.map((child, idx) => jsx("option", {
3746
+ value: child.value,
3747
+ disabled: child.disabled,
3748
+ children: child.label
3749
+ }, idx))
3750
+ }, idx);
3751
+ }
3752
+ return jsx("option", {
3753
+ value: option.value,
3754
+ disabled: option.disabled,
3755
+ children: option.label
3756
+ }, idx);
3757
+ })
3758
+ })]
3759
+ });
3760
+ }
3761
+
3762
+ /**
3763
+ * @param {object} props
3764
+ * @param {object} props.element
3765
+ * @param {string} props.id
3766
+ * @param {string} [props.description]
3767
+ * @param {string} props.label
3768
+ * @param {Function} props.getValue
3769
+ * @param {Function} props.setValue
3770
+ * @param {Function} props.onFocus
3771
+ * @param {Function} props.onBlur
3772
+ * @param {Function} props.getOptions
3773
+ * @param {boolean} [props.disabled]
3774
+ * @param {Function} [props.validate]
3775
+ * @param {string|import('preact').Component} props.tooltip
3776
+ */
3777
+ function SelectEntry(props) {
3778
+ const {
3779
+ element,
3780
+ id,
3781
+ description,
3782
+ label,
3783
+ getValue,
3784
+ setValue,
3785
+ getOptions,
3786
+ disabled,
3787
+ onFocus,
3788
+ onBlur,
3789
+ validate,
3790
+ tooltip
3791
+ } = props;
3792
+ const options = getOptions(element);
3793
+ const globalError = useError(id);
3794
+ const [localError, setLocalError] = useState(null);
3795
+ let value = getValue(element);
3796
+ useEffect(() => {
3797
+ if (isFunction(validate)) {
3798
+ const newValidationError = validate(value) || null;
3799
+ setLocalError(newValidationError);
3800
+ }
3801
+ }, [value]);
3802
+ const onChange = newValue => {
3803
+ let newValidationError = null;
3804
+ if (isFunction(validate)) {
3805
+ newValidationError = validate(newValue) || null;
3806
+ }
3807
+ setValue(newValue, newValidationError);
3808
+ setLocalError(newValidationError);
3809
+ };
3810
+ const error = globalError || localError;
3811
+ return jsxs("div", {
3812
+ class: classnames('bio-properties-panel-entry', error ? 'has-error' : ''),
3813
+ "data-entry-id": id,
3814
+ children: [jsx(Select, {
3815
+ id: id,
3816
+ label: label,
3817
+ value: value,
3818
+ onChange: onChange,
3819
+ onFocus: onFocus,
3820
+ onBlur: onBlur,
3821
+ options: options,
3822
+ disabled: disabled,
3823
+ tooltip: tooltip,
3824
+ element: element
3825
+ }, element), error && jsx("div", {
3826
+ class: "bio-properties-panel-error",
3827
+ children: error
3828
+ }), jsx(Description, {
3829
+ forId: id,
3830
+ element: element,
3831
+ value: description
3832
+ })]
3833
+ });
3834
+ }
3835
+ function isEdited$3(node) {
3836
+ return node && !!node.value;
3837
+ }
3838
+
3839
+ // helpers /////////////////
3840
+
3841
+ function prefixId$3(id) {
3842
+ return `bio-properties-panel-${id}`;
3843
+ }
3844
+
3845
+ function Simple(props) {
3846
+ const {
3847
+ debounce,
3848
+ disabled,
3849
+ element,
3850
+ getValue,
3851
+ id,
3852
+ onBlur,
3853
+ onFocus,
3854
+ setValue
3855
+ } = props;
3856
+ const value = getValue(element);
3857
+ const [localValue, setLocalValue] = useState(value);
3858
+ const handleInputCallback = useMemo(() => {
3859
+ return debounce(({
3860
+ target
3861
+ }) => setValue(target.value.length ? target.value : undefined));
3862
+ }, [setValue, debounce]);
3863
+ const handleInput = e => {
3864
+ handleInputCallback(e);
3865
+ setLocalValue(e.target.value);
3866
+ };
3867
+ useEffect(() => {
3868
+ if (value === localValue) {
3869
+ return;
3870
+ }
3871
+ setLocalValue(value);
3872
+ }, [value]);
3873
+ return jsx("div", {
3874
+ class: "bio-properties-panel-simple",
3875
+ children: jsx("input", {
3876
+ id: prefixId$2(id),
3877
+ type: "text",
3878
+ name: id,
3879
+ spellCheck: "false",
3880
+ autoComplete: "off",
3881
+ disabled: disabled,
3882
+ class: "bio-properties-panel-input",
3883
+ onInput: handleInput,
3884
+ "aria-label": localValue || '<empty>',
3885
+ onFocus: onFocus,
3886
+ onBlur: onBlur,
3887
+ value: localValue
3888
+ }, element)
3889
+ });
3890
+ }
3891
+ function isEdited$2(node) {
3892
+ return node && !!node.value;
3893
+ }
3894
+
3895
+ // helpers /////////////////
3896
+
3897
+ function prefixId$2(id) {
3898
+ return `bio-properties-panel-${id}`;
3899
+ }
3900
+
3901
+ function resizeToContents(element) {
3902
+ element.style.height = 'auto';
3903
+
3904
+ // a 2px pixel offset is required to prevent scrollbar from
3905
+ // appearing on OS with a full length scroll bar (Windows/Linux)
3906
+ element.style.height = `${element.scrollHeight + 2}px`;
3907
+ }
3908
+ function TextArea(props) {
3909
+ const {
3910
+ id,
3911
+ label,
3912
+ debounce,
3913
+ onInput,
3914
+ value = '',
3915
+ disabled,
3916
+ monospace,
3917
+ onFocus,
3918
+ onBlur,
3919
+ autoResize,
3920
+ rows = autoResize ? 1 : 2,
3921
+ tooltip
3922
+ } = props;
3923
+ const [localValue, setLocalValue] = useState(value);
3924
+ const ref = useShowEntryEvent(id);
3925
+ const handleInputCallback = useMemo(() => {
3926
+ return debounce(({
3927
+ target
3928
+ }) => onInput(target.value.length ? target.value : undefined));
3929
+ }, [onInput, debounce]);
3930
+ const handleInput = e => {
3931
+ handleInputCallback(e);
3932
+ autoResize && resizeToContents(e.target);
3933
+ setLocalValue(e.target.value);
3934
+ };
3935
+ useLayoutEffect(() => {
3936
+ autoResize && resizeToContents(ref.current);
3937
+ }, []);
3938
+ useEffect(() => {
3939
+ if (value === localValue) {
3940
+ return;
3941
+ }
3942
+ setLocalValue(value);
3943
+ }, [value]);
3944
+ return jsxs("div", {
3945
+ class: "bio-properties-panel-textarea",
3946
+ children: [jsx("label", {
3947
+ for: prefixId$1(id),
3948
+ class: "bio-properties-panel-label",
3949
+ children: jsx(TooltipWrapper, {
3950
+ value: tooltip,
3951
+ forId: id,
3952
+ element: props.element,
3953
+ children: label
3954
+ })
3955
+ }), jsx("textarea", {
3956
+ ref: ref,
3957
+ id: prefixId$1(id),
3958
+ name: id,
3959
+ spellCheck: "false",
3960
+ class: classnames('bio-properties-panel-input', monospace ? 'bio-properties-panel-input-monospace' : '', autoResize ? 'auto-resize' : ''),
3961
+ onInput: handleInput,
3962
+ onFocus: onFocus,
3963
+ onBlur: onBlur,
3964
+ rows: rows,
3965
+ value: localValue,
3966
+ disabled: disabled,
3967
+ "data-gramm": "false"
3968
+ })]
3969
+ });
3970
+ }
3971
+
3972
+ /**
3973
+ * @param {object} props
3974
+ * @param {object} props.element
3975
+ * @param {string} props.id
3976
+ * @param {string} props.description
3977
+ * @param {boolean} props.debounce
3978
+ * @param {string} props.label
3979
+ * @param {Function} props.getValue
3980
+ * @param {Function} props.setValue
3981
+ * @param {Function} props.onFocus
3982
+ * @param {Function} props.onBlur
3983
+ * @param {number} props.rows
3984
+ * @param {boolean} props.monospace
3985
+ * @param {Function} [props.validate]
3986
+ * @param {boolean} [props.disabled]
3987
+ */
3988
+ function TextAreaEntry(props) {
3989
+ const {
3990
+ element,
3991
+ id,
3992
+ description,
3993
+ debounce,
3994
+ label,
3995
+ getValue,
3996
+ setValue,
3997
+ rows,
3998
+ monospace,
3999
+ disabled,
4000
+ validate,
4001
+ onFocus,
4002
+ onBlur,
4003
+ autoResize,
4004
+ tooltip
4005
+ } = props;
4006
+ const globalError = useError(id);
4007
+ const [localError, setLocalError] = useState(null);
4008
+ let value = getValue(element);
4009
+ useEffect(() => {
4010
+ if (isFunction(validate)) {
4011
+ const newValidationError = validate(value) || null;
4012
+ setLocalError(newValidationError);
4013
+ }
4014
+ }, [value]);
4015
+ const onInput = newValue => {
4016
+ let newValidationError = null;
4017
+ if (isFunction(validate)) {
4018
+ newValidationError = validate(newValue) || null;
4019
+ }
4020
+ setValue(newValue, newValidationError);
4021
+ setLocalError(newValidationError);
4022
+ };
4023
+ const error = globalError || localError;
4024
+ return jsxs("div", {
4025
+ class: classnames('bio-properties-panel-entry', error ? 'has-error' : ''),
4026
+ "data-entry-id": id,
4027
+ children: [jsx(TextArea, {
4028
+ id: id,
4029
+ label: label,
4030
+ value: value,
4031
+ onInput: onInput,
4032
+ onFocus: onFocus,
4033
+ onBlur: onBlur,
4034
+ rows: rows,
4035
+ debounce: debounce,
4036
+ monospace: monospace,
4037
+ disabled: disabled,
4038
+ autoResize: autoResize,
4039
+ tooltip: tooltip,
4040
+ element: element
4041
+ }, element), error && jsx("div", {
4042
+ class: "bio-properties-panel-error",
4043
+ children: error
4044
+ }), jsx(Description, {
4045
+ forId: id,
4046
+ element: element,
4047
+ value: description
4048
+ })]
4049
+ });
4050
+ }
4051
+ function isEdited$1(node) {
4052
+ return node && !!node.value;
4053
+ }
4054
+
4055
+ // helpers /////////////////
4056
+
4057
+ function prefixId$1(id) {
4058
+ return `bio-properties-panel-${id}`;
4059
+ }
4060
+
4061
+ function Textfield(props) {
4062
+ const {
4063
+ debounce,
4064
+ disabled = false,
4065
+ id,
4066
+ label,
4067
+ onInput,
4068
+ onFocus,
4069
+ onBlur,
4070
+ value = '',
4071
+ tooltip
4072
+ } = props;
4073
+ const [localValue, setLocalValue] = useState(value || '');
4074
+ const ref = useShowEntryEvent(id);
4075
+ const handleInputCallback = useMemo(() => {
4076
+ return debounce(({
4077
+ target
4078
+ }) => onInput(target.value.length ? target.value : undefined));
4079
+ }, [onInput, debounce]);
4080
+ const handleInput = e => {
4081
+ handleInputCallback(e);
4082
+ setLocalValue(e.target.value);
4083
+ };
4084
+ useEffect(() => {
4085
+ if (value === localValue) {
4086
+ return;
4087
+ }
4088
+ setLocalValue(value);
4089
+ }, [value]);
4090
+ return jsxs("div", {
4091
+ class: "bio-properties-panel-textfield",
4092
+ children: [jsx("label", {
4093
+ for: prefixId(id),
4094
+ class: "bio-properties-panel-label",
4095
+ children: jsx(TooltipWrapper, {
4096
+ value: tooltip,
4097
+ forId: id,
4098
+ element: props.element,
4099
+ children: label
4100
+ })
4101
+ }), jsx("input", {
4102
+ ref: ref,
4103
+ id: prefixId(id),
4104
+ type: "text",
4105
+ name: id,
4106
+ spellCheck: "false",
4107
+ autoComplete: "off",
4108
+ disabled: disabled,
4109
+ class: "bio-properties-panel-input",
4110
+ onInput: handleInput,
4111
+ onFocus: onFocus,
4112
+ onBlur: onBlur,
4113
+ value: localValue
4114
+ })]
4115
+ });
4116
+ }
4117
+
4118
+ /**
4119
+ * @param {Object} props
4120
+ * @param {Object} props.element
4121
+ * @param {String} props.id
4122
+ * @param {String} props.description
4123
+ * @param {Boolean} props.debounce
4124
+ * @param {Boolean} props.disabled
4125
+ * @param {String} props.label
4126
+ * @param {Function} props.getValue
4127
+ * @param {Function} props.setValue
4128
+ * @param {Function} props.onFocus
4129
+ * @param {Function} props.onBlur
4130
+ * @param {string|import('preact').Component} props.tooltip
4131
+ * @param {Function} props.validate
4132
+ */
4133
+ function TextfieldEntry(props) {
4134
+ const {
4135
+ element,
4136
+ id,
4137
+ description,
4138
+ debounce,
4139
+ disabled,
4140
+ label,
4141
+ getValue,
4142
+ setValue,
4143
+ validate,
4144
+ onFocus,
4145
+ onBlur,
4146
+ tooltip
4147
+ } = props;
4148
+ const globalError = useError(id);
4149
+ const [localError, setLocalError] = useState(null);
4150
+ let value = getValue(element);
4151
+ useEffect(() => {
4152
+ if (isFunction(validate)) {
4153
+ const newValidationError = validate(value) || null;
4154
+ setLocalError(newValidationError);
4155
+ }
4156
+ }, [value]);
4157
+ const onInput = newValue => {
4158
+ let newValidationError = null;
4159
+ if (isFunction(validate)) {
4160
+ newValidationError = validate(newValue) || null;
4161
+ }
4162
+ setValue(newValue, newValidationError);
4163
+ setLocalError(newValidationError);
4164
+ };
4165
+ const error = globalError || localError;
4166
+ return jsxs("div", {
4167
+ class: classnames('bio-properties-panel-entry', error ? 'has-error' : ''),
4168
+ "data-entry-id": id,
4169
+ children: [jsx(Textfield, {
4170
+ debounce: debounce,
4171
+ disabled: disabled,
4172
+ id: id,
4173
+ label: label,
4174
+ onInput: onInput,
4175
+ onFocus: onFocus,
4176
+ onBlur: onBlur,
4177
+ value: value,
4178
+ tooltip: tooltip,
4179
+ element: element
4180
+ }, element), error && jsx("div", {
4181
+ class: "bio-properties-panel-error",
4182
+ children: error
4183
+ }), jsx(Description, {
4184
+ forId: id,
4185
+ element: element,
4186
+ value: description
4187
+ })]
4188
+ });
4189
+ }
4190
+ function isEdited(node) {
4191
+ return node && !!node.value;
4192
+ }
4193
+
4194
+ // helpers /////////////////
4195
+
4196
+ function prefixId(id) {
4197
+ return `bio-properties-panel-${id}`;
4198
+ }
4199
+
4200
+ const DEFAULT_DEBOUNCE_TIME = 300;
4201
+ function debounceInput(debounceDelay) {
4202
+ return function _debounceInput(fn) {
4203
+ if (debounceDelay !== false) {
4204
+ var debounceTime = isNumber(debounceDelay) ? debounceDelay : DEFAULT_DEBOUNCE_TIME;
4205
+ return debounce(fn, debounceTime);
4206
+ } else {
4207
+ return fn;
4208
+ }
4209
+ };
4210
+ }
4211
+ debounceInput.$inject = ['config.debounceInput'];
4212
+
4213
+ var index$1 = {
4214
+ debounceInput: ['factory', debounceInput]
4215
+ };
4216
+
4217
+ class FeelPopupModule {
4218
+ constructor(eventBus) {
4219
+ this._eventBus = eventBus;
4220
+ }
4221
+
4222
+ /**
4223
+ * Check if the FEEL popup is open.
4224
+ * @return {Boolean}
4225
+ */
4226
+ isOpen() {
4227
+ return this._eventBus.fire('feelPopup._isOpen');
4228
+ }
4229
+
4230
+ /**
4231
+ * Open the FEEL popup.
4232
+ *
4233
+ * @param {String} entryId
4234
+ * @param {Object} popupConfig
4235
+ * @param {HTMLElement} sourceElement
4236
+ */
4237
+ open(entryId, popupConfig, sourceElement) {
4238
+ return this._eventBus.fire('feelPopup._open', {
4239
+ entryId,
4240
+ popupConfig,
4241
+ sourceElement
4242
+ });
4243
+ }
4244
+
4245
+ /**
4246
+ * Close the FEEL popup.
4247
+ */
4248
+ close() {
4249
+ return this._eventBus.fire('feelPopup._close');
4250
+ }
4251
+ }
4252
+ FeelPopupModule.$inject = ['eventBus'];
4253
+
4254
+ var index = {
4255
+ feelPopup: ['type', FeelPopupModule]
4256
+ };
4257
+
4258
+ export { ArrowIcon, CheckboxEntry, CollapsibleEntry, CreateIcon, index$1 as DebounceInputModule, DeleteIcon, DescriptionContext, Description as DescriptionEntry, DragIcon, DropdownButton, ErrorsContext, EventContext, ExternalLinkIcon, FeelCheckboxEntry, FeelEntry, FeelIcon$1 as FeelIcon, FeelNumberEntry, index as FeelPopupModule, FeelTemplatingEntry, FeelTextAreaEntry, FeelToggleSwitchEntry, Group, Header, HeaderButton, LayoutContext, List as ListEntry, ListGroup, ListItem, NumberFieldEntry, Placeholder, Popup, PropertiesPanel, LayoutContext as PropertiesPanelContext, SelectEntry, Simple as SimpleEntry, TemplatingEntry, TextAreaEntry, TextfieldEntry as TextFieldEntry, ToggleSwitchEntry, TooltipContext, isEdited$5 as isCheckboxEntryEdited, isEdited$6 as isFeelEntryEdited, isEdited$7 as isNumberFieldEntryEdited, isEdited$3 as isSelectEntryEdited, isEdited$2 as isSimpleEntryEdited, isEdited$4 as isTemplatingEntryEdited, isEdited$1 as isTextAreaEntryEdited, isEdited as isTextFieldEntryEdited, isEdited$8 as isToggleSwitchEntryEdited, useDescriptionContext, useError, useErrors, useEvent, useKeyFactory, useLayoutState, usePrevious, useShowEntryEvent, useStaticCallback, useStickyIntersectionObserver, useTooltipContext };
4259
+ //# sourceMappingURL=index.esm.js.map