@bpmn-io/form-js-editor 1.3.0 → 1.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1994,7 +1994,7 @@ textarea.bio-properties-panel-input {
1994
1994
  --popup-title-color: hsla(0, 0%, 0%, 1);
1995
1995
  --feel-popup-close-background-color: hsla(219, 99%, 53%, 1);
1996
1996
  --feel-popup-gutters-background-color: hsla(0, 0%, 90%, 1);
1997
- position: absolute;
1997
+ position: fixed;
1998
1998
  display: flex;
1999
1999
  flex: auto;
2000
2000
  flex-direction: column;
@@ -1237,7 +1237,7 @@ textarea.bio-properties-panel-input {
1237
1237
  --feel-popup-close-background-color: hsla(219, 99%, 53%, 1);
1238
1238
  --feel-popup-gutters-background-color: hsla(0, 0%, 90%, 1);
1239
1239
 
1240
- position: absolute;
1240
+ position: fixed;
1241
1241
  display: flex;
1242
1242
  flex: auto;
1243
1243
  flex-direction: column;
package/dist/index.cjs CHANGED
@@ -5263,20 +5263,24 @@ function Group(props) {
5263
5263
 
5264
5264
  // set edited state depending on all entries
5265
5265
  hooks.useEffect(() => {
5266
- const hasOneEditedEntry = entries.find(entry => {
5267
- const {
5268
- id,
5269
- isEdited
5270
- } = entry;
5271
- const entryNode = minDom.query(`[data-entry-id="${id}"]`);
5272
- if (!minDash.isFunction(isEdited) || !entryNode) {
5273
- return false;
5274
- }
5275
- const inputNode = minDom.query('.bio-properties-panel-input', entryNode);
5276
- return isEdited(inputNode);
5266
+ // 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
5267
+ const scheduled = requestAnimationFrame(() => {
5268
+ const hasOneEditedEntry = entries.find(entry => {
5269
+ const {
5270
+ id,
5271
+ isEdited
5272
+ } = entry;
5273
+ const entryNode = minDom.query(`[data-entry-id="${id}"]`);
5274
+ if (!minDash.isFunction(isEdited) || !entryNode) {
5275
+ return false;
5276
+ }
5277
+ const inputNode = minDom.query('.bio-properties-panel-input', entryNode);
5278
+ return isEdited(inputNode);
5279
+ });
5280
+ setEdited(hasOneEditedEntry);
5277
5281
  });
5278
- setEdited(hasOneEditedEntry);
5279
- }, [entries]);
5282
+ return () => cancelAnimationFrame(scheduled);
5283
+ }, [entries, setEdited]);
5280
5284
 
5281
5285
  // set error state depending on all entries
5282
5286
  const allErrors = useErrors();
@@ -5704,7 +5708,11 @@ function createDragger(fn, dragPreview) {
5704
5708
  // (2) setup drag listeners
5705
5709
 
5706
5710
  // attach drag + cleanup event
5707
- document.addEventListener('dragover', onDrag);
5711
+ // we need to do this to make sure we track cursor
5712
+ // movements before we reach other drag event handlers,
5713
+ // e.g. in child containers.
5714
+ document.addEventListener('dragover', onDrag, true);
5715
+ document.addEventListener('dragenter', preventDefault, true);
5708
5716
  document.addEventListener('dragend', onEnd);
5709
5717
  document.addEventListener('drop', preventDefault);
5710
5718
  }
@@ -5718,7 +5726,8 @@ function createDragger(fn, dragPreview) {
5718
5726
  return fn.call(self, event, delta);
5719
5727
  }
5720
5728
  function onEnd() {
5721
- document.removeEventListener('dragover', onDrag);
5729
+ document.removeEventListener('dragover', onDrag, true);
5730
+ document.removeEventListener('dragenter', preventDefault, true);
5722
5731
  document.removeEventListener('dragend', onEnd);
5723
5732
  document.removeEventListener('drop', preventDefault);
5724
5733
  }
@@ -5749,8 +5758,9 @@ const noop$3 = () => {};
5749
5758
  * @param {boolean} [props.returnFocus]
5750
5759
  * @param {boolean} [props.closeOnEscape]
5751
5760
  * @param {string} props.title
5761
+ * @param {Ref} [ref]
5752
5762
  */
5753
- function Popup(props) {
5763
+ function PopupComponent(props, globalRef) {
5754
5764
  const {
5755
5765
  container,
5756
5766
  className,
@@ -5766,7 +5776,8 @@ function Popup(props) {
5766
5776
  title
5767
5777
  } = props;
5768
5778
  const focusTrapRef = hooks.useRef(null);
5769
- const popupRef = hooks.useRef(null);
5779
+ const localRef = hooks.useRef(null);
5780
+ const popupRef = globalRef || localRef;
5770
5781
  const handleKeydown = event => {
5771
5782
  // do not allow keyboard events to bubble
5772
5783
  event.stopPropagation();
@@ -5828,6 +5839,7 @@ function Popup(props) {
5828
5839
  children: props.children
5829
5840
  }), container || document.body);
5830
5841
  }
5842
+ const Popup = React.forwardRef(PopupComponent);
5831
5843
  Popup.Title = Title;
5832
5844
  Popup.Body = Body;
5833
5845
  Popup.Footer = Footer;
@@ -5836,6 +5848,7 @@ function Title(props) {
5836
5848
  children,
5837
5849
  className,
5838
5850
  draggable,
5851
+ emit = () => {},
5839
5852
  title,
5840
5853
  ...rest
5841
5854
  } = props;
@@ -5848,7 +5861,8 @@ function Title(props) {
5848
5861
  });
5849
5862
  const dragPreviewRef = hooks.useRef();
5850
5863
  const titleRef = hooks.useRef();
5851
- const onMove = minDash.throttle((_, delta) => {
5864
+ const onMove = (event, delta) => {
5865
+ cancel(event);
5852
5866
  const {
5853
5867
  x: dx,
5854
5868
  y: dy
@@ -5860,20 +5874,33 @@ function Title(props) {
5860
5874
  const popupParent = getPopupParent(titleRef.current);
5861
5875
  popupParent.style.top = newPosition.y + 'px';
5862
5876
  popupParent.style.left = newPosition.x + 'px';
5863
- });
5877
+
5878
+ // notify interested parties
5879
+ emit('dragover', {
5880
+ newPosition,
5881
+ delta
5882
+ });
5883
+ };
5864
5884
  const onMoveStart = event => {
5865
5885
  // initialize drag handler
5866
5886
  const onDragStart = createDragger(onMove, dragPreviewRef.current);
5867
5887
  onDragStart(event);
5888
+ event.stopPropagation();
5868
5889
  const popupParent = getPopupParent(titleRef.current);
5869
5890
  const bounds = popupParent.getBoundingClientRect();
5870
5891
  context.current.startPosition = {
5871
5892
  x: bounds.left,
5872
5893
  y: bounds.top
5873
5894
  };
5895
+
5896
+ // notify interested parties
5897
+ emit('dragstart');
5874
5898
  };
5875
5899
  const onMoveEnd = () => {
5876
5900
  context.current.newPosition = null;
5901
+
5902
+ // notify interested parties
5903
+ emit('dragend');
5877
5904
  };
5878
5905
  return jsxRuntime.jsxs("div", {
5879
5906
  class: classnames('bio-properties-panel-popup__header', draggable && 'draggable', className),
@@ -5926,11 +5953,19 @@ function Footer(props) {
5926
5953
  function getPopupParent(node) {
5927
5954
  return node.closest('.bio-properties-panel-popup');
5928
5955
  }
5956
+ function cancel(event) {
5957
+ event.preventDefault();
5958
+ event.stopPropagation();
5959
+ }
5929
5960
  const FEEL_POPUP_WIDTH = 700;
5930
5961
  const FEEL_POPUP_HEIGHT = 250;
5931
5962
 
5932
5963
  /**
5933
- * FEEL popup component, built as a singleton.
5964
+ * FEEL popup component, built as a singleton. Emits lifecycle events as follows:
5965
+ * - `feelPopup.open` - fired before the popup is mounted
5966
+ * - `feelPopup.opened` - fired after the popup is mounted. Event context contains the DOM node of the popup
5967
+ * - `feelPopup.close` - fired before the popup is unmounted. Event context contains the DOM node of the popup
5968
+ * - `feelPopup.closed` - fired after the popup is unmounted
5934
5969
  */
5935
5970
  function FEELPopupRoot(props) {
5936
5971
  const {
@@ -5953,17 +5988,21 @@ function FEELPopupRoot(props) {
5953
5988
  const isOpen = hooks.useCallback(() => {
5954
5989
  return !!open;
5955
5990
  }, [open]);
5991
+ useUpdateEffect(() => {
5992
+ if (!open) {
5993
+ emit('closed');
5994
+ }
5995
+ }, [open]);
5956
5996
  const handleOpen = (entryId, config, _sourceElement) => {
5957
5997
  setSource(entryId);
5958
5998
  setPopupConfig(config);
5959
5999
  setOpen(true);
5960
6000
  setSourceElement(_sourceElement);
5961
- emit('opened');
6001
+ emit('open');
5962
6002
  };
5963
6003
  const handleClose = () => {
5964
6004
  setOpen(false);
5965
6005
  setSource(null);
5966
- emit('closed');
5967
6006
  };
5968
6007
  const feelPopupContext = {
5969
6008
  open: handleOpen,
@@ -6006,6 +6045,7 @@ function FEELPopupRoot(props) {
6006
6045
  onClose: handleClose,
6007
6046
  container: popupContainer,
6008
6047
  sourceElement: sourceElement,
6048
+ emit: emit,
6009
6049
  ...popupConfig
6010
6050
  }), props.children]
6011
6051
  });
@@ -6024,9 +6064,11 @@ function FeelPopupComponent(props) {
6024
6064
  tooltipContainer,
6025
6065
  type,
6026
6066
  value,
6027
- variables
6067
+ variables,
6068
+ emit
6028
6069
  } = props;
6029
6070
  const editorRef = hooks.useRef();
6071
+ const popupRef = hooks.useRef();
6030
6072
  const isAutoCompletionOpen = hooks.useRef(false);
6031
6073
  const handleSetReturnFocus = () => {
6032
6074
  sourceElement && sourceElement.focus();
@@ -6049,9 +6091,18 @@ function FeelPopupComponent(props) {
6049
6091
  }
6050
6092
  }
6051
6093
  };
6094
+ hooks.useEffect(() => {
6095
+ emit('opened', {
6096
+ domNode: popupRef.current
6097
+ });
6098
+ return () => emit('close', {
6099
+ domNode: popupRef.current
6100
+ });
6101
+ }, []);
6052
6102
  return jsxRuntime.jsxs(Popup, {
6053
6103
  container: container,
6054
6104
  className: "bio-properties-panel-feel-popup",
6105
+ emit: emit,
6055
6106
  position: position,
6056
6107
  title: title,
6057
6108
  onClose: onClose
@@ -6065,8 +6116,10 @@ function FeelPopupComponent(props) {
6065
6116
  onPostDeactivate: handleSetReturnFocus,
6066
6117
  height: FEEL_POPUP_HEIGHT,
6067
6118
  width: FEEL_POPUP_WIDTH,
6119
+ ref: popupRef,
6068
6120
  children: [jsxRuntime.jsx(Popup.Title, {
6069
6121
  title: title,
6122
+ emit: emit,
6070
6123
  draggable: true
6071
6124
  }), jsxRuntime.jsx(Popup.Body, {
6072
6125
  children: jsxRuntime.jsxs("div", {
@@ -6116,6 +6169,23 @@ function prefixId$8(id) {
6116
6169
  function autoCompletionOpen(element) {
6117
6170
  return element.closest('.cm-editor').querySelector('.cm-tooltip-autocomplete');
6118
6171
  }
6172
+
6173
+ /**
6174
+ * This hook behaves like useEffect, but does not trigger on the first render.
6175
+ *
6176
+ * @param {Function} effect
6177
+ * @param {Array} deps
6178
+ */
6179
+ function useUpdateEffect(effect, deps) {
6180
+ const isMounted = hooks.useRef(false);
6181
+ hooks.useEffect(() => {
6182
+ if (isMounted.current) {
6183
+ return effect();
6184
+ } else {
6185
+ isMounted.current = true;
6186
+ }
6187
+ }, deps);
6188
+ }
6119
6189
  function ToggleSwitch(props) {
6120
6190
  const {
6121
6191
  id,
@@ -7011,7 +7081,7 @@ function calculatePopupPosition(element) {
7011
7081
 
7012
7082
  // todo(pinussilvestrus): make this configurable in the future
7013
7083
  function getPopupTitle(element, label) {
7014
- let popupTitle;
7084
+ let popupTitle = '';
7015
7085
  if (element && element.type) {
7016
7086
  popupTitle = `${element.type} / `;
7017
7087
  }