@pie-element/hotspot 11.1.7 → 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.
@@ -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
  }
@@ -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
  };