@khanacademy/wonder-blocks-tooltip 1.3.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.
Files changed (36) hide show
  1. package/LICENSE +21 -0
  2. package/dist/es/index.js +1133 -0
  3. package/dist/index.js +1389 -0
  4. package/dist/index.js.flow +2 -0
  5. package/docs.md +11 -0
  6. package/package.json +37 -0
  7. package/src/__tests__/__snapshots__/generated-snapshot.test.js.snap +2674 -0
  8. package/src/__tests__/generated-snapshot.test.js +475 -0
  9. package/src/components/__tests__/__snapshots__/tooltip-tail.test.js.snap +9 -0
  10. package/src/components/__tests__/__snapshots__/tooltip.test.js.snap +47 -0
  11. package/src/components/__tests__/tooltip-anchor.test.js +987 -0
  12. package/src/components/__tests__/tooltip-bubble.test.js +80 -0
  13. package/src/components/__tests__/tooltip-popper.test.js +71 -0
  14. package/src/components/__tests__/tooltip-tail.test.js +117 -0
  15. package/src/components/__tests__/tooltip.integration.test.js +79 -0
  16. package/src/components/__tests__/tooltip.test.js +401 -0
  17. package/src/components/tooltip-anchor.js +330 -0
  18. package/src/components/tooltip-bubble.js +150 -0
  19. package/src/components/tooltip-bubble.md +92 -0
  20. package/src/components/tooltip-content.js +76 -0
  21. package/src/components/tooltip-content.md +34 -0
  22. package/src/components/tooltip-popper.js +101 -0
  23. package/src/components/tooltip-tail.js +462 -0
  24. package/src/components/tooltip-tail.md +143 -0
  25. package/src/components/tooltip.js +235 -0
  26. package/src/components/tooltip.md +194 -0
  27. package/src/components/tooltip.stories.js +76 -0
  28. package/src/index.js +12 -0
  29. package/src/util/__tests__/__snapshots__/active-tracker.test.js.snap +3 -0
  30. package/src/util/__tests__/__snapshots__/ref-tracker.test.js.snap +3 -0
  31. package/src/util/__tests__/active-tracker.test.js +142 -0
  32. package/src/util/__tests__/ref-tracker.test.js +153 -0
  33. package/src/util/active-tracker.js +94 -0
  34. package/src/util/constants.js +7 -0
  35. package/src/util/ref-tracker.js +46 -0
  36. package/src/util/types.js +29 -0
package/dist/index.js ADDED
@@ -0,0 +1,1389 @@
1
+ module.exports =
2
+ /******/ (function(modules) { // webpackBootstrap
3
+ /******/ // The module cache
4
+ /******/ var installedModules = {};
5
+ /******/
6
+ /******/ // The require function
7
+ /******/ function __webpack_require__(moduleId) {
8
+ /******/
9
+ /******/ // Check if module is in cache
10
+ /******/ if(installedModules[moduleId]) {
11
+ /******/ return installedModules[moduleId].exports;
12
+ /******/ }
13
+ /******/ // Create a new module (and put it into the cache)
14
+ /******/ var module = installedModules[moduleId] = {
15
+ /******/ i: moduleId,
16
+ /******/ l: false,
17
+ /******/ exports: {}
18
+ /******/ };
19
+ /******/
20
+ /******/ // Execute the module function
21
+ /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
22
+ /******/
23
+ /******/ // Flag the module as loaded
24
+ /******/ module.l = true;
25
+ /******/
26
+ /******/ // Return the exports of the module
27
+ /******/ return module.exports;
28
+ /******/ }
29
+ /******/
30
+ /******/
31
+ /******/ // expose the modules object (__webpack_modules__)
32
+ /******/ __webpack_require__.m = modules;
33
+ /******/
34
+ /******/ // expose the module cache
35
+ /******/ __webpack_require__.c = installedModules;
36
+ /******/
37
+ /******/ // define getter function for harmony exports
38
+ /******/ __webpack_require__.d = function(exports, name, getter) {
39
+ /******/ if(!__webpack_require__.o(exports, name)) {
40
+ /******/ Object.defineProperty(exports, name, { enumerable: true, get: getter });
41
+ /******/ }
42
+ /******/ };
43
+ /******/
44
+ /******/ // define __esModule on exports
45
+ /******/ __webpack_require__.r = function(exports) {
46
+ /******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
47
+ /******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
48
+ /******/ }
49
+ /******/ Object.defineProperty(exports, '__esModule', { value: true });
50
+ /******/ };
51
+ /******/
52
+ /******/ // create a fake namespace object
53
+ /******/ // mode & 1: value is a module id, require it
54
+ /******/ // mode & 2: merge all properties of value into the ns
55
+ /******/ // mode & 4: return value when already ns object
56
+ /******/ // mode & 8|1: behave like require
57
+ /******/ __webpack_require__.t = function(value, mode) {
58
+ /******/ if(mode & 1) value = __webpack_require__(value);
59
+ /******/ if(mode & 8) return value;
60
+ /******/ if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;
61
+ /******/ var ns = Object.create(null);
62
+ /******/ __webpack_require__.r(ns);
63
+ /******/ Object.defineProperty(ns, 'default', { enumerable: true, value: value });
64
+ /******/ if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));
65
+ /******/ return ns;
66
+ /******/ };
67
+ /******/
68
+ /******/ // getDefaultExport function for compatibility with non-harmony modules
69
+ /******/ __webpack_require__.n = function(module) {
70
+ /******/ var getter = module && module.__esModule ?
71
+ /******/ function getDefault() { return module['default']; } :
72
+ /******/ function getModuleExports() { return module; };
73
+ /******/ __webpack_require__.d(getter, 'a', getter);
74
+ /******/ return getter;
75
+ /******/ };
76
+ /******/
77
+ /******/ // Object.prototype.hasOwnProperty.call
78
+ /******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
79
+ /******/
80
+ /******/ // __webpack_public_path__
81
+ /******/ __webpack_require__.p = "";
82
+ /******/
83
+ /******/
84
+ /******/ // Load entry module and return exports
85
+ /******/ return __webpack_require__(__webpack_require__.s = 11);
86
+ /******/ })
87
+ /************************************************************************/
88
+ /******/ ([
89
+ /* 0 */
90
+ /***/ (function(module, exports) {
91
+
92
+ module.exports = require("react");
93
+
94
+ /***/ }),
95
+ /* 1 */
96
+ /***/ (function(module, exports) {
97
+
98
+ module.exports = require("@khanacademy/wonder-blocks-spacing");
99
+
100
+ /***/ }),
101
+ /* 2 */
102
+ /***/ (function(module, exports) {
103
+
104
+ module.exports = require("@khanacademy/wonder-blocks-color");
105
+
106
+ /***/ }),
107
+ /* 3 */
108
+ /***/ (function(module, exports) {
109
+
110
+ module.exports = require("@khanacademy/wonder-blocks-core");
111
+
112
+ /***/ }),
113
+ /* 4 */
114
+ /***/ (function(module, exports) {
115
+
116
+ module.exports = require("aphrodite");
117
+
118
+ /***/ }),
119
+ /* 5 */
120
+ /***/ (function(module, exports) {
121
+
122
+ module.exports = require("react-dom");
123
+
124
+ /***/ }),
125
+ /* 6 */
126
+ /***/ (function(module, exports) {
127
+
128
+ module.exports = require("@khanacademy/wonder-blocks-typography");
129
+
130
+ /***/ }),
131
+ /* 7 */
132
+ /***/ (function(module, exports) {
133
+
134
+ module.exports = require("@khanacademy/wonder-blocks-modal");
135
+
136
+ /***/ }),
137
+ /* 8 */
138
+ /***/ (function(module, exports) {
139
+
140
+ function _extends() {
141
+ module.exports = _extends = Object.assign || function (target) {
142
+ for (var i = 1; i < arguments.length; i++) {
143
+ var source = arguments[i];
144
+
145
+ for (var key in source) {
146
+ if (Object.prototype.hasOwnProperty.call(source, key)) {
147
+ target[key] = source[key];
148
+ }
149
+ }
150
+ }
151
+
152
+ return target;
153
+ };
154
+
155
+ module.exports["default"] = module.exports, module.exports.__esModule = true;
156
+ return _extends.apply(this, arguments);
157
+ }
158
+
159
+ module.exports = _extends;
160
+ module.exports["default"] = module.exports, module.exports.__esModule = true;
161
+
162
+ /***/ }),
163
+ /* 9 */
164
+ /***/ (function(module, exports) {
165
+
166
+ module.exports = require("@khanacademy/wonder-blocks-layout");
167
+
168
+ /***/ }),
169
+ /* 10 */
170
+ /***/ (function(module, exports) {
171
+
172
+ module.exports = require("react-popper");
173
+
174
+ /***/ }),
175
+ /* 11 */
176
+ /***/ (function(module, __webpack_exports__, __webpack_require__) {
177
+
178
+ "use strict";
179
+ // ESM COMPAT FLAG
180
+ __webpack_require__.r(__webpack_exports__);
181
+
182
+ // EXPORTS
183
+ __webpack_require__.d(__webpack_exports__, "default", function() { return /* reexport */ tooltip_Tooltip; });
184
+ __webpack_require__.d(__webpack_exports__, "TooltipContent", function() { return /* reexport */ tooltip_content_TooltipContent; });
185
+ __webpack_require__.d(__webpack_exports__, "TooltipPopper", function() { return /* reexport */ tooltip_popper_TooltipPopper; });
186
+ __webpack_require__.d(__webpack_exports__, "TooltipTail", function() { return /* reexport */ tooltip_tail_TooltipTail; });
187
+
188
+ // EXTERNAL MODULE: external "react"
189
+ var external_react_ = __webpack_require__(0);
190
+
191
+ // EXTERNAL MODULE: external "react-dom"
192
+ var external_react_dom_ = __webpack_require__(5);
193
+
194
+ // EXTERNAL MODULE: external "@khanacademy/wonder-blocks-core"
195
+ var wonder_blocks_core_ = __webpack_require__(3);
196
+
197
+ // EXTERNAL MODULE: external "@khanacademy/wonder-blocks-modal"
198
+ var wonder_blocks_modal_ = __webpack_require__(7);
199
+
200
+ // CONCATENATED MODULE: ./packages/wonder-blocks-tooltip/src/util/active-tracker.js
201
+ /**
202
+ * This interface should be implemented by types that are interested in the
203
+ * notifications of active state being stolen. Generally, this would also be
204
+ * subscribers that may also steal active state, but not necessarily.
205
+ *
206
+ * Once implemented, the type must call subscribe on a tracker to begin
207
+ * receiving notifications.
208
+ */
209
+
210
+ /**
211
+ * This class is used to track the concept of active state (though technically
212
+ * that could be any boolean state). The tracker has a variety of subscribers
213
+ * that receive notifications of state theft and can steal the state.
214
+ *
215
+ * For the tooltip, this enables us to have a single tooltip active at any one
216
+ * time. The tracker allows tooltip anchors to coordinate which of them is
217
+ * active, and to ensure that if a different one becomes active, all the others
218
+ * know that they aren't.
219
+ *
220
+ * - When notified that the state has been stolen, subscribers can immediately
221
+ * reflect that theft (in the case of a tooltip, they would hide themselves).
222
+ * - The thief does not get notified if they were the one who stole the state
223
+ * since they should already know that they did that (this avoids having to have
224
+ * checks for reentrancy, for example).
225
+ * - When the subscriber that owns the state no longer needs it, it can
226
+ * voluntarily give it up.
227
+ * - If the state is stolen while a subscriber owns the
228
+ * state, that subscriber does not give up the state, as it doesn't have it
229
+ * anymore (it was stolen).
230
+ */
231
+ class ActiveTracker {
232
+ constructor() {
233
+ this._subscribers = [];
234
+ }
235
+
236
+ _getIndex(who) {
237
+ return this._subscribers.findIndex(v => v === who);
238
+ }
239
+ /**
240
+ * Called when a tooltip anchor becomes active so that it can tell all other
241
+ * anchors that they are no longer the active tooltip. Returns true if
242
+ * the there was a steal of active state from another anchor; otherwise, if
243
+ * no other anchor had been active, returns false.
244
+ */
245
+
246
+
247
+ steal(who) {
248
+ const wasActive = !!this._active;
249
+ this._active = true;
250
+
251
+ for (const anchor of this._subscribers) {
252
+ if (anchor === who) {
253
+ // We don't need to notify the thief.
254
+ continue;
255
+ }
256
+
257
+ anchor.activeStateStolen();
258
+ }
259
+
260
+ return wasActive;
261
+ }
262
+ /**
263
+ * Called if a tooltip doesn't want to be active anymore.
264
+ * Should not be called when being told the active spot was stolen by
265
+ * another anchor, only when the anchor is unhovered and unfocused and they
266
+ * were active.
267
+ */
268
+
269
+
270
+ giveup() {
271
+ this._active = false;
272
+ }
273
+ /**
274
+ * Subscribes a tooltip anchor to the tracker so that it can be notified of
275
+ * steals. Returns a method that can be used to unsubscribe the anchor from
276
+ * notifications.
277
+ */
278
+
279
+
280
+ subscribe(who) {
281
+ if (this._getIndex(who) >= 0) {
282
+ throw new Error("Already subscribed.");
283
+ }
284
+
285
+ this._subscribers.push(who);
286
+
287
+ const unsubscribe = () => {
288
+ const index = this._getIndex(who);
289
+
290
+ this._subscribers.splice(index, 1);
291
+ };
292
+
293
+ return unsubscribe;
294
+ }
295
+
296
+ }
297
+ // CONCATENATED MODULE: ./packages/wonder-blocks-tooltip/src/util/constants.js
298
+ /**
299
+ * The attribute used to identify a tooltip portal.
300
+ */
301
+ const TooltipPortalAttributeName = "data-tooltip-portal";
302
+ const TooltipAppearanceDelay = 100;
303
+ const TooltipDisappearanceDelay = 75;
304
+ // CONCATENATED MODULE: ./packages/wonder-blocks-tooltip/src/components/tooltip-anchor.js
305
+ /**
306
+ * This component turns the given content into an accessible anchor for
307
+ * positioning and displaying tooltips.
308
+ */
309
+
310
+
311
+
312
+
313
+
314
+ const TRACKER = new ActiveTracker();
315
+ class tooltip_anchor_TooltipAnchor extends external_react_["Component"] {
316
+ constructor(props) {
317
+ super(props);
318
+
319
+ this.activeStateStolen = () => {
320
+ // Something wants the active state.
321
+ // Do we have it? If so, let's remember that.
322
+ // If we are already active, or we're inactive but have a timeoutID,
323
+ // then it was stolen from us.
324
+ this._stolenFromUs = this.state.active || !!this._timeoutID; // Let's first tell ourselves we're not focused (otherwise the tooltip
325
+ // will be sticky on the next hover of this anchor and that just looks
326
+ // weird).
327
+
328
+ this._focused = false; // Now update our actual state.
329
+
330
+ this._setActiveState(false, true);
331
+ };
332
+
333
+ this._handleFocusIn = () => {
334
+ this._updateActiveState(this._hovered, true);
335
+ };
336
+
337
+ this._handleFocusOut = () => {
338
+ this._updateActiveState(this._hovered, false);
339
+ };
340
+
341
+ this._handleMouseEnter = () => {
342
+ this._updateActiveState(true, this._focused);
343
+ };
344
+
345
+ this._handleMouseLeave = () => {
346
+ this._updateActiveState(false, this._focused);
347
+ };
348
+
349
+ this._handleKeyUp = e => {
350
+ // We check the key as that's keyboard layout agnostic and also avoids
351
+ // the minefield of deprecated number type properties like keyCode and
352
+ // which, with the replacement code, which uses a string instead.
353
+ if (e.key === "Escape" && this.state.active) {
354
+ // Stop the event going any further.
355
+ // For cancellation events, like the Escape key, we generally should
356
+ // air on the side of caution and only allow it to cancel one thing.
357
+ // So, it's polite for us to stop propagation of the event.
358
+ // Otherwise, we end up with UX where one Escape key press
359
+ // unexpectedly cancels multiple things.
360
+ //
361
+ // For example, using Escape to close a tooltip or a dropdown while
362
+ // displaying a modal and having the modal close as well. This would
363
+ // be annoyingly bad UX.
364
+ e.preventDefault();
365
+ e.stopPropagation();
366
+
367
+ this._updateActiveState(false, false);
368
+ }
369
+ };
370
+
371
+ this._focused = false;
372
+ this._hovered = false;
373
+ this.state = {
374
+ active: false
375
+ };
376
+ }
377
+
378
+ componentDidMount() {
379
+ const anchorNode = external_react_dom_["findDOMNode"](this); // This should never happen, but we have this check here to make flow
380
+ // happy and ensure that if this does happen, we'll know about it.
381
+
382
+ if (anchorNode instanceof Text) {
383
+ throw new Error("TooltipAnchor must be applied to an Element. Text content is not supported.");
384
+ }
385
+
386
+ this._unsubscribeFromTracker = TRACKER.subscribe(this);
387
+ this._anchorNode = anchorNode;
388
+
389
+ this._updateFocusivity();
390
+
391
+ if (anchorNode) {
392
+ /**
393
+ * TODO(somewhatabstract): Work out how to allow pointer to go over
394
+ * the tooltip content to keep it active. This likely requires
395
+ * pointer events but that would break the obscurement checks we do.
396
+ * So, careful consideration required. See WB-302.
397
+ */
398
+ anchorNode.addEventListener("focusin", this._handleFocusIn);
399
+ anchorNode.addEventListener("focusout", this._handleFocusOut);
400
+ anchorNode.addEventListener("mouseenter", this._handleMouseEnter);
401
+ anchorNode.addEventListener("mouseleave", this._handleMouseLeave);
402
+ this.props.anchorRef(this._anchorNode);
403
+ }
404
+ }
405
+
406
+ componentDidUpdate(prevProps) {
407
+ if (prevProps.forceAnchorFocusivity !== this.props.forceAnchorFocusivity || prevProps.children !== this.props.children) {
408
+ this._updateFocusivity();
409
+ }
410
+ }
411
+
412
+ componentWillUnmount() {
413
+ if (this._unsubscribeFromTracker) {
414
+ this._unsubscribeFromTracker();
415
+ }
416
+
417
+ this._clearPendingAction();
418
+
419
+ const anchorNode = this._anchorNode;
420
+
421
+ if (anchorNode) {
422
+ anchorNode.removeEventListener("focusin", this._handleFocusIn);
423
+ anchorNode.removeEventListener("focusout", this._handleFocusOut);
424
+ anchorNode.removeEventListener("mouseenter", this._handleMouseEnter);
425
+ anchorNode.removeEventListener("mouseleave", this._handleMouseLeave);
426
+ }
427
+
428
+ if (this.state.active) {
429
+ document.removeEventListener("keyup", this._handleKeyUp);
430
+ }
431
+ }
432
+
433
+ _updateFocusivity() {
434
+ const anchorNode = this._anchorNode;
435
+
436
+ if (!anchorNode) {
437
+ return;
438
+ }
439
+
440
+ const {
441
+ forceAnchorFocusivity
442
+ } = this.props;
443
+ const currentTabIndex = anchorNode.getAttribute("tabindex");
444
+
445
+ if (forceAnchorFocusivity && !currentTabIndex) {
446
+ // Ensure that the anchor point is keyboard focusable so that
447
+ // we can show the tooltip for visually impaired users that don't
448
+ // use pointer devices nor assistive technology like screen readers.
449
+ anchorNode.setAttribute("tabindex", "0");
450
+ this._weSetFocusivity = true;
451
+ } else if (!forceAnchorFocusivity && currentTabIndex) {
452
+ // We may not be forcing it, but we also want to ensure that if we
453
+ // did before, we remove it.
454
+ if (this._weSetFocusivity) {
455
+ anchorNode.removeAttribute("tabindex");
456
+ this._weSetFocusivity = false;
457
+ }
458
+ }
459
+ }
460
+
461
+ _updateActiveState(hovered, focused) {
462
+ // Update our stored values.
463
+ this._hovered = hovered;
464
+ this._focused = focused;
465
+
466
+ this._setActiveState(hovered || focused);
467
+ }
468
+
469
+ _clearPendingAction() {
470
+ if (this._timeoutID) {
471
+ clearTimeout(this._timeoutID);
472
+ this._timeoutID = null;
473
+ }
474
+ }
475
+
476
+ _setActiveState(active, instant) {
477
+ if (this._stolenFromUs || active !== this.state.active || !this.state.active && this._timeoutID) {
478
+ // If we are about to lose active state or change it, we need to
479
+ // cancel any pending action to show ourselves.
480
+ // So, if active is stolen from us, we are changing active state,
481
+ // or we are inactive and have a timer, clear the action.
482
+ this._clearPendingAction();
483
+ } else if (active === this.state.active && !this._timeoutID) {
484
+ // Nothing to do if we're already active.
485
+ return;
486
+ } // Determine if we are doing things immediately or not.
487
+
488
+
489
+ instant = instant || active && TRACKER.steal(this);
490
+
491
+ if (instant) {
492
+ if (active) {
493
+ document.addEventListener("keyup", this._handleKeyUp);
494
+ } else {
495
+ document.removeEventListener("keyup", this._handleKeyUp);
496
+ }
497
+
498
+ this.setState({
499
+ active
500
+ });
501
+ this.props.onActiveChanged(active);
502
+
503
+ if (!this._stolenFromUs && !active) {
504
+ // Only the very last thing going inactive will giveup
505
+ // the stolen active state.
506
+ TRACKER.giveup();
507
+ }
508
+
509
+ this._stolenFromUs = false;
510
+ } else {
511
+ const delay = active ? TooltipAppearanceDelay : TooltipDisappearanceDelay;
512
+ this._timeoutID = setTimeout(() => {
513
+ this._timeoutID = null;
514
+
515
+ this._setActiveState(active, true);
516
+ }, delay);
517
+ }
518
+ }
519
+
520
+ _renderAnchorableChildren() {
521
+ const {
522
+ children
523
+ } = this.props;
524
+ return typeof children === "string" ? /*#__PURE__*/external_react_["createElement"](wonder_blocks_core_["Text"], null, children) : children;
525
+ }
526
+
527
+ _renderAccessibleChildren(ids) {
528
+ const anchorableChildren = this._renderAnchorableChildren();
529
+
530
+ return /*#__PURE__*/external_react_["cloneElement"](anchorableChildren, {
531
+ "aria-describedby": ids.get(tooltip_anchor_TooltipAnchor.ariaContentId)
532
+ });
533
+ }
534
+
535
+ render() {
536
+ // We need to make sure we can anchor on our content.
537
+ // If the content is just a string, we wrap it in a Text element
538
+ // so as not to affect styling or layout but still have an element
539
+ // to anchor to.
540
+ if (this.props.ids) {
541
+ return this._renderAccessibleChildren(this.props.ids);
542
+ }
543
+
544
+ return this._renderAnchorableChildren();
545
+ }
546
+
547
+ }
548
+ tooltip_anchor_TooltipAnchor.defaultProps = {
549
+ forceAnchorFocusivity: true
550
+ };
551
+ tooltip_anchor_TooltipAnchor.ariaContentId = "aria-content";
552
+ // EXTERNAL MODULE: external "aphrodite"
553
+ var external_aphrodite_ = __webpack_require__(4);
554
+
555
+ // EXTERNAL MODULE: external "@khanacademy/wonder-blocks-color"
556
+ var wonder_blocks_color_ = __webpack_require__(2);
557
+ var wonder_blocks_color_default = /*#__PURE__*/__webpack_require__.n(wonder_blocks_color_);
558
+
559
+ // EXTERNAL MODULE: external "@khanacademy/wonder-blocks-spacing"
560
+ var wonder_blocks_spacing_ = __webpack_require__(1);
561
+ var wonder_blocks_spacing_default = /*#__PURE__*/__webpack_require__.n(wonder_blocks_spacing_);
562
+
563
+ // EXTERNAL MODULE: ./node_modules/@babel/runtime/helpers/extends.js
564
+ var helpers_extends = __webpack_require__(8);
565
+ var extends_default = /*#__PURE__*/__webpack_require__.n(helpers_extends);
566
+
567
+ // CONCATENATED MODULE: ./packages/wonder-blocks-tooltip/src/components/tooltip-tail.js
568
+
569
+
570
+
571
+
572
+
573
+
574
+ // TODO(somewhatabstract): Replace this really basic unique ID work with
575
+ // something SSR-friendly and more robust.
576
+ let tempIdCounter = 0;
577
+ class tooltip_tail_TooltipTail extends external_react_["Component"] {
578
+ _calculateDimensionsFromPlacement() {
579
+ const {
580
+ placement
581
+ } = this.props; // The trimline, which we draw to make the tail flush to the bubble,
582
+ // has a thickness of 1. Since the line is drawn centered to the
583
+ // coordinates, we use an offset of 0.5 so that it properly covers what
584
+ // we want it to.
585
+
586
+ const trimlineOffset = 0.5; // Calculate the three points of the arrow. Depending on the tail's
587
+ // direction (i.e., the tooltip's "side"), we choose different points,
588
+ // and set our SVG's bounds differently.
589
+ //
590
+ // Note that when the tail points to the left or right, the width/height
591
+ // are inverted.
592
+
593
+ switch (placement) {
594
+ case "top":
595
+ return {
596
+ trimlinePoints: [`0,-${trimlineOffset}`, `${ARROW_WIDTH},-${trimlineOffset}`],
597
+ points: ["0,0", `${ARROW_WIDTH / 2},${ARROW_HEIGHT}`, `${ARROW_WIDTH},0`],
598
+ height: ARROW_HEIGHT,
599
+ width: ARROW_WIDTH
600
+ };
601
+
602
+ case "right":
603
+ return {
604
+ trimlinePoints: [`${ARROW_HEIGHT + trimlineOffset},0`, `${ARROW_HEIGHT + trimlineOffset},${ARROW_WIDTH}`],
605
+ points: [`${ARROW_HEIGHT},0`, `0,${ARROW_WIDTH / 2}`, `${ARROW_HEIGHT},${ARROW_WIDTH}`],
606
+ width: ARROW_HEIGHT,
607
+ height: ARROW_WIDTH
608
+ };
609
+
610
+ case "bottom":
611
+ return {
612
+ trimlinePoints: [`0, ${ARROW_HEIGHT + trimlineOffset}`, `${ARROW_WIDTH},${ARROW_HEIGHT + trimlineOffset}`],
613
+ points: [`0, ${ARROW_HEIGHT}`, `${ARROW_WIDTH / 2},0`, `${ARROW_WIDTH},${ARROW_HEIGHT}`],
614
+ width: ARROW_WIDTH,
615
+ height: ARROW_HEIGHT
616
+ };
617
+
618
+ case "left":
619
+ return {
620
+ trimlinePoints: [`-${trimlineOffset},0`, `-${trimlineOffset},${ARROW_WIDTH}`],
621
+ points: [`0,0`, `${ARROW_HEIGHT},${ARROW_WIDTH / 2}`, `0,${ARROW_WIDTH}`],
622
+ width: ARROW_HEIGHT,
623
+ height: ARROW_WIDTH
624
+ };
625
+
626
+ default:
627
+ throw new Error(`Unknown placement: ${placement}`);
628
+ }
629
+ }
630
+
631
+ _getFilterPositioning() {
632
+ const {
633
+ placement
634
+ } = this.props;
635
+
636
+ switch (placement) {
637
+ case "top":
638
+ return {
639
+ y: "-50%",
640
+ x: "-50%",
641
+ offsetShadowX: 0
642
+ };
643
+
644
+ case "bottom":
645
+ // No shadow on the arrow as it falls "under" the bubble.
646
+ return null;
647
+
648
+ case "left":
649
+ return {
650
+ y: "-50%",
651
+ x: "0%",
652
+ offsetShadowX: 1
653
+ };
654
+
655
+ case "right":
656
+ return {
657
+ y: "-50%",
658
+ x: "-100%",
659
+ offsetShadowX: -1
660
+ };
661
+
662
+ default:
663
+ throw new Error(`Unknown placement: ${placement}`);
664
+ }
665
+ }
666
+ /**
667
+ * Create an SVG filter that applies a blur to an element.
668
+ * We'll apply it to a dark shape outlining the tooltip, which
669
+ * will produce the overall effect of a drop-shadow.
670
+ *
671
+ * Also, scope its ID by side, so that tooltips with other
672
+ * "side" values don't end up using the wrong filter from
673
+ * elsewhere in the document. (The `height` value depends on
674
+ * which way the arrow is turned!)
675
+ */
676
+
677
+
678
+ _maybeRenderDropshadow(points) {
679
+ const position = this._getFilterPositioning();
680
+
681
+ if (!position) {
682
+ return null;
683
+ }
684
+
685
+ const {
686
+ placement
687
+ } = this.props;
688
+ const {
689
+ y,
690
+ x,
691
+ offsetShadowX
692
+ } = position;
693
+ const dropShadowFilterId = `tooltip-dropshadow-${placement}-${tempIdCounter++}`;
694
+ return [/*#__PURE__*/external_react_["createElement"]("filter", {
695
+ key: "filter",
696
+ id: dropShadowFilterId // Height and width tell the filter how big of a canvas to
697
+ // draw based on its parent size. i.e. 2 times bigger.
698
+ // This is so that the diffuse gaussian blur has space to
699
+ // bleed into.
700
+ ,
701
+ width: "200%",
702
+ height: "200%" // The x and y values tell the filter where, relative to its
703
+ // parent, it should begin showing its canvas. Without these
704
+ // the filter would clip at 0,0, which would look really
705
+ // strange.
706
+ ,
707
+ x: x,
708
+ y: y
709
+ }, /*#__PURE__*/external_react_["createElement"]("feGaussianBlur", {
710
+ in: "SourceAlpha",
711
+ stdDeviation: wonder_blocks_spacing_default.a.xxSmall_6 / 2
712
+ }), /*#__PURE__*/external_react_["createElement"]("feComponentTransfer", null, /*#__PURE__*/external_react_["createElement"]("feFuncA", {
713
+ type: "linear",
714
+ slope: "0.3"
715
+ }))),
716
+ /*#__PURE__*/
717
+
718
+ /**
719
+ * Draw the tooltip arrow and apply the blur filter we created
720
+ * above, to produce a drop shadow effect.
721
+ * We move it down a bit with a translation, so that it is what
722
+ * we want.
723
+ *
724
+ * We offset the shadow on the X-axis because for left/right
725
+ * tails, we move the tail 1px toward the bubble. If we didn't
726
+ * offset the shadow, it would crash the bubble outline.
727
+ *
728
+ * See styles below for why we offset the arrow.
729
+ */
730
+ external_react_["createElement"]("g", {
731
+ key: "dropshadow",
732
+ transform: `translate(${offsetShadowX},5.5)`
733
+ }, /*#__PURE__*/external_react_["createElement"]("polyline", {
734
+ fill: wonder_blocks_color_default.a.offBlack16,
735
+ points: points.join(" "),
736
+ stroke: wonder_blocks_color_default.a.offBlack32,
737
+ filter: `url(#${dropShadowFilterId})`
738
+ }))];
739
+ }
740
+
741
+ _minDistanceFromCorners(placement) {
742
+ const minDistanceFromCornersForTopBottom = wonder_blocks_spacing_default.a.medium_16;
743
+ const minDistanceFromCornersForLeftRight = 7;
744
+
745
+ switch (placement) {
746
+ case "top":
747
+ case "bottom":
748
+ return minDistanceFromCornersForTopBottom;
749
+
750
+ case "left":
751
+ case "right":
752
+ return minDistanceFromCornersForLeftRight;
753
+
754
+ default:
755
+ throw new Error(`Unknown placement: ${placement}`);
756
+ }
757
+ }
758
+
759
+ _getFullTailWidth() {
760
+ return ARROW_WIDTH + 2 * MIN_DISTANCE_FROM_CORNERS;
761
+ }
762
+
763
+ _getFullTailHeight() {
764
+ return ARROW_HEIGHT + DISTANCE_FROM_ANCHOR;
765
+ }
766
+
767
+ _getContainerStyle() {
768
+ const {
769
+ placement
770
+ } = this.props;
771
+ /**
772
+ * Ensure the container is sized properly for us to be placed correctly
773
+ * by the Popper.js code.
774
+ *
775
+ * Here we offset the arrow 1px toward the bubble. This ensures the arrow
776
+ * outline meets the bubble outline and allows the arrow to erase the bubble
777
+ * outline between the ends of the arrow outline. We do this so that the
778
+ * arrow outline and bubble outline create a single, seamless outline of
779
+ * the callout.
780
+ *
781
+ * NOTE: The widths and heights refer to the downward-pointing tail
782
+ * (i.e. placement="top"). When the tail points to the left or right
783
+ * instead, the width/height are inverted.
784
+ */
785
+
786
+ const fullTailWidth = this._getFullTailWidth();
787
+
788
+ const fullTailHeight = this._getFullTailHeight();
789
+
790
+ switch (placement) {
791
+ case "top":
792
+ return {
793
+ top: -1,
794
+ width: fullTailWidth,
795
+ height: fullTailHeight
796
+ };
797
+
798
+ case "right":
799
+ return {
800
+ left: 1,
801
+ width: fullTailHeight,
802
+ height: fullTailWidth
803
+ };
804
+
805
+ case "bottom":
806
+ return {
807
+ top: 1,
808
+ width: fullTailWidth,
809
+ height: fullTailHeight
810
+ };
811
+
812
+ case "left":
813
+ return {
814
+ left: -1,
815
+ width: fullTailHeight,
816
+ height: fullTailWidth
817
+ };
818
+
819
+ default:
820
+ throw new Error(`Unknown placement: ${placement}`);
821
+ }
822
+ }
823
+
824
+ _getArrowStyle() {
825
+ const {
826
+ placement
827
+ } = this.props;
828
+
829
+ switch (placement) {
830
+ case "top":
831
+ return {
832
+ marginLeft: MIN_DISTANCE_FROM_CORNERS,
833
+ marginRight: MIN_DISTANCE_FROM_CORNERS,
834
+ paddingBottom: DISTANCE_FROM_ANCHOR
835
+ };
836
+
837
+ case "right":
838
+ return {
839
+ marginTop: MIN_DISTANCE_FROM_CORNERS,
840
+ marginBottom: MIN_DISTANCE_FROM_CORNERS,
841
+ paddingLeft: DISTANCE_FROM_ANCHOR
842
+ };
843
+
844
+ case "bottom":
845
+ return {
846
+ marginLeft: MIN_DISTANCE_FROM_CORNERS,
847
+ marginRight: MIN_DISTANCE_FROM_CORNERS,
848
+ paddingTop: DISTANCE_FROM_ANCHOR
849
+ };
850
+
851
+ case "left":
852
+ return {
853
+ marginTop: MIN_DISTANCE_FROM_CORNERS,
854
+ marginBottom: MIN_DISTANCE_FROM_CORNERS,
855
+ paddingRight: DISTANCE_FROM_ANCHOR
856
+ };
857
+
858
+ default:
859
+ throw new Error(`Unknown placement: ${placement}`);
860
+ }
861
+ }
862
+
863
+ _renderArrow() {
864
+ const {
865
+ trimlinePoints,
866
+ points,
867
+ height,
868
+ width
869
+ } = this._calculateDimensionsFromPlacement();
870
+
871
+ const {
872
+ color
873
+ } = this.props;
874
+ return /*#__PURE__*/external_react_["createElement"]("svg", {
875
+ className: Object(external_aphrodite_["css"])(styles.arrow),
876
+ style: this._getArrowStyle(),
877
+ width: width,
878
+ height: height
879
+ }, this._maybeRenderDropshadow(points), /*#__PURE__*/external_react_["createElement"]("polyline", {
880
+ fill: wonder_blocks_color_default.a[color],
881
+ stroke: wonder_blocks_color_default.a[color],
882
+ points: points.join(" ")
883
+ }), /*#__PURE__*/external_react_["createElement"]("polyline", {
884
+ // Redraw the stroke on top of the background color,
885
+ // so that the ends aren't extra dark where they meet
886
+ // the border of the tooltip.
887
+ fill: wonder_blocks_color_default.a[color],
888
+ points: points.join(" "),
889
+ stroke: wonder_blocks_color_default.a.offBlack16
890
+ }), /*#__PURE__*/external_react_["createElement"]("polyline", {
891
+ stroke: wonder_blocks_color_default.a[color],
892
+ points: trimlinePoints.join(" ")
893
+ }));
894
+ }
895
+
896
+ render() {
897
+ const {
898
+ offset,
899
+ placement,
900
+ updateRef
901
+ } = this.props;
902
+ return /*#__PURE__*/external_react_["createElement"](wonder_blocks_core_["View"], {
903
+ style: [styles.tailContainer, extends_default()({}, offset), this._getContainerStyle()],
904
+ "data-placement": placement,
905
+ ref: updateRef
906
+ }, this._renderArrow());
907
+ }
908
+
909
+ }
910
+ /**
911
+ * Some constants to make style generation easier to understand.
912
+ * NOTE: The widths and heights refer to the downward-pointing tail
913
+ * (i.e. placement="top"). When the tail points to the left or right instead,
914
+ * the width/height are inverted.
915
+ */
916
+
917
+ tooltip_tail_TooltipTail.defaultProps = {
918
+ color: "white"
919
+ };
920
+ const DISTANCE_FROM_ANCHOR = wonder_blocks_spacing_default.a.xSmall_8;
921
+ const MIN_DISTANCE_FROM_CORNERS = wonder_blocks_spacing_default.a.xSmall_8;
922
+ const ARROW_WIDTH = wonder_blocks_spacing_default.a.large_24;
923
+ const ARROW_HEIGHT = wonder_blocks_spacing_default.a.small_12;
924
+ const styles = external_aphrodite_["StyleSheet"].create({
925
+ /**
926
+ * Container
927
+ */
928
+ tailContainer: {
929
+ position: "relative",
930
+ pointerEvents: "none"
931
+ },
932
+
933
+ /**
934
+ * Arrow
935
+ */
936
+ arrow: {
937
+ // Ensure the dropshadow bleeds outside our bounds.
938
+ overflow: "visible"
939
+ }
940
+ });
941
+ // CONCATENATED MODULE: ./packages/wonder-blocks-tooltip/src/components/tooltip-bubble.js
942
+
943
+
944
+
945
+
946
+
947
+
948
+ class tooltip_bubble_TooltipBubble extends external_react_["Component"] {
949
+ constructor(...args) {
950
+ super(...args);
951
+ this.state = {
952
+ active: false
953
+ };
954
+
955
+ this.handleMouseEnter = () => {
956
+ this._setActiveState(true);
957
+ };
958
+
959
+ this.handleMouseLeave = () => {
960
+ this.props.onActiveChanged(false);
961
+ };
962
+ }
963
+
964
+ _setActiveState(active) {
965
+ this.setState({
966
+ active
967
+ });
968
+ this.props.onActiveChanged(active);
969
+ }
970
+
971
+ render() {
972
+ const {
973
+ id,
974
+ children,
975
+ updateBubbleRef,
976
+ placement,
977
+ isReferenceHidden,
978
+ style,
979
+ updateTailRef,
980
+ tailOffset
981
+ } = this.props;
982
+ return /*#__PURE__*/external_react_["createElement"](wonder_blocks_core_["View"], {
983
+ id: id,
984
+ role: "tooltip",
985
+ "data-placement": placement,
986
+ onMouseEnter: this.handleMouseEnter,
987
+ onMouseLeave: this.handleMouseLeave,
988
+ ref: updateBubbleRef,
989
+ style: [isReferenceHidden && tooltip_bubble_styles.hide, tooltip_bubble_styles.bubble, tooltip_bubble_styles[`content-${placement}`], style]
990
+ }, /*#__PURE__*/external_react_["createElement"](wonder_blocks_core_["View"], {
991
+ style: tooltip_bubble_styles.content
992
+ }, children), /*#__PURE__*/external_react_["createElement"](tooltip_tail_TooltipTail, {
993
+ updateRef: updateTailRef,
994
+ placement: placement,
995
+ offset: tailOffset
996
+ }));
997
+ }
998
+
999
+ }
1000
+ const tooltip_bubble_styles = external_aphrodite_["StyleSheet"].create({
1001
+ bubble: {
1002
+ position: "absolute"
1003
+ },
1004
+
1005
+ /**
1006
+ * The hide style ensures that the bounds of the bubble stay unchanged.
1007
+ * This is because popper.js calculates the bubble position based off its
1008
+ * bounds and if we stopped rendering it entirely, it wouldn't know where to
1009
+ * place it when it reappeared.
1010
+ */
1011
+ hide: {
1012
+ pointerEvents: "none",
1013
+ opacity: 0,
1014
+ backgroundColor: "transparent",
1015
+ color: "transparent"
1016
+ },
1017
+
1018
+ /**
1019
+ * Ensure the content and tail are properly arranged.
1020
+ */
1021
+ "content-top": {
1022
+ flexDirection: "column"
1023
+ },
1024
+ "content-right": {
1025
+ flexDirection: "row-reverse"
1026
+ },
1027
+ "content-bottom": {
1028
+ flexDirection: "column-reverse"
1029
+ },
1030
+ "content-left": {
1031
+ flexDirection: "row"
1032
+ },
1033
+ content: {
1034
+ maxWidth: 472,
1035
+ borderRadius: wonder_blocks_spacing_default.a.xxxSmall_4,
1036
+ border: `solid 1px ${wonder_blocks_color_default.a.offBlack16}`,
1037
+ backgroundColor: wonder_blocks_color_default.a.white,
1038
+ boxShadow: `0 ${wonder_blocks_spacing_default.a.xSmall_8}px ${wonder_blocks_spacing_default.a.xSmall_8}px 0 ${wonder_blocks_color_default.a.offBlack8}`,
1039
+ justifyContent: "center"
1040
+ }
1041
+ });
1042
+ // EXTERNAL MODULE: external "@khanacademy/wonder-blocks-layout"
1043
+ var wonder_blocks_layout_ = __webpack_require__(9);
1044
+
1045
+ // EXTERNAL MODULE: external "@khanacademy/wonder-blocks-typography"
1046
+ var wonder_blocks_typography_ = __webpack_require__(6);
1047
+
1048
+ // CONCATENATED MODULE: ./packages/wonder-blocks-tooltip/src/components/tooltip-content.js
1049
+
1050
+
1051
+
1052
+
1053
+
1054
+
1055
+
1056
+ /**
1057
+ * This component is used to provide the content that is to be rendered in the
1058
+ * tooltip bubble.
1059
+ */
1060
+ class tooltip_content_TooltipContent extends external_react_["Component"] {
1061
+ _renderTitle() {
1062
+ const {
1063
+ title
1064
+ } = this.props;
1065
+
1066
+ if (title) {
1067
+ if (typeof title === "string") {
1068
+ return /*#__PURE__*/external_react_["createElement"](wonder_blocks_typography_["HeadingSmall"], null, title);
1069
+ } else {
1070
+ return title;
1071
+ }
1072
+ }
1073
+
1074
+ return null;
1075
+ }
1076
+
1077
+ _renderChildren() {
1078
+ const {
1079
+ children
1080
+ } = this.props;
1081
+
1082
+ if (typeof children === "string") {
1083
+ return /*#__PURE__*/external_react_["createElement"](wonder_blocks_typography_["LabelMedium"], null, children);
1084
+ } else {
1085
+ return children;
1086
+ }
1087
+ }
1088
+
1089
+ render() {
1090
+ const title = this._renderTitle();
1091
+
1092
+ const children = this._renderChildren();
1093
+
1094
+ const containerStyle = title ? tooltip_content_styles.withTitle : tooltip_content_styles.withoutTitle;
1095
+ return /*#__PURE__*/external_react_["createElement"](wonder_blocks_core_["View"], {
1096
+ style: containerStyle
1097
+ }, title, title && children && /*#__PURE__*/external_react_["createElement"](wonder_blocks_layout_["Strut"], {
1098
+ size: wonder_blocks_spacing_default.a.xxxSmall_4
1099
+ }), children);
1100
+ }
1101
+
1102
+ }
1103
+ const tooltip_content_styles = external_aphrodite_["StyleSheet"].create({
1104
+ withoutTitle: {
1105
+ padding: `10px ${wonder_blocks_spacing_default.a.medium_16}px`
1106
+ },
1107
+ withTitle: {
1108
+ padding: wonder_blocks_spacing_default.a.medium_16
1109
+ }
1110
+ });
1111
+ // EXTERNAL MODULE: external "react-popper"
1112
+ var external_react_popper_ = __webpack_require__(10);
1113
+
1114
+ // CONCATENATED MODULE: ./packages/wonder-blocks-tooltip/src/util/ref-tracker.js
1115
+ /**
1116
+ * This is a little helper that we can use to wrap the react-popper reference
1117
+ * update methods so that we can convert a regular React ref into a DOM node
1118
+ * as react-popper expects, and also ensure we only update react-popper
1119
+ * on actual changes, and not just renders of the same thing.
1120
+ */
1121
+
1122
+
1123
+ class ref_tracker_RefTracker {
1124
+ constructor() {
1125
+ this.updateRef = ref => {
1126
+ if (ref) {
1127
+ // We only want to update the reference if it is
1128
+ // actually changed. Otherwise, we can trigger another render that
1129
+ // would then update the reference again and just keep looping.
1130
+ const domNode = external_react_dom_["findDOMNode"](ref);
1131
+
1132
+ if (domNode instanceof HTMLElement && domNode !== this._lastRef) {
1133
+ this._lastRef = domNode;
1134
+ this._targetFn && this._targetFn(domNode);
1135
+ }
1136
+ }
1137
+ };
1138
+
1139
+ this.setCallback = targetFn => {
1140
+ if (this._targetFn !== targetFn) {
1141
+ if (targetFn && typeof targetFn !== "function") {
1142
+ throw new Error("targetFn must be a function");
1143
+ }
1144
+
1145
+ this._targetFn = targetFn || null;
1146
+
1147
+ if (this._lastRef && this._targetFn) {
1148
+ this._targetFn(this._lastRef);
1149
+ }
1150
+ }
1151
+ };
1152
+ }
1153
+
1154
+ }
1155
+ // CONCATENATED MODULE: ./packages/wonder-blocks-tooltip/src/components/tooltip-popper.js
1156
+ /**
1157
+ * This component is a light wrapper for react-popper, allowing us to position
1158
+ * and control the tooltip bubble location and visibility as we need.
1159
+ */
1160
+
1161
+
1162
+
1163
+ class tooltip_popper_TooltipPopper extends external_react_["Component"] {
1164
+ constructor(...args) {
1165
+ super(...args);
1166
+ this._bubbleRefTracker = new ref_tracker_RefTracker();
1167
+ this._tailRefTracker = new ref_tracker_RefTracker();
1168
+ }
1169
+
1170
+ _renderPositionedContent(popperProps) {
1171
+ const {
1172
+ children
1173
+ } = this.props; // We'll hide some complexity from the children here and ensure
1174
+ // that our placement always has a value.
1175
+
1176
+ const placement = // We know that popperProps.placement will only be one of our
1177
+ // supported values, so just cast it.
1178
+ popperProps.placement || this.props.placement; // Just in case the callbacks have changed, let's update our reference
1179
+ // trackers.
1180
+
1181
+ this._bubbleRefTracker.setCallback(popperProps.ref);
1182
+
1183
+ this._tailRefTracker.setCallback(popperProps.arrowProps.ref); // Here we translate from the react-popper's PropperChildrenProps
1184
+ // to our own TooltipBubbleProps.
1185
+
1186
+
1187
+ const bubbleProps = {
1188
+ placement,
1189
+ style: {
1190
+ // NOTE(jeresig): We can't just use `popperProps.style` here
1191
+ // as the Flow type doesn't match Aphrodite's CSS flow props
1192
+ // (as it doesn't camelCase props). So we just copy over the
1193
+ // props that we need, instead.
1194
+ top: popperProps.style.top,
1195
+ left: popperProps.style.left,
1196
+ bottom: popperProps.style.bottom,
1197
+ right: popperProps.style.right,
1198
+ position: popperProps.style.position,
1199
+ transform: popperProps.style.transform
1200
+ },
1201
+ updateBubbleRef: this._bubbleRefTracker.updateRef,
1202
+ tailOffset: {
1203
+ bottom: popperProps.arrowProps.style.bottom,
1204
+ right: popperProps.arrowProps.style.right,
1205
+ top: popperProps.arrowProps.style.top,
1206
+ left: popperProps.arrowProps.style.left,
1207
+ transform: popperProps.arrowProps.style.transform
1208
+ },
1209
+ updateTailRef: this._tailRefTracker.updateRef,
1210
+ isReferenceHidden: popperProps.isReferenceHidden
1211
+ };
1212
+ return children(bubbleProps);
1213
+ }
1214
+
1215
+ render() {
1216
+ const {
1217
+ anchorElement,
1218
+ placement
1219
+ } = this.props;
1220
+ return /*#__PURE__*/external_react_["createElement"](external_react_popper_["Popper"], {
1221
+ referenceElement: anchorElement,
1222
+ placement: placement,
1223
+ modifiers: [{
1224
+ name: "preventOverflow",
1225
+ options: {
1226
+ rootBoundary: "document"
1227
+ }
1228
+ }]
1229
+ }, props => this._renderPositionedContent(props));
1230
+ }
1231
+
1232
+ }
1233
+ // CONCATENATED MODULE: ./packages/wonder-blocks-tooltip/src/components/tooltip.js
1234
+ /**
1235
+ * The Tooltip component provides the means to anchor some additional
1236
+ * information to some content. The additional information is shown in a
1237
+ * callout that hovers above the page content. This additional information is
1238
+ * invoked by hovering over the anchored content, or focusing all or part of the
1239
+ * anchored content.
1240
+ *
1241
+ * This component is structured as follows:
1242
+ *
1243
+ * Tooltip (this component)
1244
+ * - TooltipAnchor (provides hover/focus behaviors on anchored content)
1245
+ * - TooltipPortalMounter (creates portal into which the callout is rendered)
1246
+ * --------------------------- [PORTAL BOUNDARY] ------------------------------
1247
+ * - TooltipPopper (provides positioning for the callout using react-popper)
1248
+ * - TooltipBubble (renders the callout borders, background and shadow)
1249
+ * - TooltipContent (renders the callout content; the actual information)
1250
+ * - TooltipTail (renders the callout tail and shadow that points from the
1251
+ * callout to the anchor content)
1252
+ */
1253
+
1254
+
1255
+
1256
+
1257
+
1258
+
1259
+
1260
+
1261
+ class tooltip_Tooltip extends external_react_["Component"] {
1262
+ constructor(...args) {
1263
+ super(...args);
1264
+ this.state = {
1265
+ active: false,
1266
+ activeBubble: false,
1267
+ anchorElement: null
1268
+ };
1269
+ }
1270
+
1271
+ _updateAnchorElement(ref) {
1272
+ if (ref && ref !== this.state.anchorElement) {
1273
+ this.setState({
1274
+ anchorElement: ref
1275
+ });
1276
+ }
1277
+ }
1278
+
1279
+ _renderBubbleContent() {
1280
+ const {
1281
+ title,
1282
+ content
1283
+ } = this.props;
1284
+
1285
+ if (typeof content === "string") {
1286
+ return /*#__PURE__*/external_react_["createElement"](tooltip_content_TooltipContent, {
1287
+ title: title
1288
+ }, content);
1289
+ } else if (title) {
1290
+ return /*#__PURE__*/external_react_["cloneElement"](content, {
1291
+ title
1292
+ });
1293
+ } else {
1294
+ return content;
1295
+ }
1296
+ }
1297
+
1298
+ _renderPopper(ids) {
1299
+ const {
1300
+ id
1301
+ } = this.props;
1302
+ const bubbleId = ids ? ids.get(tooltip_Tooltip.ariaContentId) : id;
1303
+
1304
+ if (!bubbleId) {
1305
+ throw new Error("Did not get an identifier factory nor a id prop");
1306
+ }
1307
+
1308
+ const {
1309
+ placement
1310
+ } = this.props;
1311
+ return /*#__PURE__*/external_react_["createElement"](tooltip_popper_TooltipPopper, {
1312
+ anchorElement: this.state.anchorElement,
1313
+ placement: placement
1314
+ }, props => /*#__PURE__*/external_react_["createElement"](tooltip_bubble_TooltipBubble, {
1315
+ id: bubbleId,
1316
+ style: props.style,
1317
+ tailOffset: props.tailOffset,
1318
+ isReferenceHidden: props.isReferenceHidden,
1319
+ placement: props.placement,
1320
+ updateTailRef: props.updateTailRef,
1321
+ updateBubbleRef: props.updateBubbleRef,
1322
+ onActiveChanged: active => this.setState({
1323
+ activeBubble: active
1324
+ })
1325
+ }, this._renderBubbleContent()));
1326
+ }
1327
+
1328
+ _getHost() {
1329
+ const {
1330
+ anchorElement
1331
+ } = this.state;
1332
+ return Object(wonder_blocks_modal_["maybeGetPortalMountedModalHostElement"])(anchorElement) || document.body;
1333
+ }
1334
+
1335
+ _renderTooltipAnchor(ids) {
1336
+ const {
1337
+ children,
1338
+ forceAnchorFocusivity
1339
+ } = this.props;
1340
+ const {
1341
+ active,
1342
+ activeBubble
1343
+ } = this.state;
1344
+
1345
+ const popperHost = this._getHost(); // TODO(kevinb): update to use ReactPopper's React 16-friendly syntax
1346
+
1347
+
1348
+ return /*#__PURE__*/external_react_["createElement"](external_react_["Fragment"], null, /*#__PURE__*/external_react_["createElement"](tooltip_anchor_TooltipAnchor, {
1349
+ forceAnchorFocusivity: forceAnchorFocusivity,
1350
+ anchorRef: r => this._updateAnchorElement(r),
1351
+ onActiveChanged: active => this.setState({
1352
+ active
1353
+ }),
1354
+ ids: ids
1355
+ }, children), popperHost && (active || activeBubble) && /*#__PURE__*/external_react_dom_["createPortal"](this._renderPopper(ids), popperHost));
1356
+ }
1357
+
1358
+ render() {
1359
+ const {
1360
+ id
1361
+ } = this.props;
1362
+
1363
+ if (id) {
1364
+ // Let's bypass the extra weight of an id provider since we don't
1365
+ // need it.
1366
+ return this._renderTooltipAnchor();
1367
+ } else {
1368
+ return /*#__PURE__*/external_react_["createElement"](wonder_blocks_core_["UniqueIDProvider"], {
1369
+ scope: "tooltip",
1370
+ mockOnFirstRender: true
1371
+ }, ids => this._renderTooltipAnchor(ids));
1372
+ }
1373
+ }
1374
+
1375
+ }
1376
+ tooltip_Tooltip.defaultProps = {
1377
+ forceAnchorFocusivity: true,
1378
+ placement: "top"
1379
+ };
1380
+ tooltip_Tooltip.ariaContentId = "aria-content";
1381
+ // CONCATENATED MODULE: ./packages/wonder-blocks-tooltip/src/index.js
1382
+
1383
+
1384
+
1385
+
1386
+
1387
+
1388
+ /***/ })
1389
+ /******/ ]);