bpmn-js 17.2.2 → 17.3.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.
@@ -1,5 +1,9 @@
1
1
  import { is } from '../../util/ModelUtil';
2
- import { isAny } from '../modeling/util/ModelingUtil';
2
+
3
+ import {
4
+ isAny,
5
+ isDirectionHorizontal
6
+ } from '../modeling/util/ModelingUtil';
3
7
 
4
8
  import {
5
9
  getMid,
@@ -47,7 +51,7 @@ export function getNewShapePosition(source, element) {
47
51
  }
48
52
 
49
53
  /**
50
- * Get the position for given new flow node. Try placing the flow node right of
54
+ * Get the position for given new flow node. Try placing the flow node right/bottom of
51
55
  * the source.
52
56
  *
53
57
  * @param {Shape} source
@@ -60,34 +64,60 @@ export function getFlowNodePosition(source, element) {
60
64
  var sourceTrbl = asTRBL(source);
61
65
  var sourceMid = getMid(source);
62
66
 
63
- var horizontalDistance = getConnectedDistance(source, {
67
+ var placeHorizontally = isDirectionHorizontal(source);
68
+
69
+ var placement = placeHorizontally ? {
70
+ directionHint: 'e',
71
+ minDistance: 80,
72
+ baseOrientation: 'left',
73
+ boundaryOrientation: 'top',
74
+ start: 'top',
75
+ end: 'bottom'
76
+ } : {
77
+ directionHint: 's',
78
+ minDistance: 90,
79
+ baseOrientation: 'top',
80
+ boundaryOrientation: 'left',
81
+ start: 'left',
82
+ end: 'right'
83
+ };
84
+
85
+ var connectedDistance = getConnectedDistance(source, {
64
86
  filter: function(connection) {
65
87
  return is(connection, 'bpmn:SequenceFlow');
66
- }
88
+ },
89
+ direction: placement.directionHint
67
90
  });
68
91
 
69
92
  var margin = 30,
70
- minDistance = 80,
71
- orientation = 'left';
93
+ minDistance = placement.minDistance,
94
+ orientation = placement.baseOrientation;
72
95
 
73
96
  if (is(source, 'bpmn:BoundaryEvent')) {
74
97
  orientation = getOrientation(source, source.host, -25);
75
98
 
76
- if (orientation.indexOf('top') !== -1) {
99
+ if (orientation.indexOf(placement.boundaryOrientation) !== -1) {
77
100
  margin *= -1;
78
101
  }
79
102
  }
80
103
 
81
- var position = {
82
- x: sourceTrbl.right + horizontalDistance + element.width / 2,
83
- y: sourceMid.y + getVerticalDistance(orientation, minDistance)
104
+ var position = placeHorizontally ? {
105
+ x: sourceTrbl.right + connectedDistance + element.width / 2,
106
+ y: sourceMid.y + getDistance(orientation, minDistance, placement)
107
+ } : {
108
+ x: sourceMid.x + getDistance(orientation, minDistance, placement),
109
+ y: sourceTrbl.bottom + connectedDistance + element.height / 2
84
110
  };
85
111
 
86
- var nextPositionDirection = {
87
- y: {
88
- margin: margin,
89
- minDistance: minDistance
90
- }
112
+ var nextPosition = {
113
+ margin: margin,
114
+ minDistance: minDistance
115
+ };
116
+
117
+ var nextPositionDirection = placeHorizontally ? {
118
+ y: nextPosition
119
+ } : {
120
+ x: nextPosition
91
121
  };
92
122
 
93
123
  return findFreePosition(source, element, position, generateGetNextPosition(nextPositionDirection));
@@ -96,13 +126,14 @@ export function getFlowNodePosition(source, element) {
96
126
  /**
97
127
  * @param {DirectionTRBL} orientation
98
128
  * @param {number} minDistance
129
+ * @param {{ start: DirectionTRBL, end: DirectionTRBL }} placement
99
130
  *
100
131
  * @return {number}
101
132
  */
102
- function getVerticalDistance(orientation, minDistance) {
103
- if (orientation.includes('top')) {
133
+ function getDistance(orientation, minDistance, placement) {
134
+ if (orientation.includes(placement.start)) {
104
135
  return -1 * minDistance;
105
- } else if (orientation.includes('bottom')) {
136
+ } else if (orientation.includes(placement.end)) {
106
137
  return minDistance;
107
138
  } else {
108
139
  return 0;
@@ -112,7 +143,7 @@ function getVerticalDistance(orientation, minDistance) {
112
143
 
113
144
  /**
114
145
  * Get the position for given text annotation. Try placing the text annotation
115
- * top-right of the source.
146
+ * top-right of the source (bottom-right in vertical layouts).
116
147
  *
117
148
  * @param {Shape} source
118
149
  * @param {Shape} element
@@ -123,22 +154,35 @@ export function getTextAnnotationPosition(source, element) {
123
154
 
124
155
  var sourceTrbl = asTRBL(source);
125
156
 
126
- var position = {
157
+ var placeHorizontally = isDirectionHorizontal(source);
158
+
159
+ var position = placeHorizontally ? {
127
160
  x: sourceTrbl.right + element.width / 2,
128
161
  y: sourceTrbl.top - 50 - element.height / 2
162
+ } : {
163
+ x: sourceTrbl.right + 50 + element.width / 2,
164
+ y: sourceTrbl.bottom + element.height / 2
129
165
  };
130
166
 
131
167
  if (isConnection(source)) {
132
168
  position = getMid(source);
133
- position.x += 100;
134
- position.y -= 50;
169
+ if (placeHorizontally) {
170
+ position.x += 100;
171
+ position.y -= 50;
172
+ } else {
173
+ position.x += 100;
174
+ position.y += 50;
175
+ }
135
176
  }
136
177
 
137
- var nextPositionDirection = {
138
- y: {
139
- margin: -30,
140
- minDistance: 20
141
- }
178
+ var nextPosition = {
179
+ margin: placeHorizontally ? -30 : 30,
180
+ minDistance: 20
181
+ };
182
+ var nextPositionDirection = placeHorizontally ? {
183
+ y: nextPosition
184
+ } : {
185
+ x: nextPosition
142
186
  };
143
187
 
144
188
  return findFreePosition(source, element, position, generateGetNextPosition(nextPositionDirection));
@@ -147,7 +191,7 @@ export function getTextAnnotationPosition(source, element) {
147
191
 
148
192
  /**
149
193
  * Get the position for given new data element. Try placing the data element
150
- * bottom-right of the source.
194
+ * bottom-right of the source (bottom-left in vertical layouts).
151
195
  *
152
196
  * @param {Shape} source
153
197
  * @param {Shape} element
@@ -158,16 +202,24 @@ export function getDataElementPosition(source, element) {
158
202
 
159
203
  var sourceTrbl = asTRBL(source);
160
204
 
161
- var position = {
205
+ var placeHorizontally = isDirectionHorizontal(source);
206
+
207
+ var position = placeHorizontally ? {
162
208
  x: sourceTrbl.right - 10 + element.width / 2,
163
209
  y: sourceTrbl.bottom + 40 + element.width / 2
210
+ } : {
211
+ x: sourceTrbl.left - 40 - element.width / 2,
212
+ y: sourceTrbl.bottom - 10 + element.height / 2
164
213
  };
165
214
 
166
- var nextPositionDirection = {
167
- x: {
168
- margin: 30,
169
- minDistance: 30
170
- }
215
+ var nextPosition = {
216
+ margin: 30,
217
+ minDistance: 30
218
+ };
219
+ var nextPositionDirection = placeHorizontally ? {
220
+ x: nextPosition
221
+ } : {
222
+ y: nextPosition
171
223
  };
172
224
 
173
225
  return findFreePosition(source, element, position, generateGetNextPosition(nextPositionDirection));
@@ -22,6 +22,8 @@ import {
22
22
 
23
23
  import { is } from '../../util/ModelUtil';
24
24
 
25
+ import { isDirectionHorizontal } from './util/ModelingUtil';
26
+
25
27
  /**
26
28
  * @typedef {import('diagram-js/lib/util/Types').Point} Point
27
29
  *
@@ -42,6 +44,51 @@ import { is } from '../../util/ModelUtil';
42
44
  var ATTACH_ORIENTATION_PADDING = -10,
43
45
  BOUNDARY_TO_HOST_THRESHOLD = 40;
44
46
 
47
+ // layout all connection between flow elements h:h, except for
48
+ // (1) outgoing of boundary events -> layout based on attach orientation and target orientation
49
+ // (2) incoming/outgoing of gateways -> v:h for outgoing, h:v for incoming
50
+ // (3) loops connect sides clockwise
51
+ var PREFERRED_LAYOUTS_HORIZONTAL = {
52
+ default: [ 'h:h' ],
53
+ fromGateway: [ 'v:h' ],
54
+ toGateway: [ 'h:v' ],
55
+ loop: {
56
+ fromTop: [ 't:r' ],
57
+ fromRight: [ 'r:b' ],
58
+ fromLeft: [ 'l:t' ],
59
+ fromBottom: [ 'b:l' ]
60
+ },
61
+ boundaryLoop: {
62
+ alternateHorizontalSide: 'b',
63
+ alternateVerticalSide: 'l',
64
+ default: 'v'
65
+ },
66
+ messageFlow: [ 'straight', 'v:v' ],
67
+ subProcess: [ 'straight', 'h:h' ],
68
+ isHorizontal: true
69
+ };
70
+
71
+ // for vertical layouts, switch h and v and loop counter-clockwise
72
+ var PREFERRED_LAYOUTS_VERTICAL = {
73
+ default: [ 'v:v' ],
74
+ fromGateway: [ 'h:v' ],
75
+ toGateway: [ 'v:h' ],
76
+ loop: {
77
+ fromTop: [ 't:l' ],
78
+ fromRight: [ 'r:t' ],
79
+ fromLeft: [ 'l:b' ],
80
+ fromBottom: [ 'b:r' ]
81
+ },
82
+ boundaryLoop: {
83
+ alternateHorizontalSide: 't',
84
+ alternateVerticalSide: 'r',
85
+ default: 'h'
86
+ },
87
+ messageFlow: [ 'straight', 'h:h' ],
88
+ subProcess: [ 'straight', 'v:v' ],
89
+ isHorizontal: false
90
+ };
91
+
45
92
  var oppositeOrientationMapping = {
46
93
  'top': 'bottom',
47
94
  'top-right': 'bottom-left',
@@ -94,9 +141,6 @@ BpmnLayouter.prototype.layoutConnection = function(connection, hints) {
94
141
  connectionEnd = getConnectionDocking(waypoints && waypoints[ waypoints.length - 1 ], target);
95
142
  }
96
143
 
97
- // TODO(nikku): support vertical modeling
98
- // and invert preferredLayouts accordingly
99
-
100
144
  if (is(connection, 'bpmn:Association') ||
101
145
  is(connection, 'bpmn:DataAssociation')) {
102
146
 
@@ -105,35 +149,36 @@ BpmnLayouter.prototype.layoutConnection = function(connection, hints) {
105
149
  }
106
150
  }
107
151
 
152
+ var layout = isDirectionHorizontal(source) ? PREFERRED_LAYOUTS_HORIZONTAL : PREFERRED_LAYOUTS_VERTICAL;
153
+
108
154
  if (is(connection, 'bpmn:MessageFlow')) {
109
- manhattanOptions = getMessageFlowManhattanOptions(source, target);
155
+ manhattanOptions = getMessageFlowManhattanOptions(source, target, layout);
110
156
  } else if (is(connection, 'bpmn:SequenceFlow') || isCompensationAssociation(source, target)) {
111
157
 
112
- // layout all connection between flow elements h:h, except for
113
- // (1) outgoing of boundary events -> layout based on attach orientation and target orientation
114
- // (2) incoming/outgoing of gateways -> v:h for outgoing, h:v for incoming
115
- // (3) loops
116
158
  if (source === target) {
117
159
  manhattanOptions = {
118
- preferredLayouts: getLoopPreferredLayout(source, connection)
160
+ preferredLayouts: getLoopPreferredLayout(source, connection, layout)
119
161
  };
120
162
  } else if (is(source, 'bpmn:BoundaryEvent')) {
121
163
  manhattanOptions = {
122
- preferredLayouts: getBoundaryEventPreferredLayouts(source, target, connectionEnd)
164
+ preferredLayouts: getBoundaryEventPreferredLayouts(source, target, connectionEnd, layout)
123
165
  };
124
166
  } else if (isExpandedSubProcess(source) || isExpandedSubProcess(target)) {
125
- manhattanOptions = getSubProcessManhattanOptions(source);
167
+ manhattanOptions = {
168
+ preferredLayouts: layout.subProcess,
169
+ preserveDocking: getSubProcessPreserveDocking(source)
170
+ };
126
171
  } else if (is(source, 'bpmn:Gateway')) {
127
172
  manhattanOptions = {
128
- preferredLayouts: [ 'v:h' ]
173
+ preferredLayouts: layout.fromGateway
129
174
  };
130
175
  } else if (is(target, 'bpmn:Gateway')) {
131
176
  manhattanOptions = {
132
- preferredLayouts: [ 'h:v' ]
177
+ preferredLayouts: layout.toGateway
133
178
  };
134
179
  } else {
135
180
  manhattanOptions = {
136
- preferredLayouts: [ 'h:h' ]
181
+ preferredLayouts: layout.default
137
182
  };
138
183
  }
139
184
  }
@@ -163,9 +208,9 @@ function getAttachOrientation(attachedElement) {
163
208
  return getOrientation(getMid(attachedElement), hostElement, ATTACH_ORIENTATION_PADDING);
164
209
  }
165
210
 
166
- function getMessageFlowManhattanOptions(source, target) {
211
+ function getMessageFlowManhattanOptions(source, target, layout) {
167
212
  return {
168
- preferredLayouts: [ 'straight', 'v:v' ],
213
+ preferredLayouts: layout.messageFlow,
169
214
  preserveDocking: getMessageFlowPreserveDocking(source, target)
170
215
  };
171
216
  }
@@ -202,13 +247,6 @@ function getMessageFlowPreserveDocking(source, target) {
202
247
  return null;
203
248
  }
204
249
 
205
- function getSubProcessManhattanOptions(source) {
206
- return {
207
- preferredLayouts: [ 'straight', 'h:h' ],
208
- preserveDocking: getSubProcessPreserveDocking(source)
209
- };
210
- }
211
-
212
250
  function getSubProcessPreserveDocking(source) {
213
251
  return isExpandedSubProcess(source) ? 'target' : 'source';
214
252
  }
@@ -271,23 +309,23 @@ function isHorizontalOrientation(orientation) {
271
309
  return orientation === 'right' || orientation === 'left';
272
310
  }
273
311
 
274
- function getLoopPreferredLayout(source, connection) {
312
+ function getLoopPreferredLayout(source, connection, layout) {
275
313
  var waypoints = connection.waypoints;
276
314
 
277
315
  var orientation = waypoints && waypoints.length && getOrientation(waypoints[0], source);
278
316
 
279
317
  if (orientation === 'top') {
280
- return [ 't:r' ];
318
+ return layout.loop.fromTop;
281
319
  } else if (orientation === 'right') {
282
- return [ 'r:b' ];
320
+ return layout.loop.fromRight;
283
321
  } else if (orientation === 'left') {
284
- return [ 'l:t' ];
322
+ return layout.loop.fromLeft;
285
323
  }
286
324
 
287
- return [ 'b:l' ];
325
+ return layout.loop.fromBottom;
288
326
  }
289
327
 
290
- function getBoundaryEventPreferredLayouts(source, target, end) {
328
+ function getBoundaryEventPreferredLayouts(source, target, end, layout) {
291
329
  var sourceMid = getMid(source),
292
330
  targetMid = getMid(target),
293
331
  attachOrientation = getAttachOrientation(source),
@@ -304,31 +342,31 @@ function getBoundaryEventPreferredLayouts(source, target, end) {
304
342
  });
305
343
 
306
344
  if (isLoop) {
307
- return getBoundaryEventLoopLayout(attachOrientation, attachedToSide, source, target, end);
345
+ return getBoundaryEventLoopLayout(attachOrientation, attachedToSide, source, target, end, layout);
308
346
  }
309
347
 
310
348
  // source layout
311
- sourceLayout = getBoundaryEventSourceLayout(attachOrientation, targetOrientation, attachedToSide);
349
+ sourceLayout = getBoundaryEventSourceLayout(attachOrientation, targetOrientation, attachedToSide, layout.isHorizontal);
312
350
 
313
351
  // target layout
314
- targetLayout = getBoundaryEventTargetLayout(attachOrientation, targetOrientation, attachedToSide);
352
+ targetLayout = getBoundaryEventTargetLayout(attachOrientation, targetOrientation, attachedToSide, layout.isHorizontal);
315
353
 
316
354
  return [ sourceLayout + ':' + targetLayout ];
317
355
  }
318
356
 
319
- function getBoundaryEventLoopLayout(attachOrientation, attachedToSide, source, target, end) {
320
- var orientation = attachedToSide ? attachOrientation : getVerticalOrientation(attachOrientation),
357
+ function getBoundaryEventLoopLayout(attachOrientation, attachedToSide, source, target, end, layout) {
358
+ var orientation = attachedToSide ? attachOrientation : layout.isHorizontal ? getVerticalOrientation(attachOrientation) : getHorizontalOrientation(attachOrientation),
321
359
  sourceLayout = orientationDirectionMapping[ orientation ],
322
360
  targetLayout;
323
361
 
324
362
  if (attachedToSide) {
325
363
  if (isHorizontalOrientation(attachOrientation)) {
326
- targetLayout = shouldConnectToSameSide('y', source, target, end) ? 'h' : 'b';
364
+ targetLayout = shouldConnectToSameSide('y', source, target, end) ? 'h' : layout.boundaryLoop.alternateHorizontalSide;
327
365
  } else {
328
- targetLayout = shouldConnectToSameSide('x', source, target, end) ? 'v' : 'l';
366
+ targetLayout = shouldConnectToSameSide('x', source, target, end) ? 'v' : layout.boundaryLoop.alternateVerticalSide;
329
367
  }
330
368
  } else {
331
- targetLayout = 'v';
369
+ targetLayout = layout.boundaryLoop.default;
332
370
  }
333
371
 
334
372
  return [ sourceLayout + ':' + targetLayout ];
@@ -351,7 +389,7 @@ function areCloseOnAxis(axis, a, b, threshold) {
351
389
  return Math.abs(a[ axis ] - b[ axis ]) < threshold;
352
390
  }
353
391
 
354
- function getBoundaryEventSourceLayout(attachOrientation, targetOrientation, attachedToSide) {
392
+ function getBoundaryEventSourceLayout(attachOrientation, targetOrientation, attachedToSide, isHorizontal) {
355
393
 
356
394
  // attached to either top, right, bottom or left side
357
395
  if (attachedToSide) {
@@ -360,20 +398,36 @@ function getBoundaryEventSourceLayout(attachOrientation, targetOrientation, atta
360
398
 
361
399
  // attached to either top-right, top-left, bottom-right or bottom-left corner
362
400
 
363
- // same vertical or opposite horizontal orientation
364
- if (isSame(
365
- getVerticalOrientation(attachOrientation), getVerticalOrientation(targetOrientation)
366
- ) || isOppositeOrientation(
367
- getHorizontalOrientation(attachOrientation), getHorizontalOrientation(targetOrientation)
368
- )) {
369
- return orientationDirectionMapping[ getVerticalOrientation(attachOrientation) ];
401
+ var verticalAttachOrientation = getVerticalOrientation(attachOrientation),
402
+ horizontalAttachOrientation = getHorizontalOrientation(attachOrientation),
403
+ verticalTargetOrientation = getVerticalOrientation(targetOrientation),
404
+ horizontalTargetOrientation = getHorizontalOrientation(targetOrientation);
405
+
406
+ if (isHorizontal) {
407
+
408
+ // same vertical or opposite horizontal orientation
409
+ if (
410
+ isSame(verticalAttachOrientation, verticalTargetOrientation) ||
411
+ isOppositeOrientation(horizontalAttachOrientation, horizontalTargetOrientation)
412
+ ) {
413
+ return orientationDirectionMapping[ verticalAttachOrientation ];
414
+ }
415
+ } else {
416
+
417
+ // same horizontal or opposite vertical orientation
418
+ if (
419
+ isSame(horizontalAttachOrientation, horizontalTargetOrientation) ||
420
+ isOppositeOrientation(verticalAttachOrientation, verticalTargetOrientation)
421
+ ) {
422
+ return orientationDirectionMapping[ horizontalAttachOrientation ];
423
+ }
370
424
  }
371
425
 
372
426
  // fallback
373
- return orientationDirectionMapping[ getHorizontalOrientation(attachOrientation) ];
427
+ return orientationDirectionMapping[ isHorizontal ? horizontalAttachOrientation : verticalAttachOrientation ];
374
428
  }
375
429
 
376
- function getBoundaryEventTargetLayout(attachOrientation, targetOrientation, attachedToSide) {
430
+ function getBoundaryEventTargetLayout(attachOrientation, targetOrientation, attachedToSide, isHorizontal) {
377
431
 
378
432
  // attached to either top, right, bottom or left side
379
433
  if (attachedToSide) {
@@ -409,14 +463,19 @@ function getBoundaryEventTargetLayout(attachOrientation, targetOrientation, atta
409
463
  }
410
464
 
411
465
  // attached to either top-right, top-left, bottom-right or bottom-left corner
466
+ // and orientation is same on the counter-axis
412
467
 
413
- // orientation is right, left
414
- // or same vertical orientation but also right or left
415
- if (isHorizontalOrientation(targetOrientation) ||
416
- (isSame(getVerticalOrientation(attachOrientation), getVerticalOrientation(targetOrientation)) &&
417
- getHorizontalOrientation(targetOrientation))) {
418
- return 'h';
468
+ if (isHorizontal) {
469
+ if (isSame(getVerticalOrientation(attachOrientation), getVerticalOrientation(targetOrientation))) {
470
+ return 'h';
471
+ } else {
472
+ return 'v';
473
+ }
419
474
  } else {
420
- return 'v';
475
+ if (isSame(getHorizontalOrientation(attachOrientation), getHorizontalOrientation(targetOrientation))) {
476
+ return 'v';
477
+ } else {
478
+ return 'h';
479
+ }
421
480
  }
422
481
  }
@@ -8,5 +8,14 @@
8
8
  */
9
9
  export function getParent(element: Element, anyType: string | string[]): Element | null;
10
10
 
11
+ /**
12
+ * Determines if the local modeling direction is vertical or horizontal.
13
+ *
14
+ * @param element
15
+ *
16
+ * @return false for vertical pools, lanes and their children. true otherwise
17
+ */
18
+ export function isDirectionHorizontal(element: Element): boolean;
19
+
11
20
  type Element = import('../../../model/Types').Element;
12
21
  export { is, isAny } from "../../../util/ModelUtil";
@@ -4,6 +4,8 @@ export { is, isAny } from '../../../util/ModelUtil';
4
4
 
5
5
  import { isAny } from '../../../util/ModelUtil';
6
6
 
7
+ import { isHorizontal } from '../../../util/DiUtil';
8
+
7
9
  /**
8
10
  * @typedef {import('../../../model/Types').Element} Element
9
11
  */
@@ -29,4 +31,25 @@ export function getParent(element, anyType) {
29
31
  }
30
32
 
31
33
  return null;
34
+ }
35
+
36
+ /**
37
+ * Determines if the local modeling direction is vertical or horizontal.
38
+ *
39
+ * @param {Element} element
40
+ *
41
+ * @return {boolean} false for vertical pools, lanes and their children. true otherwise
42
+ */
43
+ export function isDirectionHorizontal(element) {
44
+
45
+ var types = [ 'bpmn:Participant', 'bpmn:Lane' ];
46
+
47
+ var parent = getParent(element, types);
48
+ if (parent) {
49
+ return isHorizontal(parent);
50
+ } else if (isAny(element, types)) {
51
+ return isHorizontal(element);
52
+ }
53
+
54
+ return true;
32
55
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bpmn-js",
3
- "version": "17.2.2",
3
+ "version": "17.3.0",
4
4
  "description": "A bpmn 2.0 toolkit and web modeler",
5
5
  "main": "index.js",
6
6
  "files": [
@@ -69,6 +69,7 @@
69
69
  "@rollup/plugin-node-resolve": "^15.0.1",
70
70
  "@rollup/plugin-replace": "^5.0.2",
71
71
  "@rollup/plugin-terser": "^0.4.0",
72
+ "axe-core": "^4.9.0",
72
73
  "babel-loader": "^9.1.0",
73
74
  "babel-plugin-istanbul": "^6.1.1",
74
75
  "bio-dts": "^0.11.0",