@bpmn-io/properties-panel 3.7.1 → 3.9.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,9 @@ 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;
1202
+ const containerNode = hooks.useMemo(() => getContainerNode(container), [container]);
1191
1203
  const handleKeydown = event => {
1192
1204
  // do not allow keyboard events to bubble
1193
1205
  event.stopPropagation();
@@ -1247,8 +1259,9 @@ function Popup(props) {
1247
1259
  class: classnames__default["default"]('bio-properties-panel-popup', className),
1248
1260
  style: style,
1249
1261
  children: props.children
1250
- }), container || document.body);
1262
+ }), containerNode || document.body);
1251
1263
  }
1264
+ const Popup = compat.forwardRef(PopupComponent);
1252
1265
  Popup.Title = Title;
1253
1266
  Popup.Body = Body;
1254
1267
  Popup.Footer = Footer;
@@ -1257,6 +1270,7 @@ function Title(props) {
1257
1270
  children,
1258
1271
  className,
1259
1272
  draggable,
1273
+ emit = () => {},
1260
1274
  title,
1261
1275
  ...rest
1262
1276
  } = props;
@@ -1269,7 +1283,8 @@ function Title(props) {
1269
1283
  });
1270
1284
  const dragPreviewRef = hooks.useRef();
1271
1285
  const titleRef = hooks.useRef();
1272
- const onMove = minDash.throttle((_, delta) => {
1286
+ const onMove = (event, delta) => {
1287
+ cancel(event);
1273
1288
  const {
1274
1289
  x: dx,
1275
1290
  y: dy
@@ -1281,20 +1296,33 @@ function Title(props) {
1281
1296
  const popupParent = getPopupParent(titleRef.current);
1282
1297
  popupParent.style.top = newPosition.y + 'px';
1283
1298
  popupParent.style.left = newPosition.x + 'px';
1284
- });
1299
+
1300
+ // notify interested parties
1301
+ emit('dragover', {
1302
+ newPosition,
1303
+ delta
1304
+ });
1305
+ };
1285
1306
  const onMoveStart = event => {
1286
1307
  // initialize drag handler
1287
1308
  const onDragStart = createDragger(onMove, dragPreviewRef.current);
1288
1309
  onDragStart(event);
1310
+ event.stopPropagation();
1289
1311
  const popupParent = getPopupParent(titleRef.current);
1290
1312
  const bounds = popupParent.getBoundingClientRect();
1291
1313
  context.current.startPosition = {
1292
1314
  x: bounds.left,
1293
1315
  y: bounds.top
1294
1316
  };
1317
+
1318
+ // notify interested parties
1319
+ emit('dragstart');
1295
1320
  };
1296
1321
  const onMoveEnd = () => {
1297
1322
  context.current.newPosition = null;
1323
+
1324
+ // notify interested parties
1325
+ emit('dragend');
1298
1326
  };
1299
1327
  return jsxRuntime.jsxs("div", {
1300
1328
  class: classnames__default["default"]('bio-properties-panel-popup__header', draggable && 'draggable', className),
@@ -1347,12 +1375,26 @@ function Footer(props) {
1347
1375
  function getPopupParent(node) {
1348
1376
  return node.closest('.bio-properties-panel-popup');
1349
1377
  }
1378
+ function cancel(event) {
1379
+ event.preventDefault();
1380
+ event.stopPropagation();
1381
+ }
1382
+ function getContainerNode(node) {
1383
+ if (typeof node === 'string') {
1384
+ return minDom.query(node);
1385
+ }
1386
+ return node;
1387
+ }
1350
1388
 
1351
1389
  const FEEL_POPUP_WIDTH = 700;
1352
1390
  const FEEL_POPUP_HEIGHT = 250;
1353
1391
 
1354
1392
  /**
1355
- * FEEL popup component, built as a singleton.
1393
+ * FEEL popup component, built as a singleton. Emits lifecycle events as follows:
1394
+ * - `feelPopup.open` - fired before the popup is mounted
1395
+ * - `feelPopup.opened` - fired after the popup is mounted. Event context contains the DOM node of the popup
1396
+ * - `feelPopup.close` - fired before the popup is unmounted. Event context contains the DOM node of the popup
1397
+ * - `feelPopup.closed` - fired after the popup is unmounted
1356
1398
  */
1357
1399
  function FEELPopupRoot(props) {
1358
1400
  const {
@@ -1375,17 +1417,21 @@ function FEELPopupRoot(props) {
1375
1417
  const isOpen = hooks.useCallback(() => {
1376
1418
  return !!open;
1377
1419
  }, [open]);
1420
+ useUpdateEffect(() => {
1421
+ if (!open) {
1422
+ emit('closed');
1423
+ }
1424
+ }, [open]);
1378
1425
  const handleOpen = (entryId, config, _sourceElement) => {
1379
1426
  setSource(entryId);
1380
1427
  setPopupConfig(config);
1381
1428
  setOpen(true);
1382
1429
  setSourceElement(_sourceElement);
1383
- emit('opened');
1430
+ emit('open');
1384
1431
  };
1385
1432
  const handleClose = () => {
1386
1433
  setOpen(false);
1387
1434
  setSource(null);
1388
- emit('closed');
1389
1435
  };
1390
1436
  const feelPopupContext = {
1391
1437
  open: handleOpen,
@@ -1428,6 +1474,7 @@ function FEELPopupRoot(props) {
1428
1474
  onClose: handleClose,
1429
1475
  container: popupContainer,
1430
1476
  sourceElement: sourceElement,
1477
+ emit: emit,
1431
1478
  ...popupConfig
1432
1479
  }), props.children]
1433
1480
  });
@@ -1446,9 +1493,11 @@ function FeelPopupComponent(props) {
1446
1493
  tooltipContainer,
1447
1494
  type,
1448
1495
  value,
1449
- variables
1496
+ variables,
1497
+ emit
1450
1498
  } = props;
1451
1499
  const editorRef = hooks.useRef();
1500
+ const popupRef = hooks.useRef();
1452
1501
  const isAutoCompletionOpen = hooks.useRef(false);
1453
1502
  const handleSetReturnFocus = () => {
1454
1503
  sourceElement && sourceElement.focus();
@@ -1471,9 +1520,18 @@ function FeelPopupComponent(props) {
1471
1520
  }
1472
1521
  }
1473
1522
  };
1523
+ hooks.useEffect(() => {
1524
+ emit('opened', {
1525
+ domNode: popupRef.current
1526
+ });
1527
+ return () => emit('close', {
1528
+ domNode: popupRef.current
1529
+ });
1530
+ }, []);
1474
1531
  return jsxRuntime.jsxs(Popup, {
1475
1532
  container: container,
1476
1533
  className: "bio-properties-panel-feel-popup",
1534
+ emit: emit,
1477
1535
  position: position,
1478
1536
  title: title,
1479
1537
  onClose: onClose
@@ -1486,8 +1544,10 @@ function FeelPopupComponent(props) {
1486
1544
  onPostDeactivate: handleSetReturnFocus,
1487
1545
  height: FEEL_POPUP_HEIGHT,
1488
1546
  width: FEEL_POPUP_WIDTH,
1547
+ ref: popupRef,
1489
1548
  children: [jsxRuntime.jsx(Popup.Title, {
1490
1549
  title: title,
1550
+ emit: emit,
1491
1551
  draggable: true
1492
1552
  }), jsxRuntime.jsx(Popup.Body, {
1493
1553
  children: jsxRuntime.jsxs("div", {
@@ -1538,6 +1598,23 @@ function autoCompletionOpen(element) {
1538
1598
  return element.closest('.cm-editor').querySelector('.cm-tooltip-autocomplete');
1539
1599
  }
1540
1600
 
1601
+ /**
1602
+ * This hook behaves like useEffect, but does not trigger on the first render.
1603
+ *
1604
+ * @param {Function} effect
1605
+ * @param {Array} deps
1606
+ */
1607
+ function useUpdateEffect(effect, deps) {
1608
+ const isMounted = hooks.useRef(false);
1609
+ hooks.useEffect(() => {
1610
+ if (isMounted.current) {
1611
+ return effect();
1612
+ } else {
1613
+ isMounted.current = true;
1614
+ }
1615
+ }, deps);
1616
+ }
1617
+
1541
1618
  function ToggleSwitch(props) {
1542
1619
  const {
1543
1620
  id,