@bpmn-io/properties-panel 0.10.2 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.esm.js CHANGED
@@ -1,32 +1,131 @@
1
- import { useRef, useEffect, useMemo, useContext, useState } from '../preact/hooks';
2
- import { isFunction, get, assign, set, sortBy, find, isNumber, debounce } from 'min-dash';
1
+ import { useContext, useEffect, useRef, useMemo, useState, useCallback } from '../preact/hooks';
2
+ import { isArray, isFunction, isNumber, bind, assign, get, set, sortBy, find, debounce } from 'min-dash';
3
3
  import classnames from 'classnames';
4
- import { jsxs, jsx } from '../preact/jsx-runtime';
5
- import { query } from 'min-dom';
6
- import { createContext } from '../preact';
7
4
  import '../preact/compat';
5
+ import { jsx, jsxs } from '../preact/jsx-runtime';
6
+ import { query } from 'min-dom';
7
+ import { createContext, createElement } from '../preact';
8
8
 
9
- /**
10
- * @typedef { { getElementLabel: Function, getTypeLabel: Function, getElementIcon: Function } } HeaderProvider
11
- */
9
+ var ArrowIcon = function ArrowIcon(props) {
10
+ return jsx("svg", { ...props,
11
+ children: jsx("path", {
12
+ fillRule: "evenodd",
13
+ 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"
14
+ })
15
+ });
16
+ };
17
+
18
+ ArrowIcon.defaultProps = {
19
+ xmlns: "http://www.w3.org/2000/svg",
20
+ width: "16",
21
+ height: "16"
22
+ };
23
+
24
+ var CreateIcon = function CreateIcon(props) {
25
+ return jsx("svg", { ...props,
26
+ children: jsx("path", {
27
+ fillRule: "evenodd",
28
+ 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"
29
+ })
30
+ });
31
+ };
32
+
33
+ CreateIcon.defaultProps = {
34
+ xmlns: "http://www.w3.org/2000/svg",
35
+ width: "16",
36
+ height: "16"
37
+ };
38
+
39
+ var DeleteIcon = function DeleteIcon(props) {
40
+ return jsx("svg", { ...props,
41
+ children: jsx("path", {
42
+ fillRule: "evenodd",
43
+ 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"
44
+ })
45
+ });
46
+ };
47
+
48
+ DeleteIcon.defaultProps = {
49
+ xmlns: "http://www.w3.org/2000/svg",
50
+ width: "16",
51
+ height: "16"
52
+ };
53
+
54
+ var ExternalLinkIcon = function ExternalLinkIcon(props) {
55
+ return jsx("svg", { ...props,
56
+ children: jsx("path", {
57
+ fillRule: "evenodd",
58
+ clipRule: "evenodd",
59
+ 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",
60
+ fill: "#818798"
61
+ })
62
+ });
63
+ };
64
+
65
+ ExternalLinkIcon.defaultProps = {
66
+ width: "16",
67
+ height: "16",
68
+ fill: "none",
69
+ xmlns: "http://www.w3.org/2000/svg"
70
+ };
71
+
72
+ var FeelRequiredIcon = function FeelRequiredIcon(props) {
73
+ return jsxs("svg", { ...props,
74
+ children: [jsx("path", {
75
+ d: "M5.8 7.06V5.95h4.307v1.11H5.8zm0 3.071v-1.11h4.307v1.11H5.8z",
76
+ fill: "#505562"
77
+ }), jsx("path", {
78
+ fillRule: "evenodd",
79
+ clipRule: "evenodd",
80
+ d: "M8 3.268A4.732 4.732 0 1 0 12.732 8H14a6 6 0 1 1-6-6v1.268z",
81
+ fill: "#505562"
82
+ }), jsx("path", {
83
+ d: "m11.28 6.072-.832-.56 1.016-1.224L10 3.848l.312-.912 1.392.584L11.632 2h1.032l-.072 1.52 1.392-.584.312.912-1.464.44 1.008 1.224-.832.552-.864-1.296-.864 1.304z",
84
+ fill: "#505562"
85
+ })]
86
+ });
87
+ };
88
+
89
+ FeelRequiredIcon.defaultProps = {
90
+ viewBox: "0 0 16 16",
91
+ fill: "none",
92
+ xmlns: "http://www.w3.org/2000/svg"
93
+ };
94
+
95
+ var FeelOptionalIcon = function FeelOptionalIcon(props) {
96
+ return jsxs("svg", { ...props,
97
+ children: [jsx("path", {
98
+ d: "M5.845 7.04V5.93h4.307v1.11H5.845zm0 3.07V9h4.307v1.11H5.845z",
99
+ fill: "#505562"
100
+ }), jsx("path", {
101
+ fillRule: "evenodd",
102
+ clipRule: "evenodd",
103
+ d: "M3.286 8a4.714 4.714 0 1 0 9.428 0 4.714 4.714 0 0 0-9.428 0zM8 2a6 6 0 1 0 0 12A6 6 0 0 0 8 2z",
104
+ fill: "#505562"
105
+ })]
106
+ });
107
+ };
108
+
109
+ FeelOptionalIcon.defaultProps = {
110
+ viewBox: "0 0 16 16",
111
+ fill: "none",
112
+ xmlns: "http://www.w3.org/2000/svg"
113
+ };
12
114
 
13
- /**
14
- * @param {Object} props
15
- * @param {Object} props.element,
16
- * @param {HeaderProvider} props.headerProvider
17
- */
18
115
  function Header(props) {
19
116
  const {
20
117
  element,
21
118
  headerProvider
22
119
  } = props;
23
120
  const {
121
+ getElementIcon,
122
+ getDocumentationRef,
24
123
  getElementLabel,
25
- getTypeLabel,
26
- getElementIcon
124
+ getTypeLabel
27
125
  } = headerProvider;
28
126
  const label = getElementLabel(element);
29
127
  const type = getTypeLabel(element);
128
+ const documentationRef = getDocumentationRef && getDocumentationRef(element);
30
129
  const ElementIcon = getElementIcon(element);
31
130
  return jsxs("div", {
32
131
  class: "bio-properties-panel-header",
@@ -43,28 +142,624 @@ function Header(props) {
43
142
  title: type,
44
143
  class: "bio-properties-panel-header-type",
45
144
  children: type
46
- }), getElementLabel(element) ? jsx("div", {
145
+ }), label ? jsx("div", {
47
146
  title: label,
48
147
  class: "bio-properties-panel-header-label",
49
148
  children: label
50
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
+ target: "_blank",
157
+ children: jsx(ExternalLinkIcon, {})
158
+ }) : null
51
159
  })]
52
160
  });
53
161
  }
54
162
 
163
+ const DescriptionContext = createContext({
164
+ description: {},
165
+ getDescriptionForId: () => {}
166
+ });
167
+
168
+ var FN_REF = '__fn';
169
+ var DEFAULT_PRIORITY$1 = 1000;
170
+ var slice = Array.prototype.slice;
171
+ /**
172
+ * A general purpose event bus.
173
+ *
174
+ * This component is used to communicate across a diagram instance.
175
+ * Other parts of a diagram can use it to listen to and broadcast events.
176
+ *
177
+ *
178
+ * ## Registering for Events
179
+ *
180
+ * The event bus provides the {@link EventBus#on} and {@link EventBus#once}
181
+ * methods to register for events. {@link EventBus#off} can be used to
182
+ * remove event registrations. Listeners receive an instance of {@link Event}
183
+ * as the first argument. It allows them to hook into the event execution.
184
+ *
185
+ * ```javascript
186
+ *
187
+ * // listen for event
188
+ * eventBus.on('foo', function(event) {
189
+ *
190
+ * // access event type
191
+ * event.type; // 'foo'
192
+ *
193
+ * // stop propagation to other listeners
194
+ * event.stopPropagation();
195
+ *
196
+ * // prevent event default
197
+ * event.preventDefault();
198
+ * });
199
+ *
200
+ * // listen for event with custom payload
201
+ * eventBus.on('bar', function(event, payload) {
202
+ * console.log(payload);
203
+ * });
204
+ *
205
+ * // listen for event returning value
206
+ * eventBus.on('foobar', function(event) {
207
+ *
208
+ * // stop event propagation + prevent default
209
+ * return false;
210
+ *
211
+ * // stop event propagation + return custom result
212
+ * return {
213
+ * complex: 'listening result'
214
+ * };
215
+ * });
216
+ *
217
+ *
218
+ * // listen with custom priority (default=1000, higher is better)
219
+ * eventBus.on('priorityfoo', 1500, function(event) {
220
+ * console.log('invoked first!');
221
+ * });
222
+ *
223
+ *
224
+ * // listen for event and pass the context (`this`)
225
+ * eventBus.on('foobar', function(event) {
226
+ * this.foo();
227
+ * }, this);
228
+ * ```
229
+ *
230
+ *
231
+ * ## Emitting Events
232
+ *
233
+ * Events can be emitted via the event bus using {@link EventBus#fire}.
234
+ *
235
+ * ```javascript
236
+ *
237
+ * // false indicates that the default action
238
+ * // was prevented by listeners
239
+ * if (eventBus.fire('foo') === false) {
240
+ * console.log('default has been prevented!');
241
+ * };
242
+ *
243
+ *
244
+ * // custom args + return value listener
245
+ * eventBus.on('sum', function(event, a, b) {
246
+ * return a + b;
247
+ * });
248
+ *
249
+ * // you can pass custom arguments + retrieve result values.
250
+ * var sum = eventBus.fire('sum', 1, 2);
251
+ * console.log(sum); // 3
252
+ * ```
253
+ */
254
+
255
+ function EventBus() {
256
+ this._listeners = {}; // cleanup on destroy on lowest priority to allow
257
+ // message passing until the bitter end
258
+
259
+ this.on('diagram.destroy', 1, this._destroy, this);
260
+ }
261
+ /**
262
+ * Register an event listener for events with the given name.
263
+ *
264
+ * The callback will be invoked with `event, ...additionalArguments`
265
+ * that have been passed to {@link EventBus#fire}.
266
+ *
267
+ * Returning false from a listener will prevent the events default action
268
+ * (if any is specified). To stop an event from being processed further in
269
+ * other listeners execute {@link Event#stopPropagation}.
270
+ *
271
+ * Returning anything but `undefined` from a listener will stop the listener propagation.
272
+ *
273
+ * @param {string|Array<string>} events
274
+ * @param {number} [priority=1000] the priority in which this listener is called, larger is higher
275
+ * @param {Function} callback
276
+ * @param {Object} [that] Pass context (`this`) to the callback
277
+ */
278
+
279
+ EventBus.prototype.on = function (events, priority, callback, that) {
280
+ events = isArray(events) ? events : [events];
281
+
282
+ if (isFunction(priority)) {
283
+ that = callback;
284
+ callback = priority;
285
+ priority = DEFAULT_PRIORITY$1;
286
+ }
287
+
288
+ if (!isNumber(priority)) {
289
+ throw new Error('priority must be a number');
290
+ }
291
+
292
+ var actualCallback = callback;
293
+
294
+ if (that) {
295
+ actualCallback = bind(callback, that); // make sure we remember and are able to remove
296
+ // bound callbacks via {@link #off} using the original
297
+ // callback
298
+
299
+ actualCallback[FN_REF] = callback[FN_REF] || callback;
300
+ }
301
+
302
+ var self = this;
303
+ events.forEach(function (e) {
304
+ self._addListener(e, {
305
+ priority: priority,
306
+ callback: actualCallback,
307
+ next: null
308
+ });
309
+ });
310
+ };
311
+ /**
312
+ * Register an event listener that is executed only once.
313
+ *
314
+ * @param {string} event the event name to register for
315
+ * @param {number} [priority=1000] the priority in which this listener is called, larger is higher
316
+ * @param {Function} callback the callback to execute
317
+ * @param {Object} [that] Pass context (`this`) to the callback
318
+ */
319
+
320
+
321
+ EventBus.prototype.once = function (event, priority, callback, that) {
322
+ var self = this;
323
+
324
+ if (isFunction(priority)) {
325
+ that = callback;
326
+ callback = priority;
327
+ priority = DEFAULT_PRIORITY$1;
328
+ }
329
+
330
+ if (!isNumber(priority)) {
331
+ throw new Error('priority must be a number');
332
+ }
333
+
334
+ function wrappedCallback() {
335
+ wrappedCallback.__isTomb = true;
336
+ var result = callback.apply(that, arguments);
337
+ self.off(event, wrappedCallback);
338
+ return result;
339
+ } // make sure we remember and are able to remove
340
+ // bound callbacks via {@link #off} using the original
341
+ // callback
342
+
343
+
344
+ wrappedCallback[FN_REF] = callback;
345
+ this.on(event, priority, wrappedCallback);
346
+ };
347
+ /**
348
+ * Removes event listeners by event and callback.
349
+ *
350
+ * If no callback is given, all listeners for a given event name are being removed.
351
+ *
352
+ * @param {string|Array<string>} events
353
+ * @param {Function} [callback]
354
+ */
355
+
356
+
357
+ EventBus.prototype.off = function (events, callback) {
358
+ events = isArray(events) ? events : [events];
359
+ var self = this;
360
+ events.forEach(function (event) {
361
+ self._removeListener(event, callback);
362
+ });
363
+ };
364
+ /**
365
+ * Create an EventBus event.
366
+ *
367
+ * @param {Object} data
368
+ *
369
+ * @return {Object} event, recognized by the eventBus
370
+ */
371
+
372
+
373
+ EventBus.prototype.createEvent = function (data) {
374
+ var event = new InternalEvent();
375
+ event.init(data);
376
+ return event;
377
+ };
378
+ /**
379
+ * Fires a named event.
380
+ *
381
+ * @example
382
+ *
383
+ * // fire event by name
384
+ * events.fire('foo');
385
+ *
386
+ * // fire event object with nested type
387
+ * var event = { type: 'foo' };
388
+ * events.fire(event);
389
+ *
390
+ * // fire event with explicit type
391
+ * var event = { x: 10, y: 20 };
392
+ * events.fire('element.moved', event);
393
+ *
394
+ * // pass additional arguments to the event
395
+ * events.on('foo', function(event, bar) {
396
+ * alert(bar);
397
+ * });
398
+ *
399
+ * events.fire({ type: 'foo' }, 'I am bar!');
400
+ *
401
+ * @param {string} [name] the optional event name
402
+ * @param {Object} [event] the event object
403
+ * @param {...Object} additional arguments to be passed to the callback functions
404
+ *
405
+ * @return {boolean} the events return value, if specified or false if the
406
+ * default action was prevented by listeners
407
+ */
408
+
409
+
410
+ EventBus.prototype.fire = function (type, data) {
411
+ var event, firstListener, returnValue, args;
412
+ args = slice.call(arguments);
413
+
414
+ if (typeof type === 'object') {
415
+ data = type;
416
+ type = data.type;
417
+ }
418
+
419
+ if (!type) {
420
+ throw new Error('no event type specified');
421
+ }
422
+
423
+ firstListener = this._listeners[type];
424
+
425
+ if (!firstListener) {
426
+ return;
427
+ } // we make sure we fire instances of our home made
428
+ // events here. We wrap them only once, though
429
+
430
+
431
+ if (data instanceof InternalEvent) {
432
+ // we are fine, we alread have an event
433
+ event = data;
434
+ } else {
435
+ event = this.createEvent(data);
436
+ } // ensure we pass the event as the first parameter
437
+
438
+
439
+ args[0] = event; // original event type (in case we delegate)
440
+
441
+ var originalType = event.type; // update event type before delegation
442
+
443
+ if (type !== originalType) {
444
+ event.type = type;
445
+ }
446
+
447
+ try {
448
+ returnValue = this._invokeListeners(event, args, firstListener);
449
+ } finally {
450
+ // reset event type after delegation
451
+ if (type !== originalType) {
452
+ event.type = originalType;
453
+ }
454
+ } // set the return value to false if the event default
455
+ // got prevented and no other return value exists
456
+
457
+
458
+ if (returnValue === undefined && event.defaultPrevented) {
459
+ returnValue = false;
460
+ }
461
+
462
+ return returnValue;
463
+ };
464
+
465
+ EventBus.prototype.handleError = function (error) {
466
+ return this.fire('error', {
467
+ error: error
468
+ }) === false;
469
+ };
470
+
471
+ EventBus.prototype._destroy = function () {
472
+ this._listeners = {};
473
+ };
474
+
475
+ EventBus.prototype._invokeListeners = function (event, args, listener) {
476
+ var returnValue;
477
+
478
+ while (listener) {
479
+ // handle stopped propagation
480
+ if (event.cancelBubble) {
481
+ break;
482
+ }
483
+
484
+ returnValue = this._invokeListener(event, args, listener);
485
+ listener = listener.next;
486
+ }
487
+
488
+ return returnValue;
489
+ };
490
+
491
+ EventBus.prototype._invokeListener = function (event, args, listener) {
492
+ var returnValue;
493
+
494
+ if (listener.callback.__isTomb) {
495
+ return returnValue;
496
+ }
497
+
498
+ try {
499
+ // returning false prevents the default action
500
+ returnValue = invokeFunction(listener.callback, args); // stop propagation on return value
501
+
502
+ if (returnValue !== undefined) {
503
+ event.returnValue = returnValue;
504
+ event.stopPropagation();
505
+ } // prevent default on return false
506
+
507
+
508
+ if (returnValue === false) {
509
+ event.preventDefault();
510
+ }
511
+ } catch (error) {
512
+ if (!this.handleError(error)) {
513
+ console.error('unhandled error in event listener', error);
514
+ throw error;
515
+ }
516
+ }
517
+
518
+ return returnValue;
519
+ };
520
+ /*
521
+ * Add new listener with a certain priority to the list
522
+ * of listeners (for the given event).
523
+ *
524
+ * The semantics of listener registration / listener execution are
525
+ * first register, first serve: New listeners will always be inserted
526
+ * after existing listeners with the same priority.
527
+ *
528
+ * Example: Inserting two listeners with priority 1000 and 1300
529
+ *
530
+ * * before: [ 1500, 1500, 1000, 1000 ]
531
+ * * after: [ 1500, 1500, (new=1300), 1000, 1000, (new=1000) ]
532
+ *
533
+ * @param {string} event
534
+ * @param {Object} listener { priority, callback }
535
+ */
536
+
537
+
538
+ EventBus.prototype._addListener = function (event, newListener) {
539
+ var listener = this._getListeners(event),
540
+ previousListener; // no prior listeners
541
+
542
+
543
+ if (!listener) {
544
+ this._setListeners(event, newListener);
545
+
546
+ return;
547
+ } // ensure we order listeners by priority from
548
+ // 0 (high) to n > 0 (low)
549
+
550
+
551
+ while (listener) {
552
+ if (listener.priority < newListener.priority) {
553
+ newListener.next = listener;
554
+
555
+ if (previousListener) {
556
+ previousListener.next = newListener;
557
+ } else {
558
+ this._setListeners(event, newListener);
559
+ }
560
+
561
+ return;
562
+ }
563
+
564
+ previousListener = listener;
565
+ listener = listener.next;
566
+ } // add new listener to back
567
+
568
+
569
+ previousListener.next = newListener;
570
+ };
571
+
572
+ EventBus.prototype._getListeners = function (name) {
573
+ return this._listeners[name];
574
+ };
575
+
576
+ EventBus.prototype._setListeners = function (name, listener) {
577
+ this._listeners[name] = listener;
578
+ };
579
+
580
+ EventBus.prototype._removeListener = function (event, callback) {
581
+ var listener = this._getListeners(event),
582
+ nextListener,
583
+ previousListener,
584
+ listenerCallback;
585
+
586
+ if (!callback) {
587
+ // clear listeners
588
+ this._setListeners(event, null);
589
+
590
+ return;
591
+ }
592
+
593
+ while (listener) {
594
+ nextListener = listener.next;
595
+ listenerCallback = listener.callback;
596
+
597
+ if (listenerCallback === callback || listenerCallback[FN_REF] === callback) {
598
+ if (previousListener) {
599
+ previousListener.next = nextListener;
600
+ } else {
601
+ // new first listener
602
+ this._setListeners(event, nextListener);
603
+ }
604
+ }
605
+
606
+ previousListener = listener;
607
+ listener = nextListener;
608
+ }
609
+ };
610
+ /**
611
+ * A event that is emitted via the event bus.
612
+ */
613
+
614
+
615
+ function InternalEvent() {}
616
+
617
+ InternalEvent.prototype.stopPropagation = function () {
618
+ this.cancelBubble = true;
619
+ };
620
+
621
+ InternalEvent.prototype.preventDefault = function () {
622
+ this.defaultPrevented = true;
623
+ };
624
+
625
+ InternalEvent.prototype.init = function (data) {
626
+ assign(this, data || {});
627
+ };
628
+ /**
629
+ * Invoke function. Be fast...
630
+ *
631
+ * @param {Function} fn
632
+ * @param {Array<Object>} args
633
+ *
634
+ * @return {Any}
635
+ */
636
+
637
+
638
+ function invokeFunction(fn, args) {
639
+ return fn.apply(null, args);
640
+ }
641
+
642
+ /**
643
+ * @typedef {Function} <propertiesPanel.showEntry> callback
644
+ *
645
+ * @example
646
+ *
647
+ * useEvent('propertiesPanel.showEntry', ({ focus = false, ...rest }) => {
648
+ * // ...
649
+ * });
650
+ *
651
+ * @param {Object} context
652
+ * @param {boolean} [context.focus]
653
+ *
654
+ * @returns void
655
+ */
656
+ const eventBus = new EventBus();
657
+ const EventContext = createContext({
658
+ eventBus
659
+ });
660
+
661
+ const LayoutContext = createContext({
662
+ layout: {},
663
+ setLayout: () => {},
664
+ getLayoutForKey: () => {},
665
+ setLayoutForKey: () => {}
666
+ });
667
+
668
+ /**
669
+ * Accesses the global DescriptionContext and returns a description for a given id and element.
670
+ *
671
+ * @example
672
+ * ```jsx
673
+ * function TextField(props) {
674
+ * const description = useDescriptionContext('input1', element);
675
+ * }
676
+ * ```
677
+ *
678
+ * @param {string} id
679
+ * @param {djs.model.Base} element
680
+ *
681
+ * @returns {string}
682
+ */
683
+
684
+ function useDescriptionContext(id, element) {
685
+ const {
686
+ getDescriptionForId
687
+ } = useContext(DescriptionContext);
688
+ return getDescriptionForId(id, element);
689
+ }
690
+
691
+ const DEFAULT_PRIORITY = 1000;
55
692
  /**
56
- * @pinussilvestrus: we need to introduce our own hook to persist the previous
57
- * state on updates.
693
+ * Subscribe to an event.
58
694
  *
59
- * cf. https://reactjs.org/docs/hooks-faq.html#how-to-get-the-previous-props-or-state
695
+ * @param {string} event
696
+ * @param {Function} callback
697
+ * @param {number} [priority]
698
+ *
699
+ * @returns {import('preact').Ref}
60
700
  */
61
701
 
62
- function usePrevious(value) {
63
- const ref = useRef();
702
+ function useEvent(event, callback, priority = DEFAULT_PRIORITY) {
703
+ const {
704
+ eventBus
705
+ } = useContext(EventContext);
64
706
  useEffect(() => {
65
- ref.current = value;
707
+ eventBus.on(event, priority, callback);
708
+ return () => eventBus.off(event, callback);
709
+ }, [event, eventBus, callback, priority]);
710
+ }
711
+
712
+ const HIGH_PRIORITY = 10000;
713
+ /**
714
+ * Buffer events and re-fire during passive effect phase.
715
+ *
716
+ * @param {string[]} bufferedEvents
717
+ * @param {Object} [eventBus]
718
+ */
719
+
720
+ function useEventBuffer(bufferedEvents, eventBus) {
721
+ const buffer = useRef([]),
722
+ buffering = useRef(true);
723
+
724
+ const createCallback = event => data => {
725
+ if (buffering.current === true) {
726
+ buffer.current.unshift([event, data]);
727
+ }
728
+ }; // (1) buffer events
729
+
730
+
731
+ useEffect(() => {
732
+ if (!eventBus) {
733
+ return;
734
+ }
735
+
736
+ const listeners = bufferedEvents.map(event => {
737
+ return [event, createCallback(event)];
738
+ });
739
+ listeners.forEach(([event, callback]) => {
740
+ eventBus.on(event, HIGH_PRIORITY, callback);
741
+ });
742
+ return () => {
743
+ listeners.forEach(([event, callback]) => {
744
+ eventBus.off(event, callback);
745
+ });
746
+ };
747
+ }, [bufferedEvents, eventBus]); // (2) re-fire events
748
+
749
+ useEffect(() => {
750
+ if (!eventBus) {
751
+ return;
752
+ }
753
+
754
+ buffering.current = false;
755
+
756
+ while (buffer.current.length) {
757
+ const [event, data] = buffer.current.pop();
758
+ eventBus.fire(event, data);
759
+ }
760
+
761
+ buffering.current = true;
66
762
  });
67
- return ref.current;
68
763
  }
69
764
 
70
765
  const KEY_LENGTH = 6;
@@ -105,18 +800,6 @@ function useKeyFactory(dependencies = []) {
105
800
  return getKey;
106
801
  }
107
802
 
108
- const DescriptionContext = createContext({
109
- description: {},
110
- getDescriptionForId: () => {}
111
- });
112
-
113
- const LayoutContext = createContext({
114
- layout: {},
115
- setLayout: () => {},
116
- getLayoutForKey: () => {},
117
- setLayoutForKey: () => {}
118
- });
119
-
120
803
  /**
121
804
  * Creates a state that persists in the global LayoutContext.
122
805
  *
@@ -152,80 +835,101 @@ function useLayoutState(path, defaultValue) {
152
835
  }
153
836
 
154
837
  /**
155
- * Accesses the global DescriptionContext and returns a description for a given id and element.
838
+ * @pinussilvestrus: we need to introduce our own hook to persist the previous
839
+ * state on updates.
156
840
  *
157
- * @example
158
- * ```jsx
159
- * function TextField(props) {
160
- * const description = useDescriptionContext('input1', element);
161
- * }
162
- * ```
841
+ * cf. https://reactjs.org/docs/hooks-faq.html#how-to-get-the-previous-props-or-state
842
+ */
843
+
844
+ function usePrevious(value) {
845
+ const ref = useRef();
846
+ useEffect(() => {
847
+ ref.current = value;
848
+ });
849
+ return ref.current;
850
+ }
851
+
852
+ /**
853
+ * Subscribe to `propertiesPanel.showEntry`.
163
854
  *
164
- * @param {string} id
165
- * @param {djs.model.Base} element
855
+ * @param {Function} show
166
856
  *
167
- * @returns {string}
857
+ * @returns {import('preact').Ref}
168
858
  */
169
859
 
170
- function useDescriptionContext(id, element) {
860
+ function useShowEntryEvent(show) {
171
861
  const {
172
- getDescriptionForId
173
- } = useContext(DescriptionContext);
174
- return getDescriptionForId(id, element);
175
- }
176
-
177
- var ArrowIcon = function ArrowIcon(props) {
178
- return jsx("svg", { ...props,
179
- children: jsx("path", {
180
- fillRule: "evenodd",
181
- 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"
182
- })
183
- });
184
- };
862
+ onShow
863
+ } = useContext(LayoutContext);
864
+ const ref = useRef();
865
+ const [focus, setFocus] = useState(false);
866
+ const onShowEntry = useCallback(event => {
867
+ if (show(event)) {
868
+ if (isFunction(onShow)) {
869
+ onShow();
870
+ }
185
871
 
186
- ArrowIcon.defaultProps = {
187
- xmlns: "http://www.w3.org/2000/svg",
188
- width: "16",
189
- height: "16"
190
- };
872
+ if (event.focus && !focus) {
873
+ setFocus(true);
874
+ }
875
+ }
876
+ }, [show]);
877
+ useEffect(() => {
878
+ if (focus && ref.current) {
879
+ if (isFunction(ref.current.focus)) {
880
+ ref.current.focus();
881
+ }
191
882
 
192
- var CreateIcon = function CreateIcon(props) {
193
- return jsx("svg", { ...props,
194
- children: jsx("path", {
195
- fillRule: "evenodd",
196
- 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"
197
- })
198
- });
199
- };
883
+ if (isFunction(ref.current.select)) {
884
+ ref.current.select();
885
+ }
200
886
 
201
- CreateIcon.defaultProps = {
202
- xmlns: "http://www.w3.org/2000/svg",
203
- width: "16",
204
- height: "16"
205
- };
887
+ setFocus(false);
888
+ }
889
+ }, [focus]);
890
+ useEvent('propertiesPanel.showEntry', onShowEntry);
891
+ return ref;
892
+ }
206
893
 
207
- var DeleteIcon = function DeleteIcon(props) {
208
- return jsx("svg", { ...props,
209
- children: jsx("path", {
210
- fillRule: "evenodd",
211
- 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"
212
- })
213
- });
214
- };
894
+ /**
895
+ * Subscribe to `propertiesPanel.showError`. On `propertiesPanel.showError` set
896
+ * temporary error. Fire `propertiesPanel.showEntry` for temporary error to be
897
+ * visible. Unset error on `propertiesPanel.updated`.
898
+ *
899
+ * @param {Function} show
900
+ *
901
+ * @returns {import('preact').Ref}
902
+ */
215
903
 
216
- DeleteIcon.defaultProps = {
217
- xmlns: "http://www.w3.org/2000/svg",
218
- width: "16",
219
- height: "16"
220
- };
904
+ function useShowErrorEvent(show) {
905
+ const {
906
+ eventBus
907
+ } = useContext(EventContext);
908
+ const [temporaryError, setTemporaryError] = useState(null);
909
+ const onPropertiesPanelUpdated = useCallback(() => setTemporaryError(null), []);
910
+ useEvent('propertiesPanel.updated', onPropertiesPanelUpdated);
911
+ const onShowError = useCallback(event => {
912
+ setTemporaryError(null);
913
+
914
+ if (show(event)) {
915
+ eventBus.fire('propertiesPanel.showEntry', event);
916
+ setTemporaryError(event.message);
917
+ }
918
+ }, [show]);
919
+ useEvent('propertiesPanel.showError', onShowError);
920
+ return temporaryError;
921
+ }
221
922
 
222
923
  function Group(props) {
223
924
  const {
224
- id,
925
+ element,
225
926
  entries = [],
226
- label
927
+ id,
928
+ label,
929
+ shouldOpen = false
227
930
  } = props;
228
- const [open, setOpen] = useLayoutState(['groups', id, 'open'], false);
931
+ const [open, setOpen] = useLayoutState(['groups', id, 'open'], shouldOpen);
932
+ const onShow = useCallback(() => setOpen(true), [setOpen]);
229
933
 
230
934
  const toggleOpen = () => setOpen(!open);
231
935
 
@@ -248,6 +952,9 @@ function Group(props) {
248
952
  });
249
953
  setEdited(hasOneEditedEntry);
250
954
  }, [entries]);
955
+ const propertiesPanelContext = { ...useContext(LayoutContext),
956
+ onShow
957
+ };
251
958
  return jsxs("div", {
252
959
  class: "bio-properties-panel-group",
253
960
  "data-group-id": 'group-' + id,
@@ -270,7 +977,19 @@ function Group(props) {
270
977
  })]
271
978
  }), jsx("div", {
272
979
  class: classnames('bio-properties-panel-group-entries', open ? 'open' : ''),
273
- children: entries.map(e => e.component)
980
+ children: jsx(LayoutContext.Provider, {
981
+ value: propertiesPanelContext,
982
+ children: entries.map(entry => {
983
+ const {
984
+ component: Component,
985
+ id
986
+ } = entry;
987
+ return createElement(Component, { ...entry,
988
+ element: element,
989
+ key: id
990
+ });
991
+ })
992
+ })
274
993
  })]
275
994
  });
276
995
  }
@@ -286,9 +1005,10 @@ const DEFAULT_LAYOUT = {
286
1005
  open: true
287
1006
  };
288
1007
  const DEFAULT_DESCRIPTION = {};
1008
+ const bufferedEvents = ['propertiesPanel.showEntry', 'propertiesPanel.showError'];
289
1009
  /**
290
1010
  * @typedef { {
291
- * component: import('preact').ComponentChild,
1011
+ * component: import('preact').Component,
292
1012
  * id: String,
293
1013
  * isEdited?: Function
294
1014
  * } } EntryDefinition
@@ -317,7 +1037,8 @@ const DEFAULT_DESCRIPTION = {};
317
1037
  * component?: import('preact').Component,
318
1038
  * entries: Array<EntryDefinition>,
319
1039
  * id: String,
320
- * label: String
1040
+ * label: String,
1041
+ * shouldOpen?: Boolean
321
1042
  * } } GroupDefinition
322
1043
  *
323
1044
  * @typedef { {
@@ -354,7 +1075,8 @@ function PropertiesPanel(props) {
354
1075
  layoutConfig = {},
355
1076
  layoutChanged,
356
1077
  descriptionConfig = {},
357
- descriptionLoaded
1078
+ descriptionLoaded,
1079
+ eventBus
358
1080
  } = props; // set-up layout context
359
1081
 
360
1082
  const [layout, setLayout] = useState(createLayout(layoutConfig));
@@ -395,6 +1117,13 @@ function PropertiesPanel(props) {
395
1117
  description,
396
1118
  getDescriptionForId
397
1119
  };
1120
+ useEventBuffer(bufferedEvents, eventBus);
1121
+ const eventContext = {
1122
+ eventBus
1123
+ };
1124
+ const propertiesPanelContext = {
1125
+ element
1126
+ };
398
1127
 
399
1128
  if (!element) {
400
1129
  return jsx("div", {
@@ -403,28 +1132,34 @@ function PropertiesPanel(props) {
403
1132
  });
404
1133
  }
405
1134
 
406
- return jsx(DescriptionContext.Provider, {
407
- value: descriptionContext,
408
- children: jsx(LayoutContext.Provider, {
409
- value: layoutContext,
410
- children: jsxs("div", {
411
- class: classnames('bio-properties-panel', layout.open ? 'open' : ''),
412
- children: [jsx(Header, {
413
- element: element,
414
- headerProvider: headerProvider
415
- }), jsx("div", {
416
- class: "bio-properties-panel-scroll-container",
417
- children: groups.map(group => {
418
- const {
419
- component: GroupComponent = Group,
420
- id
421
- } = group;
422
- return jsx(GroupComponent, {
1135
+ return jsx(LayoutContext.Provider, {
1136
+ value: propertiesPanelContext,
1137
+ children: jsx(DescriptionContext.Provider, {
1138
+ value: descriptionContext,
1139
+ children: jsx(LayoutContext.Provider, {
1140
+ value: layoutContext,
1141
+ children: jsx(EventContext.Provider, {
1142
+ value: eventContext,
1143
+ children: jsxs("div", {
1144
+ class: classnames('bio-properties-panel', layout.open ? 'open' : ''),
1145
+ children: [jsx(Header, {
423
1146
  element: element,
424
- ...group
425
- }, id);
1147
+ headerProvider: headerProvider
1148
+ }), jsx("div", {
1149
+ class: "bio-properties-panel-scroll-container",
1150
+ children: groups.map(group => {
1151
+ const {
1152
+ component: Component = Group,
1153
+ id
1154
+ } = group;
1155
+ return createElement(Component, { ...group,
1156
+ key: id,
1157
+ element: element
1158
+ });
1159
+ })
1160
+ })]
426
1161
  })
427
- })]
1162
+ })
428
1163
  })
429
1164
  })
430
1165
  });
@@ -555,16 +1290,29 @@ function HeaderButton(props) {
555
1290
 
556
1291
  function CollapsibleEntry(props) {
557
1292
  const {
558
- id,
1293
+ element,
559
1294
  entries = [],
1295
+ id,
560
1296
  label,
561
- remove,
562
- open: shouldOpen
1297
+ open: shouldOpen,
1298
+ remove
563
1299
  } = props;
564
1300
  const [open, setOpen] = useState(shouldOpen);
565
1301
 
566
- const toggleOpen = () => setOpen(!open); // todo(pinussilvestrus): translate once we have a translate mechanism for the core
1302
+ const toggleOpen = () => setOpen(!open);
1303
+
1304
+ const {
1305
+ onShow
1306
+ } = useContext(LayoutContext);
1307
+ const propertiesPanelContext = { ...useContext(LayoutContext),
1308
+ onShow: useCallback(() => {
1309
+ setOpen(true);
567
1310
 
1311
+ if (isFunction(onShow)) {
1312
+ onShow();
1313
+ }
1314
+ }, [onShow, setOpen])
1315
+ }; // todo(pinussilvestrus): translate once we have a translate mechanism for the core
568
1316
 
569
1317
  const placeholderLabel = '<empty>';
570
1318
  return jsxs("div", {
@@ -591,15 +1339,27 @@ function CollapsibleEntry(props) {
591
1339
  }) : null]
592
1340
  }), jsx("div", {
593
1341
  class: classnames('bio-properties-panel-collapsible-entry-entries', open ? 'open' : ''),
594
- children: entries.map(e => e.component)
1342
+ children: jsx(LayoutContext.Provider, {
1343
+ value: propertiesPanelContext,
1344
+ children: entries.map(entry => {
1345
+ const {
1346
+ component: Component,
1347
+ id
1348
+ } = entry;
1349
+ return createElement(Component, { ...entry,
1350
+ element: element,
1351
+ key: id
1352
+ });
1353
+ })
1354
+ })
595
1355
  })]
596
1356
  });
597
1357
  }
598
1358
 
599
1359
  function ListItem(props) {
600
1360
  const {
601
- autoOpen,
602
- autoFocusEntry
1361
+ autoFocusEntry,
1362
+ autoOpen
603
1363
  } = props; // focus specified entry on auto open
604
1364
 
605
1365
  useEffect(() => {
@@ -624,7 +1384,7 @@ function ListItem(props) {
624
1384
  });
625
1385
  }
626
1386
 
627
- const noop = () => {};
1387
+ const noop$3 = () => {};
628
1388
  /**
629
1389
  * @param {import('../PropertiesPanel').ListGroupDefinition} props
630
1390
  */
@@ -632,15 +1392,16 @@ const noop = () => {};
632
1392
 
633
1393
  function ListGroup(props) {
634
1394
  const {
1395
+ add,
635
1396
  element,
636
1397
  id,
637
1398
  items,
638
1399
  label,
639
- add,
640
- shouldSort = true,
641
- shouldOpen = true
1400
+ shouldOpen = true,
1401
+ shouldSort = true
642
1402
  } = props;
643
1403
  const [open, setOpen] = useLayoutState(['groups', id, 'open'], false);
1404
+ const onShow = useCallback(() => setOpen(true), [setOpen]);
644
1405
  const [ordering, setOrdering] = useState([]);
645
1406
  const [newItemAdded, setNewItemAdded] = useState(false);
646
1407
  const prevItems = usePrevious(items);
@@ -715,12 +1476,15 @@ function ListGroup(props) {
715
1476
  const toggleOpen = () => setOpen(!open);
716
1477
 
717
1478
  const hasItems = !!items.length;
1479
+ const propertiesPanelContext = { ...useContext(LayoutContext),
1480
+ onShow
1481
+ };
718
1482
  return jsxs("div", {
719
1483
  class: "bio-properties-panel-group",
720
1484
  "data-group-id": 'group-' + id,
721
1485
  children: [jsxs("div", {
722
1486
  class: classnames('bio-properties-panel-group-header', hasItems ? '' : 'empty', hasItems && open ? 'open' : ''),
723
- onClick: hasItems ? toggleOpen : noop,
1487
+ onClick: hasItems ? toggleOpen : noop$3,
724
1488
  children: [jsx("div", {
725
1489
  title: label,
726
1490
  class: "bio-properties-panel-group-header-title",
@@ -749,18 +1513,27 @@ function ListGroup(props) {
749
1513
  })]
750
1514
  }), jsx("div", {
751
1515
  class: classnames('bio-properties-panel-list', open && hasItems ? 'open' : ''),
752
- children: ordering.map((o, index) => {
753
- const item = getItem(items, o);
754
-
755
- if (!item) {
756
- return;
757
- }
758
-
759
- return jsx(ListItem, {
760
- // if item was added, open first or last item based on ordering
761
- autoOpen: newItemAdded && (shouldSort ? index === 0 : index === ordering.length - 1),
762
- ...item
763
- }, item.id);
1516
+ children: jsx(LayoutContext.Provider, {
1517
+ value: propertiesPanelContext,
1518
+ children: ordering.map((o, index) => {
1519
+ const item = getItem(items, o);
1520
+
1521
+ if (!item) {
1522
+ return;
1523
+ }
1524
+
1525
+ const {
1526
+ id
1527
+ } = item; // if item was added, open first or last item based on ordering
1528
+
1529
+ const autoOpen = newItemAdded && (shouldSort ? index === 0 : index === ordering.length - 1);
1530
+ return createElement(ListItem, { ...item,
1531
+ autoOpen: autoOpen,
1532
+ element: element,
1533
+ index: index,
1534
+ key: id
1535
+ });
1536
+ })
764
1537
  })
765
1538
  })]
766
1539
  });
@@ -799,13 +1572,16 @@ function Description(props) {
799
1572
  }
800
1573
  }
801
1574
 
1575
+ const noop$2 = () => {};
1576
+
802
1577
  function Checkbox(props) {
803
1578
  const {
804
1579
  id,
805
1580
  label,
806
1581
  onChange,
807
1582
  disabled,
808
- value = false
1583
+ value = false,
1584
+ show = noop$2
809
1585
  } = props;
810
1586
 
811
1587
  const handleChange = ({
@@ -814,9 +1590,11 @@ function Checkbox(props) {
814
1590
  onChange(target.checked);
815
1591
  };
816
1592
 
1593
+ const ref = useShowEntryEvent(show);
817
1594
  return jsxs("div", {
818
1595
  class: "bio-properties-panel-checkbox",
819
1596
  children: [jsx("input", {
1597
+ ref: ref,
820
1598
  id: prefixId$6(id),
821
1599
  name: id,
822
1600
  type: "checkbox",
@@ -851,18 +1629,24 @@ function CheckboxEntry(props) {
851
1629
  label,
852
1630
  getValue,
853
1631
  setValue,
854
- disabled
1632
+ disabled,
1633
+ show = noop$2
855
1634
  } = props;
856
1635
  const value = getValue(element);
1636
+ const error = useShowErrorEvent(show);
857
1637
  return jsxs("div", {
858
1638
  class: "bio-properties-panel-entry bio-properties-panel-checkbox-entry",
859
1639
  "data-entry-id": id,
860
1640
  children: [jsx(Checkbox, {
1641
+ disabled: disabled,
861
1642
  id: id,
862
1643
  label: label,
863
1644
  onChange: setValue,
864
- value: value,
865
- disabled: disabled
1645
+ show: show,
1646
+ value: value
1647
+ }), error && jsx("div", {
1648
+ class: "bio-properties-panel-error",
1649
+ children: error
866
1650
  }), jsx(Description, {
867
1651
  forId: id,
868
1652
  element: element,
@@ -883,7 +1667,7 @@ function List(props) {
883
1667
  id,
884
1668
  element,
885
1669
  items = [],
886
- renderItem,
1670
+ component,
887
1671
  label = '<empty>',
888
1672
  open: shouldOpen,
889
1673
  onAdd,
@@ -953,12 +1737,13 @@ function List(props) {
953
1737
  })]
954
1738
  }), hasItems && jsx(ItemsList, {
955
1739
  autoFocusEntry: autoFocusEntry,
1740
+ component: component,
1741
+ element: element,
956
1742
  id: id,
957
- open: open,
958
1743
  items: sortedItems,
959
1744
  newItems: newItems,
960
1745
  onRemove: onRemove,
961
- renderItem: renderItem
1746
+ open: open
962
1747
  })]
963
1748
  });
964
1749
  }
@@ -966,12 +1751,13 @@ function List(props) {
966
1751
  function ItemsList(props) {
967
1752
  const {
968
1753
  autoFocusEntry,
1754
+ component: Component,
1755
+ element,
969
1756
  id,
970
1757
  items,
971
1758
  newItems,
972
- open,
973
1759
  onRemove,
974
- renderItem
1760
+ open
975
1761
  } = props;
976
1762
  const getKey = useKeyFactory();
977
1763
  const newItem = newItems[0];
@@ -998,7 +1784,13 @@ function ItemsList(props) {
998
1784
  const key = getKey(item);
999
1785
  return jsxs("li", {
1000
1786
  class: "bio-properties-panel-list-entry-item",
1001
- children: [renderItem(item, index, item === newItem), onRemove && jsx("button", {
1787
+ children: [jsx(Component, {
1788
+ element: element,
1789
+ id: id,
1790
+ index: index,
1791
+ item: item,
1792
+ open: item === newItem
1793
+ }), onRemove && jsx("button", {
1002
1794
  type: "button",
1003
1795
  title: "Delete item",
1004
1796
  class: "bio-properties-panel-remove-entry bio-properties-panel-remove-list-entry",
@@ -1161,6 +1953,25 @@ function prefixId$5(id) {
1161
1953
  return `bio-properties-panel-${id}`;
1162
1954
  }
1163
1955
 
1956
+ const noop$1 = () => {};
1957
+ /**
1958
+ * @typedef { { value: string, label: string, disabled: boolean } } Option
1959
+ */
1960
+
1961
+ /**
1962
+ * Provides basic select input.
1963
+ *
1964
+ * @param {object} props
1965
+ * @param {string} props.id
1966
+ * @param {string[]} props.path
1967
+ * @param {string} props.label
1968
+ * @param {Function} props.onChange
1969
+ * @param {Array<Option>} [props.options]
1970
+ * @param {string} props.value
1971
+ * @param {boolean} [props.disabled]
1972
+ */
1973
+
1974
+
1164
1975
  function Select(props) {
1165
1976
  const {
1166
1977
  id,
@@ -1168,8 +1979,10 @@ function Select(props) {
1168
1979
  onChange,
1169
1980
  options = [],
1170
1981
  value,
1171
- disabled
1982
+ disabled,
1983
+ show = noop$1
1172
1984
  } = props;
1985
+ const ref = useShowEntryEvent(show);
1173
1986
 
1174
1987
  const handleChange = ({
1175
1988
  target
@@ -1184,6 +1997,7 @@ function Select(props) {
1184
1997
  class: "bio-properties-panel-label",
1185
1998
  children: label
1186
1999
  }), jsx("select", {
2000
+ ref: ref,
1187
2001
  id: prefixId$4(id),
1188
2002
  name: id,
1189
2003
  class: "bio-properties-panel-input",
@@ -1222,12 +2036,14 @@ function SelectEntry(props) {
1222
2036
  getValue,
1223
2037
  setValue,
1224
2038
  getOptions,
1225
- disabled
2039
+ disabled,
2040
+ show = noop$1
1226
2041
  } = props;
1227
2042
  const value = getValue(element);
1228
2043
  const options = getOptions(element);
2044
+ const error = useShowErrorEvent(show);
1229
2045
  return jsxs("div", {
1230
- class: "bio-properties-panel-entry",
2046
+ class: classnames('bio-properties-panel-entry', error ? 'has-error' : ''),
1231
2047
  "data-entry-id": id,
1232
2048
  children: [jsx(Select, {
1233
2049
  id: id,
@@ -1235,7 +2051,11 @@ function SelectEntry(props) {
1235
2051
  value: value,
1236
2052
  onChange: setValue,
1237
2053
  options: options,
1238
- disabled: disabled
2054
+ disabled: disabled,
2055
+ show: show
2056
+ }), error && jsx("div", {
2057
+ class: "bio-properties-panel-error",
2058
+ children: error
1239
2059
  }), jsx(Description, {
1240
2060
  forId: id,
1241
2061
  element: element,
@@ -1294,12 +2114,27 @@ function prefixId$3(id) {
1294
2114
  return `bio-properties-panel-${id}`;
1295
2115
  }
1296
2116
 
2117
+ function FeelIcon(props) {
2118
+ const {
2119
+ label,
2120
+ feel = false
2121
+ } = props;
2122
+ const feelRequiredLabel = ' must be a FEEL expression';
2123
+ const feelOptionalLabel = ' can optionally be a FEEL expression';
2124
+ return jsx("i", {
2125
+ class: "bio-properties-panel-feel-icon",
2126
+ title: label + (feel === 'required' ? feelRequiredLabel : feelOptionalLabel),
2127
+ children: feel === 'required' ? jsx(FeelRequiredIcon, {}) : jsx(FeelOptionalIcon, {})
2128
+ });
2129
+ }
2130
+
1297
2131
  function TextArea(props) {
1298
2132
  const {
1299
2133
  id,
1300
2134
  label,
1301
2135
  rows = 2,
1302
2136
  debounce,
2137
+ feel,
1303
2138
  onInput,
1304
2139
  value = '',
1305
2140
  disabled,
@@ -1312,10 +2147,13 @@ function TextArea(props) {
1312
2147
  }, [onInput, debounce]);
1313
2148
  return jsxs("div", {
1314
2149
  class: "bio-properties-panel-textarea",
1315
- children: [jsx("label", {
2150
+ children: [jsxs("label", {
1316
2151
  for: prefixId$2(id),
1317
2152
  class: "bio-properties-panel-label",
1318
- children: label
2153
+ children: [label, feel && jsx(FeelIcon, {
2154
+ feel: feel,
2155
+ label: label
2156
+ })]
1319
2157
  }), jsx("textarea", {
1320
2158
  id: prefixId$2(id),
1321
2159
  name: id,
@@ -1351,6 +2189,7 @@ function TextAreaEntry(props) {
1351
2189
  id,
1352
2190
  description,
1353
2191
  debounce,
2192
+ feel,
1354
2193
  label,
1355
2194
  getValue,
1356
2195
  setValue,
@@ -1370,6 +2209,7 @@ function TextAreaEntry(props) {
1370
2209
  rows: rows,
1371
2210
  debounce: debounce,
1372
2211
  monospace: monospace,
2212
+ feel: feel,
1373
2213
  disabled: disabled
1374
2214
  }), jsx(Description, {
1375
2215
  forId: id,
@@ -1386,6 +2226,8 @@ function prefixId$2(id) {
1386
2226
  return `bio-properties-panel-${id}`;
1387
2227
  }
1388
2228
 
2229
+ const noop = () => {};
2230
+
1389
2231
  function Textfield(props) {
1390
2232
  const {
1391
2233
  debounce,
@@ -1393,8 +2235,11 @@ function Textfield(props) {
1393
2235
  id,
1394
2236
  label,
1395
2237
  onInput,
1396
- value = ''
2238
+ feel = false,
2239
+ value = '',
2240
+ show = noop
1397
2241
  } = props;
2242
+ const ref = useShowEntryEvent(show);
1398
2243
  const handleInput = useMemo(() => {
1399
2244
  return debounce(({
1400
2245
  target
@@ -1402,11 +2247,15 @@ function Textfield(props) {
1402
2247
  }, [onInput, debounce]);
1403
2248
  return jsxs("div", {
1404
2249
  class: "bio-properties-panel-textfield",
1405
- children: [jsx("label", {
2250
+ children: [jsxs("label", {
1406
2251
  for: prefixId$1(id),
1407
2252
  class: "bio-properties-panel-label",
1408
- children: label
2253
+ children: [label, feel && jsx(FeelIcon, {
2254
+ feel: feel,
2255
+ label: label
2256
+ })]
1409
2257
  }), jsx("input", {
2258
+ ref: ref,
1410
2259
  id: prefixId$1(id),
1411
2260
  type: "text",
1412
2261
  name: id,
@@ -1442,55 +2291,65 @@ function TextfieldEntry(props) {
1442
2291
  description,
1443
2292
  debounce,
1444
2293
  disabled,
2294
+ feel,
1445
2295
  label,
1446
2296
  getValue,
1447
2297
  setValue,
1448
- validate
2298
+ validate,
2299
+ show = noop
1449
2300
  } = props;
1450
- const [error, setError] = useState(null);
1451
- const [invalidValueCache, setInvalidValueCache] = useState(null);
2301
+ const [cachedInvalidValue, setCachedInvalidValue] = useState(null);
2302
+ const [validationError, setValidationError] = useState(null);
1452
2303
  let value = getValue(element);
1453
- const prevValue = usePrevious(value); // validate again when value prop changed
1454
-
2304
+ const previousValue = usePrevious(value);
1455
2305
  useEffect(() => {
1456
- const err = validate ? validate(value) : null;
1457
- setError(err);
1458
- }, [value]); // validate on change
2306
+ if (isFunction(validate)) {
2307
+ const newValidationError = validate(value) || null;
2308
+ setValidationError(newValidationError);
2309
+ }
2310
+ }, [value]);
2311
+
2312
+ const onInput = newValue => {
2313
+ let newValidationError = null;
1459
2314
 
1460
- const handleChange = newValue => {
1461
- const err = validate ? validate(newValue) : null;
2315
+ if (isFunction(validate)) {
2316
+ newValidationError = validate(newValue) || null;
2317
+ }
1462
2318
 
1463
- if (err) {
1464
- setInvalidValueCache(newValue);
2319
+ if (newValidationError) {
2320
+ setCachedInvalidValue(newValue);
1465
2321
  } else {
1466
2322
  setValue(newValue);
1467
2323
  }
1468
2324
 
1469
- setError(err);
1470
- }; // keep showing invalid value on errors, although it was not set
1471
-
2325
+ setValidationError(newValidationError);
2326
+ };
1472
2327
 
1473
- if (prevValue === value && error) {
1474
- value = invalidValueCache;
2328
+ if (previousValue === value && validationError) {
2329
+ value = cachedInvalidValue;
1475
2330
  }
1476
2331
 
2332
+ const temporaryError = useShowErrorEvent(show);
2333
+ const error = temporaryError || validationError;
1477
2334
  return jsxs("div", {
1478
2335
  class: classnames('bio-properties-panel-entry', error ? 'has-error' : ''),
1479
2336
  "data-entry-id": id,
1480
2337
  children: [jsx(Textfield, {
2338
+ debounce: debounce,
2339
+ disabled: disabled,
2340
+ feel: feel,
1481
2341
  id: id,
1482
2342
  label: label,
1483
- value: value,
1484
- onInput: handleChange,
1485
- debounce: debounce,
1486
- disabled: disabled
2343
+ onInput: onInput,
2344
+ show: show,
2345
+ value: value
2346
+ }), error && jsx("div", {
2347
+ class: "bio-properties-panel-error",
2348
+ children: error
1487
2349
  }), jsx(Description, {
1488
2350
  forId: id,
1489
2351
  element: element,
1490
2352
  value: description
1491
- }), error && jsx("div", {
1492
- class: "bio-properties-panel-error",
1493
- children: error
1494
2353
  })]
1495
2354
  });
1496
2355
  }
@@ -1606,5 +2465,5 @@ var index = {
1606
2465
  debounceInput: ['factory', debounceInput]
1607
2466
  };
1608
2467
 
1609
- export { ArrowIcon, CheckboxEntry, CollapsibleEntry, CreateIcon, index as DebounceInputModule, DeleteIcon, DescriptionContext, Description as DescriptionEntry, DropdownButton, Group, Header, HeaderButton, LayoutContext, List as ListEntry, ListGroup, ListItem, NumberFieldEntry, PropertiesPanel, SelectEntry, Simple as SimpleEntry, TextAreaEntry, TextfieldEntry as TextFieldEntry, ToggleSwitchEntry, isEdited$6 as isCheckboxEntryEdited, isEdited$5 as isNumberFieldEntryEdited, isEdited$4 as isSelectEntryEdited, isEdited$3 as isSimpleEntryEdited, isEdited$2 as isTextAreaEntryEdited, isEdited$1 as isTextFieldEntryEdited, isEdited as isToggleSwitchEntryEdited, useDescriptionContext, useKeyFactory, useLayoutState, usePrevious };
2468
+ export { ArrowIcon, CheckboxEntry, CollapsibleEntry, CreateIcon, index as DebounceInputModule, DeleteIcon, DescriptionContext, Description as DescriptionEntry, DropdownButton, EventContext, ExternalLinkIcon, FeelOptionalIcon, FeelRequiredIcon, Group, Header, HeaderButton, LayoutContext, List as ListEntry, ListGroup, ListItem, NumberFieldEntry, PropertiesPanel, LayoutContext as PropertiesPanelContext, SelectEntry, Simple as SimpleEntry, TextAreaEntry, TextfieldEntry as TextFieldEntry, ToggleSwitchEntry, isEdited$6 as isCheckboxEntryEdited, isEdited$5 as isNumberFieldEntryEdited, isEdited$4 as isSelectEntryEdited, isEdited$3 as isSimpleEntryEdited, isEdited$2 as isTextAreaEntryEdited, isEdited$1 as isTextFieldEntryEdited, isEdited as isToggleSwitchEntryEdited, useDescriptionContext, useEvent, useEventBuffer, useKeyFactory, useLayoutState, usePrevious, useShowEntryEvent, useShowErrorEvent };
1610
2469
  //# sourceMappingURL=index.esm.js.map