@bpmn-io/properties-panel 3.47.0 → 3.48.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.
@@ -1277,8 +1277,13 @@ textarea.bio-properties-panel-input {
1277
1277
  transform: rotate(90deg);
1278
1278
  }
1279
1279
 
1280
+ .bio-properties-panel-textarea-container {
1281
+ position: relative;
1282
+ }
1283
+
1280
1284
  .bio-properties-panel-feelers-editor-container .bio-properties-panel-feelers-editor__open-popup-placeholder,
1281
- .bio-properties-panel-feel-editor-container .bio-properties-panel-feel-editor__open-popup-placeholder {
1285
+ .bio-properties-panel-feel-editor-container .bio-properties-panel-feel-editor__open-popup-placeholder,
1286
+ .bio-properties-panel-textarea-container .bio-properties-panel-textarea__open-popup-placeholder {
1282
1287
  display: none;
1283
1288
  justify-content: center;
1284
1289
  flex-direction: column;
@@ -1298,11 +1303,22 @@ textarea.bio-properties-panel-input {
1298
1303
  display: none;
1299
1304
  }
1300
1305
 
1306
+ .bio-properties-panel-textarea-container.popupOpen .bio-properties-panel-input {
1307
+ visibility: hidden;
1308
+ }
1309
+
1301
1310
  .bio-properties-panel-feelers-editor-container.popupOpen .bio-properties-panel-feelers-editor__open-popup-placeholder,
1302
- .bio-properties-panel-feel-editor-container.popupOpen .bio-properties-panel-feel-editor__open-popup-placeholder {
1311
+ .bio-properties-panel-feel-editor-container.popupOpen .bio-properties-panel-feel-editor__open-popup-placeholder,
1312
+ .bio-properties-panel-textarea-container.popupOpen .bio-properties-panel-textarea__open-popup-placeholder {
1303
1313
  display: flex;
1304
1314
  }
1305
1315
 
1316
+ .bio-properties-panel-textarea-container.popupOpen .bio-properties-panel-textarea__open-popup-placeholder {
1317
+ position: absolute;
1318
+ inset: 0;
1319
+ justify-content: flex-start;
1320
+ }
1321
+
1306
1322
  .bio-properties-panel-popup {
1307
1323
  --popup-background-color: hsla(0, 0%, 96%, 1);
1308
1324
  --popup-header-background-color: white;
@@ -1379,6 +1395,7 @@ textarea.bio-properties-panel-input {
1379
1395
 
1380
1396
  .bio-properties-panel-popup .bio-properties-panel-popup__title {
1381
1397
  margin-left: 8px;
1398
+ margin-right: auto;
1382
1399
  }
1383
1400
 
1384
1401
  .bio-properties-panel-popup .bio-properties-panel-popup__title::first-letter {
@@ -1428,11 +1445,6 @@ textarea.bio-properties-panel-input {
1428
1445
  right: 8px;
1429
1446
  }
1430
1447
 
1431
- .bio-properties-panel-feel-popup {
1432
- min-height: 400px;
1433
- width: fit-content;
1434
- }
1435
-
1436
1448
  .bio-properties-panel-feel-popup .bio-properties-panel-feel-popup__body {
1437
1449
  display: flex;
1438
1450
  margin: 0;
@@ -1492,7 +1504,8 @@ textarea.bio-properties-panel-input {
1492
1504
  }
1493
1505
 
1494
1506
  .bio-properties-panel-feelers-editor-container .bio-properties-panel-open-feel-popup,
1495
- .bio-properties-panel-feel-container .bio-properties-panel-open-feel-popup {
1507
+ .bio-properties-panel-feel-container .bio-properties-panel-open-feel-popup,
1508
+ .bio-properties-panel-textarea-container .bio-properties-panel-open-feel-popup {
1496
1509
  position: absolute;
1497
1510
  top: 0;
1498
1511
  right: 0;
@@ -1507,20 +1520,24 @@ textarea.bio-properties-panel-input {
1507
1520
  }
1508
1521
 
1509
1522
  .bio-properties-panel-feelers-editor-container .bio-properties-panel-open-feel-popup svg,
1510
- .bio-properties-panel-feel-container .bio-properties-panel-open-feel-popup svg {
1523
+ .bio-properties-panel-feel-container .bio-properties-panel-open-feel-popup svg,
1524
+ .bio-properties-panel-textarea-container .bio-properties-panel-open-feel-popup svg {
1511
1525
  width: 16px;
1512
1526
  height: 16px;
1513
1527
  }
1514
1528
 
1515
1529
  .bio-properties-panel-feelers-editor-container:hover .bio-properties-panel-open-feel-popup,
1516
1530
  .bio-properties-panel-feel-container:hover .bio-properties-panel-open-feel-popup,
1531
+ .bio-properties-panel-textarea-container:hover .bio-properties-panel-open-feel-popup,
1517
1532
  .bio-properties-panel-feelers-editor-container:focus-within .bio-properties-panel-open-feel-popup,
1518
- .bio-properties-panel-feel-container:focus-within .bio-properties-panel-open-feel-popup {
1533
+ .bio-properties-panel-feel-container:focus-within .bio-properties-panel-open-feel-popup,
1534
+ .bio-properties-panel-textarea-container:focus-within .bio-properties-panel-open-feel-popup {
1519
1535
  display: block;
1520
1536
  }
1521
1537
 
1522
1538
  .bio-properties-panel-feelers-editor-container .bio-properties-panel-open-feel-popup:hover,
1523
- .bio-properties-panel-feel-container .bio-properties-panel-open-feel-popup:hover {
1539
+ .bio-properties-panel-feel-container .bio-properties-panel-open-feel-popup:hover,
1540
+ .bio-properties-panel-textarea-container .bio-properties-panel-open-feel-popup:hover {
1524
1541
  color: var(--feel-open-popup-hover-color);
1525
1542
  }
1526
1543
 
@@ -1563,6 +1580,15 @@ textarea.bio-properties-panel-input {
1563
1580
  text-align: center;
1564
1581
  }
1565
1582
 
1583
+ .bio-properties-panel-text-popup .bio-properties-panel-input {
1584
+ width: 100%;
1585
+ height: 100%;
1586
+ min-height: 100%;
1587
+ resize: none;
1588
+ border: none;
1589
+ padding: 8px 12px;
1590
+ }
1591
+
1566
1592
  /* Checkbox Group */
1567
1593
  .bio-properties-panel-checkbox-group .bio-properties-panel-checkbox-group-entries > .bio-properties-panel-entry {
1568
1594
  margin: 0;
package/dist/index.esm.js CHANGED
@@ -4083,6 +4083,7 @@ function resizeToContents(element) {
4083
4083
  function TextArea(props) {
4084
4084
  const {
4085
4085
  id,
4086
+ element,
4086
4087
  label,
4087
4088
  debounce,
4088
4089
  onInput: commitValue,
@@ -4095,10 +4096,21 @@ function TextArea(props) {
4095
4096
  autoResize = true,
4096
4097
  placeholder,
4097
4098
  rows = autoResize ? 1 : 2,
4098
- tooltip
4099
+ tooltip,
4100
+ translate = translateFallback
4099
4101
  } = props;
4100
4102
  const [localValue, setLocalValue] = useState(value);
4101
4103
  const ref = useShowEntryEvent(id);
4104
+ const containerRef = useRef();
4105
+
4106
+ // keep a live reference to the current value so callbacks captured by the
4107
+ // popup (frozen at open time) can compare against the latest value
4108
+ const localValueRef = useRef(localValue);
4109
+ localValueRef.current = localValue;
4110
+ const {
4111
+ eventBus
4112
+ } = useContext(EventContext);
4113
+ const [isPopupOpen, setIsPopupOpen] = useState(false);
4102
4114
  const onInput = useCallback(newValue => {
4103
4115
  const newModelValue = newValue === '' ? undefined : newValue;
4104
4116
  commitValue(newModelValue);
@@ -4110,7 +4122,6 @@ function TextArea(props) {
4110
4122
  */
4111
4123
  const handleInput = useDebounce(onInput, debounce);
4112
4124
  const handleLocalInput = e => {
4113
- autoResize && resizeToContents(e.target);
4114
4125
  if (e.target.value === localValue) {
4115
4126
  return;
4116
4127
  }
@@ -4155,9 +4166,40 @@ function TextArea(props) {
4155
4166
  handleInput.flush?.();
4156
4167
  }
4157
4168
  };
4169
+ const handlePopupInput = newValue => {
4170
+ if (newValue === localValueRef.current) {
4171
+ return;
4172
+ }
4173
+ setLocalValue(newValue);
4174
+ handleInput(newValue);
4175
+ };
4176
+ const handleOpenPopup = () => {
4177
+ const isOpen = eventBus.fire('propertiesPanel.openPopup', {
4178
+ element,
4179
+ entryId: id,
4180
+ label,
4181
+ onInput: handlePopupInput,
4182
+ sourceElement: ref.current,
4183
+ value: localValue
4184
+ });
4185
+ if (isOpen) {
4186
+ eventBus.once('propertiesPanelPopup.close', () => {
4187
+ handleInput.flush?.();
4188
+ setIsPopupOpen(false);
4189
+ });
4190
+ }
4191
+ setIsPopupOpen(isOpen === true);
4192
+ };
4193
+ useEffect(() => {
4194
+ return () => {
4195
+ eventBus && eventBus.fire('propertiesPanel.closePopup');
4196
+ };
4197
+ }, []);
4198
+
4199
+ // auto-resize on any value change (typing, popup edits, external updates)
4158
4200
  useLayoutEffect(() => {
4159
4201
  autoResize && resizeToContents(ref.current);
4160
- }, []);
4202
+ }, [localValue]);
4161
4203
  useLayoutEffect(() => {
4162
4204
  visible && autoResize && resizeToContents(ref.current);
4163
4205
  }, [visible]);
@@ -4175,25 +4217,35 @@ function TextArea(props) {
4175
4217
  children: jsx(TooltipWrapper, {
4176
4218
  value: tooltip,
4177
4219
  forId: id,
4178
- element: props.element,
4220
+ element: element,
4179
4221
  children: label
4180
4222
  })
4181
- }), jsx("textarea", {
4182
- ref: ref,
4183
- id: prefixId$2(id),
4184
- name: id,
4185
- spellCheck: "false",
4186
- class: classnames('bio-properties-panel-input', monospace ? 'bio-properties-panel-input-monospace' : '', autoResize ? 'auto-resize' : ''),
4187
- onInput: handleLocalInput,
4188
- onFocus: onFocus,
4189
- onKeyDown: handleOnKeyDown,
4190
- onBlur: handleOnBlur,
4191
- onPaste: handleOnPaste,
4192
- placeholder: placeholder,
4193
- rows: rows,
4194
- value: localValue,
4195
- disabled: disabled,
4196
- "data-gramm": "false"
4223
+ }), jsxs("div", {
4224
+ class: classnames('bio-properties-panel-textarea-container', isPopupOpen ? 'popupOpen' : null),
4225
+ ref: containerRef,
4226
+ children: [isPopupOpen && jsx("div", {
4227
+ class: "bio-properties-panel-textarea__open-popup-placeholder",
4228
+ children: translate('Opened in editor')
4229
+ }), jsx("textarea", {
4230
+ ref: ref,
4231
+ id: prefixId$2(id),
4232
+ name: id,
4233
+ spellCheck: "false",
4234
+ class: classnames('bio-properties-panel-input', monospace ? 'bio-properties-panel-input-monospace' : '', autoResize ? 'auto-resize' : ''),
4235
+ onInput: handleLocalInput,
4236
+ onFocus: onFocus,
4237
+ onKeyDown: handleOnKeyDown,
4238
+ onBlur: handleOnBlur,
4239
+ onPaste: handleOnPaste,
4240
+ placeholder: placeholder,
4241
+ rows: rows,
4242
+ value: localValue,
4243
+ disabled: disabled,
4244
+ "data-gramm": "false"
4245
+ }), eventBus && !disabled && jsx(OpenPopupButton, {
4246
+ onClick: handleOpenPopup,
4247
+ translate: translate
4248
+ })]
4197
4249
  })]
4198
4250
  });
4199
4251
  }
@@ -4214,6 +4266,7 @@ function TextArea(props) {
4214
4266
  * @param {boolean} props.monospace
4215
4267
  * @param {Function} [props.validate]
4216
4268
  * @param {boolean} [props.disabled]
4269
+ * @param {Function} [props.translate]
4217
4270
  */
4218
4271
  function TextAreaEntry(props) {
4219
4272
  const {
@@ -4233,7 +4286,8 @@ function TextAreaEntry(props) {
4233
4286
  onPaste,
4234
4287
  placeholder,
4235
4288
  autoResize,
4236
- tooltip
4289
+ tooltip,
4290
+ translate
4237
4291
  } = props;
4238
4292
  const globalError = useError(id);
4239
4293
  const [localError, setLocalError] = useState(null);
@@ -4274,6 +4328,7 @@ function TextAreaEntry(props) {
4274
4328
  placeholder: placeholder,
4275
4329
  autoResize: autoResize,
4276
4330
  tooltip: tooltip,
4331
+ translate: translate,
4277
4332
  element: element
4278
4333
  }, element), error && jsx("div", {
4279
4334
  class: "bio-properties-panel-error",
@@ -4615,6 +4670,9 @@ const noop = () => {};
4615
4670
  * @param {Function} [props.onPostDeactivate]
4616
4671
  * @param {boolean} [props.returnFocus]
4617
4672
  * @param {boolean} [props.closeOnEscape]
4673
+ * @param {(event: KeyboardEvent) => boolean} [props.allowFocusMove] -
4674
+ * Whether a Tab keypress may move focus. Return false to leave the keypress
4675
+ * to the focused element, e.g. an editor navigating snippet placeholders.
4618
4676
  * @param {string} props.title
4619
4677
  * @param {Ref} [ref]
4620
4678
  */
@@ -4630,6 +4688,7 @@ function PopupComponent(props, globalRef) {
4630
4688
  onPostDeactivate = noop,
4631
4689
  returnFocus = true,
4632
4690
  closeOnEscape = true,
4691
+ allowFocusMove = () => true,
4633
4692
  title
4634
4693
  } = props;
4635
4694
  const focusTrapRef = useRef(null);
@@ -4679,6 +4738,8 @@ function PopupComponent(props, globalRef) {
4679
4738
  clickOutsideDeactivates: true,
4680
4739
  delayInitialFocus,
4681
4740
  fallbackFocus: popupRef.current,
4741
+ isKeyForward: event => isTab(event) && !event.shiftKey && allowFocusMove(event),
4742
+ isKeyBackward: event => isTab(event) && event.shiftKey && allowFocusMove(event),
4682
4743
  onPostActivate,
4683
4744
  onPostDeactivate,
4684
4745
  returnFocusOnDeactivate: returnFocus
@@ -4698,10 +4759,10 @@ function PopupComponent(props, globalRef) {
4698
4759
  children: props.children
4699
4760
  });
4700
4761
  }
4701
- const Popup = forwardRef(PopupComponent);
4702
- Popup.Title = Title;
4703
- Popup.Body = Body;
4704
- Popup.Footer = Footer;
4762
+ const Popup$1 = forwardRef(PopupComponent);
4763
+ Popup$1.Title = Title;
4764
+ Popup$1.Body = Body;
4765
+ Popup$1.Footer = Footer;
4705
4766
  function Title(props) {
4706
4767
  const {
4707
4768
  children,
@@ -4812,6 +4873,9 @@ function Footer(props) {
4812
4873
 
4813
4874
  // helpers //////////////////////
4814
4875
 
4876
+ function isTab(event) {
4877
+ return event.key === 'Tab';
4878
+ }
4815
4879
  function getPopupParent(node) {
4816
4880
  return node.closest('.bio-properties-panel-popup');
4817
4881
  }
@@ -4839,13 +4903,13 @@ function cancel(event) {
4839
4903
  */
4840
4904
 
4841
4905
  const FEEL_POPUP_WIDTH = 700;
4842
- const FEEL_POPUP_HEIGHT = 250;
4906
+ const FEEL_POPUP_HEIGHT = 400;
4843
4907
 
4844
4908
  /**
4845
4909
  * FEEL expression editor popup component
4846
4910
  * @param {FeelPopupProps} props
4847
4911
  */
4848
- function FeelPopup$1(props) {
4912
+ function FeelPopup(props) {
4849
4913
  const {
4850
4914
  entryId,
4851
4915
  onInput,
@@ -4890,18 +4954,19 @@ function FeelPopup$1(props) {
4890
4954
  editorRef.current.focus();
4891
4955
  }
4892
4956
  }, [editorRef]);
4893
- return jsxs(Popup, {
4957
+ return jsxs(Popup$1, {
4894
4958
  className: "bio-properties-panel-feel-popup",
4895
4959
  position: position,
4896
4960
  title: title,
4897
4961
  returnFocus: false,
4898
4962
  closeOnEscape: false,
4899
4963
  delayInitialFocus: false,
4964
+ allowFocusMove: event => !isSnippetNavigation(event),
4900
4965
  onPostDeactivate: handleSetReturnFocus,
4901
4966
  height: FEEL_POPUP_HEIGHT,
4902
4967
  width: FEEL_POPUP_WIDTH,
4903
4968
  ref: popupRef,
4904
- children: [jsx(Popup.Title, {
4969
+ children: [jsx(Popup$1.Title, {
4905
4970
  title: title,
4906
4971
  eventBus: eventBus,
4907
4972
  showCloseButton: true,
@@ -4919,14 +4984,13 @@ function FeelPopup$1(props) {
4919
4984
  }, index);
4920
4985
  })
4921
4986
  })
4922
- }), jsx(Popup.Body, {
4987
+ }), jsx(Popup$1.Body, {
4923
4988
  children: jsxs("div", {
4924
4989
  onKeyDownCapture: onKeyDownCapture,
4925
4990
  onKeyDown: onKeyDown,
4926
4991
  class: "bio-properties-panel-feel-popup__body",
4927
4992
  children: [type === 'feel' && jsx(FeelEditor, {
4928
4993
  enableGutters: true,
4929
- id: prefixId(entryId),
4930
4994
  name: entryId,
4931
4995
  onInput: onInput,
4932
4996
  value: value,
@@ -4935,7 +4999,6 @@ function FeelPopup$1(props) {
4935
4999
  ref: editorRef,
4936
5000
  tooltipContainer: tooltipContainer
4937
5001
  }), type === 'feelers' && jsx(TemplatingEditor, {
4938
- id: prefixId(entryId),
4939
5002
  contentAttributes: {
4940
5003
  'aria-label': title
4941
5004
  },
@@ -4952,14 +5015,17 @@ function FeelPopup$1(props) {
4952
5015
  })]
4953
5016
  });
4954
5017
  }
4955
- function prefixId(id) {
4956
- return `bio-properties-panel-${id}`;
4957
- }
4958
5018
  function autoCompletionOpen(element) {
4959
5019
  const editor = element.closest('.cm-editor');
4960
5020
  return editor ? editor.querySelector('.cm-tooltip-autocomplete') : null;
4961
5021
  }
4962
5022
 
5023
+ // while a snippet is active, Tab navigates its placeholders inside the editor
5024
+ function isSnippetNavigation(event) {
5025
+ const editor = event.target.closest('.cm-editor');
5026
+ return !!(editor && editor.querySelector('.cm-snippetField'));
5027
+ }
5028
+
4963
5029
  function getPopupTitle({
4964
5030
  element,
4965
5031
  label
@@ -4980,7 +5046,102 @@ function getPopupPosition() {
4980
5046
  }
4981
5047
 
4982
5048
  /**
4983
- * FEEL popup component, built as a singleton.
5049
+ * @typedef {Object} TextPopupProps
5050
+ * @property {string} entryId
5051
+ * @property {Function} onInput
5052
+ * @property {Function} onClose
5053
+ * @property {string} title
5054
+ * @property {string} value
5055
+ * @property {Object} [position]
5056
+ * @property {HTMLElement} [sourceElement]
5057
+ * @property {Object} [eventBus]
5058
+ */
5059
+
5060
+ const TEXT_POPUP_WIDTH = 700;
5061
+ const TEXT_POPUP_HEIGHT = 400;
5062
+
5063
+ /**
5064
+ * Plain text editor popup component.
5065
+ *
5066
+ * Intentionally kept separate from the FEEL popup: the two are expected to
5067
+ * diverge as we add FEEL-specific capabilities that do not apply to plain text.
5068
+ *
5069
+ * @param {TextPopupProps} props
5070
+ */
5071
+ function TextPopup(props) {
5072
+ const {
5073
+ entryId,
5074
+ onInput,
5075
+ onClose,
5076
+ title,
5077
+ value,
5078
+ position,
5079
+ sourceElement,
5080
+ eventBus
5081
+ } = props;
5082
+ const editorRef = useRef();
5083
+ const popupRef = useRef();
5084
+ const handleSetReturnFocus = () => {
5085
+ sourceElement && sourceElement.focus();
5086
+ };
5087
+ useEffect(() => {
5088
+ // set focus on editor when popup is opened
5089
+ if (editorRef.current) {
5090
+ editorRef.current.focus();
5091
+ }
5092
+ }, [editorRef]);
5093
+ return jsxs(Popup$1, {
5094
+ className: "bio-properties-panel-text-popup",
5095
+ position: position,
5096
+ title: title,
5097
+ returnFocus: false,
5098
+ closeOnEscape: true,
5099
+ delayInitialFocus: false,
5100
+ onClose: onClose,
5101
+ onPostDeactivate: handleSetReturnFocus,
5102
+ height: TEXT_POPUP_HEIGHT,
5103
+ width: TEXT_POPUP_WIDTH,
5104
+ ref: popupRef,
5105
+ children: [jsx(Popup$1.Title, {
5106
+ title: title,
5107
+ eventBus: eventBus,
5108
+ showCloseButton: true,
5109
+ closeButtonTooltip: "Save and close",
5110
+ onClose: onClose,
5111
+ draggable: true
5112
+ }), jsx(Popup$1.Body, {
5113
+ children: jsx("textarea", {
5114
+ id: prefixId(entryId),
5115
+ name: entryId,
5116
+ class: "bio-properties-panel-input",
5117
+ ref: editorRef,
5118
+ onInput: e => onInput(e.target.value),
5119
+ value: value || '',
5120
+ spellCheck: "false",
5121
+ autoComplete: "off",
5122
+ "aria-label": title,
5123
+ "data-gramm": "false"
5124
+ })
5125
+ })]
5126
+ });
5127
+ }
5128
+
5129
+ // The in-panel field stays mounted (hidden) while the popup is open, so we
5130
+ // scope the popup editor id to keep it unique in the DOM.
5131
+ function prefixId(id) {
5132
+ return `bio-properties-panel-popup-${id}`;
5133
+ }
5134
+
5135
+ const DEFAULT_POPUP_TYPE = 'text';
5136
+
5137
+ // consumers registering via #registerProvider default to DEFAULT_PRIORITY,
5138
+ // so their providers take precedence over the LOW_PRIORITY built-ins.
5139
+ const DEFAULT_PRIORITY = 1000;
5140
+ const LOW_PRIORITY = 500;
5141
+
5142
+ /**
5143
+ * Popup manager, built as a singleton. Renders the registered provider for a
5144
+ * given popup type; consumers may plug in their own via #registerProvider.
4984
5145
  *
4985
5146
  * In order to implement a custom replacement, handle the following events:
4986
5147
  * - `propertiesPanel.openPopup`
@@ -4993,16 +5154,19 @@ function getPopupPosition() {
4993
5154
  * - `feelPopup.close` - fired before the popup is unmounted. Event context contains the DOM node of the popup as `domNode`
4994
5155
  * - `feelPopup.closed` - fired after the popup is unmounted
4995
5156
  */
4996
- class FeelPopup {
5157
+ class Popup {
4997
5158
  constructor(eventBus, config = {}) {
4998
5159
  this._eventBus = eventBus;
4999
5160
  this._config = config;
5000
5161
  this._isOpen = false;
5001
- eventBus.on('propertiesPanel.openPopup', (_, context) => {
5002
- this.open(context.entryId, context, context.sourceElement);
5003
5162
 
5004
- // return true to indicate that popup was opened
5005
- return true;
5163
+ // built-in providers, registered at LOW_PRIORITY so consumers can
5164
+ // override them via #registerProvider using the default priority
5165
+ this.registerProvider('feel', LOW_PRIORITY, FeelPopup);
5166
+ this.registerProvider('feelers', LOW_PRIORITY, FeelPopup);
5167
+ this.registerProvider('text', LOW_PRIORITY, TextPopup);
5168
+ eventBus.on('propertiesPanel.openPopup', (_, context) => {
5169
+ return this.open(context.entryId, context, context.sourceElement);
5006
5170
  });
5007
5171
  eventBus.on(['propertiesPanel.closePopup', 'propertiesPanel.detach'], () => {
5008
5172
  this.close();
@@ -5010,7 +5174,43 @@ class FeelPopup {
5010
5174
  }
5011
5175
 
5012
5176
  /**
5013
- * Check if the FEEL popup is open.
5177
+ * Register a popup provider (component) for a given type.
5178
+ *
5179
+ * A higher `priority` wins when multiple providers are registered for the
5180
+ * same type; the built-in providers use a low priority so consumers override
5181
+ * them by default.
5182
+ *
5183
+ * @param {string} type
5184
+ * @param {number} [priority=DEFAULT_PRIORITY]
5185
+ * @param {Function|import('preact').Component} provider
5186
+ */
5187
+ registerProvider(type, priority, provider) {
5188
+ if (!provider) {
5189
+ provider = priority;
5190
+ priority = DEFAULT_PRIORITY;
5191
+ }
5192
+ this._eventBus.on('propertiesPanelPopup.getProviders.' + type, priority, function (event) {
5193
+ event.providers.push(provider);
5194
+ });
5195
+ }
5196
+
5197
+ /**
5198
+ * Get the popup providers registered for a type.
5199
+ *
5200
+ * @param {string} type
5201
+ * @return {Array<Function|import('preact').Component>}
5202
+ */
5203
+ _getProviders(type) {
5204
+ const event = this._eventBus.createEvent({
5205
+ type: 'propertiesPanelPopup.getProviders.' + type,
5206
+ providers: []
5207
+ });
5208
+ this._eventBus.fire(event);
5209
+ return event.providers;
5210
+ }
5211
+
5212
+ /**
5213
+ * Check if the popup is open.
5014
5214
  * @return {Boolean}
5015
5215
  */
5016
5216
  isOpen() {
@@ -5018,7 +5218,7 @@ class FeelPopup {
5018
5218
  }
5019
5219
 
5020
5220
  /**
5021
- * Open the FEEL popup.
5221
+ * Open the popup.
5022
5222
  *
5023
5223
  * @param {String} entryId
5024
5224
  * @param {Object} popupConfig
@@ -5027,7 +5227,7 @@ class FeelPopup {
5027
5227
  open(entryId, popupConfig, sourceElement) {
5028
5228
  // close before opening a new one
5029
5229
  this.close();
5030
- this._openPopup({
5230
+ return this._openPopup({
5031
5231
  ...popupConfig,
5032
5232
  entryId,
5033
5233
  sourceElement
@@ -5035,7 +5235,7 @@ class FeelPopup {
5035
5235
  }
5036
5236
 
5037
5237
  /**
5038
- * Close the FEEL popup.
5238
+ * Close the popup.
5039
5239
  */
5040
5240
  close() {
5041
5241
  this._closePopup();
@@ -5045,13 +5245,18 @@ class FeelPopup {
5045
5245
  element,
5046
5246
  label,
5047
5247
  sourceElement,
5048
- type
5248
+ type = DEFAULT_POPUP_TYPE
5049
5249
  } = context;
5250
+ const component = this._getProviders(type)[0];
5251
+ if (!component) {
5252
+ return false;
5253
+ }
5050
5254
  this._isOpen = true;
5051
5255
  this._eventBus.fire('propertiesPanelPopup.open', {
5052
5256
  container: this._config.feelPopupContainer,
5053
5257
  config: {
5054
5258
  ...context,
5259
+ component,
5055
5260
  links: this._config.getFeelPopupLinks?.(type) || [],
5056
5261
  onClose: () => {
5057
5262
  this._closePopup();
@@ -5068,6 +5273,7 @@ class FeelPopup {
5068
5273
  })
5069
5274
  }
5070
5275
  });
5276
+ return true;
5071
5277
  }
5072
5278
  _closePopup() {
5073
5279
  if (this._isOpen) {
@@ -5076,9 +5282,9 @@ class FeelPopup {
5076
5282
  }
5077
5283
  }
5078
5284
  }
5079
- FeelPopup.$inject = ['eventBus', 'config.propertiesPanel'];
5285
+ Popup.$inject = ['eventBus', 'config.propertiesPanel'];
5080
5286
 
5081
- class FeelPopupRenderer {
5287
+ class PopupRenderer {
5082
5288
  constructor(eventBus) {
5083
5289
  this._eventBus = eventBus;
5084
5290
  this._container = null;
@@ -5104,7 +5310,8 @@ class FeelPopupRenderer {
5104
5310
  // a custom renderer would have to use that context provider as well to have
5105
5311
  // access to the event bus and other services
5106
5312
  this._emit('feelPopup.open');
5107
- render(jsx(FeelPopup$1, {
5313
+ const Component = config.component;
5314
+ render(jsx(Component, {
5108
5315
  ...config,
5109
5316
  eventBus: this._eventBus
5110
5317
  }), element);
@@ -5128,7 +5335,7 @@ class FeelPopupRenderer {
5128
5335
  this._eventBus.fire(event, context);
5129
5336
  }
5130
5337
  }
5131
- FeelPopupRenderer.$inject = ['eventBus'];
5338
+ PopupRenderer.$inject = ['eventBus'];
5132
5339
 
5133
5340
  // helpers /////////////////
5134
5341
 
@@ -5146,8 +5353,8 @@ function getContainer(container) {
5146
5353
 
5147
5354
  var index = {
5148
5355
  __init__: ['feelPopup', 'feelPopupRenderer'],
5149
- feelPopup: ['type', FeelPopup],
5150
- feelPopupRenderer: ['type', FeelPopupRenderer]
5356
+ feelPopup: ['type', Popup],
5357
+ feelPopupRenderer: ['type', PopupRenderer]
5151
5358
  };
5152
5359
 
5153
5360
  export { Arrow as ArrowIcon, CheckboxEntry, CheckboxGroup, Close as CloseIcon, CollapsibleEntry, Create as CreateIcon, index$1 as DebounceInputModule, Delete as DeleteIcon, DescriptionContext, Description as DescriptionEntry, Drag as DragIcon, DropdownButton, ErrorsContext, EventContext, ExternalLink as ExternalLinkIcon, FeelCheckboxEntry, FeelEntry, Feel as FeelIcon, FeelLanguageContext, FeelNumberEntry, index as FeelPopupModule, FeelTemplatingEntry, FeelTextAreaEntry, FeelToggleSwitchEntry, Group, Header, HeaderButton, JsonEditorEntry, Launch as LaunchIcon, LayoutContext, List as ListEntry, ListGroup, ListItem, NumberFieldEntry, OpenPopup as OpenPopupIcon, Placeholder, PropertiesPanel, LayoutContext as PropertiesPanelContext, SelectEntry, Simple as SimpleEntry, TemplatingEntry, TextAreaEntry, TextfieldEntry as TextFieldEntry, ToggleSwitchEntry, TooltipContext, TooltipWrapper as TooltipEntry, isEdited$9 as isCheckboxEntryEdited, isEdited$5 as isFeelEntryEdited, isEdited$8 as isJsonEditorEntryEdited, isEdited$6 as isNumberFieldEntryEdited, isEdited$3 as isSelectEntryEdited, isEdited$2 as isSimpleEntryEdited, isEdited$4 as isTemplatingEntryEdited, isEdited$1 as isTextAreaEntryEdited, isEdited as isTextFieldEntryEdited, isEdited$7 as isToggleSwitchEntryEdited, useDebounce, useDescriptionContext, useElementVisible, useError, useErrors, useEvent, useKeyFactory, useLayoutState, usePrevious, useShowEntryEvent, useStaticCallback, useStickyIntersectionObserver, useTooltipContext };