@bpmn-io/properties-panel 3.18.2 → 3.19.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.esm.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { useContext, useState, useRef, useEffect, useMemo, useCallback, useLayoutEffect } from '../preact/hooks';
2
- import { isFunction, isString, isArray, get, assign, set, sortBy, find, isNumber, debounce } from 'min-dash';
2
+ import { isFunction, isString, isArray, get, assign, set, isNumber, debounce } from 'min-dash';
3
3
  import { createPortal, forwardRef } from '../preact/compat';
4
4
  import { jsx, jsxs, Fragment } from '../preact/jsx-runtime';
5
5
  import { createContext, createElement } from '../preact';
@@ -2666,7 +2666,6 @@ const DEFAULT_TOOLTIP = {};
2666
2666
  * id: String,
2667
2667
  * items: Array<ListItemDefinition>,
2668
2668
  * label: String,
2669
- * shouldSort?: Boolean,
2670
2669
  * shouldOpen?: Boolean
2671
2670
  * } } ListGroupDefinition
2672
2671
  *
@@ -3099,6 +3098,7 @@ function ListItem(props) {
3099
3098
  } else if (isFunction(focusableInput.focus)) {
3100
3099
  focusableInput.focus();
3101
3100
  }
3101
+ focusableInput.scrollIntoView();
3102
3102
  }
3103
3103
  }
3104
3104
  }, [autoOpen, autoFocusEntry]);
@@ -3123,97 +3123,61 @@ function ListGroup(props) {
3123
3123
  id,
3124
3124
  items,
3125
3125
  label,
3126
- shouldOpen = true,
3127
- shouldSort = true
3126
+ shouldOpen = true
3128
3127
  } = props;
3128
+ useEffect(() => {
3129
+ if (props.shouldSort != undefined) {
3130
+ console.warn('the property \'shouldSort\' is no longer supported');
3131
+ }
3132
+ }, [props.shouldSort]);
3129
3133
  const groupRef = useRef(null);
3130
3134
  const [open, setOpen] = useLayoutState(['groups', id, 'open'], false);
3131
3135
  const [sticky, setSticky] = useState(false);
3132
3136
  const onShow = useCallback(() => setOpen(true), [setOpen]);
3133
- const [ordering, setOrdering] = useState([]);
3134
- const [newItemAdded, setNewItemAdded] = useState(false);
3137
+ const [localItems, setLocalItems] = useState([]);
3138
+ const [newlyAddedItemIds, setNewlyAddedItemIds] = useState([]);
3135
3139
 
3136
3140
  // Flag to mark that add button was clicked in the last render cycle
3137
3141
  const [addTriggered, setAddTriggered] = useState(false);
3138
- const prevItems = usePrevious(items);
3139
3142
  const prevElement = usePrevious(element);
3140
3143
  const elementChanged = element !== prevElement;
3141
- const shouldHandleEffects = !elementChanged && (shouldSort || shouldOpen);
3142
-
3143
- // reset initial ordering when element changes (before first render)
3144
- if (elementChanged) {
3145
- setOrdering(createOrdering(shouldSort ? sortItems(items) : items));
3146
- }
3147
-
3148
- // keep ordering in sync to items - and open changes
3149
-
3150
- // (0) set initial ordering from given items
3144
+ const shouldHandleEffects = !elementChanged && shouldOpen;
3145
+
3146
+ // (0) delay setting items
3147
+ //
3148
+ // We need to this to align the render cycles of items
3149
+ // with the detection of newly added items.
3150
+ // This is important, because the autoOpen property can
3151
+ // only set per list item on its very first render.
3151
3152
  useEffect(() => {
3152
- if (!prevItems || !shouldSort) {
3153
- setOrdering(createOrdering(items));
3154
- }
3155
- }, [items, element]);
3153
+ setLocalItems(items);
3154
+ }, [items]);
3156
3155
 
3157
- // (1) items were added
3156
+ // (1) handle auto opening when items were added
3158
3157
  useEffect(() => {
3159
3158
  // reset addTriggered flag
3160
3159
  setAddTriggered(false);
3161
- if (shouldHandleEffects && prevItems && items.length > prevItems.length) {
3162
- let add = [];
3163
- items.forEach(item => {
3164
- if (!ordering.includes(item.id)) {
3165
- add.push(item.id);
3160
+ if (shouldHandleEffects && localItems) {
3161
+ if (addTriggered) {
3162
+ const previousItemIds = localItems.map(item => item.id);
3163
+ const currentItemsIds = items.map(item => item.id);
3164
+ const newItemIds = currentItemsIds.filter(itemId => !previousItemIds.includes(itemId));
3165
+
3166
+ // open if not open, configured and triggered by add button
3167
+ //
3168
+ // TODO(marstamm): remove once we refactor layout handling for listGroups.
3169
+ // Ideally, opening should be handled as part of the `add` callback and
3170
+ // not be a concern for the ListGroup component.
3171
+ if (!open && shouldOpen && newItemIds.length > 0) {
3172
+ toggleOpen();
3166
3173
  }
3167
- });
3168
- let newOrdering = ordering;
3169
-
3170
- // open if not open, configured and triggered by add button
3171
- //
3172
- // TODO(marstamm): remove once we refactor layout handling for listGroups.
3173
- // Ideally, opening should be handled as part of the `add` callback and
3174
- // not be a concern for the ListGroup component.
3175
- if (addTriggered && !open && shouldOpen) {
3176
- toggleOpen();
3177
- }
3178
-
3179
- // filter when not open and configured
3180
- if (!open && shouldSort) {
3181
- newOrdering = createOrdering(sortItems(items));
3182
- }
3183
-
3184
- // add new items on top or bottom depending on sorting behavior
3185
- newOrdering = newOrdering.filter(item => !add.includes(item));
3186
- if (shouldSort) {
3187
- newOrdering.unshift(...add);
3174
+ setNewlyAddedItemIds(newItemIds);
3188
3175
  } else {
3189
- newOrdering.push(...add);
3176
+ // ignore newly added items that do not result from a triggered add
3177
+ setNewlyAddedItemIds([]);
3190
3178
  }
3191
- setOrdering(newOrdering);
3192
- setNewItemAdded(addTriggered);
3193
- } else {
3194
- setNewItemAdded(false);
3195
- }
3196
- }, [items, open, shouldHandleEffects, addTriggered]);
3197
-
3198
- // (2) sort items on open if shouldSort is set
3199
- useEffect(() => {
3200
- if (shouldSort && open && !newItemAdded) {
3201
- setOrdering(createOrdering(sortItems(items)));
3202
- }
3203
- }, [open, shouldSort]);
3204
-
3205
- // (3) items were deleted
3206
- useEffect(() => {
3207
- if (shouldHandleEffects && prevItems && items.length < prevItems.length) {
3208
- let keep = [];
3209
- ordering.forEach(o => {
3210
- if (getItem(items, o)) {
3211
- keep.push(o);
3212
- }
3213
- });
3214
- setOrdering(keep);
3215
3179
  }
3216
- }, [items, shouldHandleEffects]);
3180
+ }, [items, open, shouldHandleEffects, addTriggered, localItems]);
3217
3181
 
3218
3182
  // set css class when group is sticky to top
3219
3183
  useStickyIntersectionObserver(groupRef, 'div.bio-properties-panel-scroll-container', setSticky);
@@ -3285,8 +3249,7 @@ function ListGroup(props) {
3285
3249
  class: classnames('bio-properties-panel-list', open && hasItems ? 'open' : ''),
3286
3250
  children: jsx(LayoutContext.Provider, {
3287
3251
  value: propertiesPanelContext,
3288
- children: ordering.map((o, index) => {
3289
- const item = getItem(items, o);
3252
+ children: localItems.map((item, index) => {
3290
3253
  if (!item) {
3291
3254
  return;
3292
3255
  }
@@ -3296,7 +3259,7 @@ function ListGroup(props) {
3296
3259
 
3297
3260
  // if item was added, open it
3298
3261
  // Existing items will not be affected as autoOpen is only applied on first render
3299
- const autoOpen = newItemAdded;
3262
+ const autoOpen = newlyAddedItemIds.includes(item.id);
3300
3263
  return createElement(ListItem, {
3301
3264
  ...item,
3302
3265
  autoOpen: autoOpen,
@@ -3310,21 +3273,6 @@ function ListGroup(props) {
3310
3273
  });
3311
3274
  }
3312
3275
 
3313
- // helpers ////////////////////
3314
-
3315
- /**
3316
- * Sorts given items alphanumeric by label
3317
- */
3318
- function sortItems(items) {
3319
- return sortBy(items, i => i.label.toLowerCase());
3320
- }
3321
- function getItem(items, id) {
3322
- return find(items, i => i.id === id);
3323
- }
3324
- function createOrdering(items) {
3325
- return items.map(i => i.id);
3326
- }
3327
-
3328
3276
  function Checkbox(props) {
3329
3277
  const {
3330
3278
  id,
@@ -3616,16 +3564,12 @@ function List(props) {
3616
3564
  onAdd,
3617
3565
  onRemove,
3618
3566
  autoFocusEntry,
3619
- compareFn,
3620
3567
  ...restProps
3621
3568
  } = props;
3622
3569
  const [open, setOpen] = useState(!!shouldOpen);
3623
3570
  const hasItems = !!items.length;
3624
3571
  const toggleOpen = () => hasItems && setOpen(!open);
3625
- const opening = !usePrevious(open) && open;
3626
3572
  const elementChanged = usePrevious(element) !== element;
3627
- const shouldReset = opening || elementChanged;
3628
- const sortedItems = useSortedItems(items, compareFn, shouldReset);
3629
3573
  const newItems = useNewItems(items, elementChanged);
3630
3574
  useEffect(() => {
3631
3575
  if (open && !hasItems) {
@@ -3683,7 +3627,7 @@ function List(props) {
3683
3627
  component: component,
3684
3628
  element: element,
3685
3629
  id: id,
3686
- items: sortedItems,
3630
+ items: items,
3687
3631
  newItems: newItems,
3688
3632
  onRemove: onRemove,
3689
3633
  open: open
@@ -3747,41 +3691,6 @@ function ItemsList(props) {
3747
3691
  })
3748
3692
  });
3749
3693
  }
3750
-
3751
- /**
3752
- * Place new items in the beginning of the list and sort the rest with provided function.
3753
- *
3754
- * @template Item
3755
- * @param {Item[]} currentItems
3756
- * @param {(a: Item, b: Item) => 0 | 1 | -1} [compareFn] function used to sort items
3757
- * @param {boolean} [shouldReset=false] set to `true` to reset state of the hook
3758
- * @returns {Item[]}
3759
- */
3760
- function useSortedItems(currentItems, compareFn, shouldReset = false) {
3761
- const itemsRef = useRef(currentItems.slice());
3762
-
3763
- // (1) Reset and optionally sort.
3764
- if (shouldReset) {
3765
- itemsRef.current = currentItems.slice();
3766
- if (compareFn) {
3767
- itemsRef.current.sort(compareFn);
3768
- }
3769
- } else {
3770
- const items = itemsRef.current;
3771
-
3772
- // (2) Add new item to the list.
3773
- for (const item of currentItems) {
3774
- if (!items.includes(item)) {
3775
- // Unshift or push depending on whether we have a compareFn
3776
- compareFn ? items.unshift(item) : items.push(item);
3777
- }
3778
- }
3779
-
3780
- // (3) Filter out removed items.
3781
- itemsRef.current = items.filter(item => currentItems.includes(item));
3782
- }
3783
- return itemsRef.current;
3784
- }
3785
3694
  function useNewItems(items = [], shouldReset) {
3786
3695
  const previousItems = usePrevious(items.slice()) || [];
3787
3696
  if (shouldReset) {