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.
@@ -89,6 +89,10 @@ var propTypes = {
89
89
  * Emits position obj {x, y} of hotspot to be added.
90
90
  */
91
91
  onAddHotspotPosition: PropTypes__default.default.func,
92
+ /** Callback when a hotspot is dragged to new position in isEditable mode
93
+ * Emits new hotspots and updated position obj {x, y} of hotspot.
94
+ */
95
+ onUpdateHotspotPosition: PropTypes__default.default.func,
92
96
  /** Callback when a hotspot is clicked in isEditable mode, emits position obj {x, y} */
93
97
  onSelectHotspot: PropTypes__default.default.func,
94
98
  /**
@@ -129,6 +133,7 @@ var defaultProps = {
129
133
  isHotspotDataLoading: false,
130
134
  isEditable: false,
131
135
  onAddHotspotPosition: function onAddHotspotPosition() {},
136
+ onUpdateHotspotPosition: function onUpdateHotspotPosition() {},
132
137
  onSelectHotspot: function onSelectHotspot() {},
133
138
  onHotspotContentChanged: function onHotspotContentChanged() {},
134
139
  background: '#eee',
@@ -247,6 +252,7 @@ var calculateHotspotContainerLayout = function calculateHotspotContainerLayout(_
247
252
  var width;
248
253
  var height;
249
254
  var top;
255
+ var left;
250
256
 
251
257
  // CONTAIN
252
258
  if (objectFit === 'contain') {
@@ -254,32 +260,38 @@ var calculateHotspotContainerLayout = function calculateHotspotContainerLayout(_
254
260
  width = imageWidth;
255
261
  height = imageWidth / imageRatio;
256
262
  top = imageScale > 1 ? imageOffsetY : imageObjectFitOffsetY;
263
+ left = imageScale > 1 ? 0 : (containerWidth - imageWidth) / 2;
257
264
  } else if (imageOrientation === 'portrait') {
258
265
  width = imageHeight / imageRatio;
259
266
  height = imageHeight;
260
267
  top = imageOffsetY;
268
+ left = (containerWidth - width) / 2;
261
269
  }
262
270
  // FILL
263
271
  } else if (objectFit === 'fill') {
264
272
  width = imageScale > 1 ? imageWidth : containerWidth;
265
273
  height = imageScale > 1 ? imageHeight : containerHeight;
266
274
  top = imageOffsetY;
275
+ left = 0;
267
276
  // NO OBJECT FIT
268
277
  } else if (!objectFit) {
269
278
  if (imageOrientation === 'landscape') {
270
279
  width = imageWidth;
271
280
  height = imageWidth / imageRatio;
272
281
  top = imageOffsetY;
282
+ left = 0;
273
283
  } else if (imageOrientation === 'portrait') {
274
284
  width = imageHeight / imageRatio;
275
285
  height = imageHeight;
276
286
  top = imageOffsetY;
287
+ left = (containerWidth - width) / 2;
277
288
  }
278
289
  }
279
290
  return {
280
291
  width: width,
281
292
  height: height,
282
- top: top
293
+ top: top,
294
+ left: left
283
295
  };
284
296
  };
285
297
  var calculateObjectFitOffset = function calculateObjectFitOffset(_ref5) {
@@ -524,6 +536,7 @@ var ImageHotspots = function ImageHotspots(_ref9) {
524
536
  isEditable = _ref9.isEditable,
525
537
  isHotspotDataLoading = _ref9.isHotspotDataLoading,
526
538
  onAddHotspotPosition = _ref9.onAddHotspotPosition,
539
+ onUpdateHotspotPosition = _ref9.onUpdateHotspotPosition,
527
540
  onSelectHotspot = _ref9.onSelectHotspot,
528
541
  onHotspotContentChanged = _ref9.onHotspotContentChanged,
529
542
  zoomMax = _ref9.zoomMax,
@@ -568,9 +581,52 @@ var ImageHotspots = function ImageHotspots(_ref9) {
568
581
  _useState12 = _slicedToArray__default.default(_useState11, 2),
569
582
  options = _useState12[0],
570
583
  setOptions = _useState12[1];
584
+ // Tracks if a hotspot is being dragged and which one.
585
+ var _useState13 = React.useState(null),
586
+ _useState14 = _slicedToArray__default.default(_useState13, 2),
587
+ draggingHotspotId = _useState14[0],
588
+ setDraggingHotspotId = _useState14[1];
589
+
590
+ // Ref for outer container
591
+ var containerRef = React.useRef(null);
592
+ var dragStateRef = React.useRef({
593
+ // Flag to indicate if a drag is active
594
+ isDragging: false,
595
+ // Stores the starting mouse position of a drag
596
+ dragStartPosition: {
597
+ x: 0,
598
+ y: 0
599
+ },
600
+ // Tracks the current position during a drag
601
+ currentDargPosition: null,
602
+ // Stores layout of image and container
603
+ layout: {
604
+ rect: null,
605
+ hotspotLayout: null
606
+ },
607
+ // Tracks Scheduled Animation Frames
608
+ frame: null
609
+ });
610
+
611
+ // Minimum pixel movement before a drag is initiated
612
+ var DRAG_THRESHOLD = 5;
571
613
  var mergedI18n = React.useMemo(function () {
572
614
  return _objectSpread(_objectSpread({}, defaultProps.i18n), i18n);
573
615
  }, [i18n]);
616
+ var hotspotsWithId = React.useMemo(function () {
617
+ return hotspots.map(function (hotspot, index) {
618
+ return _objectSpread(_objectSpread({}, hotspot), {}, {
619
+ id: "".concat(hotspot.x, "-").concat(hotspot.y, "-").concat(index)
620
+ });
621
+ });
622
+ }, [hotspots]);
623
+ var _useState15 = React.useState(hotspotsWithId),
624
+ _useState16 = _slicedToArray__default.default(_useState15, 2),
625
+ editableHotspots = _useState16[0],
626
+ setEditableHotspots = _useState16[1];
627
+ React.useEffect(function () {
628
+ setEditableHotspots(hotspotsWithId);
629
+ }, [hotspotsWithId]);
574
630
  var handleCtrlKeyUp = React.useCallback(function (event) {
575
631
  // Was the control key unpressed
576
632
  if (event.key === KeyCodeConstants.keyboardKeys.CONTROL) {
@@ -642,6 +698,11 @@ var ImageHotspots = function ImageHotspots(_ref9) {
642
698
  objectFit: displayOption
643
699
  };
644
700
  var onHotspotClicked = React.useCallback(function (evt, position) {
701
+ // prevent click behavior after drag
702
+ if (dragStateRef.current.isDragging) {
703
+ return;
704
+ }
705
+
645
706
  // It is possible to receive two events here, one Mouse event and one Pointer event.
646
707
  // When used in the ImageHotspots component the Pointer event can somehow be from a
647
708
  // previously clicked hotspot. See https://github.com/carbon-design-system/carbon-addons-iot-react/issues/1803
@@ -650,6 +711,108 @@ var ImageHotspots = function ImageHotspots(_ref9) {
650
711
  onSelectHotspot(position);
651
712
  }
652
713
  }, [onSelectHotspot, isEditable]);
714
+ var handleMouseDownHotspot = React.useCallback(function (e, id1) {
715
+ var hotspot = editableHotspots.find(function (h) {
716
+ return h.id === id1;
717
+ });
718
+ if (!isEditable || (hotspot === null || hotspot === void 0 ? void 0 : hotspot.type) === 'dynamic') {
719
+ return;
720
+ }
721
+ e.stopPropagation();
722
+ setDraggingHotspotId(id1);
723
+ dragStateRef.current.isDragging = true;
724
+ dragStateRef.current.dragStartPosition = {
725
+ x: e.clientX,
726
+ y: e.clientY
727
+ };
728
+
729
+ // Calculate layout once at drag start
730
+ if (containerRef.current) {
731
+ var rect = containerRef.current.getBoundingClientRect();
732
+ var hotspotLayout = calculateHotspotContainerLayout(image, container, displayOption);
733
+ dragStateRef.current.layout = {
734
+ rect: rect,
735
+ hotspotLayout: hotspotLayout
736
+ };
737
+ }
738
+ }, [container, displayOption, image, isEditable, editableHotspots]);
739
+ var handleMouseMoveHotspot = React.useCallback(function (e) {
740
+ if (draggingHotspotId !== null && containerRef.current) {
741
+ var _dragStateRef$current = dragStateRef.current,
742
+ isDragging = _dragStateRef$current.isDragging,
743
+ dragStartPosition = _dragStateRef$current.dragStartPosition,
744
+ layout = _dragStateRef$current.layout;
745
+ var dx = e.clientX - dragStartPosition.x;
746
+ var dy = e.clientY - dragStartPosition.y;
747
+ var distance = Math.sqrt(dx * dx + dy * dy);
748
+ if (distance > DRAG_THRESHOLD && isDragging) {
749
+ var rect = layout.rect,
750
+ hotspotLayout = layout.hotspotLayout;
751
+ var x = (e.clientX - rect.left - hotspotLayout.left) / hotspotLayout.width * 100;
752
+ var y = (e.clientY - rect.top - hotspotLayout.top) / hotspotLayout.height * 100;
753
+ // Clamp within image boundaries
754
+ x = Math.max(0, Math.min(100, x));
755
+ y = Math.max(0, Math.min(100, y));
756
+ dragStateRef.current.currentDargPosition = {
757
+ x: x,
758
+ y: y
759
+ };
760
+
761
+ // throttle with requestAnimationFrame
762
+ if (dragStateRef.current.frame === null) {
763
+ dragStateRef.current.frame = requestAnimationFrame(function () {
764
+ setEditableHotspots(function (prev) {
765
+ return prev.map(function (item) {
766
+ return item.id === draggingHotspotId ? _objectSpread(_objectSpread({}, item), {}, {
767
+ x: dragStateRef.current.currentDargPosition.x,
768
+ y: dragStateRef.current.currentDargPosition.y
769
+ }) : item;
770
+ });
771
+ });
772
+ dragStateRef.current.frame = null;
773
+ });
774
+ }
775
+ }
776
+ }
777
+ }, [draggingHotspotId]);
778
+ var handleMouseUpHotspot = React.useCallback(function (e) {
779
+ if (draggingHotspotId !== null && dragStateRef.current.isDragging) {
780
+ e.stopPropagation();
781
+ var _ref10 = dragStateRef.current.currentDargPosition || {},
782
+ x = _ref10.x,
783
+ y = _ref10.y;
784
+ onUpdateHotspotPosition({
785
+ newHotspots: editableHotspots,
786
+ position: {
787
+ x: x,
788
+ y: y
789
+ }
790
+ });
791
+ setDraggingHotspotId(null);
792
+ dragStateRef.current.currentDargPosition = null;
793
+ dragStateRef.current.isDragging = false;
794
+ }
795
+ }, [editableHotspots, draggingHotspotId, onUpdateHotspotPosition]);
796
+ React.useEffect(function () {
797
+ var dragState = dragStateRef.current;
798
+ return function () {
799
+ if (dragState.frame !== null) {
800
+ cancelAnimationFrame(dragState.frame);
801
+ }
802
+ };
803
+ }, []);
804
+
805
+ // Listens to mouse movement for dragging hotspot
806
+ React.useEffect(function () {
807
+ if (!isEditable) return undefined;
808
+ var containerElement = containerRef.current;
809
+ containerElement.addEventListener('mousemove', handleMouseMoveHotspot);
810
+ containerElement.addEventListener('mouseup', handleMouseUpHotspot);
811
+ return function () {
812
+ containerElement.removeEventListener('mousemove', handleMouseMoveHotspot);
813
+ containerElement.removeEventListener('mouseup', handleMouseUpHotspot);
814
+ };
815
+ }, [draggingHotspotId, handleMouseMoveHotspot, handleMouseUpHotspot, isEditable]);
653
816
  var getIconRenderFunction = React.useCallback(function () {
654
817
  return renderIconByName || (Array.isArray(icons) ? function (name, props) {
655
818
  var _icons$find;
@@ -668,7 +831,7 @@ var ImageHotspots = function ImageHotspots(_ref9) {
668
831
 
669
832
  // Performance improvement
670
833
  var cachedHotspots = React.useMemo(function () {
671
- return hotspots.map(function (hotspot, index) {
834
+ return editableHotspots.map(function (hotspot, index) {
672
835
  var _hotspot$content;
673
836
  var x = hotspot.x,
674
837
  y = hotspot.y;
@@ -678,10 +841,10 @@ var ImageHotspots = function ImageHotspots(_ref9) {
678
841
  // Determine whether the icon needs to be dynamically overridden by a threshold
679
842
  var matchingAttributeThresholds = [];
680
843
  if ((_hotspot$content = hotspot.content) !== null && _hotspot$content !== void 0 && _hotspot$content.attributes) {
681
- hotspot.content.attributes.forEach(function (_ref10) {
844
+ hotspot.content.attributes.forEach(function (_ref11) {
682
845
  var _hotspot$content2;
683
- var thresholds = _ref10.thresholds,
684
- dataSourceId = _ref10.dataSourceId;
846
+ var thresholds = _ref11.thresholds,
847
+ dataSourceId = _ref11.dataSourceId;
685
848
  if (!isEmpty(thresholds) && !isEmpty((_hotspot$content2 = hotspot.content) === null || _hotspot$content2 === void 0 ? void 0 : _hotspot$content2.values)) {
686
849
  var _hotspot$content3;
687
850
  var attributeThresholds = cardUtilityFunctions.findMatchingThresholds(thresholds.map(function (threshold) {
@@ -707,10 +870,13 @@ var ImageHotspots = function ImageHotspots(_ref9) {
707
870
  key: "".concat(x, "-").concat(y, "-").concat(index),
708
871
  renderIconByName: getIconRenderFunction(),
709
872
  isSelected: hotspotIsSelected,
710
- onClick: onHotspotClicked
873
+ onClick: onHotspotClicked,
874
+ onMouseDown: function onMouseDown(e) {
875
+ return handleMouseDownHotspot(e, hotspot.id);
876
+ }
711
877
  }));
712
878
  });
713
- }, [hotspots, selectedHotspots, locale, getIconRenderFunction, isEditable, onHotspotContentChanged, mergedI18n, onHotspotClicked]);
879
+ }, [editableHotspots, selectedHotspots, locale, getIconRenderFunction, isEditable, onHotspotContentChanged, mergedI18n, onHotspotClicked, handleMouseDownHotspot]);
714
880
  var hotspotsStyle = {
715
881
  position: 'absolute',
716
882
  left: image.offsetX,
@@ -753,7 +919,8 @@ var ImageHotspots = function ImageHotspots(_ref9) {
753
919
  // If we leave the container, stop detecting the drag
754
920
  stopDrag(cursor, setCursor);
755
921
  }
756
- }
922
+ },
923
+ ref: containerRef
757
924
  }, src ? /*#__PURE__*/React__default.default.createElement("img", {
758
925
  id: id,
759
926
  className: "".concat(iotPrefix, "--image-card-img"),
@@ -980,6 +1147,17 @@ ImageHotspots.__docgenInfo = {
980
1147
  },
981
1148
  "required": false
982
1149
  },
1150
+ "onUpdateHotspotPosition": {
1151
+ "defaultValue": {
1152
+ "value": "() => {}",
1153
+ "computed": false
1154
+ },
1155
+ "description": "Callback when a hotspot is dragged to new position in isEditable mode\nEmits new hotspots and updated position obj {x, y} of hotspot.",
1156
+ "type": {
1157
+ "name": "func"
1158
+ },
1159
+ "required": false
1160
+ },
983
1161
  "onSelectHotspot": {
984
1162
  "defaultValue": {
985
1163
  "value": "() => {}",
@@ -40829,7 +40829,7 @@ html[dir=rtl] .iot--time-picker__wrapper .iot--time-picker__controls {
40829
40829
 
40830
40830
  .iot--value-card__attribute {
40831
40831
  display: flex;
40832
- height: 54px;
40832
+ height: 44px;
40833
40833
  align-items: baseline;
40834
40834
  padding-right: 1rem;
40835
40835
  }