carbon-addons-iot-react 5.8.0 → 5.8.2

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.
@@ -6,7 +6,7 @@
6
6
  .#{$iot-prefix}--value-card__attribute {
7
7
  display: flex;
8
8
  // need a set height to determine truncation vs wrapping. This is the max font size allowed
9
- height: 54px;
9
+ height: 44px;
10
10
  // line up the value and unit
11
11
  align-items: baseline;
12
12
  padding-right: $spacing-05;
package/package.json CHANGED
@@ -353,7 +353,7 @@
353
353
  "whatwg-fetch": "^3.0.0"
354
354
  },
355
355
  "sideEffects": false,
356
- "version": "5.8.0",
356
+ "version": "5.8.2",
357
357
  "resolutions": {
358
358
  "chokidar": "3.3.1",
359
359
  "react-grid-layout": "1.2.2",
@@ -6,7 +6,7 @@
6
6
  .#{$iot-prefix}--value-card__attribute {
7
7
  display: flex;
8
8
  // need a set height to determine truncation vs wrapping. This is the max font size allowed
9
- height: 54px;
9
+ height: 44px;
10
10
  // line up the value and unit
11
11
  align-items: baseline;
12
12
  padding-right: $spacing-05;
@@ -302625,7 +302625,7 @@ ${formatRule(Codicon.menuSubmenu)}
302625
302625
  }, typeof value === 'number' ? formatNumberWithPrecision(value, !isNil(precision) ? precision : Math.abs(value) < 1 ? value === 0 ? 0 : 3 // for small decimals give 3 spots
302626
302626
  : 1,
302627
302627
  // otherwise 1 spot if precision isn't set
302628
- locale) : value, unit && value !== '--' && /*#__PURE__*/React$1.createElement("span", {
302628
+ locale) : typeof value === 'boolean' ? String(value) : value, unit && value !== '--' && /*#__PURE__*/React$1.createElement("span", {
302629
302629
  className: "".concat(iotPrefix$Z, "--hotspot-content-unit")
302630
302630
  }, unit))));
302631
302631
  }));
@@ -302766,6 +302766,10 @@ ${formatRule(Codicon.menuSubmenu)}
302766
302766
  * Emits position obj {x, y} of hotspot to be added.
302767
302767
  */
302768
302768
  onAddHotspotPosition: PropTypes.func,
302769
+ /** Callback when a hotspot is dragged to new position in isEditable mode
302770
+ * Emits new hotspots and updated position obj {x, y} of hotspot.
302771
+ */
302772
+ onUpdateHotspotPosition: PropTypes.func,
302769
302773
  /** Callback when a hotspot is clicked in isEditable mode, emits position obj {x, y} */
302770
302774
  onSelectHotspot: PropTypes.func,
302771
302775
  /**
@@ -302806,6 +302810,7 @@ ${formatRule(Codicon.menuSubmenu)}
302806
302810
  isHotspotDataLoading: false,
302807
302811
  isEditable: false,
302808
302812
  onAddHotspotPosition: function onAddHotspotPosition() {},
302813
+ onUpdateHotspotPosition: function onUpdateHotspotPosition() {},
302809
302814
  onSelectHotspot: function onSelectHotspot() {},
302810
302815
  onHotspotContentChanged: function onHotspotContentChanged() {},
302811
302816
  background: '#eee',
@@ -302924,6 +302929,7 @@ ${formatRule(Codicon.menuSubmenu)}
302924
302929
  var width;
302925
302930
  var height;
302926
302931
  var top;
302932
+ var left;
302927
302933
 
302928
302934
  // CONTAIN
302929
302935
  if (objectFit === 'contain') {
@@ -302931,32 +302937,38 @@ ${formatRule(Codicon.menuSubmenu)}
302931
302937
  width = imageWidth;
302932
302938
  height = imageWidth / imageRatio;
302933
302939
  top = imageScale > 1 ? imageOffsetY : imageObjectFitOffsetY;
302940
+ left = imageScale > 1 ? 0 : (containerWidth - imageWidth) / 2;
302934
302941
  } else if (imageOrientation === 'portrait') {
302935
302942
  width = imageHeight / imageRatio;
302936
302943
  height = imageHeight;
302937
302944
  top = imageOffsetY;
302945
+ left = (containerWidth - width) / 2;
302938
302946
  }
302939
302947
  // FILL
302940
302948
  } else if (objectFit === 'fill') {
302941
302949
  width = imageScale > 1 ? imageWidth : containerWidth;
302942
302950
  height = imageScale > 1 ? imageHeight : containerHeight;
302943
302951
  top = imageOffsetY;
302952
+ left = 0;
302944
302953
  // NO OBJECT FIT
302945
302954
  } else if (!objectFit) {
302946
302955
  if (imageOrientation === 'landscape') {
302947
302956
  width = imageWidth;
302948
302957
  height = imageWidth / imageRatio;
302949
302958
  top = imageOffsetY;
302959
+ left = 0;
302950
302960
  } else if (imageOrientation === 'portrait') {
302951
302961
  width = imageHeight / imageRatio;
302952
302962
  height = imageHeight;
302953
302963
  top = imageOffsetY;
302964
+ left = (containerWidth - width) / 2;
302954
302965
  }
302955
302966
  }
302956
302967
  return {
302957
302968
  width: width,
302958
302969
  height: height,
302959
- top: top
302970
+ top: top,
302971
+ left: left
302960
302972
  };
302961
302973
  };
302962
302974
  var calculateObjectFitOffset = function calculateObjectFitOffset(_ref5) {
@@ -303201,6 +303213,7 @@ ${formatRule(Codicon.menuSubmenu)}
303201
303213
  isEditable = _ref9.isEditable,
303202
303214
  isHotspotDataLoading = _ref9.isHotspotDataLoading,
303203
303215
  onAddHotspotPosition = _ref9.onAddHotspotPosition,
303216
+ onUpdateHotspotPosition = _ref9.onUpdateHotspotPosition,
303204
303217
  onSelectHotspot = _ref9.onSelectHotspot,
303205
303218
  onHotspotContentChanged = _ref9.onHotspotContentChanged,
303206
303219
  zoomMax = _ref9.zoomMax,
@@ -303245,9 +303258,52 @@ ${formatRule(Codicon.menuSubmenu)}
303245
303258
  _useState12 = _slicedToArray$9(_useState11, 2),
303246
303259
  options = _useState12[0],
303247
303260
  setOptions = _useState12[1];
303261
+ // Tracks if a hotspot is being dragged and which one.
303262
+ var _useState13 = React$1.useState(null),
303263
+ _useState14 = _slicedToArray$9(_useState13, 2),
303264
+ draggingHotspotId = _useState14[0],
303265
+ setDraggingHotspotId = _useState14[1];
303266
+
303267
+ // Ref for outer container
303268
+ var containerRef = React$1.useRef(null);
303269
+ var dragStateRef = React$1.useRef({
303270
+ // Flag to indicate if a drag is active
303271
+ isDragging: false,
303272
+ // Stores the starting mouse position of a drag
303273
+ dragStartPosition: {
303274
+ x: 0,
303275
+ y: 0
303276
+ },
303277
+ // Tracks the current position during a drag
303278
+ currentDargPosition: null,
303279
+ // Stores layout of image and container
303280
+ layout: {
303281
+ rect: null,
303282
+ hotspotLayout: null
303283
+ },
303284
+ // Tracks Scheduled Animation Frames
303285
+ frame: null
303286
+ });
303287
+
303288
+ // Minimum pixel movement before a drag is initiated
303289
+ var DRAG_THRESHOLD = 5;
303248
303290
  var mergedI18n = React$1.useMemo(function () {
303249
303291
  return _objectSpread$T(_objectSpread$T({}, defaultProps$13.i18n), i18n);
303250
303292
  }, [i18n]);
303293
+ var hotspotsWithId = React$1.useMemo(function () {
303294
+ return hotspots.map(function (hotspot, index) {
303295
+ return _objectSpread$T(_objectSpread$T({}, hotspot), {}, {
303296
+ id: "".concat(hotspot.x, "-").concat(hotspot.y, "-").concat(index)
303297
+ });
303298
+ });
303299
+ }, [hotspots]);
303300
+ var _useState15 = React$1.useState(hotspotsWithId),
303301
+ _useState16 = _slicedToArray$9(_useState15, 2),
303302
+ editableHotspots = _useState16[0],
303303
+ setEditableHotspots = _useState16[1];
303304
+ React$1.useEffect(function () {
303305
+ setEditableHotspots(hotspotsWithId);
303306
+ }, [hotspotsWithId]);
303251
303307
  var handleCtrlKeyUp = React$1.useCallback(function (event) {
303252
303308
  // Was the control key unpressed
303253
303309
  if (event.key === keyboardKeys.CONTROL) {
@@ -303319,6 +303375,11 @@ ${formatRule(Codicon.menuSubmenu)}
303319
303375
  objectFit: displayOption
303320
303376
  };
303321
303377
  var onHotspotClicked = React$1.useCallback(function (evt, position) {
303378
+ // prevent click behavior after drag
303379
+ if (dragStateRef.current.isDragging) {
303380
+ return;
303381
+ }
303382
+
303322
303383
  // It is possible to receive two events here, one Mouse event and one Pointer event.
303323
303384
  // When used in the ImageHotspots component the Pointer event can somehow be from a
303324
303385
  // previously clicked hotspot. See https://github.com/carbon-design-system/carbon-addons-iot-react/issues/1803
@@ -303327,6 +303388,108 @@ ${formatRule(Codicon.menuSubmenu)}
303327
303388
  onSelectHotspot(position);
303328
303389
  }
303329
303390
  }, [onSelectHotspot, isEditable]);
303391
+ var handleMouseDownHotspot = React$1.useCallback(function (e, id1) {
303392
+ var hotspot = editableHotspots.find(function (h) {
303393
+ return h.id === id1;
303394
+ });
303395
+ if (!isEditable || (hotspot === null || hotspot === void 0 ? void 0 : hotspot.type) === 'dynamic') {
303396
+ return;
303397
+ }
303398
+ e.stopPropagation();
303399
+ setDraggingHotspotId(id1);
303400
+ dragStateRef.current.isDragging = true;
303401
+ dragStateRef.current.dragStartPosition = {
303402
+ x: e.clientX,
303403
+ y: e.clientY
303404
+ };
303405
+
303406
+ // Calculate layout once at drag start
303407
+ if (containerRef.current) {
303408
+ var rect = containerRef.current.getBoundingClientRect();
303409
+ var hotspotLayout = calculateHotspotContainerLayout(image, container, displayOption);
303410
+ dragStateRef.current.layout = {
303411
+ rect: rect,
303412
+ hotspotLayout: hotspotLayout
303413
+ };
303414
+ }
303415
+ }, [container, displayOption, image, isEditable, editableHotspots]);
303416
+ var handleMouseMoveHotspot = React$1.useCallback(function (e) {
303417
+ if (draggingHotspotId !== null && containerRef.current) {
303418
+ var _dragStateRef$current = dragStateRef.current,
303419
+ isDragging = _dragStateRef$current.isDragging,
303420
+ dragStartPosition = _dragStateRef$current.dragStartPosition,
303421
+ layout = _dragStateRef$current.layout;
303422
+ var dx = e.clientX - dragStartPosition.x;
303423
+ var dy = e.clientY - dragStartPosition.y;
303424
+ var distance = Math.sqrt(dx * dx + dy * dy);
303425
+ if (distance > DRAG_THRESHOLD && isDragging) {
303426
+ var rect = layout.rect,
303427
+ hotspotLayout = layout.hotspotLayout;
303428
+ var x = (e.clientX - rect.left - hotspotLayout.left) / hotspotLayout.width * 100;
303429
+ var y = (e.clientY - rect.top - hotspotLayout.top) / hotspotLayout.height * 100;
303430
+ // Clamp within image boundaries
303431
+ x = Math.max(0, Math.min(100, x));
303432
+ y = Math.max(0, Math.min(100, y));
303433
+ dragStateRef.current.currentDargPosition = {
303434
+ x: x,
303435
+ y: y
303436
+ };
303437
+
303438
+ // throttle with requestAnimationFrame
303439
+ if (dragStateRef.current.frame === null) {
303440
+ dragStateRef.current.frame = requestAnimationFrame(function () {
303441
+ setEditableHotspots(function (prev) {
303442
+ return prev.map(function (item) {
303443
+ return item.id === draggingHotspotId ? _objectSpread$T(_objectSpread$T({}, item), {}, {
303444
+ x: dragStateRef.current.currentDargPosition.x,
303445
+ y: dragStateRef.current.currentDargPosition.y
303446
+ }) : item;
303447
+ });
303448
+ });
303449
+ dragStateRef.current.frame = null;
303450
+ });
303451
+ }
303452
+ }
303453
+ }
303454
+ }, [draggingHotspotId]);
303455
+ var handleMouseUpHotspot = React$1.useCallback(function (e) {
303456
+ if (draggingHotspotId !== null && dragStateRef.current.isDragging) {
303457
+ e.stopPropagation();
303458
+ var _ref10 = dragStateRef.current.currentDargPosition || {},
303459
+ x = _ref10.x,
303460
+ y = _ref10.y;
303461
+ onUpdateHotspotPosition({
303462
+ newHotspots: editableHotspots,
303463
+ position: {
303464
+ x: x,
303465
+ y: y
303466
+ }
303467
+ });
303468
+ setDraggingHotspotId(null);
303469
+ dragStateRef.current.currentDargPosition = null;
303470
+ dragStateRef.current.isDragging = false;
303471
+ }
303472
+ }, [editableHotspots, draggingHotspotId, onUpdateHotspotPosition]);
303473
+ React$1.useEffect(function () {
303474
+ var dragState = dragStateRef.current;
303475
+ return function () {
303476
+ if (dragState.frame !== null) {
303477
+ cancelAnimationFrame(dragState.frame);
303478
+ }
303479
+ };
303480
+ }, []);
303481
+
303482
+ // Listens to mouse movement for dragging hotspot
303483
+ React$1.useEffect(function () {
303484
+ if (!isEditable) return undefined;
303485
+ var containerElement = containerRef.current;
303486
+ containerElement.addEventListener('mousemove', handleMouseMoveHotspot);
303487
+ containerElement.addEventListener('mouseup', handleMouseUpHotspot);
303488
+ return function () {
303489
+ containerElement.removeEventListener('mousemove', handleMouseMoveHotspot);
303490
+ containerElement.removeEventListener('mouseup', handleMouseUpHotspot);
303491
+ };
303492
+ }, [draggingHotspotId, handleMouseMoveHotspot, handleMouseUpHotspot, isEditable]);
303330
303493
  var getIconRenderFunction = React$1.useCallback(function () {
303331
303494
  return renderIconByName || (Array.isArray(icons) ? function (name, props) {
303332
303495
  var _icons$find;
@@ -303345,7 +303508,7 @@ ${formatRule(Codicon.menuSubmenu)}
303345
303508
 
303346
303509
  // Performance improvement
303347
303510
  var cachedHotspots = React$1.useMemo(function () {
303348
- return hotspots.map(function (hotspot, index) {
303511
+ return editableHotspots.map(function (hotspot, index) {
303349
303512
  var _hotspot$content;
303350
303513
  var x = hotspot.x,
303351
303514
  y = hotspot.y;
@@ -303355,10 +303518,10 @@ ${formatRule(Codicon.menuSubmenu)}
303355
303518
  // Determine whether the icon needs to be dynamically overridden by a threshold
303356
303519
  var matchingAttributeThresholds = [];
303357
303520
  if ((_hotspot$content = hotspot.content) !== null && _hotspot$content !== void 0 && _hotspot$content.attributes) {
303358
- hotspot.content.attributes.forEach(function (_ref10) {
303521
+ hotspot.content.attributes.forEach(function (_ref11) {
303359
303522
  var _hotspot$content2;
303360
- var thresholds = _ref10.thresholds,
303361
- dataSourceId = _ref10.dataSourceId;
303523
+ var thresholds = _ref11.thresholds,
303524
+ dataSourceId = _ref11.dataSourceId;
303362
303525
  if (!isEmpty(thresholds) && !isEmpty((_hotspot$content2 = hotspot.content) === null || _hotspot$content2 === void 0 ? void 0 : _hotspot$content2.values)) {
303363
303526
  var _hotspot$content3;
303364
303527
  var attributeThresholds = findMatchingThresholds(thresholds.map(function (threshold) {
@@ -303384,10 +303547,13 @@ ${formatRule(Codicon.menuSubmenu)}
303384
303547
  key: "".concat(x, "-").concat(y, "-").concat(index),
303385
303548
  renderIconByName: getIconRenderFunction(),
303386
303549
  isSelected: hotspotIsSelected,
303387
- onClick: onHotspotClicked
303550
+ onClick: onHotspotClicked,
303551
+ onMouseDown: function onMouseDown(e) {
303552
+ return handleMouseDownHotspot(e, hotspot.id);
303553
+ }
303388
303554
  }));
303389
303555
  });
303390
- }, [hotspots, selectedHotspots, locale, getIconRenderFunction, isEditable, onHotspotContentChanged, mergedI18n, onHotspotClicked]);
303556
+ }, [editableHotspots, selectedHotspots, locale, getIconRenderFunction, isEditable, onHotspotContentChanged, mergedI18n, onHotspotClicked, handleMouseDownHotspot]);
303391
303557
  var hotspotsStyle = {
303392
303558
  position: 'absolute',
303393
303559
  left: image.offsetX,
@@ -303430,7 +303596,8 @@ ${formatRule(Codicon.menuSubmenu)}
303430
303596
  // If we leave the container, stop detecting the drag
303431
303597
  stopDrag(cursor, setCursor);
303432
303598
  }
303433
- }
303599
+ },
303600
+ ref: containerRef
303434
303601
  }, src ? /*#__PURE__*/React$1.createElement("img", {
303435
303602
  id: id,
303436
303603
  className: "".concat(iotPrefix$Y, "--image-card-img"),
@@ -303657,6 +303824,17 @@ ${formatRule(Codicon.menuSubmenu)}
303657
303824
  },
303658
303825
  "required": false
303659
303826
  },
303827
+ "onUpdateHotspotPosition": {
303828
+ "defaultValue": {
303829
+ "value": "() => {}",
303830
+ "computed": false
303831
+ },
303832
+ "description": "Callback when a hotspot is dragged to new position in isEditable mode\nEmits new hotspots and updated position obj {x, y} of hotspot.",
303833
+ "type": {
303834
+ "name": "func"
303835
+ },
303836
+ "required": false
303837
+ },
303660
303838
  "onSelectHotspot": {
303661
303839
  "defaultValue": {
303662
303840
  "value": "() => {}",
@@ -316150,6 +316328,7 @@ ${formatRule(Codicon.menuSubmenu)}
316150
316328
  hotspotDataSourceChange: 'HOTSPOT_DATA_SOURCE_CHANGE',
316151
316329
  hotspotDataSourceSettingsChange: 'HOTSPOT_DATA_SOURCE_SETTINGS_CHANGE',
316152
316330
  hotspotTooltipChange: 'HOTSPOT_TOOLTIP_CHANGE',
316331
+ hotspotPositionChange: 'HOTSPOT_POSITION_CHANGE',
316153
316332
  hotspotSelect: 'HOTSPOT_SELECT',
316154
316333
  hotspotsAdd: 'HOTSPOTS_ADD',
316155
316334
  textHotspotStyleChange: 'TEXT_HOTSPOT_STYLE_CHANGE',
@@ -316252,10 +316431,32 @@ ${formatRule(Codicon.menuSubmenu)}
316252
316431
  };
316253
316432
  return getHotspotUpdate(state, _mergeSpec);
316254
316433
  }
316434
+ // HOTSPOT POSITION CHANGE
316435
+ case hotspotActionTypes.hotspotPositionChange:
316436
+ {
316437
+ var isPositionAvailable = !state.hotspots.find(function (hotspot) {
316438
+ return isHotspotMatch(hotspot, payload.position);
316439
+ });
316440
+ if (isPositionAvailable) {
316441
+ // Find the updated hotspot in the new hotspots array to maintain selection
316442
+ var updatedSelectedHotspot = payload.newHotspots.find(function (hotspot) {
316443
+ return isHotspotMatch(hotspot, payload.position);
316444
+ });
316445
+ return update(state, {
316446
+ hotspots: {
316447
+ $set: payload.newHotspots
316448
+ },
316449
+ selectedHotspot: {
316450
+ $set: updatedSelectedHotspot
316451
+ }
316452
+ });
316453
+ }
316454
+ return state;
316455
+ }
316255
316456
  // HOTSPOTS ADD
316256
316457
  case hotspotActionTypes.hotspotsAdd:
316257
316458
  {
316258
- var isPositionAvailable = !state.hotspots.find(function (hotspot) {
316459
+ var _isPositionAvailable = !state.hotspots.find(function (hotspot) {
316259
316460
  return isHotspotMatch(hotspot, payload.position);
316260
316461
  });
316261
316462
  var defaultContent = state.currentType === hotspotTypes.TEXT ? {
@@ -316266,7 +316467,7 @@ ${formatRule(Codicon.menuSubmenu)}
316266
316467
  content: defaultContent,
316267
316468
  type: createableType
316268
316469
  });
316269
- return isPositionAvailable ? update(state, {
316470
+ return _isPositionAvailable ? update(state, {
316270
316471
  selectedHotspot: {
316271
316472
  $set: newHotspot
316272
316473
  },
@@ -316294,7 +316495,7 @@ ${formatRule(Codicon.menuSubmenu)}
316294
316495
  $set: hotspot
316295
316496
  },
316296
316497
  currentType: {
316297
- $set: (_hotspot$type = hotspot.type) !== null && _hotspot$type !== void 0 ? _hotspot$type : defaultTypeWhenMissing
316498
+ $set: (_hotspot$type = hotspot === null || hotspot === void 0 ? void 0 : hotspot.type) !== null && _hotspot$type !== void 0 ? _hotspot$type : defaultTypeWhenMissing
316298
316499
  }
316299
316500
  });
316300
316501
  }
@@ -316493,6 +316694,12 @@ ${formatRule(Codicon.menuSubmenu)}
316493
316694
  payload: hotspotContent
316494
316695
  });
316495
316696
  };
316697
+ var updateHotspotPosition = function updateHotspotPosition(hotspotPosition) {
316698
+ return dispatch({
316699
+ type: hotspotActionTypes.hotspotPositionChange,
316700
+ payload: hotspotPosition
316701
+ });
316702
+ };
316496
316703
 
316497
316704
  /** Updates the properties of the text hotspot, passes a payload like {color: 'blue'} */
316498
316705
  var updateTextHotspotStyle = function updateTextHotspotStyle(textHotspotStyle) {
@@ -316580,6 +316787,7 @@ ${formatRule(Codicon.menuSubmenu)}
316580
316787
  switchCurrentType: switchCurrentType,
316581
316788
  updateHotspotDataSource: updateHotspotDataSource,
316582
316789
  updateHotspotTooltip: updateHotspotTooltip,
316790
+ updateHotspotPosition: updateHotspotPosition,
316583
316791
  updateTextHotspotStyle: updateTextHotspotStyle,
316584
316792
  updateTextHotspotContent: updateTextHotspotContent,
316585
316793
  updateDynamicHotspotSourceX: updateDynamicHotspotSourceX,
@@ -317119,6 +317327,7 @@ ${formatRule(Codicon.menuSubmenu)}
317119
317327
  switchCurrentType = _useHotspotEditorStat.switchCurrentType,
317120
317328
  updateHotspotDataSource = _useHotspotEditorStat.updateHotspotDataSource,
317121
317329
  updateHotspotTooltip = _useHotspotEditorStat.updateHotspotTooltip,
317330
+ updateHotspotPosition = _useHotspotEditorStat.updateHotspotPosition,
317122
317331
  updateTextHotspotStyle = _useHotspotEditorStat.updateTextHotspotStyle,
317123
317332
  updateTextHotspotContent = _useHotspotEditorStat.updateTextHotspotContent,
317124
317333
  updateDynamicHotspotSourceX = _useHotspotEditorStat.updateDynamicHotspotSourceX,
@@ -317184,6 +317393,8 @@ ${formatRule(Codicon.menuSubmenu)}
317184
317393
  }
317185
317394
  var hotspotsWithoutExampleValues = filteredHotspots.map(function (hotspot) {
317186
317395
  return update(hotspot, {
317396
+ $unset: ['id'],
317397
+ // Remove the internal id added for drag tracking
317187
317398
  content: {
317188
317399
  $unset: ['values']
317189
317400
  }
@@ -317433,6 +317644,7 @@ ${formatRule(Codicon.menuSubmenu)}
317433
317644
  hotspotDefaults: hotspotDefaults
317434
317645
  });
317435
317646
  },
317647
+ onUpdateHotspotPosition: updateHotspotPosition,
317436
317648
  onSelectHotspot: setSelectedHotspot,
317437
317649
  selectedHotspots: getSelectedHotspotsList(selectedHotspot, hotspots),
317438
317650
  src: cardConfig.content.src,