@bpmn-io/properties-panel 3.39.0 → 3.40.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.
@@ -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.esm.js CHANGED
@@ -1,4 +1,4 @@
1
- import { useContext, useState, useRef, useEffect, useCallback, useMemo, useLayoutEffect } from '../preact/hooks';
1
+ import { useContext, useState, useRef, useEffect, useLayoutEffect, useCallback, useMemo } from '../preact/hooks';
2
2
  import { isFunction, isArray, get, assign, set, isString, isNumber, debounce } from 'min-dash';
3
3
  import { createPortal, forwardRef } from '../preact/compat';
4
4
  import { jsx, jsxs, Fragment } from '../preact/jsx-runtime';
@@ -343,6 +343,8 @@ function Tooltip(props) {
343
343
  hideDelay = 250
344
344
  } = props;
345
345
  const [visible, setVisible] = useState(false);
346
+ const [tooltipPosition, setTooltipPosition] = useState(null);
347
+ const [arrowOffset, setArrowOffset] = useState(null);
346
348
  const showTimeoutRef = useRef(null);
347
349
  const hideTimeoutRef = useRef(null);
348
350
  const wrapperRef = useRef(null);
@@ -396,6 +398,20 @@ function Tooltip(props) {
396
398
  document.removeEventListener('mousedown', handleClickOutside);
397
399
  };
398
400
  }, [visible, hide]);
401
+ useLayoutEffect(() => {
402
+ if (!visible || position) {
403
+ setTooltipPosition(null);
404
+ setArrowOffset(null);
405
+ return;
406
+ }
407
+ if (!wrapperRef.current || !tooltipRef.current) return;
408
+ const {
409
+ tooltipPosition: newPosition,
410
+ arrowOffset: newArrowOffset
411
+ } = getTooltipPosition(wrapperRef.current, tooltipRef.current, direction);
412
+ setTooltipPosition(newPosition);
413
+ setArrowOffset(newArrowOffset);
414
+ }, [visible, position]);
399
415
  const handleMouseLeave = ({
400
416
  relatedTarget
401
417
  }) => {
@@ -431,12 +447,14 @@ function Tooltip(props) {
431
447
  e.code === 'Escape' && hide(false);
432
448
  };
433
449
  const renderTooltip = () => {
450
+ const tooltipStyle = position || (tooltipPosition ? `right: ${tooltipPosition.right}; top: ${tooltipPosition.top}px;` : undefined);
451
+ const arrowStyle = arrowOffset != null ? `margin-top: ${arrowOffset}px;` : undefined;
434
452
  return jsxs("div", {
435
453
  class: `bio-properties-panel-tooltip ${direction}`,
436
454
  role: "tooltip",
437
455
  id: "bio-properties-panel-tooltip",
438
456
  "aria-labelledby": forId,
439
- style: position || getTooltipPosition(wrapperRef.current),
457
+ style: tooltipStyle,
440
458
  ref: tooltipRef,
441
459
  onClick: e => e.stopPropagation(),
442
460
  onMouseEnter: handleTooltipMouseEnter,
@@ -445,7 +463,8 @@ function Tooltip(props) {
445
463
  class: "bio-properties-panel-tooltip-content",
446
464
  children: value
447
465
  }), jsx("div", {
448
- class: "bio-properties-panel-tooltip-arrow"
466
+ class: "bio-properties-panel-tooltip-arrow",
467
+ style: arrowStyle
449
468
  })]
450
469
  });
451
470
  };
@@ -464,11 +483,47 @@ function Tooltip(props) {
464
483
 
465
484
  // helper
466
485
 
467
- function getTooltipPosition(refElement) {
486
+ function getTooltipPosition(refElement, tooltipElement, direction) {
487
+ if (!refElement) {
488
+ return {
489
+ tooltipPosition: null,
490
+ arrowOffset: null
491
+ };
492
+ }
468
493
  const refPosition = refElement.getBoundingClientRect();
469
494
  const right = `calc(100% - ${refPosition.x}px)`;
470
- const top = `${refPosition.top - 10}px`;
471
- return `right: ${right}; top: ${top};`;
495
+ let top = refPosition.top - 10;
496
+ let arrowOffset = null;
497
+
498
+ // Ensure that the tooltip is within the viewport, adjust the top position if needed.
499
+ // This is only relevant for the 'right' direction for now
500
+ if (tooltipElement && direction === 'right') {
501
+ const tooltipRect = tooltipElement.getBoundingClientRect();
502
+ const viewportHeight = window.innerHeight;
503
+ const minTop = 0;
504
+ const maxTop = viewportHeight - tooltipRect.height;
505
+ const originalTop = top;
506
+ if (top > maxTop) {
507
+ top = maxTop;
508
+ }
509
+ if (top < minTop) {
510
+ top = minTop;
511
+ }
512
+
513
+ // Adjust the arrow position if the tooltip had to be moved to stay within viewport
514
+ if (top !== originalTop) {
515
+ const defaultMarginTop = 16;
516
+ const topDiff = top - originalTop;
517
+ arrowOffset = defaultMarginTop - topDiff;
518
+ }
519
+ }
520
+ return {
521
+ tooltipPosition: {
522
+ right,
523
+ top
524
+ },
525
+ arrowOffset
526
+ };
472
527
  }
473
528
 
474
529
  /**
@@ -2363,7 +2418,7 @@ function prefixId$6(id) {
2363
2418
  const noop$2 = () => {};
2364
2419
 
2365
2420
  /**
2366
- * @typedef {'required'|'optional'|'static'} FeelType
2421
+ * @typedef {'required'|'optional'|'optional-default-enabled'|'static'} FeelType
2367
2422
  */
2368
2423
 
2369
2424
  /**
@@ -2406,7 +2461,7 @@ function FeelTextfield(props) {
2406
2461
  OptionalComponent = OptionalFeelInput,
2407
2462
  tooltip
2408
2463
  } = props;
2409
- const [localValue, setLocalValue] = useState(value);
2464
+ const [localValue, setLocalValue] = useState(getInitialFeelLocalValue(feel, value));
2410
2465
  const editorRef = useShowEntryEvent(id);
2411
2466
  const containerRef = useRef();
2412
2467
  const onInput = useCallback(newValue => {
@@ -2415,8 +2470,8 @@ function FeelTextfield(props) {
2415
2470
  const newModelValue = newValue === '' || newValue === '=' ? undefined : newValue;
2416
2471
  commitValue(newModelValue);
2417
2472
  }, [commitValue]);
2418
- const feelActive = isString(localValue) && localValue.startsWith('=') || feel === 'required';
2419
- const feelOnlyValue = isString(localValue) && localValue.startsWith('=') ? localValue.substring(1) : localValue;
2473
+ const feelActive = isFeelActive(feel, localValue);
2474
+ const feelOnlyValue = getFeelValue(localValue);
2420
2475
  const feelLanguageContext = useContext(FeelLanguageContext);
2421
2476
  const [focus, _setFocus] = useState(undefined);
2422
2477
  const {
@@ -2603,7 +2658,7 @@ function FeelTextfield(props) {
2603
2658
  ref: containerRef,
2604
2659
  children: [jsx(FeelIndicator, {
2605
2660
  active: feelActive,
2606
- disabled: feel !== 'optional' || disabled,
2661
+ disabled: !isFeelOptional(feel) || disabled,
2607
2662
  onClick: handleFeelToggle
2608
2663
  }), feelActive ? jsx(FeelEditor, {
2609
2664
  name: id,
@@ -3116,6 +3171,68 @@ function prefixId$5(id) {
3116
3171
  return `bio-properties-panel-${id}`;
3117
3172
  }
3118
3173
 
3174
+ /**
3175
+ * Determine if FEEL is optional for the configured {@link FeelType}.
3176
+ *
3177
+ * @param {FeelType} feelType
3178
+ *
3179
+ * @return {boolean}
3180
+ */
3181
+ function isFeelOptional(feelType) {
3182
+ return feelType === 'optional' || feelType === 'optional-default-enabled';
3183
+ }
3184
+
3185
+ /**
3186
+ * Determine if FEEL editing is currently active.
3187
+ *
3188
+ * @param {FeelType} feelType
3189
+ * @param {string} localValue
3190
+ *
3191
+ * @return {boolean}
3192
+ */
3193
+ function isFeelActive(feelType, localValue) {
3194
+ if (feelType === 'required') {
3195
+ return true;
3196
+ }
3197
+ if (isString(localValue)) {
3198
+ if (localValue.startsWith('=')) {
3199
+ return true;
3200
+ }
3201
+ }
3202
+ return false;
3203
+ }
3204
+
3205
+ /**
3206
+ * @template T
3207
+ * @param {T} value
3208
+ *
3209
+ * @return {string|T}
3210
+ */
3211
+ function getFeelValue(value) {
3212
+ if (isString(value) && value.startsWith('=')) {
3213
+ return value.substring(1);
3214
+ }
3215
+ return value;
3216
+ }
3217
+
3218
+ /**
3219
+ * Initialize local FEEL value.
3220
+ *
3221
+ * `optional-default-enabled` starts in FEEL mode if no value or empty string is provided.
3222
+ *
3223
+ * @template T
3224
+ * @param {FeelType} feelType
3225
+ * @param {T} value
3226
+ *
3227
+ * @return {string|T}
3228
+ */
3229
+ function getInitialFeelLocalValue(feelType, value) {
3230
+ if (feelType === 'optional-default-enabled' && (value === undefined || value === '')) {
3231
+ return '=';
3232
+ }
3233
+ return value;
3234
+ }
3235
+
3119
3236
  const noop$1 = () => {};
3120
3237
 
3121
3238
  /**