@khanacademy/wonder-blocks-popover 3.2.7 → 3.2.9

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.
@@ -16,8 +16,9 @@ import PopoverContentCore from "./popover-content-core";
16
16
  import PopoverContext from "./popover-context";
17
17
  import PopoverAnchor from "./popover-anchor";
18
18
  import PopoverDialog from "./popover-dialog";
19
- import FocusManager from "./focus-manager";
20
19
  import PopoverEventListener from "./popover-event-listener";
20
+ import InitialFocus from "./initial-focus";
21
+ import FocusManager from "./focus-manager";
21
22
 
22
23
  type PopoverContents =
23
24
  | React.ReactElement<React.ComponentProps<typeof PopoverContent>>
@@ -101,6 +102,20 @@ type Props = AriaProps &
101
102
  * Whether to show the popover tail or not. Defaults to true.
102
103
  */
103
104
  showTail: boolean;
105
+ /**
106
+ * Optional property to enable the portal functionality of popover.
107
+ * This is very handy in cases where the Popover can't be easily
108
+ * injected into the DOM structure and requires portaling to
109
+ * the trigger location.
110
+ *
111
+ * Set to "true" by default.
112
+ *
113
+ * CAUTION: Turning off portal could cause some clipping issues
114
+ * especially around legacy code with usage of z-indexing,
115
+ * Use caution when turning this functionality off and ensure
116
+ * your content does not get clipped or hidden.
117
+ */
118
+ portal?: boolean;
104
119
  }>;
105
120
 
106
121
  type State = Readonly<{
@@ -121,6 +136,7 @@ type State = Readonly<{
121
136
  type DefaultProps = Readonly<{
122
137
  placement: Props["placement"];
123
138
  showTail: Props["showTail"];
139
+ portal: Props["portal"];
124
140
  }>;
125
141
 
126
142
  /**
@@ -150,6 +166,7 @@ export default class Popover extends React.Component<Props, State> {
150
166
  static defaultProps: DefaultProps = {
151
167
  placement: "top",
152
168
  showTail: true,
169
+ portal: true,
153
170
  };
154
171
 
155
172
  /**
@@ -255,33 +272,44 @@ export default class Popover extends React.Component<Props, State> {
255
272
  }
256
273
 
257
274
  renderPopper(uniqueId: string): React.ReactNode {
258
- const {initialFocusId, placement, showTail} = this.props;
275
+ const {initialFocusId, placement, showTail, portal} = this.props;
259
276
  const {anchorElement} = this.state;
260
277
 
261
- return (
262
- <FocusManager
263
- anchorElement={anchorElement}
264
- initialFocusId={initialFocusId}
265
- >
266
- <TooltipPopper
278
+ const popperContent = (
279
+ <TooltipPopper anchorElement={anchorElement} placement={placement}>
280
+ {(props: PopperElementProps) => (
281
+ <PopoverDialog
282
+ {...props}
283
+ aria-describedby={`${uniqueId}-content`}
284
+ aria-labelledby={`${uniqueId}-title`}
285
+ id={uniqueId}
286
+ onUpdate={(placement) => this.setState({placement})}
287
+ showTail={showTail}
288
+ >
289
+ {this.renderContent(uniqueId)}
290
+ </PopoverDialog>
291
+ )}
292
+ </TooltipPopper>
293
+ );
294
+
295
+ if (portal) {
296
+ return (
297
+ <FocusManager
267
298
  anchorElement={anchorElement}
268
- placement={placement}
299
+ initialFocusId={initialFocusId}
269
300
  >
270
- {(props: PopperElementProps) => (
271
- <PopoverDialog
272
- {...props}
273
- aria-describedby={`${uniqueId}-content`}
274
- aria-labelledby={`${uniqueId}-title`}
275
- id={uniqueId}
276
- onUpdate={(placement) => this.setState({placement})}
277
- showTail={showTail}
278
- >
279
- {this.renderContent(uniqueId)}
280
- </PopoverDialog>
281
- )}
282
- </TooltipPopper>
283
- </FocusManager>
284
- );
301
+ {popperContent}
302
+ </FocusManager>
303
+ );
304
+ } else {
305
+ return (
306
+ // Ensures the user is focused on the first available element
307
+ // when popover is rendered without the focus manager.
308
+ <InitialFocus initialFocusId={initialFocusId}>
309
+ {popperContent}
310
+ </InitialFocus>
311
+ );
312
+ }
285
313
  }
286
314
 
287
315
  getHost(): Element | null | undefined {
@@ -295,10 +323,29 @@ export default class Popover extends React.Component<Props, State> {
295
323
  );
296
324
  }
297
325
 
326
+ renderPortal(uniqueId: string, opened: boolean) {
327
+ if (!opened) {
328
+ return null;
329
+ }
330
+
331
+ const {portal} = this.props;
332
+ const popperHost = this.getHost();
333
+
334
+ // Attach the popover to a Portal
335
+ if (portal && popperHost) {
336
+ return ReactDOM.createPortal(
337
+ this.renderPopper(uniqueId),
338
+ popperHost,
339
+ );
340
+ }
341
+
342
+ // Otherwise, append the dialog next to the trigger element
343
+ return this.renderPopper(uniqueId);
344
+ }
345
+
298
346
  render(): React.ReactNode {
299
347
  const {children, dismissEnabled, id} = this.props;
300
348
  const {opened, placement} = this.state;
301
- const popperHost = this.getHost();
302
349
 
303
350
  return (
304
351
  <PopoverContext.Provider
@@ -319,12 +366,7 @@ export default class Popover extends React.Component<Props, State> {
319
366
  >
320
367
  {children}
321
368
  </PopoverAnchor>
322
- {popperHost &&
323
- opened &&
324
- ReactDOM.createPortal(
325
- this.renderPopper(uniqueId),
326
- popperHost,
327
- )}
369
+ {this.renderPortal(uniqueId, opened)}
328
370
  </React.Fragment>
329
371
  )}
330
372
  </IDProvider>