@bpmn-io/properties-panel 3.7.0 → 3.8.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.js CHANGED
@@ -677,20 +677,24 @@ function Group(props) {
677
677
 
678
678
  // set edited state depending on all entries
679
679
  hooks.useEffect(() => {
680
- const hasOneEditedEntry = entries.find(entry => {
681
- const {
682
- id,
683
- isEdited
684
- } = entry;
685
- const entryNode = minDom.query(`[data-entry-id="${id}"]`);
686
- if (!minDash.isFunction(isEdited) || !entryNode) {
687
- return false;
688
- }
689
- const inputNode = minDom.query('.bio-properties-panel-input', entryNode);
690
- return isEdited(inputNode);
680
+ // 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
681
+ const scheduled = requestAnimationFrame(() => {
682
+ const hasOneEditedEntry = entries.find(entry => {
683
+ const {
684
+ id,
685
+ isEdited
686
+ } = entry;
687
+ const entryNode = minDom.query(`[data-entry-id="${id}"]`);
688
+ if (!minDash.isFunction(isEdited) || !entryNode) {
689
+ return false;
690
+ }
691
+ const inputNode = minDom.query('.bio-properties-panel-input', entryNode);
692
+ return isEdited(inputNode);
693
+ });
694
+ setEdited(hasOneEditedEntry);
691
695
  });
692
- setEdited(hasOneEditedEntry);
693
- }, [entries]);
696
+ return () => cancelAnimationFrame(scheduled);
697
+ }, [entries, setEdited]);
694
698
 
695
699
  // set error state depending on all entries
696
700
  const allErrors = useErrors();
@@ -1124,7 +1128,11 @@ function createDragger(fn, dragPreview) {
1124
1128
  // (2) setup drag listeners
1125
1129
 
1126
1130
  // attach drag + cleanup event
1127
- document.addEventListener('dragover', onDrag);
1131
+ // we need to do this to make sure we track cursor
1132
+ // movements before we reach other drag event handlers,
1133
+ // e.g. in child containers.
1134
+ document.addEventListener('dragover', onDrag, true);
1135
+ document.addEventListener('dragenter', preventDefault, true);
1128
1136
  document.addEventListener('dragend', onEnd);
1129
1137
  document.addEventListener('drop', preventDefault);
1130
1138
  }
@@ -1138,7 +1146,8 @@ function createDragger(fn, dragPreview) {
1138
1146
  return fn.call(self, event, delta);
1139
1147
  }
1140
1148
  function onEnd() {
1141
- document.removeEventListener('dragover', onDrag);
1149
+ document.removeEventListener('dragover', onDrag, true);
1150
+ document.removeEventListener('dragenter', preventDefault, true);
1142
1151
  document.removeEventListener('dragend', onEnd);
1143
1152
  document.removeEventListener('drop', preventDefault);
1144
1153
  }
@@ -1170,8 +1179,9 @@ const noop$3 = () => {};
1170
1179
  * @param {boolean} [props.returnFocus]
1171
1180
  * @param {boolean} [props.closeOnEscape]
1172
1181
  * @param {string} props.title
1182
+ * @param {Ref} [ref]
1173
1183
  */
1174
- function Popup(props) {
1184
+ function PopupComponent(props, globalRef) {
1175
1185
  const {
1176
1186
  container,
1177
1187
  className,
@@ -1187,7 +1197,8 @@ function Popup(props) {
1187
1197
  title
1188
1198
  } = props;
1189
1199
  const focusTrapRef = hooks.useRef(null);
1190
- const popupRef = hooks.useRef(null);
1200
+ const localRef = hooks.useRef(null);
1201
+ const popupRef = globalRef || localRef;
1191
1202
  const handleKeydown = event => {
1192
1203
  // do not allow keyboard events to bubble
1193
1204
  event.stopPropagation();
@@ -1249,6 +1260,7 @@ function Popup(props) {
1249
1260
  children: props.children
1250
1261
  }), container || document.body);
1251
1262
  }
1263
+ const Popup = compat.forwardRef(PopupComponent);
1252
1264
  Popup.Title = Title;
1253
1265
  Popup.Body = Body;
1254
1266
  Popup.Footer = Footer;
@@ -1257,6 +1269,7 @@ function Title(props) {
1257
1269
  children,
1258
1270
  className,
1259
1271
  draggable,
1272
+ emit = () => {},
1260
1273
  title,
1261
1274
  ...rest
1262
1275
  } = props;
@@ -1269,7 +1282,8 @@ function Title(props) {
1269
1282
  });
1270
1283
  const dragPreviewRef = hooks.useRef();
1271
1284
  const titleRef = hooks.useRef();
1272
- const onMove = minDash.throttle((_, delta) => {
1285
+ const onMove = (event, delta) => {
1286
+ cancel(event);
1273
1287
  const {
1274
1288
  x: dx,
1275
1289
  y: dy
@@ -1281,20 +1295,33 @@ function Title(props) {
1281
1295
  const popupParent = getPopupParent(titleRef.current);
1282
1296
  popupParent.style.top = newPosition.y + 'px';
1283
1297
  popupParent.style.left = newPosition.x + 'px';
1284
- });
1298
+
1299
+ // notify interested parties
1300
+ emit('dragover', {
1301
+ newPosition,
1302
+ delta
1303
+ });
1304
+ };
1285
1305
  const onMoveStart = event => {
1286
1306
  // initialize drag handler
1287
1307
  const onDragStart = createDragger(onMove, dragPreviewRef.current);
1288
1308
  onDragStart(event);
1309
+ event.stopPropagation();
1289
1310
  const popupParent = getPopupParent(titleRef.current);
1290
1311
  const bounds = popupParent.getBoundingClientRect();
1291
1312
  context.current.startPosition = {
1292
1313
  x: bounds.left,
1293
1314
  y: bounds.top
1294
1315
  };
1316
+
1317
+ // notify interested parties
1318
+ emit('dragstart');
1295
1319
  };
1296
1320
  const onMoveEnd = () => {
1297
1321
  context.current.newPosition = null;
1322
+
1323
+ // notify interested parties
1324
+ emit('dragend');
1298
1325
  };
1299
1326
  return jsxRuntime.jsxs("div", {
1300
1327
  class: classnames__default["default"]('bio-properties-panel-popup__header', draggable && 'draggable', className),
@@ -1347,12 +1374,20 @@ function Footer(props) {
1347
1374
  function getPopupParent(node) {
1348
1375
  return node.closest('.bio-properties-panel-popup');
1349
1376
  }
1377
+ function cancel(event) {
1378
+ event.preventDefault();
1379
+ event.stopPropagation();
1380
+ }
1350
1381
 
1351
1382
  const FEEL_POPUP_WIDTH = 700;
1352
1383
  const FEEL_POPUP_HEIGHT = 250;
1353
1384
 
1354
1385
  /**
1355
- * FEEL popup component, built as a singleton.
1386
+ * FEEL popup component, built as a singleton. Emits lifecycle events as follows:
1387
+ * - `feelPopup.open` - fired before the popup is mounted
1388
+ * - `feelPopup.opened` - fired after the popup is mounted. Event context contains the DOM node of the popup
1389
+ * - `feelPopup.close` - fired before the popup is unmounted. Event context contains the DOM node of the popup
1390
+ * - `feelPopup.closed` - fired after the popup is unmounted
1356
1391
  */
1357
1392
  function FEELPopupRoot(props) {
1358
1393
  const {
@@ -1375,17 +1410,21 @@ function FEELPopupRoot(props) {
1375
1410
  const isOpen = hooks.useCallback(() => {
1376
1411
  return !!open;
1377
1412
  }, [open]);
1413
+ useUpdateEffect(() => {
1414
+ if (!open) {
1415
+ emit('closed');
1416
+ }
1417
+ }, [open]);
1378
1418
  const handleOpen = (entryId, config, _sourceElement) => {
1379
1419
  setSource(entryId);
1380
1420
  setPopupConfig(config);
1381
1421
  setOpen(true);
1382
1422
  setSourceElement(_sourceElement);
1383
- emit('opened');
1423
+ emit('open');
1384
1424
  };
1385
1425
  const handleClose = () => {
1386
1426
  setOpen(false);
1387
1427
  setSource(null);
1388
- emit('closed');
1389
1428
  };
1390
1429
  const feelPopupContext = {
1391
1430
  open: handleOpen,
@@ -1428,6 +1467,7 @@ function FEELPopupRoot(props) {
1428
1467
  onClose: handleClose,
1429
1468
  container: popupContainer,
1430
1469
  sourceElement: sourceElement,
1470
+ emit: emit,
1431
1471
  ...popupConfig
1432
1472
  }), props.children]
1433
1473
  });
@@ -1446,9 +1486,11 @@ function FeelPopupComponent(props) {
1446
1486
  tooltipContainer,
1447
1487
  type,
1448
1488
  value,
1449
- variables
1489
+ variables,
1490
+ emit
1450
1491
  } = props;
1451
1492
  const editorRef = hooks.useRef();
1493
+ const popupRef = hooks.useRef();
1452
1494
  const isAutoCompletionOpen = hooks.useRef(false);
1453
1495
  const handleSetReturnFocus = () => {
1454
1496
  sourceElement && sourceElement.focus();
@@ -1471,9 +1513,18 @@ function FeelPopupComponent(props) {
1471
1513
  }
1472
1514
  }
1473
1515
  };
1516
+ hooks.useEffect(() => {
1517
+ emit('opened', {
1518
+ domNode: popupRef.current
1519
+ });
1520
+ return () => emit('close', {
1521
+ domNode: popupRef.current
1522
+ });
1523
+ }, []);
1474
1524
  return jsxRuntime.jsxs(Popup, {
1475
1525
  container: container,
1476
1526
  className: "bio-properties-panel-feel-popup",
1527
+ emit: emit,
1477
1528
  position: position,
1478
1529
  title: title,
1479
1530
  onClose: onClose
@@ -1486,8 +1537,10 @@ function FeelPopupComponent(props) {
1486
1537
  onPostDeactivate: handleSetReturnFocus,
1487
1538
  height: FEEL_POPUP_HEIGHT,
1488
1539
  width: FEEL_POPUP_WIDTH,
1540
+ ref: popupRef,
1489
1541
  children: [jsxRuntime.jsx(Popup.Title, {
1490
1542
  title: title,
1543
+ emit: emit,
1491
1544
  draggable: true
1492
1545
  }), jsxRuntime.jsx(Popup.Body, {
1493
1546
  children: jsxRuntime.jsxs("div", {
@@ -1538,6 +1591,23 @@ function autoCompletionOpen(element) {
1538
1591
  return element.closest('.cm-editor').querySelector('.cm-tooltip-autocomplete');
1539
1592
  }
1540
1593
 
1594
+ /**
1595
+ * This hook behaves like useEffect, but does not trigger on the first render.
1596
+ *
1597
+ * @param {Function} effect
1598
+ * @param {Array} deps
1599
+ */
1600
+ function useUpdateEffect(effect, deps) {
1601
+ const isMounted = hooks.useRef(false);
1602
+ hooks.useEffect(() => {
1603
+ if (isMounted.current) {
1604
+ return effect();
1605
+ } else {
1606
+ isMounted.current = true;
1607
+ }
1608
+ }, deps);
1609
+ }
1610
+
1541
1611
  function ToggleSwitch(props) {
1542
1612
  const {
1543
1613
  id,
@@ -2489,7 +2559,7 @@ function calculatePopupPosition(element) {
2489
2559
 
2490
2560
  // todo(pinussilvestrus): make this configurable in the future
2491
2561
  function getPopupTitle(element, label) {
2492
- let popupTitle;
2562
+ let popupTitle = '';
2493
2563
  if (element && element.type) {
2494
2564
  popupTitle = `${element.type} / `;
2495
2565
  }