@khanacademy/perseus 0.4.1 → 0.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,182 +0,0 @@
1
- // @flow
2
- /**
3
- * This is FlexiblePosition, part of NewTooltip.
4
- *
5
- * This generalizable component attempts to center its children relative to a
6
- * given point... but refuses to allow the children to overflow their
7
- * container. If the children *would* overflow, we first use the Flexbox
8
- * algorithm to shift the element off-center. If that still doesn't work (i.e.,
9
- * the children are larger than the container), then we shrink the children.
10
- *
11
- * This layout algorithm is entirely implemented via Flexbox. We have three
12
- * elements: the pre-content spacer, the content, and the post-content spacer.
13
- * The pre-content spacer and post-content spacer use their basis sizes to
14
- * position the content at the target point, then shrink by equal amounts in
15
- * order to *center* the content at the target point.
16
- *
17
- * To understand the algorithm, first consider *just* the spacer elements.
18
- * If the container is 80px wide, and we're targeting a point 60px from the
19
- * right and 20px from the left, then the spacer elements will use those values
20
- * as their flex-basis:
21
- *
22
- * [ pre-content ][ post-content ]
23
- *
24
- * They take up 100% of the container by default. But, if the content element
25
- * _also_ wants space, then the pre-content and post-content spacers need to
26
- * shrink. We've configured them to shrink by equal amounts, which therefore
27
- * center-aligns the target element at the target point.
28
- *
29
- * [ pre-content ][content ][post-content ]
30
- *
31
- * (Incidentally, the flex-shrink property is surprisingly complicated: it's
32
- * defined relative to the element's own flex-basis. So, to get the spacers to
33
- * shrink at equal rates, we define each spacer's flex-shrink to match the
34
- * _other_ element's flex-basis. Fun! ^_^`)
35
- *
36
- * TODO(mdr): This component currently only supports horizontal positioning,
37
- * but we'll probably extend it later to support vertical positioning, too.
38
- */
39
- import {StyleSheet, css} from "aphrodite";
40
- import * as React from "react";
41
-
42
- type FlexiblePositionProps = {
43
- children: React.Node,
44
- // The direction along which to position the element.
45
- //
46
- // If "horizontal", this will be a full-width element that horizontally
47
- // positions its children near a target x-coordinate.
48
- //
49
- // If "vertical", this will be a full-height element that vertically
50
- // positions its children near a target y-coordinate.
51
- direction: "horizontal" | "vertical",
52
- // Specify the target point, by providing its distance from the flex-start
53
- // and its distance from the flex-end. (These, together, must sum to the
54
- // total container size.)
55
- //
56
- // If `direction` is "horizontal", then `pxFromStart` is the target
57
- // position's offset from the left of the container, and `pxFromEnd` is the
58
- // offset from the right.
59
- //
60
- // If `direction` is "vertical", then `pxFromStart` is the target
61
- // position's offset from the top of the container, and `pxFromEnd` is the
62
- // offset from the bottom.
63
- pxFromStart: number,
64
- pxFromEnd: number,
65
- ...
66
- };
67
-
68
- export default class FlexiblePosition extends React.PureComponent<FlexiblePositionProps> {
69
- render(): React.Element<"div"> {
70
- const {direction, pxFromStart, pxFromEnd} = this.props;
71
-
72
- const wrapperStyles = [
73
- styles.wrapper,
74
- direction === "horizontal" && styles.wrapperHorizontal,
75
- direction === "vertical" && styles.wrapperVertical,
76
- ];
77
- const contentStyles = [
78
- styles.content,
79
- direction === "horizontal" && styles.contentHorizontal,
80
- direction === "vertical" && styles.contentVertical,
81
- ];
82
-
83
- // We need to handle the zero cases specially, because otherwise we get
84
- // spacer elements with `flex-shrink: 0` - that is, they don't shrink
85
- // for the content at all!
86
- if (pxFromStart === 0) {
87
- return (
88
- <div className={css(...wrapperStyles)}>
89
- <div className={css(...contentStyles)}>
90
- {this.props.children}
91
- </div>
92
- </div>
93
- );
94
- }
95
- if (pxFromEnd === 0) {
96
- return (
97
- <div
98
- className={css(...wrapperStyles, styles.wrapperAlignToEnd)}
99
- >
100
- <div className={css(...contentStyles)}>
101
- {this.props.children}
102
- </div>
103
- </div>
104
- );
105
- }
106
-
107
- return (
108
- <div className={css(...wrapperStyles)}>
109
- <div
110
- style={{
111
- flexShrink: pxFromEnd,
112
- flexBasis: pxFromStart,
113
- }}
114
- />
115
- <div className={css(...contentStyles)}>
116
- {this.props.children}
117
- </div>
118
- <div
119
- style={{
120
- flexShrink: pxFromStart,
121
- flexBasis: pxFromEnd,
122
- }}
123
- />
124
- </div>
125
- );
126
- }
127
- }
128
-
129
- const styles = StyleSheet.create({
130
- wrapper: {
131
- display: "flex",
132
- overflow: "hidden",
133
- },
134
-
135
- wrapperHorizontal: {
136
- flexDirection: "row",
137
-
138
- // The FlexiblePosition container takes the full width of its parent,
139
- // and will not allow overflow (see `styles.wrapper`). This ensures
140
- // that we prefer to shrink our contents, rather than overflow our
141
- // parent.
142
- width: "100%",
143
- },
144
-
145
- wrapperVertical: {
146
- flexDirection: "column",
147
-
148
- // The FlexiblePosition container takes the full height of its parent,
149
- // and will not allow overflow (see `styles.wrapper`). This ensures
150
- // that we prefer to shrink our contents, rather than overflow our
151
- // parent.
152
- height: "100%",
153
- },
154
-
155
- wrapperAlignToEnd: {
156
- justifyContent: "flex-end",
157
- },
158
-
159
- content: {
160
- // The content element must not grow or shrink, and instead always use
161
- // its basis value.
162
- flex: "0 0 auto",
163
- },
164
-
165
- contentHorizontal: {
166
- // The only case in which the content element should "shrink" is when
167
- // the content itself is too large for the container. In this case, we
168
- // constrain the size with maximum width. (We can't use flex-shrink for
169
- // this behavior, because we need the spacer elements to be prioritized
170
- // as the only flex-shrinkable elements.)
171
- maxWidth: "100%",
172
- },
173
-
174
- contentVertical: {
175
- // The only case in which the content element should "shrink" is when
176
- // the content itself is too large for the container. In this case, we
177
- // constrain the size with maximum height. (We can't use flex-shrink
178
- // for this behavior, because we need the spacer elements to be
179
- // prioritized as the only flex-shrinkable elements.)
180
- maxHeight: "100%",
181
- },
182
- });
@@ -1,184 +0,0 @@
1
- // @flow
2
- /**
3
- * This is TooltipArrow, part of NewTooltip.
4
- *
5
- * This component manages the visuals for the tooltip "arrow" UI element, which
6
- * is visually attached to the TooltipBubble, and points to the target element.
7
- *
8
- * This component and TooltipBubble both count as part of the tooltip, and
9
- * trigger mouse events like `onClick`, `onMouseEnter`, and `onMouseLeave`.
10
- */
11
- import {StyleSheet, css} from "aphrodite";
12
- import * as React from "react";
13
-
14
- import {Errors} from "../../logging/log.js";
15
- import {PerseusError} from "../../perseus-error.js";
16
-
17
- import {
18
- arrowHeight,
19
- arrowWidth,
20
- dropShadowOpacity,
21
- dropShadowRadius,
22
- dropShadowXOffset,
23
- dropShadowYOffset,
24
- } from "./style-constants.js";
25
- import {getTooltipColors} from "./util.js";
26
-
27
- import type {NewTooltipProps} from "./types.js";
28
-
29
- type TooltipArrowProps = {|
30
- tooltipProps: {
31
- side: NewTooltipProps["side"],
32
- color: NewTooltipProps["color"],
33
- inverted: NewTooltipProps["inverted"],
34
-
35
- onClick?: NewTooltipProps["onClick"],
36
- onMouseEnter?: NewTooltipProps["onMouseEnter"],
37
- onMouseLeave?: NewTooltipProps["onMouseLeave"],
38
- ...
39
- },
40
- |};
41
-
42
- export default class TooltipArrow extends React.PureComponent<TooltipArrowProps> {
43
- render(): React.Element<"svg"> {
44
- const {onClick, onMouseEnter, onMouseLeave, side} =
45
- this.props.tooltipProps;
46
- const {backgroundColor} = getTooltipColors(this.props.tooltipProps);
47
-
48
- // Draw the three points of the arrow. Depending on the arrow's
49
- // direction (i.e., the tooltip's "side"), we choose different points,
50
- // and set our SVG's bounds differently.
51
- //
52
- // Note that `arrowWidth` and `arrowHeight` refer to the
53
- // downward-pointing arrow (i.e. side="top"). When the arrow points to
54
- // the left or right instead, the width/height are inverted.
55
- let points;
56
- let svgWidth;
57
- let svgHeight;
58
- if (side === "top") {
59
- points = [
60
- "0,0",
61
- `${arrowWidth / 2},${arrowHeight}`,
62
- `${arrowWidth},0`,
63
- ];
64
- svgWidth = arrowWidth;
65
- svgHeight = arrowHeight;
66
- } else if (side === "right") {
67
- points = [
68
- `${arrowHeight},0`,
69
- `0,${arrowWidth / 2}`,
70
- `${arrowHeight},${arrowWidth}`,
71
- ];
72
- svgWidth = arrowHeight;
73
- svgHeight = arrowWidth;
74
- } else if (side === "bottom") {
75
- points = [
76
- `0,${arrowHeight}`,
77
- `${arrowWidth / 2},0`,
78
- `${arrowWidth},${arrowHeight}`,
79
- ];
80
- svgWidth = arrowWidth;
81
- svgHeight = arrowHeight;
82
- } else if (side === "left") {
83
- points = [
84
- "0,0",
85
- `${arrowHeight},${arrowWidth / 2}`,
86
- `0,${arrowWidth}`,
87
- ];
88
- svgWidth = arrowHeight;
89
- svgHeight = arrowWidth;
90
- } else {
91
- throw new PerseusError(
92
- `unexpected side ${side}`,
93
- Errors.InvalidInput,
94
- );
95
- }
96
-
97
- // This SVG is copied from tooltip-package/tooltip.jsx - with some minor
98
- // modifications, because styling is handled differently.
99
- return (
100
- <svg
101
- width={svgWidth}
102
- height={svgHeight}
103
- className={css(styles.tooltipArrow)}
104
- >
105
- {/* Create an SVG filter that applies a blur to an element.
106
- * We'll apply it to a dark shape outlining the tooltip, which
107
- * will produce the overall effect of a drop-shadow.
108
- *
109
- * Also, scope its ID by side, so that tooltips with other
110
- * "side" values don't end up using the wrong filter from
111
- * elsewhere in the document. (The `height` value depends on
112
- * which way the arrow is turned!)
113
- *
114
- * In general, it's not *great* that multiple tooltips of the
115
- * same side will yield ID conflicts... but at least they'll
116
- * be the same filter, so it doesn't matter which one we
117
- * reference, so long as it's for the correct side. */}
118
- <filter id={`tooltip-dropshadow-${side}`} height="150%">
119
- <feOffset
120
- dx={dropShadowXOffset}
121
- dy={dropShadowYOffset}
122
- result="offsetblur"
123
- />
124
- <feGaussianBlur
125
- in="SourceAlpha"
126
- stdDeviation={dropShadowRadius / 2}
127
- />
128
- <feComponentTransfer>
129
- <feFuncA type="linear" slope={dropShadowOpacity} />
130
- </feComponentTransfer>
131
- <feMerge>
132
- <feMergeNode />
133
- <feMergeNode in="SourceGraphic" />
134
- </feMerge>
135
- </filter>
136
-
137
- {/* Draw the background of the tooltip arrow. */}
138
- <polyline
139
- fill={backgroundColor}
140
- stroke={backgroundColor}
141
- points={points.join(" ")}
142
- // We attach our mouse event styles and handlers to this
143
- // element, instead of the root SVG element. That way, our
144
- // target area will only include the actual _arrow_ shape,
145
- // rather than the entire bounding rectangle.
146
- className={css(
147
- onClick && styles.tooltipArrowShapeWithOnClick,
148
- )}
149
- onClick={onClick}
150
- onMouseEnter={onMouseEnter}
151
- onMouseLeave={onMouseLeave}
152
- />
153
-
154
- {/* Draw an outline around the tooltip arrow, and apply the
155
- * blur filter we created above, to produce a drop shadow
156
- * effect. */}
157
- <polyline
158
- // Redraw the stroke on top of the background color,
159
- // so that the ends aren't extra dark where they meet
160
- // the border of the tooltip.
161
- fill={backgroundColor}
162
- points={points.join(" ")}
163
- stroke="rgba(0, 0, 0, 0.1)"
164
- filter={`url(#tooltip-dropshadow-${side})`}
165
- />
166
- </svg>
167
- );
168
- }
169
- }
170
-
171
- const styles = StyleSheet.create({
172
- tooltipArrow: {
173
- display: "block",
174
-
175
- // Ensure that clicking on the tooltip's empty space doesn't clear the
176
- // user's text selection elsewhere in the page.
177
- userSelect: "none",
178
- },
179
-
180
- tooltipArrowShapeWithOnClick: {
181
- pointerEvents: "auto",
182
- cursor: "pointer",
183
- },
184
- });
@@ -1,223 +0,0 @@
1
- // @flow
2
- /**
3
- * This is TooltipBubble, part of NewTooltip.
4
- *
5
- * This component manages the visuals for the tooltip "bubble" UI element,
6
- * which contains the tooltip content.
7
- *
8
- * This component and TooltipArrow both count as part of the tooltip, and
9
- * trigger mouse events like `onClick`, `onMouseEnter`, and `onMouseLeave`.
10
- */
11
- import {UniqueIDProvider} from "@khanacademy/wonder-blocks-core";
12
- import * as i18n from "@khanacademy/wonder-blocks-i18n";
13
- import {StyleSheet, css} from "aphrodite";
14
- import * as React from "react";
15
-
16
- import {typography} from "../../styles/global-styles.js";
17
- import a11yStyles from "../../util/a11y.js";
18
- import Icon from "../icon.jsx";
19
-
20
- import {
21
- bubbleBorderRadius,
22
- dropShadowOpacity,
23
- dropShadowRadius,
24
- dropShadowXOffset,
25
- dropShadowYOffset,
26
- } from "./style-constants.js";
27
- import {getTooltipColors} from "./util.js";
28
-
29
- import type {NewTooltipProps} from "./types.js";
30
- import type {IIdentifierFactory} from "@khanacademy/wonder-blocks-core";
31
-
32
- const closeIconPath =
33
- "M6.26353762,4.99851587 L9.73097464,1.53107884 C10.0836373,1.17841618 10.0842213,0.612127047 9.73530496,0.263210718 C9.38395604,-0.0881381913 8.81874474,-0.0837668714 8.46743686,0.267541014 L4.99999981,3.73497806 L1.5325628,0.267541051 C1.1812549,-0.0837668481 0.616043606, -0.0881381955 0.264694717,0.263210694 C-0.0842215912,0.612127004 -0.0836375768,1.17841613 0.269025093,1.5310788 L3.73646206,4.9985158 L0.269025109,8.46595276 C-0.083637537,8.81861541 -0.0842215923, 9.38490462 0.264694642,9.73382106 C0.616043456,10.0851701 1.18125469, 10.0807988 1.53256259,9.72949093 L4.99999988,6.26205363 L8.46743739, 9.72949117 C8.8187453,10.0807991 9.38395655,10.0851704 9.73530537, 9.73382138 C10.0842216,9.38490498 10.0836375,8.81861579 9.73097488, 8.46595313 L6.26353762,4.99851587 Z";
34
-
35
- type TooltipBubbleProps = {|
36
- tooltipProps: NewTooltipProps,
37
- dismiss: () => void,
38
- |};
39
-
40
- export default class TooltipBubble extends React.PureComponent<TooltipBubbleProps> {
41
- _handleClickClose: () => void = () => this.props.dismiss();
42
-
43
- _renderCloseButton(
44
- ids: IIdentifierFactory,
45
- ): null | React.Element<"button"> {
46
- const {inverted, dismissOnClickClose} = this.props.tooltipProps;
47
-
48
- if (!dismissOnClickClose) {
49
- // When `dismissOnClickClose` is not set, don't render a close
50
- // button at all.
51
- return null;
52
- }
53
-
54
- const buttonDescriptionId = ids.get("button-description");
55
- const headerId = this.getA11yHeaderId(ids);
56
-
57
- return (
58
- <button
59
- className={css(styles.closeIcon)}
60
- onClick={this._handleClickClose}
61
- aria-describedby={`${buttonDescriptionId} ${headerId}`}
62
- >
63
- <Icon
64
- title={i18n._("Close")}
65
- icon={closeIconPath}
66
- size={10}
67
- color={
68
- // Use a light icon if we're inverted, or dark
69
- // otherwise.
70
- inverted
71
- ? "rgba(255, 255, 255, 0.7)"
72
- : "rgba(0, 0, 0, 0.15)"
73
- }
74
- />
75
- <div
76
- id={buttonDescriptionId}
77
- className={css(a11yStyles.srOnly)}
78
- >
79
- {i18n._("Closes the tooltip: ")}
80
- </div>
81
- </button>
82
- );
83
- }
84
-
85
- getA11yHeaderId(ids: IIdentifierFactory): string {
86
- const {
87
- tooltipProps: {a11y},
88
- } = this.props;
89
- if (!a11y || !a11y.title) {
90
- // No title specified so assume our content labels us.
91
- return this.getA11yContentId(ids);
92
- }
93
- if (typeof a11y.title === "string") {
94
- // We have a string title, so we'll generate our own label.
95
- return ids.get("header");
96
- }
97
- // We've been given a specific identifier, so use that.
98
- return a11y.title.id;
99
- }
100
-
101
- getA11yContentId(ids: IIdentifierFactory): string {
102
- return ids.get("content");
103
- }
104
-
105
- renderTooltip(ids: IIdentifierFactory): React.Element<"div"> {
106
- const {
107
- content,
108
- dismissOnClickClose,
109
- noPadding,
110
- onClick,
111
- onMouseEnter,
112
- onMouseLeave,
113
- a11y,
114
- testId,
115
- } = this.props.tooltipProps;
116
- const a11yTitle = a11y && a11y.title;
117
- const a11yAssertiveness = a11y && a11y.assertiveness;
118
- const {backgroundColor, textColor} = getTooltipColors(
119
- this.props.tooltipProps,
120
- );
121
- const headerId = this.getA11yHeaderId(ids);
122
- const contentId = this.getA11yContentId(ids);
123
- return (
124
- <div
125
- className={css(
126
- styles.tooltipBubble,
127
- !noPadding && styles.tooltipBubbleWithPadding,
128
- dismissOnClickClose && styles.tooltipBubbleWithCloseIcon,
129
- onClick && styles.tooltipBubbleWithOnClick,
130
- )}
131
- onClick={onClick}
132
- onMouseEnter={onMouseEnter}
133
- onMouseLeave={onMouseLeave}
134
- style={{backgroundColor, color: textColor}}
135
- role="region"
136
- aria-labelledby={headerId}
137
- aria-describedby={contentId === headerId ? null : contentId}
138
- data-test-id={testId}
139
- >
140
- {this._renderCloseButton(ids)}
141
- <div aria-live={a11yAssertiveness}>
142
- {typeof a11yTitle === "string" ? (
143
- <div className={css(a11yStyles.srOnly)} id={headerId}>
144
- {a11yTitle}
145
- </div>
146
- ) : null}
147
- <div id={contentId} className={css(styles.tooltipContent)}>
148
- {content}
149
- </div>
150
- </div>
151
- </div>
152
- );
153
- }
154
-
155
- render(): React.Node {
156
- return (
157
- <UniqueIDProvider mockOnFirstRender={false} scope="tooltip-bubble">
158
- {(ids) => this.renderTooltip(ids)}
159
- </UniqueIDProvider>
160
- );
161
- }
162
- }
163
-
164
- // These styles are copied from tooltip-package/tooltip.jsx - with some minor
165
- // modifications, because positioning is handled differently.
166
- //
167
- // TODO(mdr): Also, the standard tooltip seemed to have `transition` styles
168
- // designed to fade-in the tooltip... but they don't seem to work? I've
169
- // removed them for now. We'd probably have to use an
170
- // AphroditeCSSTransitionGroup, in order to initialize the opacity to 0,
171
- // then wait a tick before setting the opacity to 1.
172
- const styles = StyleSheet.create({
173
- tooltipBubble: {
174
- // layout styles
175
- boxSizing: "border-box",
176
- position: "relative", // for the close icon
177
-
178
- // content styles
179
- ...typography.labelMedium,
180
-
181
- // border and box shadow styles
182
- borderRadius: bubbleBorderRadius,
183
- border: "1px solid rgba(0, 0, 0, 0.1)",
184
- boxShadow:
185
- `${dropShadowXOffset}px ${dropShadowYOffset}px ` +
186
- `${dropShadowRadius}px 0px ` +
187
- `rgba(0, 0, 0, ${dropShadowOpacity})`,
188
-
189
- // Ensure that clicking on the tooltip's empty space doesn't clear the
190
- // user's text selection elsewhere in the page. (We reset this rule in
191
- // `tooltipContent`, so that the user _can_ select text _inside_ the
192
- // tooltip.)
193
- userSelect: "none",
194
- },
195
-
196
- tooltipBubbleWithPadding: {
197
- padding: "10px 23px",
198
- },
199
-
200
- tooltipBubbleWithCloseIcon: {
201
- paddingRight: 34,
202
- },
203
-
204
- tooltipBubbleWithOnClick: {
205
- cursor: "pointer",
206
- },
207
-
208
- tooltipContent: {
209
- // Reset the `userSelect: none` rule in `tooltipBubble`, to enable the
210
- // user to select the tooltip's content.
211
- userSelect: "auto",
212
- },
213
-
214
- closeIcon: {
215
- display: "block",
216
- position: "absolute",
217
- right: 10,
218
- top: 10,
219
- padding: 0,
220
- background: "inherit",
221
- border: "none",
222
- },
223
- });
@@ -1,101 +0,0 @@
1
- // @flow
2
- /**
3
- * This is TooltipPortal, part of NewTooltip.
4
- *
5
- * This component renders nothing inline - but it mounts a TooltipPositioner in
6
- * `#outer-wrapper`, and unmounts it when this unmounts.
7
- *
8
- * This is a lot like a React 16 "portal", but we're in React 15, so we sorta
9
- * have to reimplement that behavior ourselves ^_^`
10
- * https://reactjs.org/docs/portals.html
11
- *
12
- * This component has a public method `remeasure`, to delegate `remeasure`
13
- * calls down from NewTooltip to TooltipPositioner.
14
- *
15
- * TODO(mdr): Once we upgrade to React 16, replace some of this logic with a
16
- * built-in React portal.
17
- */
18
- import * as React from "react";
19
- import ReactDOM from "react-dom";
20
-
21
- import TooltipPositioner from "./tooltip-positioner.jsx";
22
-
23
- import type {NewTooltipProps} from "./types.js";
24
-
25
- type TooltipPortalProps = {|
26
- tooltipProps: NewTooltipProps,
27
- dismiss: () => void,
28
- rootElement: HTMLElement,
29
- targetElement: HTMLElement,
30
- isAboveModal?: boolean,
31
- |};
32
- export default class TooltipPortal extends React.Component<TooltipPortalProps> {
33
- _mountNode: ?HTMLElement;
34
- _tooltipPositioner: ?TooltipPositioner;
35
-
36
- // We mounted, so we should mount the TooltipPositioner! Create a node for
37
- // it, then mount it there.
38
- componentDidMount() {
39
- const mountNode = document.createElement("div");
40
- this._mountNode = mountNode;
41
- this.props.rootElement.appendChild(mountNode);
42
-
43
- this._renderInMountNode(mountNode);
44
- }
45
-
46
- // We updated, so we should update to the TooltipPositioner! Re-call
47
- // ReactDOM.render, which will trigger an update rather than a mount,
48
- // because the component is already mounted in that location.
49
- componentDidUpdate() {
50
- const mountNode = this._mountNode;
51
- if (!mountNode) {
52
- return;
53
- }
54
-
55
- this._renderInMountNode(mountNode);
56
- }
57
-
58
- // We unmounted, so we should unmount the TooltipPositioner! Ask React to
59
- // unmount it, then destroy the node we created.
60
- componentWillUnmount() {
61
- const mountNode = this._mountNode;
62
- if (!mountNode) {
63
- return;
64
- }
65
-
66
- ReactDOM.unmountComponentAtNode(mountNode);
67
- this.props.rootElement.removeChild(mountNode);
68
- }
69
-
70
- remeasure() {
71
- this._tooltipPositioner && this._tooltipPositioner.remeasure();
72
- }
73
-
74
- _renderInMountNode(mountNode: HTMLElement) {
75
- const {
76
- tooltipProps,
77
- dismiss,
78
- rootElement,
79
- targetElement,
80
- isAboveModal,
81
- } = this.props;
82
-
83
- // TODO(LP-11406): Replace with React Portal
84
- // eslint-disable-next-line no-restricted-syntax
85
- ReactDOM.render(
86
- <TooltipPositioner
87
- tooltipProps={tooltipProps}
88
- dismiss={dismiss}
89
- rootElement={rootElement}
90
- targetElement={targetElement}
91
- isAboveModal={isAboveModal}
92
- ref={(node) => (this._tooltipPositioner = node)}
93
- />,
94
- mountNode,
95
- );
96
- }
97
-
98
- render(): null {
99
- return null;
100
- }
101
- }