@pie-element/hotspot 11.1.6 → 11.2.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.
@@ -71,7 +71,7 @@ describe('RectComponent', () => {
71
71
  const { container } = render(<RectComponent {...defaultProps} />);
72
72
  const rects = container.querySelectorAll('[data-testid="rect"]');
73
73
  const mainRect = rects[rects.length - 1];
74
-
74
+
75
75
  expect(mainRect).toHaveAttribute('x', '10');
76
76
  expect(mainRect).toHaveAttribute('y', '20');
77
77
  expect(mainRect).toHaveAttribute('width', '100');
@@ -82,7 +82,7 @@ describe('RectComponent', () => {
82
82
  const { container } = render(<RectComponent {...defaultProps} />);
83
83
  const rects = container.querySelectorAll('[data-testid="rect"]');
84
84
  const mainRect = rects[rects.length - 1];
85
-
85
+
86
86
  expect(mainRect).toHaveAttribute('fill', '#FF0000');
87
87
  });
88
88
 
@@ -90,14 +90,14 @@ describe('RectComponent', () => {
90
90
  const { container } = render(<RectComponent {...defaultProps} selected={true} />);
91
91
  const rects = container.querySelectorAll('[data-testid="rect"]');
92
92
  const mainRect = rects[rects.length - 1];
93
-
93
+
94
94
  expect(mainRect).toHaveAttribute('fill', '#00FF00');
95
95
  });
96
96
 
97
97
  it('should apply scale transform', () => {
98
98
  const { getByTestId } = render(<RectComponent {...defaultProps} scale={1.5} />);
99
99
  const group = getByTestId('group');
100
-
100
+
101
101
  expect(group).toHaveAttribute('scaleX', '1.5');
102
102
  expect(group).toHaveAttribute('scaleY', '1.5');
103
103
  });
@@ -105,7 +105,7 @@ describe('RectComponent', () => {
105
105
  it('should render with default scale of 1', () => {
106
106
  const { getByTestId } = render(<RectComponent {...defaultProps} />);
107
107
  const group = getByTestId('group');
108
-
108
+
109
109
  expect(group).toHaveAttribute('scaleX', '1');
110
110
  expect(group).toHaveAttribute('scaleY', '1');
111
111
  });
@@ -117,9 +117,9 @@ describe('RectComponent', () => {
117
117
  const { container } = render(<RectComponent {...defaultProps} onClick={onClick} />);
118
118
  const rects = container.querySelectorAll('[data-testid="rect"]');
119
119
  const mainRect = rects[rects.length - 1];
120
-
120
+
121
121
  fireEvent.click(mainRect);
122
-
122
+
123
123
  expect(onClick).toHaveBeenCalledWith({
124
124
  id: 'rect1',
125
125
  selected: true,
@@ -132,21 +132,21 @@ describe('RectComponent', () => {
132
132
  const { container, rerender } = render(<RectComponent {...defaultProps} onClick={onClick} selected={false} />);
133
133
  const rects = container.querySelectorAll('[data-testid="rect"]');
134
134
  const mainRect = rects[rects.length - 1];
135
-
135
+
136
136
  fireEvent.click(mainRect);
137
-
137
+
138
138
  expect(onClick).toHaveBeenCalledWith({
139
139
  id: 'rect1',
140
140
  selected: true,
141
141
  selector: 'Mouse',
142
142
  });
143
-
143
+
144
144
  rerender(<RectComponent {...defaultProps} onClick={onClick} selected={true} />);
145
-
145
+
146
146
  const rectsAfter = container.querySelectorAll('[data-testid="rect"]');
147
147
  const mainRectAfter = rectsAfter[rectsAfter.length - 1];
148
148
  fireEvent.click(mainRectAfter);
149
-
149
+
150
150
  expect(onClick).toHaveBeenCalledWith({
151
151
  id: 'rect1',
152
152
  selected: false,
@@ -159,9 +159,9 @@ describe('RectComponent', () => {
159
159
  const { container } = render(<RectComponent {...defaultProps} onClick={onClick} disabled={true} />);
160
160
  const rects = container.querySelectorAll('[data-testid="rect"]');
161
161
  const mainRect = rects[rects.length - 1];
162
-
162
+
163
163
  fireEvent.click(mainRect);
164
-
164
+
165
165
  expect(onClick).not.toHaveBeenCalled();
166
166
  });
167
167
 
@@ -169,9 +169,9 @@ describe('RectComponent', () => {
169
169
  const { container } = render(<RectComponent {...defaultProps} />);
170
170
  const rects = container.querySelectorAll('[data-testid="rect"]');
171
171
  const mainRect = rects[rects.length - 1];
172
-
172
+
173
173
  fireEvent.mouseEnter(mainRect);
174
-
174
+
175
175
  expect(document.body.style.cursor).toBe('pointer');
176
176
  });
177
177
 
@@ -179,9 +179,9 @@ describe('RectComponent', () => {
179
179
  const { container } = render(<RectComponent {...defaultProps} disabled={true} />);
180
180
  const rects = container.querySelectorAll('[data-testid="rect"]');
181
181
  const mainRect = rects[rects.length - 1];
182
-
182
+
183
183
  fireEvent.mouseEnter(mainRect);
184
-
184
+
185
185
  expect(document.body.style.cursor).toBe('default');
186
186
  });
187
187
 
@@ -189,10 +189,10 @@ describe('RectComponent', () => {
189
189
  const { container } = render(<RectComponent {...defaultProps} />);
190
190
  const rects = container.querySelectorAll('[data-testid="rect"]');
191
191
  const mainRect = rects[rects.length - 1];
192
-
192
+
193
193
  fireEvent.mouseEnter(mainRect);
194
194
  fireEvent.mouseLeave(mainRect);
195
-
195
+
196
196
  expect(document.body.style.cursor).toBe('default');
197
197
  });
198
198
  });
@@ -202,9 +202,9 @@ describe('RectComponent', () => {
202
202
  const { container } = render(<RectComponent {...defaultProps} hoverOutlineColor="#FFFF00" />);
203
203
  const rects = container.querySelectorAll('[data-testid="rect"]');
204
204
  const mainRect = rects[rects.length - 1];
205
-
205
+
206
206
  fireEvent.mouseEnter(mainRect);
207
-
207
+
208
208
  const rectsAfterHover = container.querySelectorAll('[data-testid="rect"]');
209
209
  expect(rectsAfterHover.length).toBeGreaterThan(1);
210
210
  });
@@ -215,12 +215,12 @@ describe('RectComponent', () => {
215
215
  );
216
216
  const rects = container.querySelectorAll('[data-testid="rect"]');
217
217
  const mainRect = rects[rects.length - 1];
218
-
218
+
219
219
  fireEvent.mouseEnter(mainRect);
220
-
220
+
221
221
  const hoverRect = container.querySelector('[stroke="#FFFF00"]');
222
222
  if (hoverRect) {
223
- expect(hoverRect).toHaveAttribute('stroke', 'transparent');
223
+ expect(hoverRect).toHaveAttribute('stroke', '#FFFF00');
224
224
  }
225
225
  });
226
226
  });
@@ -236,7 +236,7 @@ describe('RectComponent', () => {
236
236
  showCorrectEnabled={false}
237
237
  />
238
238
  );
239
-
239
+
240
240
  const icon = getByTestId('icon-image');
241
241
  expect(icon).toBeInTheDocument();
242
242
  expect(icon).toHaveAttribute('data-src');
@@ -252,7 +252,7 @@ describe('RectComponent', () => {
252
252
  showCorrectEnabled={false}
253
253
  />
254
254
  );
255
-
255
+
256
256
  const icon = getByTestId('icon-image');
257
257
  expect(icon).toBeInTheDocument();
258
258
  });
@@ -267,7 +267,7 @@ describe('RectComponent', () => {
267
267
  showCorrectEnabled={false}
268
268
  />
269
269
  );
270
-
270
+
271
271
  const icon = getByTestId('icon-image');
272
272
  expect(icon).toBeInTheDocument();
273
273
  });
@@ -282,7 +282,7 @@ describe('RectComponent', () => {
282
282
  showCorrectEnabled={false}
283
283
  />
284
284
  );
285
-
285
+
286
286
  const icon = queryByTestId('icon-image');
287
287
  expect(icon).not.toBeInTheDocument();
288
288
  });
@@ -297,7 +297,7 @@ describe('RectComponent', () => {
297
297
  showCorrectEnabled={true}
298
298
  />
299
299
  );
300
-
300
+
301
301
  const icon = getByTestId('icon-image');
302
302
  expect(icon).toBeInTheDocument();
303
303
  });
@@ -312,7 +312,7 @@ describe('RectComponent', () => {
312
312
  showCorrectEnabled={true}
313
313
  />
314
314
  );
315
-
315
+
316
316
  const icon = getByTestId('icon-image');
317
317
  expect(icon).toBeInTheDocument();
318
318
  });
@@ -327,7 +327,7 @@ describe('RectComponent', () => {
327
327
  showCorrectEnabled={true}
328
328
  />
329
329
  );
330
-
330
+
331
331
  const icon = queryByTestId('icon-image');
332
332
  expect(icon).not.toBeInTheDocument();
333
333
  });
@@ -342,7 +342,7 @@ describe('RectComponent', () => {
342
342
  showCorrectEnabled={true}
343
343
  />
344
344
  );
345
-
345
+
346
346
  const icon = queryByTestId('icon-image');
347
347
  expect(icon).not.toBeInTheDocument();
348
348
  });
@@ -355,7 +355,7 @@ describe('RectComponent', () => {
355
355
  markAsCorrect={true}
356
356
  />
357
357
  );
358
-
358
+
359
359
  const rects = container.querySelectorAll('[data-testid="rect"]');
360
360
  const mainRect = rects[rects.length - 1];
361
361
  expect(mainRect).toHaveAttribute('stroke', 'green');
@@ -370,7 +370,7 @@ describe('RectComponent', () => {
370
370
  markAsCorrect={false}
371
371
  />
372
372
  );
373
-
373
+
374
374
  const rects = container.querySelectorAll('[data-testid="rect"]');
375
375
  const mainRect = rects[rects.length - 1];
376
376
  expect(mainRect).toHaveAttribute('stroke', 'red');
@@ -387,7 +387,7 @@ describe('RectComponent', () => {
387
387
  showCorrectEnabled={false}
388
388
  />
389
389
  );
390
-
390
+
391
391
  const icon = getByTestId('icon-image');
392
392
  expect(icon).toHaveAttribute('data-tooltip', 'Correct answer!');
393
393
  });
@@ -408,7 +408,7 @@ describe('RectComponent', () => {
408
408
  showCorrectEnabled={false}
409
409
  />
410
410
  );
411
-
411
+
412
412
  const icon = getByTestId('icon-image');
413
413
  // Icon should be centered: x + width/2 - 10, y + height/2 - 10
414
414
  expect(icon).toHaveAttribute('data-x', '50'); // 10 + 100/2 - 10
@@ -50,6 +50,7 @@ class CircleComponent extends React.Component {
50
50
  hoverOutlineColor,
51
51
  outlineColor,
52
52
  selected,
53
+ focused,
53
54
  x,
54
55
  y,
55
56
  evaluateText,
@@ -88,20 +89,10 @@ class CircleComponent extends React.Component {
88
89
  }
89
90
  }
90
91
 
91
- const useHoveredStyle = hovered && hoverOutlineColor;
92
+ const useHoveredStyle = (hovered || focused) && hoverOutlineColor;
92
93
 
93
94
  return (
94
- <Group scaleX={scale} scaleY={scale}>
95
- {useHoveredStyle && (
96
- <Rect
97
- x={x - radius}
98
- y={y - radius}
99
- width={radius * 2}
100
- height={radius * 2}
101
- stroke={selected ? 'transparent' : hoverOutlineColor}
102
- strokeWidth={strokeWidth}
103
- />
104
- )}
95
+ <Group scaleX={scale} scaleY={scale} onMouseLeave={this.handleMouseLeave} onMouseEnter={this.handleMouseEnter}>
105
96
  <Circle
106
97
  radius={radius}
107
98
  fill={selected && selectedHotspotColor ? selectedHotspotColor : hotspotColor}
@@ -110,11 +101,20 @@ class CircleComponent extends React.Component {
110
101
  draggable={false}
111
102
  stroke={useHoveredStyle && !selected ? 'transparent' : outlineColorParsed}
112
103
  strokeWidth={useHoveredStyle && !selected ? 0 : outlineWidth}
113
- onMouseLeave={this.handleMouseLeave}
114
- onMouseEnter={this.handleMouseEnter}
115
104
  x={x}
116
105
  y={y}
117
106
  />
107
+ {useHoveredStyle && (
108
+ <Rect
109
+ x={x - radius}
110
+ y={y - radius}
111
+ width={radius * 2}
112
+ height={radius * 2}
113
+ stroke={hoverOutlineColor}
114
+ strokeWidth={strokeWidth}
115
+ listening={false}
116
+ />
117
+ )}
118
118
  {isEvaluateMode && iconSrc ? <ImageComponent src={iconSrc} x={iconX} y={iconY} tooltip={evaluateText} /> : null}
119
119
  </Group>
120
120
  );
@@ -128,6 +128,7 @@ CircleComponent.propTypes = {
128
128
  isCorrect: PropTypes.oneOfType([PropTypes.bool, PropTypes.string]),
129
129
  isEvaluateMode: PropTypes.bool.isRequired,
130
130
  disabled: PropTypes.bool.isRequired,
131
+ focused: PropTypes.bool,
131
132
  hoverOutlineColor: PropTypes.string,
132
133
  onClick: PropTypes.func.isRequired,
133
134
  outlineColor: PropTypes.string.isRequired,
@@ -145,6 +146,7 @@ CircleComponent.propTypes = {
145
146
  CircleComponent.defaultProps = {
146
147
  isCorrect: false,
147
148
  evaluateText: null,
149
+ focused: false,
148
150
  strokeWidth: 5,
149
151
  scale: 1,
150
152
  };
@@ -14,8 +14,19 @@ const BaseContainer = styled('div')(({ theme }) => ({
14
14
  background: theme.palette.common.white,
15
15
  border: `${theme.spacing(1)} solid ${theme.palette.common.white}`,
16
16
  width: 'fit-content',
17
+ maxWidth: '100%',
18
+ overflowX: 'auto',
17
19
  }));
18
20
 
21
+ const HiddenFocusable = styled('span')({
22
+ position: 'absolute',
23
+ width: 1,
24
+ height: 1,
25
+ overflow: 'hidden',
26
+ clip: 'rect(0, 0, 0, 0)',
27
+ whiteSpace: 'nowrap',
28
+ });
29
+
19
30
  const ImageContainer = styled('div')({
20
31
  position: 'relative',
21
32
  width: 'fit-content',
@@ -34,6 +45,13 @@ const StyledStage = styled(Stage)({
34
45
  });
35
46
 
36
47
  export class Container extends React.Component {
48
+ constructor(props) {
49
+ super(props);
50
+ this.state = {
51
+ focusedShapeId: null,
52
+ };
53
+ }
54
+
37
55
  isSelected(shape) {
38
56
  const selectedShape = this.props.session.answers.filter((answer) => answer.id === shape.id)[0];
39
57
  return !!selectedShape;
@@ -57,6 +75,44 @@ export class Container extends React.Component {
57
75
  return null;
58
76
  };
59
77
 
78
+ getAllShapesSorted = () => {
79
+ const { shapes: { rectangles = [], polygons = [], circles = [] } } = this.props;
80
+ const allShapes = [
81
+ ...rectangles.map((s) => ({ ...s, type: 'rectangle' })),
82
+ ...polygons.map((s) => ({ ...s, type: 'polygon' })),
83
+ ...circles.map((s) => ({ ...s, type: 'circle' })),
84
+ ];
85
+ allShapes.sort((a, b) => String(a.id).localeCompare(String(b.id), undefined, { numeric: true }));
86
+
87
+ return allShapes;
88
+ };
89
+
90
+ handleShapeFocus = (shapeId) => {
91
+ this.setState({ focusedShapeId: shapeId });
92
+ };
93
+
94
+ handleShapeBlur = () => {
95
+ this.setState({ focusedShapeId: null });
96
+ };
97
+
98
+ handleShapeKeyDown = (e, shapeId) => {
99
+ if (e.key === 'Enter' || e.key === ' ') {
100
+ e.preventDefault();
101
+
102
+ const { onSelectChoice, disabled } = this.props;
103
+
104
+ if (!disabled) {
105
+ const shape = this.getAllShapesSorted().find((s) => s.id === shapeId);
106
+
107
+ if (shape) {
108
+ const selected = this.isSelected(shape);
109
+
110
+ onSelectChoice({ id: shapeId, selected: !selected, selector: 'Keyboard' });
111
+ }
112
+ }
113
+ }
114
+ };
115
+
60
116
  render() {
61
117
  const {
62
118
  dimensions: { width: withProp, height: heightProp },
@@ -74,9 +130,13 @@ export class Container extends React.Component {
74
130
  showCorrect,
75
131
  } = this.props;
76
132
 
133
+ const { focusedShapeId } = this.state;
134
+
77
135
  const width = withProp * SCALE;
78
136
  const height = heightProp * SCALE;
79
137
 
138
+ const sortedShapes = this.getAllShapesSorted();
139
+
80
140
  return (
81
141
  <BaseContainer style={{ padding: strokeWidth / 2 }}>
82
142
  {imageUrl ? (
@@ -125,6 +185,7 @@ export class Container extends React.Component {
125
185
  strokeWidth={strokeWidth}
126
186
  markAsCorrect={markAsCorrect}
127
187
  showCorrectEnabled={showCorrect}
188
+ focused={focusedShapeId === shape.id}
128
189
  />
129
190
  );
130
191
  })}
@@ -153,6 +214,7 @@ export class Container extends React.Component {
153
214
  selectedHotspotColor={selectedHotspotColor}
154
215
  hoverOutlineColor={hoverOutlineColor}
155
216
  showCorrectEnabled={showCorrect}
217
+ focused={focusedShapeId === polygon.id}
156
218
  />
157
219
  );
158
220
  })}
@@ -183,11 +245,29 @@ export class Container extends React.Component {
183
245
  selectedHotspotColor={selectedHotspotColor}
184
246
  hoverOutlineColor={hoverOutlineColor}
185
247
  showCorrectEnabled={showCorrect}
248
+ focused={focusedShapeId === shape.id}
186
249
  />
187
250
  );
188
251
  })}
189
252
  </Layer>
190
253
  </StyledStage>
254
+
255
+ {sortedShapes.map((shape) => {
256
+ const selected = this.isSelected(shape);
257
+
258
+ return (
259
+ <HiddenFocusable
260
+ key={`focus-${shape.id}`}
261
+ tabIndex={disabled ? -1 : 0}
262
+ role="button"
263
+ aria-label={shape.ariaLabel || ''}
264
+ aria-pressed={selected}
265
+ onFocus={() => this.handleShapeFocus(shape.id)}
266
+ onBlur={this.handleShapeBlur}
267
+ onKeyDown={(e) => this.handleShapeKeyDown(e, shape.id)}
268
+ />
269
+ );
270
+ })}
191
271
  </BaseContainer>
192
272
  );
193
273
  }
@@ -1,7 +1,9 @@
1
1
  import React from 'react';
2
+ import ReactDOM from 'react-dom';
2
3
  import PropTypes from 'prop-types';
3
4
  import CorrectAnswerToggle from '@pie-lib/correct-answer-toggle';
4
5
  import { color, Collapsible, hasText, PreviewPrompt, UiLayout, hasMedia } from '@pie-lib/render-ui';
6
+ import { renderMath } from '@pie-lib/math-rendering';
5
7
  import { styled } from '@mui/material/styles';
6
8
 
7
9
  import Container from './container';
@@ -54,6 +56,18 @@ class HotspotComponent extends React.Component {
54
56
  if (target) {
55
57
  this.observer.observe(target, { attributes: true, attributeFilter: ['style'] });
56
58
  }
59
+
60
+ // eslint-disable-next-line react/no-find-dom-node
61
+ const domNode = ReactDOM.findDOMNode(this);
62
+
63
+ renderMath(domNode);
64
+ }
65
+
66
+ componentDidUpdate() {
67
+ // eslint-disable-next-line react/no-find-dom-node
68
+ const domNode = ReactDOM.findDOMNode(this);
69
+
70
+ renderMath(domNode);
57
71
  }
58
72
 
59
73
  componentWillUnmount() {
@@ -68,6 +68,7 @@ class PolygonComponent extends React.Component {
68
68
  hoverOutlineColor,
69
69
  outlineColor,
70
70
  selected,
71
+ focused,
71
72
  points,
72
73
  evaluateText,
73
74
  strokeWidth,
@@ -120,7 +121,7 @@ class PolygonComponent extends React.Component {
120
121
  iconSrc = faWrong;
121
122
  }
122
123
  }
123
- const useHoveredStyle = hovered && hoverOutlineColor;
124
+ const useHoveredStyle = (hovered || focused) && hoverOutlineColor;
124
125
 
125
126
  const xValues = pointsParsed.filter((_, index) => index % 2 === 0); // Even indices are x-coordinates
126
127
  const yValues = pointsParsed.filter((_, index) => index % 2 !== 0); // Odd indices are y-coordinates
@@ -136,31 +137,30 @@ class PolygonComponent extends React.Component {
136
137
  const rectHeight = maxY - minY;
137
138
 
138
139
  return (
139
- <Group scaleX={scale} scaleY={scale}>
140
- {useHoveredStyle && (
141
- <Rect
142
- x={rectX}
143
- y={rectY}
144
- width={rectWidth}
145
- height={rectHeight}
146
- stroke={selected ? 'transparent' : hoverOutlineColor}
147
- strokeWidth={strokeWidth}
148
- />
149
- )}
140
+ <Group scaleX={scale} scaleY={scale} onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave}>
150
141
  <Line
151
142
  points={pointsParsed}
152
143
  closed={true}
153
- fill={selected && selectedHotspotColor? selectedHotspotColor : hotspotColor}
144
+ fill={selected && selectedHotspotColor ? selectedHotspotColor : hotspotColor}
154
145
  onClick={this.handleClick}
155
146
  onTap={this.handleClick}
156
147
  draggable={false}
157
148
  stroke={useHoveredStyle && !selected ? 'transparent' : outlineColorParsed}
158
149
  strokeWidth={useHoveredStyle && !selected ? 0 : outlineWidth}
159
- onMouseLeave={this.handleMouseLeave}
160
- onMouseEnter={this.handleMouseEnter}
161
- cursor='pointer'
162
- position='relative'
150
+ cursor="pointer"
151
+ position="relative"
163
152
  />
153
+ {useHoveredStyle && (
154
+ <Rect
155
+ x={rectX}
156
+ y={rectY}
157
+ width={rectWidth}
158
+ height={rectHeight}
159
+ stroke={hoverOutlineColor}
160
+ strokeWidth={strokeWidth}
161
+ listening={false}
162
+ />
163
+ )}
164
164
  {isEvaluateMode && iconSrc ? <ImageComponent src={iconSrc} x={iconX} y={iconY} tooltip={evaluateText} /> : null}
165
165
  </Group>
166
166
  );
@@ -174,6 +174,7 @@ PolygonComponent.propTypes = {
174
174
  isEvaluateMode: PropTypes.bool.isRequired,
175
175
  hoverOutlineColor: PropTypes.string,
176
176
  disabled: PropTypes.bool.isRequired,
177
+ focused: PropTypes.bool,
177
178
  onClick: PropTypes.func.isRequired,
178
179
  outlineColor: PropTypes.string.isRequired,
179
180
  points: PropTypes.array.isRequired,
@@ -188,6 +189,7 @@ PolygonComponent.propTypes = {
188
189
 
189
190
  PolygonComponent.defaultProps = {
190
191
  evaluateText: null,
192
+ focused: false,
191
193
  strokeWidth: 5,
192
194
  scale: 1,
193
195
  };
@@ -51,6 +51,7 @@ class RectComponent extends React.Component {
51
51
  isEvaluateMode,
52
52
  outlineColor,
53
53
  selected,
54
+ focused,
54
55
  width,
55
56
  x,
56
57
  y,
@@ -60,6 +61,7 @@ class RectComponent extends React.Component {
60
61
  markAsCorrect,
61
62
  showCorrectEnabled,
62
63
  } = this.props;
64
+ const { hovered } = this.state;
63
65
 
64
66
  const outlineColorParsed = isEvaluateMode
65
67
  ? this.getEvaluateOutlineColor(isCorrect, markAsCorrect, outlineColor)
@@ -102,22 +104,10 @@ class RectComponent extends React.Component {
102
104
  }
103
105
  }
104
106
 
105
- const { hovered } = this.state;
106
- const useHoveredStyle = hovered && hoverOutlineColor;
107
+ const useHoveredStyle = (hovered || focused) && hoverOutlineColor;
107
108
 
108
109
  return (
109
- <Group scaleX={scale} scaleY={scale}>
110
- {useHoveredStyle && (
111
- <Rect
112
- x={x}
113
- y={y}
114
- width={width}
115
- height={height}
116
- stroke={selected ? 'transparent' : hoverOutlineColor}
117
- strokeWidth={strokeWidth}
118
- listening={false}
119
- />
120
- )}
110
+ <Group scaleX={scale} scaleY={scale} onMouseLeave={this.handleMouseLeave} onMouseEnter={this.handleMouseEnter}>
121
111
  <Rect
122
112
  x={x}
123
113
  y={y}
@@ -129,10 +119,19 @@ class RectComponent extends React.Component {
129
119
  draggable={false}
130
120
  stroke={useHoveredStyle && !selected ? 'transparent' : outlineColorParsed}
131
121
  strokeWidth={useHoveredStyle && !selected ? 0 : outlineWidth}
132
- onMouseLeave={this.handleMouseLeave}
133
- onMouseEnter={this.handleMouseEnter}
134
122
  cursor="pointer"
135
123
  />
124
+ {useHoveredStyle && (
125
+ <Rect
126
+ x={x}
127
+ y={y}
128
+ width={width}
129
+ height={height}
130
+ stroke={hoverOutlineColor}
131
+ strokeWidth={strokeWidth}
132
+ listening={false}
133
+ />
134
+ )}
136
135
  {isEvaluateMode && iconSrc ? <ImageComponent src={iconSrc} x={iconX} y={iconY} tooltip={evaluateText} /> : null}
137
136
  </Group>
138
137
  );
@@ -147,6 +146,7 @@ RectComponent.propTypes = {
147
146
  isEvaluateMode: PropTypes.bool.isRequired,
148
147
  hoverOutlineColor: PropTypes.string,
149
148
  disabled: PropTypes.bool.isRequired,
149
+ focused: PropTypes.bool,
150
150
  onClick: PropTypes.func.isRequired,
151
151
  outlineColor: PropTypes.string.isRequired,
152
152
  selected: PropTypes.bool.isRequired,
@@ -164,6 +164,7 @@ RectComponent.propTypes = {
164
164
  RectComponent.defaultProps = {
165
165
  isCorrect: false,
166
166
  evaluateText: null,
167
+ focused: false,
167
168
  strokeWidth: 5,
168
169
  scale: 1,
169
170
  };