@bpmn-io/form-js-editor 1.19.0 → 1.20.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.
@@ -1979,7 +1979,7 @@ textarea.bio-properties-panel-input {
1979
1979
  display: flex;
1980
1980
  color: var(--color-white, white);
1981
1981
  position: fixed;
1982
- z-index: 1000;
1982
+ z-index: 1001;
1983
1983
  max-width: 300px;
1984
1984
  font-size: var(--text-size-small);
1985
1985
  font-family: var(--font-family);
@@ -1176,7 +1176,7 @@ textarea.bio-properties-panel-input {
1176
1176
  display: flex;
1177
1177
  color: var(--color-white, white);
1178
1178
  position: fixed;
1179
- z-index: 1000;
1179
+ z-index: 1001;
1180
1180
  max-width: 300px;
1181
1181
  font-size: var(--text-size-small);
1182
1182
  font-family: var(--font-family);
package/dist/index.cjs CHANGED
@@ -5720,6 +5720,8 @@ function Tooltip(props) {
5720
5720
  hideDelay = 250
5721
5721
  } = props;
5722
5722
  const [visible, setVisible] = hooks.useState(false);
5723
+ const [tooltipPosition, setTooltipPosition] = hooks.useState(null);
5724
+ const [arrowOffset, setArrowOffset] = hooks.useState(null);
5723
5725
  const showTimeoutRef = hooks.useRef(null);
5724
5726
  const hideTimeoutRef = hooks.useRef(null);
5725
5727
  const wrapperRef = hooks.useRef(null);
@@ -5773,6 +5775,20 @@ function Tooltip(props) {
5773
5775
  document.removeEventListener('mousedown', handleClickOutside);
5774
5776
  };
5775
5777
  }, [visible, hide]);
5778
+ hooks.useLayoutEffect(() => {
5779
+ if (!visible || position) {
5780
+ setTooltipPosition(null);
5781
+ setArrowOffset(null);
5782
+ return;
5783
+ }
5784
+ if (!wrapperRef.current || !tooltipRef.current) return;
5785
+ const {
5786
+ tooltipPosition: newPosition,
5787
+ arrowOffset: newArrowOffset
5788
+ } = getTooltipPosition(wrapperRef.current, tooltipRef.current, direction);
5789
+ setTooltipPosition(newPosition);
5790
+ setArrowOffset(newArrowOffset);
5791
+ }, [visible, position]);
5776
5792
  const handleMouseLeave = ({
5777
5793
  relatedTarget
5778
5794
  }) => {
@@ -5808,12 +5824,14 @@ function Tooltip(props) {
5808
5824
  e.code === 'Escape' && hide(false);
5809
5825
  };
5810
5826
  const renderTooltip = () => {
5827
+ const tooltipStyle = position || (tooltipPosition ? `right: ${tooltipPosition.right}; top: ${tooltipPosition.top}px;` : undefined);
5828
+ const arrowStyle = arrowOffset != null ? `margin-top: ${arrowOffset}px;` : undefined;
5811
5829
  return jsxRuntime.jsxs("div", {
5812
5830
  class: `bio-properties-panel-tooltip ${direction}`,
5813
5831
  role: "tooltip",
5814
5832
  id: "bio-properties-panel-tooltip",
5815
5833
  "aria-labelledby": forId,
5816
- style: position || getTooltipPosition(wrapperRef.current),
5834
+ style: tooltipStyle,
5817
5835
  ref: tooltipRef,
5818
5836
  onClick: e => e.stopPropagation(),
5819
5837
  onMouseEnter: handleTooltipMouseEnter,
@@ -5822,7 +5840,8 @@ function Tooltip(props) {
5822
5840
  class: "bio-properties-panel-tooltip-content",
5823
5841
  children: value
5824
5842
  }), jsxRuntime.jsx("div", {
5825
- class: "bio-properties-panel-tooltip-arrow"
5843
+ class: "bio-properties-panel-tooltip-arrow",
5844
+ style: arrowStyle
5826
5845
  })]
5827
5846
  });
5828
5847
  };
@@ -5841,11 +5860,47 @@ function Tooltip(props) {
5841
5860
 
5842
5861
  // helper
5843
5862
 
5844
- function getTooltipPosition(refElement) {
5863
+ function getTooltipPosition(refElement, tooltipElement, direction) {
5864
+ if (!refElement) {
5865
+ return {
5866
+ tooltipPosition: null,
5867
+ arrowOffset: null
5868
+ };
5869
+ }
5845
5870
  const refPosition = refElement.getBoundingClientRect();
5846
5871
  const right = `calc(100% - ${refPosition.x}px)`;
5847
- const top = `${refPosition.top - 10}px`;
5848
- return `right: ${right}; top: ${top};`;
5872
+ let top = refPosition.top - 10;
5873
+ let arrowOffset = null;
5874
+
5875
+ // Ensure that the tooltip is within the viewport, adjust the top position if needed.
5876
+ // This is only relevant for the 'right' direction for now
5877
+ if (tooltipElement && direction === 'right') {
5878
+ const tooltipRect = tooltipElement.getBoundingClientRect();
5879
+ const viewportHeight = window.innerHeight;
5880
+ const minTop = 0;
5881
+ const maxTop = viewportHeight - tooltipRect.height;
5882
+ const originalTop = top;
5883
+ if (top > maxTop) {
5884
+ top = maxTop;
5885
+ }
5886
+ if (top < minTop) {
5887
+ top = minTop;
5888
+ }
5889
+
5890
+ // Adjust the arrow position if the tooltip had to be moved to stay within viewport
5891
+ if (top !== originalTop) {
5892
+ const defaultMarginTop = 16;
5893
+ const topDiff = top - originalTop;
5894
+ arrowOffset = defaultMarginTop - topDiff;
5895
+ }
5896
+ }
5897
+ return {
5898
+ tooltipPosition: {
5899
+ right,
5900
+ top
5901
+ },
5902
+ arrowOffset
5903
+ };
5849
5904
  }
5850
5905
 
5851
5906
  /**
@@ -6083,12 +6138,17 @@ function useStickyIntersectionObserver(ref, scrollContainerSelector, setSticky)
6083
6138
  * The `callback` reference is static and can be safely used in external
6084
6139
  * libraries or as a prop that does not cause rerendering of children.
6085
6140
  *
6141
+ * The ref update is deferred to useLayoutEffect to prevent stale-closure
6142
+ * bugs when Chrome fires blur on elements removed during re-render.
6143
+ *
6086
6144
  * @param {Function} callback function with changing reference
6087
6145
  * @returns {Function} static function reference
6088
6146
  */
6089
6147
  function useStaticCallback(callback) {
6090
6148
  const callbackRef = hooks.useRef(callback);
6091
- callbackRef.current = callback;
6149
+ hooks.useLayoutEffect(() => {
6150
+ callbackRef.current = callback;
6151
+ });
6092
6152
  return hooks.useCallback((...args) => callbackRef.current(...args), []);
6093
6153
  }
6094
6154
  function useElementVisible(element) {
@@ -7344,7 +7404,7 @@ function ToggleSwitchEntry(props) {
7344
7404
  inline: inline,
7345
7405
  tooltip: tooltip,
7346
7406
  element: element
7347
- }), jsxRuntime.jsx(Description$1, {
7407
+ }, element), jsxRuntime.jsx(Description$1, {
7348
7408
  forId: id,
7349
7409
  element: element,
7350
7410
  value: description
@@ -7517,7 +7577,7 @@ function prefixId$6(id) {
7517
7577
  const noop$2 = () => {};
7518
7578
 
7519
7579
  /**
7520
- * @typedef {'required'|'optional'|'static'} FeelType
7580
+ * @typedef {'required'|'optional'|'optional-default-enabled'|'static'} FeelType
7521
7581
  */
7522
7582
 
7523
7583
  /**
@@ -7560,7 +7620,7 @@ function FeelTextfield(props) {
7560
7620
  OptionalComponent = OptionalFeelInput,
7561
7621
  tooltip
7562
7622
  } = props;
7563
- const [localValue, setLocalValue] = hooks.useState(value);
7623
+ const [localValue, setLocalValue] = hooks.useState(getInitialFeelLocalValue(feel, value));
7564
7624
  const editorRef = useShowEntryEvent(id);
7565
7625
  const containerRef = hooks.useRef();
7566
7626
  const onInput = hooks.useCallback(newValue => {
@@ -7569,8 +7629,8 @@ function FeelTextfield(props) {
7569
7629
  const newModelValue = newValue === '' || newValue === '=' ? undefined : newValue;
7570
7630
  commitValue(newModelValue);
7571
7631
  }, [commitValue]);
7572
- const feelActive = minDash.isString(localValue) && localValue.startsWith('=') || feel === 'required';
7573
- const feelOnlyValue = minDash.isString(localValue) && localValue.startsWith('=') ? localValue.substring(1) : localValue;
7632
+ const feelActive = isFeelActive(feel, localValue);
7633
+ const feelOnlyValue = getFeelValue(localValue);
7574
7634
  const feelLanguageContext = hooks.useContext(FeelLanguageContext);
7575
7635
  const [focus, _setFocus] = hooks.useState(undefined);
7576
7636
  const {
@@ -7633,7 +7693,7 @@ function FeelTextfield(props) {
7633
7693
  };
7634
7694
  const handleOnKeyDown = e => {
7635
7695
  if (isCmdWithChar(e)) {
7636
- handleInput.flush();
7696
+ handleInput.flush?.();
7637
7697
  }
7638
7698
  };
7639
7699
  const handleLint = useStaticCallback((lint = []) => {
@@ -7757,7 +7817,7 @@ function FeelTextfield(props) {
7757
7817
  ref: containerRef,
7758
7818
  children: [jsxRuntime.jsx(FeelIndicator, {
7759
7819
  active: feelActive,
7760
- disabled: feel !== 'optional' || disabled,
7820
+ disabled: !isFeelOptional(feel) || disabled,
7761
7821
  onClick: handleFeelToggle
7762
7822
  }), feelActive ? jsxRuntime.jsx(FeelEditor, {
7763
7823
  name: id,
@@ -8215,6 +8275,68 @@ function prefixId$5(id) {
8215
8275
  return `bio-properties-panel-${id}`;
8216
8276
  }
8217
8277
 
8278
+ /**
8279
+ * Determine if FEEL is optional for the configured {@link FeelType}.
8280
+ *
8281
+ * @param {FeelType} feelType
8282
+ *
8283
+ * @return {boolean}
8284
+ */
8285
+ function isFeelOptional(feelType) {
8286
+ return feelType === 'optional' || feelType === 'optional-default-enabled';
8287
+ }
8288
+
8289
+ /**
8290
+ * Determine if FEEL editing is currently active.
8291
+ *
8292
+ * @param {FeelType} feelType
8293
+ * @param {string} localValue
8294
+ *
8295
+ * @return {boolean}
8296
+ */
8297
+ function isFeelActive(feelType, localValue) {
8298
+ if (feelType === 'required') {
8299
+ return true;
8300
+ }
8301
+ if (minDash.isString(localValue)) {
8302
+ if (localValue.startsWith('=')) {
8303
+ return true;
8304
+ }
8305
+ }
8306
+ return false;
8307
+ }
8308
+
8309
+ /**
8310
+ * @template T
8311
+ * @param {T} value
8312
+ *
8313
+ * @return {string|T}
8314
+ */
8315
+ function getFeelValue(value) {
8316
+ if (minDash.isString(value) && value.startsWith('=')) {
8317
+ return value.substring(1);
8318
+ }
8319
+ return value;
8320
+ }
8321
+
8322
+ /**
8323
+ * Initialize local FEEL value.
8324
+ *
8325
+ * `optional-default-enabled` starts in FEEL mode if no value or empty string is provided.
8326
+ *
8327
+ * @template T
8328
+ * @param {FeelType} feelType
8329
+ * @param {T} value
8330
+ *
8331
+ * @return {string|T}
8332
+ */
8333
+ function getInitialFeelLocalValue(feelType, value) {
8334
+ if (feelType === 'optional-default-enabled' && (value === undefined || value === '')) {
8335
+ return '=';
8336
+ }
8337
+ return value;
8338
+ }
8339
+
8218
8340
  /**
8219
8341
  * @typedef { { value: string, label: string, disabled: boolean, children: { value: string, label: string, disabled: boolean } } } Option
8220
8342
  */
@@ -8465,7 +8587,7 @@ function TextArea(props) {
8465
8587
  };
8466
8588
  const handleOnKeyDown = e => {
8467
8589
  if (isCmdWithChar(e)) {
8468
- handleInput.flush();
8590
+ handleInput.flush?.();
8469
8591
  }
8470
8592
  };
8471
8593
  hooks.useLayoutEffect(() => {
@@ -8680,7 +8802,7 @@ function Textfield(props) {
8680
8802
  }, [value]);
8681
8803
  const handleOnKeyDown = e => {
8682
8804
  if (isCmdWithChar(e)) {
8683
- handleInput.flush();
8805
+ handleInput.flush?.();
8684
8806
  }
8685
8807
  };
8686
8808
  return jsxRuntime.jsxs("div", {
@@ -9048,6 +9170,7 @@ function Title(props) {
9048
9170
  class: "bio-properties-panel-popup__title",
9049
9171
  children: title
9050
9172
  }), children, showCloseButton && jsxRuntime.jsx("button", {
9173
+ type: "button",
9051
9174
  title: closeButtonTooltip,
9052
9175
  class: "bio-properties-panel-popup__close",
9053
9176
  onClick: onClose,
@@ -13820,7 +13943,7 @@ function CustomPropertiesGroup(field, editField) {
13820
13943
  event.stopPropagation();
13821
13944
  return editField(field, ['properties'], removeKey(properties, key));
13822
13945
  };
13823
- const id = `property-${field.id}-${index}`;
13946
+ const id = `property-${index}`;
13824
13947
  return {
13825
13948
  autoFocusEntry: id + '-key',
13826
13949
  entries: CustomValueEntry({