@khanacademy/wonder-blocks-tooltip 1.3.9 → 1.3.12
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.
- package/CHANGELOG.md +27 -0
- package/dist/es/index.js +39 -281
- package/package.json +6 -6
- package/src/util/__tests__/active-tracker.test.js +2 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,32 @@
|
|
|
1
1
|
# @khanacademy/wonder-blocks-tooltip
|
|
2
2
|
|
|
3
|
+
## 1.3.12
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Updated dependencies [5f4a4297]
|
|
8
|
+
- Updated dependencies [2b96fd59]
|
|
9
|
+
- @khanacademy/wonder-blocks-core@4.3.2
|
|
10
|
+
- @khanacademy/wonder-blocks-layout@1.4.10
|
|
11
|
+
- @khanacademy/wonder-blocks-modal@2.3.2
|
|
12
|
+
- @khanacademy/wonder-blocks-typography@1.1.32
|
|
13
|
+
|
|
14
|
+
## 1.3.11
|
|
15
|
+
|
|
16
|
+
### Patch Changes
|
|
17
|
+
|
|
18
|
+
- @khanacademy/wonder-blocks-core@4.3.1
|
|
19
|
+
- @khanacademy/wonder-blocks-layout@1.4.9
|
|
20
|
+
- @khanacademy/wonder-blocks-modal@2.3.1
|
|
21
|
+
- @khanacademy/wonder-blocks-typography@1.1.31
|
|
22
|
+
|
|
23
|
+
## 1.3.10
|
|
24
|
+
|
|
25
|
+
### Patch Changes
|
|
26
|
+
|
|
27
|
+
- Updated dependencies [7eaf74bd]
|
|
28
|
+
- @khanacademy/wonder-blocks-modal@2.3.0
|
|
29
|
+
|
|
3
30
|
## 1.3.9
|
|
4
31
|
|
|
5
32
|
### Patch Changes
|
package/dist/es/index.js
CHANGED
|
@@ -10,36 +10,6 @@ import { Strut } from '@khanacademy/wonder-blocks-layout';
|
|
|
10
10
|
import { HeadingSmall, LabelMedium } from '@khanacademy/wonder-blocks-typography';
|
|
11
11
|
import { Popper } from 'react-popper';
|
|
12
12
|
|
|
13
|
-
/**
|
|
14
|
-
* This interface should be implemented by types that are interested in the
|
|
15
|
-
* notifications of active state being stolen. Generally, this would also be
|
|
16
|
-
* subscribers that may also steal active state, but not necessarily.
|
|
17
|
-
*
|
|
18
|
-
* Once implemented, the type must call subscribe on a tracker to begin
|
|
19
|
-
* receiving notifications.
|
|
20
|
-
*/
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* This class is used to track the concept of active state (though technically
|
|
24
|
-
* that could be any boolean state). The tracker has a variety of subscribers
|
|
25
|
-
* that receive notifications of state theft and can steal the state.
|
|
26
|
-
*
|
|
27
|
-
* For the tooltip, this enables us to have a single tooltip active at any one
|
|
28
|
-
* time. The tracker allows tooltip anchors to coordinate which of them is
|
|
29
|
-
* active, and to ensure that if a different one becomes active, all the others
|
|
30
|
-
* know that they aren't.
|
|
31
|
-
*
|
|
32
|
-
* - When notified that the state has been stolen, subscribers can immediately
|
|
33
|
-
* reflect that theft (in the case of a tooltip, they would hide themselves).
|
|
34
|
-
* - The thief does not get notified if they were the one who stole the state
|
|
35
|
-
* since they should already know that they did that (this avoids having to have
|
|
36
|
-
* checks for reentrancy, for example).
|
|
37
|
-
* - When the subscriber that owns the state no longer needs it, it can
|
|
38
|
-
* voluntarily give it up.
|
|
39
|
-
* - If the state is stolen while a subscriber owns the
|
|
40
|
-
* state, that subscriber does not give up the state, as it doesn't have it
|
|
41
|
-
* anymore (it was stolen).
|
|
42
|
-
*/
|
|
43
13
|
class ActiveTracker {
|
|
44
14
|
constructor() {
|
|
45
15
|
this._subscribers = [];
|
|
@@ -48,13 +18,6 @@ class ActiveTracker {
|
|
|
48
18
|
_getIndex(who) {
|
|
49
19
|
return this._subscribers.findIndex(v => v === who);
|
|
50
20
|
}
|
|
51
|
-
/**
|
|
52
|
-
* Called when a tooltip anchor becomes active so that it can tell all other
|
|
53
|
-
* anchors that they are no longer the active tooltip. Returns true if
|
|
54
|
-
* the there was a steal of active state from another anchor; otherwise, if
|
|
55
|
-
* no other anchor had been active, returns false.
|
|
56
|
-
*/
|
|
57
|
-
|
|
58
21
|
|
|
59
22
|
steal(who) {
|
|
60
23
|
const wasActive = !!this._active;
|
|
@@ -62,7 +25,6 @@ class ActiveTracker {
|
|
|
62
25
|
|
|
63
26
|
for (const anchor of this._subscribers) {
|
|
64
27
|
if (anchor === who) {
|
|
65
|
-
// We don't need to notify the thief.
|
|
66
28
|
continue;
|
|
67
29
|
}
|
|
68
30
|
|
|
@@ -71,23 +33,10 @@ class ActiveTracker {
|
|
|
71
33
|
|
|
72
34
|
return wasActive;
|
|
73
35
|
}
|
|
74
|
-
/**
|
|
75
|
-
* Called if a tooltip doesn't want to be active anymore.
|
|
76
|
-
* Should not be called when being told the active spot was stolen by
|
|
77
|
-
* another anchor, only when the anchor is unhovered and unfocused and they
|
|
78
|
-
* were active.
|
|
79
|
-
*/
|
|
80
|
-
|
|
81
36
|
|
|
82
37
|
giveup() {
|
|
83
38
|
this._active = false;
|
|
84
39
|
}
|
|
85
|
-
/**
|
|
86
|
-
* Subscribes a tooltip anchor to the tracker so that it can be notified of
|
|
87
|
-
* steals. Returns a method that can be used to unsubscribe the anchor from
|
|
88
|
-
* notifications.
|
|
89
|
-
*/
|
|
90
|
-
|
|
91
40
|
|
|
92
41
|
subscribe(who) {
|
|
93
42
|
if (this._getIndex(who) >= 0) {
|
|
@@ -107,31 +56,17 @@ class ActiveTracker {
|
|
|
107
56
|
|
|
108
57
|
}
|
|
109
58
|
|
|
110
|
-
/**
|
|
111
|
-
* The attribute used to identify a tooltip portal.
|
|
112
|
-
*/
|
|
113
59
|
const TooltipAppearanceDelay = 100;
|
|
114
60
|
const TooltipDisappearanceDelay = 75;
|
|
115
61
|
|
|
116
|
-
/**
|
|
117
|
-
* This component turns the given content into an accessible anchor for
|
|
118
|
-
* positioning and displaying tooltips.
|
|
119
|
-
*/
|
|
120
62
|
const TRACKER = new ActiveTracker();
|
|
121
63
|
class TooltipAnchor extends React.Component {
|
|
122
64
|
constructor(props) {
|
|
123
65
|
super(props);
|
|
124
66
|
|
|
125
67
|
this.activeStateStolen = () => {
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
// If we are already active, or we're inactive but have a timeoutID,
|
|
129
|
-
// then it was stolen from us.
|
|
130
|
-
this._stolenFromUs = this.state.active || !!this._timeoutID; // Let's first tell ourselves we're not focused (otherwise the tooltip
|
|
131
|
-
// will be sticky on the next hover of this anchor and that just looks
|
|
132
|
-
// weird).
|
|
133
|
-
|
|
134
|
-
this._focused = false; // Now update our actual state.
|
|
68
|
+
this._stolenFromUs = this.state.active || !!this._timeoutID;
|
|
69
|
+
this._focused = false;
|
|
135
70
|
|
|
136
71
|
this._setActiveState(false, true);
|
|
137
72
|
};
|
|
@@ -153,20 +88,7 @@ class TooltipAnchor extends React.Component {
|
|
|
153
88
|
};
|
|
154
89
|
|
|
155
90
|
this._handleKeyUp = e => {
|
|
156
|
-
// We check the key as that's keyboard layout agnostic and also avoids
|
|
157
|
-
// the minefield of deprecated number type properties like keyCode and
|
|
158
|
-
// which, with the replacement code, which uses a string instead.
|
|
159
91
|
if (e.key === "Escape" && this.state.active) {
|
|
160
|
-
// Stop the event going any further.
|
|
161
|
-
// For cancellation events, like the Escape key, we generally should
|
|
162
|
-
// air on the side of caution and only allow it to cancel one thing.
|
|
163
|
-
// So, it's polite for us to stop propagation of the event.
|
|
164
|
-
// Otherwise, we end up with UX where one Escape key press
|
|
165
|
-
// unexpectedly cancels multiple things.
|
|
166
|
-
//
|
|
167
|
-
// For example, using Escape to close a tooltip or a dropdown while
|
|
168
|
-
// displaying a modal and having the modal close as well. This would
|
|
169
|
-
// be annoyingly bad UX.
|
|
170
92
|
e.preventDefault();
|
|
171
93
|
e.stopPropagation();
|
|
172
94
|
|
|
@@ -182,8 +104,7 @@ class TooltipAnchor extends React.Component {
|
|
|
182
104
|
}
|
|
183
105
|
|
|
184
106
|
componentDidMount() {
|
|
185
|
-
const anchorNode = ReactDOM.findDOMNode(this);
|
|
186
|
-
// happy and ensure that if this does happen, we'll know about it.
|
|
107
|
+
const anchorNode = ReactDOM.findDOMNode(this);
|
|
187
108
|
|
|
188
109
|
if (anchorNode instanceof Text) {
|
|
189
110
|
throw new Error("TooltipAnchor must be applied to an Element. Text content is not supported.");
|
|
@@ -195,12 +116,6 @@ class TooltipAnchor extends React.Component {
|
|
|
195
116
|
this._updateFocusivity();
|
|
196
117
|
|
|
197
118
|
if (anchorNode) {
|
|
198
|
-
/**
|
|
199
|
-
* TODO(somewhatabstract): Work out how to allow pointer to go over
|
|
200
|
-
* the tooltip content to keep it active. This likely requires
|
|
201
|
-
* pointer events but that would break the obscurement checks we do.
|
|
202
|
-
* So, careful consideration required. See WB-302.
|
|
203
|
-
*/
|
|
204
119
|
anchorNode.addEventListener("focusin", this._handleFocusIn);
|
|
205
120
|
anchorNode.addEventListener("focusout", this._handleFocusOut);
|
|
206
121
|
anchorNode.addEventListener("mouseenter", this._handleMouseEnter);
|
|
@@ -249,14 +164,9 @@ class TooltipAnchor extends React.Component {
|
|
|
249
164
|
const currentTabIndex = anchorNode.getAttribute("tabindex");
|
|
250
165
|
|
|
251
166
|
if (forceAnchorFocusivity && !currentTabIndex) {
|
|
252
|
-
// Ensure that the anchor point is keyboard focusable so that
|
|
253
|
-
// we can show the tooltip for visually impaired users that don't
|
|
254
|
-
// use pointer devices nor assistive technology like screen readers.
|
|
255
167
|
anchorNode.setAttribute("tabindex", "0");
|
|
256
168
|
this._weSetFocusivity = true;
|
|
257
169
|
} else if (!forceAnchorFocusivity && currentTabIndex) {
|
|
258
|
-
// We may not be forcing it, but we also want to ensure that if we
|
|
259
|
-
// did before, we remove it.
|
|
260
170
|
if (this._weSetFocusivity) {
|
|
261
171
|
anchorNode.removeAttribute("tabindex");
|
|
262
172
|
this._weSetFocusivity = false;
|
|
@@ -265,7 +175,6 @@ class TooltipAnchor extends React.Component {
|
|
|
265
175
|
}
|
|
266
176
|
|
|
267
177
|
_updateActiveState(hovered, focused) {
|
|
268
|
-
// Update our stored values.
|
|
269
178
|
this._hovered = hovered;
|
|
270
179
|
this._focused = focused;
|
|
271
180
|
|
|
@@ -281,16 +190,10 @@ class TooltipAnchor extends React.Component {
|
|
|
281
190
|
|
|
282
191
|
_setActiveState(active, instant) {
|
|
283
192
|
if (this._stolenFromUs || active !== this.state.active || !this.state.active && this._timeoutID) {
|
|
284
|
-
// If we are about to lose active state or change it, we need to
|
|
285
|
-
// cancel any pending action to show ourselves.
|
|
286
|
-
// So, if active is stolen from us, we are changing active state,
|
|
287
|
-
// or we are inactive and have a timer, clear the action.
|
|
288
193
|
this._clearPendingAction();
|
|
289
194
|
} else if (active === this.state.active && !this._timeoutID) {
|
|
290
|
-
// Nothing to do if we're already active.
|
|
291
195
|
return;
|
|
292
|
-
}
|
|
293
|
-
|
|
196
|
+
}
|
|
294
197
|
|
|
295
198
|
instant = instant || active && TRACKER.steal(this);
|
|
296
199
|
|
|
@@ -307,8 +210,6 @@ class TooltipAnchor extends React.Component {
|
|
|
307
210
|
this.props.onActiveChanged(active);
|
|
308
211
|
|
|
309
212
|
if (!this._stolenFromUs && !active) {
|
|
310
|
-
// Only the very last thing going inactive will giveup
|
|
311
|
-
// the stolen active state.
|
|
312
213
|
TRACKER.giveup();
|
|
313
214
|
}
|
|
314
215
|
|
|
@@ -327,22 +228,18 @@ class TooltipAnchor extends React.Component {
|
|
|
327
228
|
const {
|
|
328
229
|
children
|
|
329
230
|
} = this.props;
|
|
330
|
-
return typeof children === "string" ?
|
|
231
|
+
return typeof children === "string" ? React.createElement(Text$1, null, children) : children;
|
|
331
232
|
}
|
|
332
233
|
|
|
333
234
|
_renderAccessibleChildren(ids) {
|
|
334
235
|
const anchorableChildren = this._renderAnchorableChildren();
|
|
335
236
|
|
|
336
|
-
return
|
|
237
|
+
return React.cloneElement(anchorableChildren, {
|
|
337
238
|
"aria-describedby": ids.get(TooltipAnchor.ariaContentId)
|
|
338
239
|
});
|
|
339
240
|
}
|
|
340
241
|
|
|
341
242
|
render() {
|
|
342
|
-
// We need to make sure we can anchor on our content.
|
|
343
|
-
// If the content is just a string, we wrap it in a Text element
|
|
344
|
-
// so as not to affect styling or layout but still have an element
|
|
345
|
-
// to anchor to.
|
|
346
243
|
if (this.props.ids) {
|
|
347
244
|
return this._renderAccessibleChildren(this.props.ids);
|
|
348
245
|
}
|
|
@@ -356,24 +253,13 @@ TooltipAnchor.defaultProps = {
|
|
|
356
253
|
};
|
|
357
254
|
TooltipAnchor.ariaContentId = "aria-content";
|
|
358
255
|
|
|
359
|
-
// TODO(somewhatabstract): Replace this really basic unique ID work with
|
|
360
|
-
// something SSR-friendly and more robust.
|
|
361
256
|
let tempIdCounter = 0;
|
|
362
257
|
class TooltipTail extends React.Component {
|
|
363
258
|
_calculateDimensionsFromPlacement() {
|
|
364
259
|
const {
|
|
365
260
|
placement
|
|
366
|
-
} = this.props;
|
|
367
|
-
|
|
368
|
-
// coordinates, we use an offset of 0.5 so that it properly covers what
|
|
369
|
-
// we want it to.
|
|
370
|
-
|
|
371
|
-
const trimlineOffset = 0.5; // Calculate the three points of the arrow. Depending on the tail's
|
|
372
|
-
// direction (i.e., the tooltip's "side"), we choose different points,
|
|
373
|
-
// and set our SVG's bounds differently.
|
|
374
|
-
//
|
|
375
|
-
// Note that when the tail points to the left or right, the width/height
|
|
376
|
-
// are inverted.
|
|
261
|
+
} = this.props;
|
|
262
|
+
const trimlineOffset = 0.5;
|
|
377
263
|
|
|
378
264
|
switch (placement) {
|
|
379
265
|
case "top":
|
|
@@ -427,7 +313,6 @@ class TooltipTail extends React.Component {
|
|
|
427
313
|
};
|
|
428
314
|
|
|
429
315
|
case "bottom":
|
|
430
|
-
// No shadow on the arrow as it falls "under" the bubble.
|
|
431
316
|
return null;
|
|
432
317
|
|
|
433
318
|
case "left":
|
|
@@ -448,17 +333,6 @@ class TooltipTail extends React.Component {
|
|
|
448
333
|
throw new Error(`Unknown placement: ${placement}`);
|
|
449
334
|
}
|
|
450
335
|
}
|
|
451
|
-
/**
|
|
452
|
-
* Create an SVG filter that applies a blur to an element.
|
|
453
|
-
* We'll apply it to a dark shape outlining the tooltip, which
|
|
454
|
-
* will produce the overall effect of a drop-shadow.
|
|
455
|
-
*
|
|
456
|
-
* Also, scope its ID by side, so that tooltips with other
|
|
457
|
-
* "side" values don't end up using the wrong filter from
|
|
458
|
-
* elsewhere in the document. (The `height` value depends on
|
|
459
|
-
* which way the arrow is turned!)
|
|
460
|
-
*/
|
|
461
|
-
|
|
462
336
|
|
|
463
337
|
_maybeRenderDropshadow(points) {
|
|
464
338
|
const position = this._getFilterPositioning();
|
|
@@ -476,46 +350,23 @@ class TooltipTail extends React.Component {
|
|
|
476
350
|
offsetShadowX
|
|
477
351
|
} = position;
|
|
478
352
|
const dropShadowFilterId = `tooltip-dropshadow-${placement}-${tempIdCounter++}`;
|
|
479
|
-
return [
|
|
353
|
+
return [React.createElement("filter", {
|
|
480
354
|
key: "filter",
|
|
481
|
-
id: dropShadowFilterId
|
|
482
|
-
// draw based on its parent size. i.e. 2 times bigger.
|
|
483
|
-
// This is so that the diffuse gaussian blur has space to
|
|
484
|
-
// bleed into.
|
|
485
|
-
,
|
|
355
|
+
id: dropShadowFilterId,
|
|
486
356
|
width: "200%",
|
|
487
|
-
height: "200%"
|
|
488
|
-
// parent, it should begin showing its canvas. Without these
|
|
489
|
-
// the filter would clip at 0,0, which would look really
|
|
490
|
-
// strange.
|
|
491
|
-
,
|
|
357
|
+
height: "200%",
|
|
492
358
|
x: x,
|
|
493
359
|
y: y
|
|
494
|
-
},
|
|
360
|
+
}, React.createElement("feGaussianBlur", {
|
|
495
361
|
in: "SourceAlpha",
|
|
496
362
|
stdDeviation: Spacing.xxSmall_6 / 2
|
|
497
|
-
}),
|
|
363
|
+
}), React.createElement("feComponentTransfer", null, React.createElement("feFuncA", {
|
|
498
364
|
type: "linear",
|
|
499
365
|
slope: "0.3"
|
|
500
|
-
}))),
|
|
501
|
-
/*#__PURE__*/
|
|
502
|
-
|
|
503
|
-
/**
|
|
504
|
-
* Draw the tooltip arrow and apply the blur filter we created
|
|
505
|
-
* above, to produce a drop shadow effect.
|
|
506
|
-
* We move it down a bit with a translation, so that it is what
|
|
507
|
-
* we want.
|
|
508
|
-
*
|
|
509
|
-
* We offset the shadow on the X-axis because for left/right
|
|
510
|
-
* tails, we move the tail 1px toward the bubble. If we didn't
|
|
511
|
-
* offset the shadow, it would crash the bubble outline.
|
|
512
|
-
*
|
|
513
|
-
* See styles below for why we offset the arrow.
|
|
514
|
-
*/
|
|
515
|
-
React.createElement("g", {
|
|
366
|
+
}))), React.createElement("g", {
|
|
516
367
|
key: "dropshadow",
|
|
517
368
|
transform: `translate(${offsetShadowX},5.5)`
|
|
518
|
-
},
|
|
369
|
+
}, React.createElement("polyline", {
|
|
519
370
|
fill: Colors.offBlack16,
|
|
520
371
|
points: points.join(" "),
|
|
521
372
|
stroke: Colors.offBlack32,
|
|
@@ -553,20 +404,6 @@ class TooltipTail extends React.Component {
|
|
|
553
404
|
const {
|
|
554
405
|
placement
|
|
555
406
|
} = this.props;
|
|
556
|
-
/**
|
|
557
|
-
* Ensure the container is sized properly for us to be placed correctly
|
|
558
|
-
* by the Popper.js code.
|
|
559
|
-
*
|
|
560
|
-
* Here we offset the arrow 1px toward the bubble. This ensures the arrow
|
|
561
|
-
* outline meets the bubble outline and allows the arrow to erase the bubble
|
|
562
|
-
* outline between the ends of the arrow outline. We do this so that the
|
|
563
|
-
* arrow outline and bubble outline create a single, seamless outline of
|
|
564
|
-
* the callout.
|
|
565
|
-
*
|
|
566
|
-
* NOTE: The widths and heights refer to the downward-pointing tail
|
|
567
|
-
* (i.e. placement="top"). When the tail points to the left or right
|
|
568
|
-
* instead, the width/height are inverted.
|
|
569
|
-
*/
|
|
570
407
|
|
|
571
408
|
const fullTailWidth = this._getFullTailWidth();
|
|
572
409
|
|
|
@@ -656,23 +493,20 @@ class TooltipTail extends React.Component {
|
|
|
656
493
|
const {
|
|
657
494
|
color
|
|
658
495
|
} = this.props;
|
|
659
|
-
return
|
|
496
|
+
return React.createElement("svg", {
|
|
660
497
|
className: css(styles$2.arrow),
|
|
661
498
|
style: this._getArrowStyle(),
|
|
662
499
|
width: width,
|
|
663
500
|
height: height
|
|
664
|
-
}, this._maybeRenderDropshadow(points),
|
|
501
|
+
}, this._maybeRenderDropshadow(points), React.createElement("polyline", {
|
|
665
502
|
fill: Colors[color],
|
|
666
503
|
stroke: Colors[color],
|
|
667
504
|
points: points.join(" ")
|
|
668
|
-
}),
|
|
669
|
-
// Redraw the stroke on top of the background color,
|
|
670
|
-
// so that the ends aren't extra dark where they meet
|
|
671
|
-
// the border of the tooltip.
|
|
505
|
+
}), React.createElement("polyline", {
|
|
672
506
|
fill: Colors[color],
|
|
673
507
|
points: points.join(" "),
|
|
674
508
|
stroke: Colors.offBlack16
|
|
675
|
-
}),
|
|
509
|
+
}), React.createElement("polyline", {
|
|
676
510
|
stroke: Colors[color],
|
|
677
511
|
points: trimlinePoints.join(" ")
|
|
678
512
|
}));
|
|
@@ -684,7 +518,7 @@ class TooltipTail extends React.Component {
|
|
|
684
518
|
placement,
|
|
685
519
|
updateRef
|
|
686
520
|
} = this.props;
|
|
687
|
-
return
|
|
521
|
+
return React.createElement(View, {
|
|
688
522
|
style: [styles$2.tailContainer, _extends({}, offset), this._getContainerStyle()],
|
|
689
523
|
"data-placement": placement,
|
|
690
524
|
ref: updateRef
|
|
@@ -692,13 +526,6 @@ class TooltipTail extends React.Component {
|
|
|
692
526
|
}
|
|
693
527
|
|
|
694
528
|
}
|
|
695
|
-
/**
|
|
696
|
-
* Some constants to make style generation easier to understand.
|
|
697
|
-
* NOTE: The widths and heights refer to the downward-pointing tail
|
|
698
|
-
* (i.e. placement="top"). When the tail points to the left or right instead,
|
|
699
|
-
* the width/height are inverted.
|
|
700
|
-
*/
|
|
701
|
-
|
|
702
529
|
TooltipTail.defaultProps = {
|
|
703
530
|
color: "white"
|
|
704
531
|
};
|
|
@@ -707,19 +534,11 @@ const MIN_DISTANCE_FROM_CORNERS = Spacing.xSmall_8;
|
|
|
707
534
|
const ARROW_WIDTH = Spacing.large_24;
|
|
708
535
|
const ARROW_HEIGHT = Spacing.small_12;
|
|
709
536
|
const styles$2 = StyleSheet.create({
|
|
710
|
-
/**
|
|
711
|
-
* Container
|
|
712
|
-
*/
|
|
713
537
|
tailContainer: {
|
|
714
538
|
position: "relative",
|
|
715
539
|
pointerEvents: "none"
|
|
716
540
|
},
|
|
717
|
-
|
|
718
|
-
/**
|
|
719
|
-
* Arrow
|
|
720
|
-
*/
|
|
721
541
|
arrow: {
|
|
722
|
-
// Ensure the dropshadow bleeds outside our bounds.
|
|
723
542
|
overflow: "visible"
|
|
724
543
|
}
|
|
725
544
|
});
|
|
@@ -758,7 +577,7 @@ class TooltipBubble extends React.Component {
|
|
|
758
577
|
updateTailRef,
|
|
759
578
|
tailOffset
|
|
760
579
|
} = this.props;
|
|
761
|
-
return
|
|
580
|
+
return React.createElement(View, {
|
|
762
581
|
id: id,
|
|
763
582
|
role: "tooltip",
|
|
764
583
|
"data-placement": placement,
|
|
@@ -766,9 +585,9 @@ class TooltipBubble extends React.Component {
|
|
|
766
585
|
onMouseLeave: this.handleMouseLeave,
|
|
767
586
|
ref: updateBubbleRef,
|
|
768
587
|
style: [isReferenceHidden && styles$1.hide, styles$1.bubble, styles$1[`content-${placement}`], style]
|
|
769
|
-
},
|
|
588
|
+
}, React.createElement(View, {
|
|
770
589
|
style: styles$1.content
|
|
771
|
-
}, children),
|
|
590
|
+
}, children), React.createElement(TooltipTail, {
|
|
772
591
|
updateRef: updateTailRef,
|
|
773
592
|
placement: placement,
|
|
774
593
|
offset: tailOffset
|
|
@@ -780,23 +599,12 @@ const styles$1 = StyleSheet.create({
|
|
|
780
599
|
bubble: {
|
|
781
600
|
position: "absolute"
|
|
782
601
|
},
|
|
783
|
-
|
|
784
|
-
/**
|
|
785
|
-
* The hide style ensures that the bounds of the bubble stay unchanged.
|
|
786
|
-
* This is because popper.js calculates the bubble position based off its
|
|
787
|
-
* bounds and if we stopped rendering it entirely, it wouldn't know where to
|
|
788
|
-
* place it when it reappeared.
|
|
789
|
-
*/
|
|
790
602
|
hide: {
|
|
791
603
|
pointerEvents: "none",
|
|
792
604
|
opacity: 0,
|
|
793
605
|
backgroundColor: "transparent",
|
|
794
606
|
color: "transparent"
|
|
795
607
|
},
|
|
796
|
-
|
|
797
|
-
/**
|
|
798
|
-
* Ensure the content and tail are properly arranged.
|
|
799
|
-
*/
|
|
800
608
|
"content-top": {
|
|
801
609
|
flexDirection: "column"
|
|
802
610
|
},
|
|
@@ -819,10 +627,6 @@ const styles$1 = StyleSheet.create({
|
|
|
819
627
|
}
|
|
820
628
|
});
|
|
821
629
|
|
|
822
|
-
/**
|
|
823
|
-
* This component is used to provide the content that is to be rendered in the
|
|
824
|
-
* tooltip bubble.
|
|
825
|
-
*/
|
|
826
630
|
class TooltipContent extends React.Component {
|
|
827
631
|
_renderTitle() {
|
|
828
632
|
const {
|
|
@@ -831,7 +635,7 @@ class TooltipContent extends React.Component {
|
|
|
831
635
|
|
|
832
636
|
if (title) {
|
|
833
637
|
if (typeof title === "string") {
|
|
834
|
-
return
|
|
638
|
+
return React.createElement(HeadingSmall, null, title);
|
|
835
639
|
} else {
|
|
836
640
|
return title;
|
|
837
641
|
}
|
|
@@ -846,7 +650,7 @@ class TooltipContent extends React.Component {
|
|
|
846
650
|
} = this.props;
|
|
847
651
|
|
|
848
652
|
if (typeof children === "string") {
|
|
849
|
-
return
|
|
653
|
+
return React.createElement(LabelMedium, null, children);
|
|
850
654
|
} else {
|
|
851
655
|
return children;
|
|
852
656
|
}
|
|
@@ -858,9 +662,9 @@ class TooltipContent extends React.Component {
|
|
|
858
662
|
const children = this._renderChildren();
|
|
859
663
|
|
|
860
664
|
const containerStyle = title ? styles.withTitle : styles.withoutTitle;
|
|
861
|
-
return
|
|
665
|
+
return React.createElement(View, {
|
|
862
666
|
style: containerStyle
|
|
863
|
-
}, title, title && children &&
|
|
667
|
+
}, title, title && children && React.createElement(Strut, {
|
|
864
668
|
size: Spacing.xxxSmall_4
|
|
865
669
|
}), children);
|
|
866
670
|
}
|
|
@@ -875,19 +679,10 @@ const styles = StyleSheet.create({
|
|
|
875
679
|
}
|
|
876
680
|
});
|
|
877
681
|
|
|
878
|
-
/**
|
|
879
|
-
* This is a little helper that we can use to wrap the react-popper reference
|
|
880
|
-
* update methods so that we can convert a regular React ref into a DOM node
|
|
881
|
-
* as react-popper expects, and also ensure we only update react-popper
|
|
882
|
-
* on actual changes, and not just renders of the same thing.
|
|
883
|
-
*/
|
|
884
682
|
class RefTracker {
|
|
885
683
|
constructor() {
|
|
886
684
|
this.updateRef = ref => {
|
|
887
685
|
if (ref) {
|
|
888
|
-
// We only want to update the reference if it is
|
|
889
|
-
// actually changed. Otherwise, we can trigger another render that
|
|
890
|
-
// would then update the reference again and just keep looping.
|
|
891
686
|
const domNode = ReactDOM.findDOMNode(ref);
|
|
892
687
|
|
|
893
688
|
if (domNode instanceof HTMLElement && domNode !== this._lastRef) {
|
|
@@ -914,10 +709,6 @@ class RefTracker {
|
|
|
914
709
|
|
|
915
710
|
}
|
|
916
711
|
|
|
917
|
-
/**
|
|
918
|
-
* This component is a light wrapper for react-popper, allowing us to position
|
|
919
|
-
* and control the tooltip bubble location and visibility as we need.
|
|
920
|
-
*/
|
|
921
712
|
class TooltipPopper extends React.Component {
|
|
922
713
|
constructor(...args) {
|
|
923
714
|
super(...args);
|
|
@@ -928,27 +719,16 @@ class TooltipPopper extends React.Component {
|
|
|
928
719
|
_renderPositionedContent(popperProps) {
|
|
929
720
|
const {
|
|
930
721
|
children
|
|
931
|
-
} = this.props;
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
const placement = // We know that popperProps.placement will only be one of our
|
|
935
|
-
// supported values, so just cast it.
|
|
936
|
-
popperProps.placement || this.props.placement; // Just in case the callbacks have changed, let's update our reference
|
|
937
|
-
// trackers.
|
|
722
|
+
} = this.props;
|
|
723
|
+
const placement = popperProps.placement || this.props.placement;
|
|
938
724
|
|
|
939
725
|
this._bubbleRefTracker.setCallback(popperProps.ref);
|
|
940
726
|
|
|
941
|
-
this._tailRefTracker.setCallback(popperProps.arrowProps.ref);
|
|
942
|
-
// to our own TooltipBubbleProps.
|
|
943
|
-
|
|
727
|
+
this._tailRefTracker.setCallback(popperProps.arrowProps.ref);
|
|
944
728
|
|
|
945
729
|
const bubbleProps = {
|
|
946
730
|
placement,
|
|
947
731
|
style: {
|
|
948
|
-
// NOTE(jeresig): We can't just use `popperProps.style` here
|
|
949
|
-
// as the Flow type doesn't match Aphrodite's CSS flow props
|
|
950
|
-
// (as it doesn't camelCase props). So we just copy over the
|
|
951
|
-
// props that we need, instead.
|
|
952
732
|
top: popperProps.style.top,
|
|
953
733
|
left: popperProps.style.left,
|
|
954
734
|
bottom: popperProps.style.bottom,
|
|
@@ -975,7 +755,7 @@ class TooltipPopper extends React.Component {
|
|
|
975
755
|
anchorElement,
|
|
976
756
|
placement
|
|
977
757
|
} = this.props;
|
|
978
|
-
return
|
|
758
|
+
return React.createElement(Popper, {
|
|
979
759
|
referenceElement: anchorElement,
|
|
980
760
|
strategy: "fixed",
|
|
981
761
|
placement: placement,
|
|
@@ -990,25 +770,6 @@ class TooltipPopper extends React.Component {
|
|
|
990
770
|
|
|
991
771
|
}
|
|
992
772
|
|
|
993
|
-
/**
|
|
994
|
-
* The Tooltip component provides the means to anchor some additional
|
|
995
|
-
* information to some content. The additional information is shown in a
|
|
996
|
-
* callout that hovers above the page content. This additional information is
|
|
997
|
-
* invoked by hovering over the anchored content, or focusing all or part of the
|
|
998
|
-
* anchored content.
|
|
999
|
-
*
|
|
1000
|
-
* This component is structured as follows:
|
|
1001
|
-
*
|
|
1002
|
-
* Tooltip (this component)
|
|
1003
|
-
* - TooltipAnchor (provides hover/focus behaviors on anchored content)
|
|
1004
|
-
* - TooltipPortalMounter (creates portal into which the callout is rendered)
|
|
1005
|
-
* --------------------------- [PORTAL BOUNDARY] ------------------------------
|
|
1006
|
-
* - TooltipPopper (provides positioning for the callout using react-popper)
|
|
1007
|
-
* - TooltipBubble (renders the callout borders, background and shadow)
|
|
1008
|
-
* - TooltipContent (renders the callout content; the actual information)
|
|
1009
|
-
* - TooltipTail (renders the callout tail and shadow that points from the
|
|
1010
|
-
* callout to the anchor content)
|
|
1011
|
-
*/
|
|
1012
773
|
class Tooltip extends React.Component {
|
|
1013
774
|
constructor(...args) {
|
|
1014
775
|
super(...args);
|
|
@@ -1034,11 +795,11 @@ class Tooltip extends React.Component {
|
|
|
1034
795
|
} = this.props;
|
|
1035
796
|
|
|
1036
797
|
if (typeof content === "string") {
|
|
1037
|
-
return
|
|
798
|
+
return React.createElement(TooltipContent, {
|
|
1038
799
|
title: title
|
|
1039
800
|
}, content);
|
|
1040
801
|
} else if (title) {
|
|
1041
|
-
return
|
|
802
|
+
return React.cloneElement(content, {
|
|
1042
803
|
title
|
|
1043
804
|
});
|
|
1044
805
|
} else {
|
|
@@ -1059,10 +820,10 @@ class Tooltip extends React.Component {
|
|
|
1059
820
|
const {
|
|
1060
821
|
placement
|
|
1061
822
|
} = this.props;
|
|
1062
|
-
return
|
|
823
|
+
return React.createElement(TooltipPopper, {
|
|
1063
824
|
anchorElement: this.state.anchorElement,
|
|
1064
825
|
placement: placement
|
|
1065
|
-
}, props =>
|
|
826
|
+
}, props => React.createElement(TooltipBubble, {
|
|
1066
827
|
id: bubbleId,
|
|
1067
828
|
style: props.style,
|
|
1068
829
|
tailOffset: props.tailOffset,
|
|
@@ -1093,17 +854,16 @@ class Tooltip extends React.Component {
|
|
|
1093
854
|
activeBubble
|
|
1094
855
|
} = this.state;
|
|
1095
856
|
|
|
1096
|
-
const popperHost = this._getHost();
|
|
1097
|
-
|
|
857
|
+
const popperHost = this._getHost();
|
|
1098
858
|
|
|
1099
|
-
return
|
|
859
|
+
return React.createElement(React.Fragment, null, React.createElement(TooltipAnchor, {
|
|
1100
860
|
forceAnchorFocusivity: forceAnchorFocusivity,
|
|
1101
861
|
anchorRef: r => this._updateAnchorElement(r),
|
|
1102
862
|
onActiveChanged: active => this.setState({
|
|
1103
863
|
active
|
|
1104
864
|
}),
|
|
1105
865
|
ids: ids
|
|
1106
|
-
}, children), popperHost && (active || activeBubble) &&
|
|
866
|
+
}, children), popperHost && (active || activeBubble) && ReactDOM.createPortal(this._renderPopper(ids), popperHost));
|
|
1107
867
|
}
|
|
1108
868
|
|
|
1109
869
|
render() {
|
|
@@ -1112,11 +872,9 @@ class Tooltip extends React.Component {
|
|
|
1112
872
|
} = this.props;
|
|
1113
873
|
|
|
1114
874
|
if (id) {
|
|
1115
|
-
// Let's bypass the extra weight of an id provider since we don't
|
|
1116
|
-
// need it.
|
|
1117
875
|
return this._renderTooltipAnchor();
|
|
1118
876
|
} else {
|
|
1119
|
-
return
|
|
877
|
+
return React.createElement(UniqueIDProvider, {
|
|
1120
878
|
scope: "tooltip",
|
|
1121
879
|
mockOnFirstRender: true
|
|
1122
880
|
}, ids => this._renderTooltipAnchor(ids));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@khanacademy/wonder-blocks-tooltip",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.12",
|
|
4
4
|
"design": "v1",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -17,11 +17,11 @@
|
|
|
17
17
|
"dependencies": {
|
|
18
18
|
"@babel/runtime": "^7.16.3",
|
|
19
19
|
"@khanacademy/wonder-blocks-color": "^1.1.20",
|
|
20
|
-
"@khanacademy/wonder-blocks-core": "^4.3.
|
|
21
|
-
"@khanacademy/wonder-blocks-layout": "^1.4.
|
|
22
|
-
"@khanacademy/wonder-blocks-modal": "^2.2
|
|
20
|
+
"@khanacademy/wonder-blocks-core": "^4.3.2",
|
|
21
|
+
"@khanacademy/wonder-blocks-layout": "^1.4.10",
|
|
22
|
+
"@khanacademy/wonder-blocks-modal": "^2.3.2",
|
|
23
23
|
"@khanacademy/wonder-blocks-spacing": "^3.0.5",
|
|
24
|
-
"@khanacademy/wonder-blocks-typography": "^1.1.
|
|
24
|
+
"@khanacademy/wonder-blocks-typography": "^1.1.32"
|
|
25
25
|
},
|
|
26
26
|
"peerDependencies": {
|
|
27
27
|
"@popperjs/core": "^2.10.1",
|
|
@@ -31,6 +31,6 @@
|
|
|
31
31
|
"react-popper": "^2.0.0"
|
|
32
32
|
},
|
|
33
33
|
"devDependencies": {
|
|
34
|
-
"wb-dev-build-settings": "^0.
|
|
34
|
+
"wb-dev-build-settings": "^0.4.0"
|
|
35
35
|
}
|
|
36
36
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// @flow
|
|
2
|
-
import ActiveTracker
|
|
2
|
+
import ActiveTracker from "../active-tracker.js";
|
|
3
|
+
import type {IActiveTrackerSubscriber} from "../active-tracker.js";
|
|
3
4
|
|
|
4
5
|
class MockSubscriber implements IActiveTrackerSubscriber {
|
|
5
6
|
activeStateStolen = jest.fn();
|