@instructure/ui-popover 11.6.0 → 11.6.1-snapshot-129

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 (56) hide show
  1. package/CHANGELOG.md +38 -288
  2. package/es/Popover/{index.js → v1/index.js} +3 -3
  3. package/es/Popover/v2/index.js +499 -0
  4. package/es/Popover/v2/props.js +26 -0
  5. package/es/Popover/v2/styles.js +39 -0
  6. package/es/{index.js → exports/a.js} +1 -1
  7. package/{src/index.ts → es/exports/b.js} +1 -2
  8. package/lib/Popover/{index.js → v1/index.js} +5 -6
  9. package/lib/Popover/v2/index.js +514 -0
  10. package/lib/Popover/v2/props.js +31 -0
  11. package/lib/Popover/v2/styles.js +45 -0
  12. package/lib/{index.js → exports/a.js} +2 -2
  13. package/lib/exports/b.js +12 -0
  14. package/package.json +46 -24
  15. package/src/Popover/{index.tsx → v1/index.tsx} +5 -5
  16. package/src/Popover/v2/README.md +455 -0
  17. package/src/Popover/v2/index.tsx +645 -0
  18. package/src/Popover/v2/props.ts +354 -0
  19. package/src/Popover/v2/styles.ts +45 -0
  20. package/src/exports/a.ts +25 -0
  21. package/src/exports/b.ts +25 -0
  22. package/tsconfig.build.tsbuildinfo +1 -1
  23. package/types/Popover/v1/index.d.ts +83 -0
  24. package/types/Popover/v1/index.d.ts.map +1 -0
  25. package/types/Popover/v1/props.d.ts.map +1 -0
  26. package/types/Popover/v1/styles.d.ts.map +1 -0
  27. package/types/Popover/v1/theme.d.ts.map +1 -0
  28. package/types/Popover/v2/index.d.ts.map +1 -0
  29. package/types/Popover/v2/props.d.ts +225 -0
  30. package/types/Popover/v2/props.d.ts.map +1 -0
  31. package/types/Popover/v2/styles.d.ts +13 -0
  32. package/types/Popover/v2/styles.d.ts.map +1 -0
  33. package/types/exports/a.d.ts +3 -0
  34. package/types/exports/a.d.ts.map +1 -0
  35. package/types/exports/b.d.ts +3 -0
  36. package/types/exports/b.d.ts.map +1 -0
  37. package/types/Popover/index.d.ts.map +0 -1
  38. package/types/Popover/props.d.ts.map +0 -1
  39. package/types/Popover/styles.d.ts.map +0 -1
  40. package/types/Popover/theme.d.ts.map +0 -1
  41. package/types/index.d.ts +0 -3
  42. package/types/index.d.ts.map +0 -1
  43. /package/es/Popover/{props.js → v1/props.js} +0 -0
  44. /package/es/Popover/{styles.js → v1/styles.js} +0 -0
  45. /package/es/Popover/{theme.js → v1/theme.js} +0 -0
  46. /package/lib/Popover/{props.js → v1/props.js} +0 -0
  47. /package/lib/Popover/{styles.js → v1/styles.js} +0 -0
  48. /package/lib/Popover/{theme.js → v1/theme.js} +0 -0
  49. /package/src/Popover/{README.md → v1/README.md} +0 -0
  50. /package/src/Popover/{props.ts → v1/props.ts} +0 -0
  51. /package/src/Popover/{styles.ts → v1/styles.ts} +0 -0
  52. /package/src/Popover/{theme.ts → v1/theme.ts} +0 -0
  53. /package/types/Popover/{props.d.ts → v1/props.d.ts} +0 -0
  54. /package/types/Popover/{styles.d.ts → v1/styles.d.ts} +0 -0
  55. /package/types/Popover/{theme.d.ts → v1/theme.d.ts} +0 -0
  56. /package/types/Popover/{index.d.ts → v2/index.d.ts} +0 -0
@@ -0,0 +1,645 @@
1
+ /*
2
+ * The MIT License (MIT)
3
+ *
4
+ * Copyright (c) 2015 - present Instructure, Inc.
5
+ *
6
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ * of this software and associated documentation files (the "Software"), to deal
8
+ * in the Software without restriction, including without limitation the rights
9
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ * copies of the Software, and to permit persons to whom the Software is
11
+ * furnished to do so, subject to the following conditions:
12
+ *
13
+ * The above copyright notice and this permission notice shall be included in all
14
+ * copies or substantial portions of the Software.
15
+ *
16
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ * SOFTWARE.
23
+ */
24
+
25
+ import { Component } from 'react'
26
+ import keycode from 'keycode'
27
+
28
+ import {
29
+ Position,
30
+ parsePlacement,
31
+ mirrorHorizontalPlacement
32
+ } from '@instructure/ui-position'
33
+ import { ContextView, View } from '@instructure/ui-view/latest'
34
+ import { Dialog } from '@instructure/ui-dialog'
35
+ import { textDirectionContextConsumer } from '@instructure/ui-i18n'
36
+ import {
37
+ findDOMNode,
38
+ containsActiveElement,
39
+ requestAnimationFrame,
40
+ handleMouseOverOut
41
+ } from '@instructure/ui-dom-utils'
42
+
43
+ import {
44
+ safeCloneElement,
45
+ callRenderProp,
46
+ withDeterministicId
47
+ } from '@instructure/ui-react-utils'
48
+ import {
49
+ createChainedFunction,
50
+ shallowEqual,
51
+ px,
52
+ combineDataCid
53
+ } from '@instructure/ui-utils'
54
+ import { logError as error } from '@instructure/console'
55
+ import { FocusRegion } from '@instructure/ui-a11y-utils'
56
+
57
+ import type { RequestAnimationFrameType } from '@instructure/ui-dom-utils'
58
+ import type { ViewProps, ContextViewProps } from '@instructure/ui-view/latest'
59
+ import type { PositionProps } from '@instructure/ui-position'
60
+ import type { DialogProps } from '@instructure/ui-dialog'
61
+
62
+ import { withStyle } from '@instructure/emotion'
63
+
64
+ import generateStyle from './styles'
65
+
66
+ import type { PopoverProps, PopoverState } from './props'
67
+ import { allowedProps } from './props'
68
+ import type { Renderable } from '@instructure/shared-types'
69
+ /**
70
+ ---
71
+ category: components
72
+ tags: overlay, portal, dialog
73
+ ---
74
+ **/
75
+ @withDeterministicId()
76
+ @textDirectionContextConsumer()
77
+ @withStyle(generateStyle)
78
+ class Popover extends Component<PopoverProps, PopoverState> {
79
+ static readonly componentId = 'Popover'
80
+
81
+ static allowedProps = allowedProps
82
+
83
+ static defaultProps = {
84
+ defaultIsShowingContent: false,
85
+ placement: 'bottom center',
86
+ stacking: 'topmost',
87
+ shadow: 'above',
88
+ offsetX: 0,
89
+ offsetY: 0,
90
+ color: 'primary',
91
+ on: ['hover', 'focus'],
92
+ withArrow: true,
93
+ constrain: 'window',
94
+ insertAt: 'bottom',
95
+ shouldAlignArrow: false,
96
+ shouldTrackPosition: true,
97
+ shouldRenderOffscreen: false,
98
+ shouldContainFocus: false,
99
+ shouldReturnFocus: true,
100
+ shouldCloseOnDocumentClick: true,
101
+ shouldFocusContentOnTriggerBlur: false,
102
+ shouldCloseOnEscape: true,
103
+ shouldSetAriaExpanded: true
104
+ }
105
+
106
+ constructor(props: PopoverProps) {
107
+ super(props)
108
+ this._renderTriggerProp = this.props.renderTrigger
109
+ this._renderTrigger = callRenderProp(this.props.renderTrigger)
110
+ this.state = {
111
+ placement: props.placement,
112
+ offsetX: props.offsetX,
113
+ offsetY: props.offsetY,
114
+ isShowingContent:
115
+ typeof props.isShowingContent === 'undefined'
116
+ ? props.defaultIsShowingContent
117
+ : undefined
118
+ }
119
+
120
+ this._id = this.props.id || props.deterministicId!()
121
+ this._raf = []
122
+
123
+ this._handleMouseOver = handleMouseOverOut.bind(
124
+ null,
125
+ (event: React.MouseEvent) => {
126
+ this.show(event)
127
+ clearTimeout(this.mouseOutTimeout!)
128
+ }
129
+ )
130
+ this._handleMouseOut = handleMouseOverOut.bind(
131
+ null,
132
+ (event: React.MouseEvent) => {
133
+ // this is needed bc the trigger mouseOut fires before tooltip mouseOver
134
+ this.mouseOutTimeout = setTimeout(() => {
135
+ this.hide(event)
136
+ }, 1)
137
+ }
138
+ )
139
+ }
140
+
141
+ private _handleMouseOver: React.MouseEventHandler
142
+ private _handleMouseOut: React.MouseEventHandler
143
+
144
+ private _id: string
145
+ private _raf: RequestAnimationFrameType[] = []
146
+ private _trigger: React.ReactInstance | null = null
147
+ private _view: View | ContextView | null = null
148
+ private _dialog: Dialog | null = null
149
+ _contentElement: Element | null = null
150
+ private _focusRegion?: FocusRegion
151
+ // renderTrigger needs to be a variable because if it's a function it will
152
+ // recreate the trigger on each render which will trigger MouseOver events
153
+ // that will make the tooltip reappear and the trigger cannot accept
154
+ // onClick events (since the state change caused by MouseDown recreates it)
155
+ private _renderTrigger?: React.ReactElement
156
+ private _renderTriggerProp?: Renderable
157
+ private mouseOutTimeout?: ReturnType<typeof setTimeout>
158
+
159
+ ref: Element | null = null
160
+
161
+ handleRef = (el: Element | null) => {
162
+ const { elementRef } = this.props
163
+
164
+ this.ref = el
165
+
166
+ if (typeof elementRef === 'function') {
167
+ elementRef(el)
168
+ }
169
+ }
170
+
171
+ get isTooltip() {
172
+ return (
173
+ this.props.shouldRenderOffscreen &&
174
+ !this.props.shouldReturnFocus &&
175
+ !this.props.shouldContainFocus &&
176
+ !this.props.shouldFocusContentOnTriggerBlur
177
+ )
178
+ }
179
+
180
+ componentDidMount() {
181
+ if (this.isTooltip) {
182
+ // if popover is being used as a tooltip with no focusable content
183
+ // manage its FocusRegion internally rather than registering it with
184
+ // the FocusRegionManager via Dialog
185
+ this._focusRegion = new FocusRegion(this._contentElement, {
186
+ shouldCloseOnEscape: this.props.shouldCloseOnEscape,
187
+ shouldCloseOnDocumentClick: false,
188
+ onDismiss: this.hide,
189
+ isTooltip: true
190
+ })
191
+
192
+ if (this.shown) {
193
+ this._focusRegion.activate()
194
+ }
195
+ }
196
+ }
197
+
198
+ componentWillUnmount() {
199
+ this._raf.forEach((request) => request.cancel())
200
+ this._raf = []
201
+
202
+ if (this._focusRegion) {
203
+ this._focusRegion.deactivate()
204
+ this._focusRegion.blur()
205
+ }
206
+ }
207
+
208
+ shouldComponentUpdate(nextProps: PopoverProps, nextState: PopoverState) {
209
+ return (
210
+ !shallowEqual(this.props, nextProps) ||
211
+ !shallowEqual(this.state, nextState)
212
+ )
213
+ }
214
+
215
+ componentDidUpdate(prevProps: PopoverProps, prevState: PopoverState) {
216
+ if (this._focusRegion && this.isTooltip) {
217
+ // if focus region exists, popover is acting as a tooltip
218
+ // so we manually activate and deactivate the region when showing/hiding
219
+ if (
220
+ (!prevProps.isShowingContent && this.props.isShowingContent) ||
221
+ (!prevState.isShowingContent && this.state.isShowingContent)
222
+ ) {
223
+ // changed from hiding to showing
224
+ this._focusRegion.activate()
225
+ this._focusRegion.focus()
226
+ }
227
+
228
+ if (
229
+ (prevProps.isShowingContent && !this.props.isShowingContent) ||
230
+ (prevState.isShowingContent && !this.state.isShowingContent)
231
+ ) {
232
+ // changed from showing to hiding
233
+ this._focusRegion.deactivate()
234
+ }
235
+ }
236
+
237
+ // since `offsetX`, `offsetY` and `placement` are saved into the state
238
+ // in the constructor and used from the state later,
239
+ // we need to update the state if these props change
240
+ if (
241
+ this.props.offsetX !== prevProps.offsetX ||
242
+ this.props.offsetY !== prevProps.offsetY ||
243
+ this.props.placement !== prevProps.placement ||
244
+ this.props.shouldAlignArrow !== prevProps.shouldAlignArrow ||
245
+ this.props.withArrow !== prevProps.withArrow
246
+ ) {
247
+ this.setState({
248
+ ...this.computeOffsets(this.placement)
249
+ })
250
+ }
251
+ }
252
+
253
+ /**
254
+ * Offsets the popover by the arrow size
255
+ */
256
+ computeOffsets(placement: PopoverProps['placement']) {
257
+ let { offsetX, offsetY } = this.props
258
+
259
+ if (this.props.shouldAlignArrow && this._view) {
260
+ const secondaryPlacement = parsePlacement(placement!)[1]
261
+
262
+ // arrowSize and arrowBorderWidth are component theme variables
263
+ // declared in ContextView's styles.js
264
+ const { arrowSize = 0, arrowBorderWidth = 0 } = (
265
+ this._view as ContextView
266
+ ).props.styles!
267
+
268
+ const offsetAmount = (px(arrowSize) + px(arrowBorderWidth)) * 2
269
+
270
+ if (secondaryPlacement === 'start') {
271
+ offsetX = offsetAmount
272
+ } else if (secondaryPlacement === 'end') {
273
+ offsetX = -offsetAmount
274
+ } else if (secondaryPlacement === 'top') {
275
+ offsetY = offsetAmount
276
+ } else if (secondaryPlacement === 'bottom') {
277
+ offsetY = -offsetAmount
278
+ }
279
+ }
280
+
281
+ return { offsetX, offsetY }
282
+ }
283
+
284
+ get placement(): PopoverProps['placement'] {
285
+ let { placement } = this.props
286
+ const { dir } = this.props
287
+ const isRtl = dir === textDirectionContextConsumer.DIRECTION.rtl
288
+ if (isRtl) {
289
+ placement = mirrorHorizontalPlacement(placement!, ' ')
290
+ }
291
+
292
+ return !this.shown && this.props.shouldRenderOffscreen
293
+ ? 'offscreen'
294
+ : placement
295
+ }
296
+
297
+ get positionProps(): Partial<PositionProps> {
298
+ return {
299
+ offsetX: this.state.offsetX,
300
+ offsetY: this.state.offsetY,
301
+ shouldTrackPosition: this.props.shouldTrackPosition && this.shown,
302
+ insertAt: this.props.insertAt,
303
+ placement: this.placement,
304
+ constrain: this.props.constrain,
305
+ onPositioned: this.handlePositioned,
306
+ onPositionChanged: this.handlePositionChanged,
307
+ target: this.props.positionTarget,
308
+ containerDisplay: this.props.positionContainerDisplay,
309
+ mountNode: this.props.mountNode,
310
+ id: this._id
311
+ }
312
+ }
313
+
314
+ get shown() {
315
+ return typeof this.props.isShowingContent === 'undefined'
316
+ ? this.state.isShowingContent
317
+ : this.props.isShowingContent
318
+ }
319
+
320
+ get defaultFocusElement() {
321
+ return this.props.defaultFocusElement
322
+ }
323
+
324
+ show = (event: React.UIEvent | React.FocusEvent) => {
325
+ if (typeof this.props.isShowingContent === 'undefined') {
326
+ this.setState({ isShowingContent: true })
327
+ }
328
+ this.props.onShowContent?.(event)
329
+ }
330
+
331
+ hide = (event: React.UIEvent | React.FocusEvent, documentClick = false) => {
332
+ const { onHideContent, isShowingContent } = this.props
333
+
334
+ if (typeof isShowingContent === 'undefined') {
335
+ // uncontrolled, set state, fire callbacks
336
+ this.setState(({ isShowingContent }) => {
337
+ if (isShowingContent) {
338
+ onHideContent?.(event, { documentClick })
339
+ }
340
+ return { isShowingContent: false }
341
+ })
342
+ } else if (isShowingContent) {
343
+ // controlled, fire callback
344
+ onHideContent?.(event, { documentClick })
345
+ }
346
+ }
347
+
348
+ toggle = (event: React.MouseEvent) => {
349
+ if (this.shown) {
350
+ this.hide(event)
351
+ } else {
352
+ this.show(event)
353
+ }
354
+ }
355
+
356
+ handleDialogDismiss: DialogProps['onDismiss'] = (event, documentClick) => {
357
+ if (
358
+ !this.props.shouldReturnFocus &&
359
+ this.props.shouldFocusContentOnTriggerBlur
360
+ ) {
361
+ const trigger = findDOMNode(this._trigger)
362
+
363
+ if (trigger && typeof (trigger as HTMLElement).focus === 'function') {
364
+ ;(trigger as HTMLElement).focus()
365
+ }
366
+ }
367
+ this.hide(event, documentClick)
368
+ }
369
+
370
+ handleDialogBlur = (event: React.UIEvent | React.FocusEvent) => {
371
+ if (
372
+ (event as React.KeyboardEvent).keyCode === keycode.codes.tab &&
373
+ (event as React.KeyboardEvent).shiftKey &&
374
+ this.props.shouldFocusContentOnTriggerBlur
375
+ ) {
376
+ return
377
+ }
378
+ this.hide(event)
379
+ }
380
+
381
+ handleTriggerKeyDown = (event: React.KeyboardEvent) => {
382
+ if (!this.props.shouldFocusContentOnTriggerBlur) {
383
+ return
384
+ }
385
+
386
+ if (event.keyCode === keycode.codes.tab && !event.shiftKey) {
387
+ event.preventDefault()
388
+ this._raf.push(
389
+ requestAnimationFrame(() => {
390
+ this._dialog && this._dialog.focus()
391
+ })
392
+ )
393
+ }
394
+ }
395
+
396
+ handleTriggerKeyUp = (event: React.KeyboardEvent) => {
397
+ if (event.keyCode === keycode.codes.esc && this.shown && this.isTooltip) {
398
+ // if popover is tooltip, it is managing its own focus region so we need
399
+ // to prevent esc keyup event from reaching FocusRegionManager
400
+ event.preventDefault()
401
+ this.hide(event)
402
+ }
403
+ }
404
+
405
+ handleTriggerBlur = (event: React.FocusEvent) => {
406
+ const { on } = this.props
407
+
408
+ if (on && on.indexOf('focus') > -1) {
409
+ this._raf.push(
410
+ requestAnimationFrame(() => {
411
+ if (!containsActiveElement(this._view)) {
412
+ this.hide(event)
413
+ }
414
+ })
415
+ )
416
+ }
417
+ }
418
+
419
+ handlePositioned: PositionProps['onPositioned'] = (position) => {
420
+ const placement = position.placement
421
+ this.setState({
422
+ placement,
423
+ ...this.computeOffsets(placement)
424
+ })
425
+ this.props.onPositioned?.(position)
426
+ }
427
+
428
+ handlePositionChanged: PositionProps['onPositionChanged'] = (position) => {
429
+ const placement = position.placement
430
+ this.setState({
431
+ placement,
432
+ ...this.computeOffsets(placement)
433
+ })
434
+ this.props.onPositionChanged?.(position)
435
+ }
436
+
437
+ renderTrigger() {
438
+ if (this._renderTriggerProp != this.props.renderTrigger) {
439
+ this._renderTriggerProp = this.props.renderTrigger
440
+ this._renderTrigger = callRenderProp(this.props.renderTrigger)
441
+ }
442
+ let trigger = this._renderTrigger
443
+
444
+ if (trigger) {
445
+ const { on } = this.props
446
+
447
+ let onClick: React.MouseEventHandler | undefined = undefined
448
+ let onFocus: React.FocusEventHandler | undefined = undefined
449
+ let onMouseOut: React.MouseEventHandler | undefined = undefined
450
+ let onMouseOver: React.MouseEventHandler | undefined = undefined
451
+ let expanded: string | undefined
452
+
453
+ if (on && on.indexOf('click') > -1) {
454
+ onClick = (event: React.MouseEvent) => {
455
+ this.toggle(event)
456
+ }
457
+ }
458
+
459
+ if (on && on.indexOf('hover') > -1) {
460
+ error(
461
+ !(on === 'hover'),
462
+ '[Popover] Specifying only the `"hover"` trigger limits the visibility' +
463
+ ' of the Popover to just mouse users. Consider also including the `"focus"` trigger ' +
464
+ 'so that touch and keyboard only users can see the Popover content as well.'
465
+ )
466
+ onMouseOver = this._handleMouseOver
467
+ onMouseOut = this._handleMouseOut
468
+ }
469
+
470
+ if (on && on.indexOf('focus') > -1) {
471
+ onFocus = (event: React.FocusEvent) => {
472
+ this.show(event)
473
+ }
474
+ }
475
+
476
+ if (this.props.shouldSetAriaExpanded) {
477
+ // only set aria-expanded if popover can contain focus
478
+ expanded = this.shown ? 'true' : 'false'
479
+
480
+ if ('aria-expanded' in this.props) {
481
+ // @ts-expect-error It is an escape hatch, in case someone
482
+ // wants to remove/override aria-expanded even when shouldSetAriaExpanded is set
483
+ expanded = this.props['aria-expanded']
484
+ }
485
+ } else {
486
+ expanded = undefined
487
+ }
488
+
489
+ trigger = safeCloneElement(trigger, {
490
+ ref: (el: React.ReactInstance | null) => {
491
+ this._trigger = el
492
+ },
493
+ 'aria-expanded': expanded,
494
+ 'data-popover-trigger': true,
495
+ onKeyDown: createChainedFunction(
496
+ this.handleTriggerKeyDown,
497
+ this.props.onKeyDown
498
+ ),
499
+ onKeyUp: createChainedFunction(
500
+ this.handleTriggerKeyUp,
501
+ this.props.onKeyUp
502
+ ),
503
+ onClick: createChainedFunction(onClick, this.props.onClick),
504
+ onBlur: createChainedFunction(
505
+ this.handleTriggerBlur,
506
+ this.props.onBlur
507
+ ),
508
+ onFocus: createChainedFunction(onFocus, this.props.onFocus),
509
+ onMouseOut: createChainedFunction(onMouseOut, this.props.onMouseOut),
510
+ onMouseOver: createChainedFunction(onMouseOver, this.props.onMouseOver)
511
+ })
512
+ }
513
+
514
+ return trigger
515
+ }
516
+
517
+ renderContent() {
518
+ let content = callRenderProp(this.props.children)
519
+
520
+ if (this.shown && !this.isTooltip) {
521
+ // if popover is NOT being used as a tooltip, create a Dialog
522
+ // to manage the content FocusRegion, when showing
523
+ content = (
524
+ <Dialog
525
+ open={this.shown}
526
+ label={this.props.screenReaderLabel}
527
+ ref={(el) => {
528
+ this._dialog = el
529
+ }}
530
+ display="block"
531
+ onBlur={this.handleDialogBlur}
532
+ onDismiss={this.handleDialogDismiss}
533
+ liveRegion={this.props.liveRegion}
534
+ defaultFocusElement={this.props.defaultFocusElement}
535
+ shouldContainFocus={this.props.shouldContainFocus}
536
+ shouldReturnFocus={this.props.shouldReturnFocus}
537
+ shouldFocusOnOpen={!this.props.shouldFocusContentOnTriggerBlur}
538
+ shouldCloseOnDocumentClick={this.props.shouldCloseOnDocumentClick}
539
+ shouldCloseOnEscape={this.props.shouldCloseOnEscape}
540
+ >
541
+ {content}
542
+ </Dialog>
543
+ )
544
+ }
545
+
546
+ if (this.shown || this.props.shouldRenderOffscreen) {
547
+ const color = this.props.color
548
+
549
+ let viewProps: (Partial<ViewProps> | Partial<ContextViewProps>) & {
550
+ ref: any
551
+ } = {
552
+ // TODO: try to type `ref` better, LegacyRef<T> was not compatible
553
+ ref: (c: View | ContextView | null) => (this._view = c),
554
+ elementRef: (el: Element | null) => {
555
+ this._contentElement = el
556
+ this.props.contentRef?.(el)
557
+ },
558
+ background: color,
559
+ stacking: this.props.stacking,
560
+ shadow: this.props.shadow,
561
+ display: 'block'
562
+ }
563
+
564
+ if (this.isTooltip) {
565
+ viewProps = {
566
+ ...viewProps,
567
+ // Because of a11y reasons popovers should not be hidden when hovered over
568
+ onMouseOver: this._handleMouseOver as any,
569
+ onMouseOut: this._handleMouseOut as any
570
+ }
571
+ }
572
+
573
+ const { placement } = this.state
574
+ const { styles } = this.props
575
+
576
+ if (this.props.withArrow) {
577
+ viewProps = {
578
+ ...viewProps,
579
+ // TODO: remove background override after contextview is updated
580
+ background: color === 'primary' ? 'default' : 'inverse',
581
+ placement:
582
+ this.props.dir === textDirectionContextConsumer.DIRECTION.rtl
583
+ ? mirrorHorizontalPlacement(placement!, ' ')
584
+ : placement
585
+ } as Partial<ContextViewProps> & { ref: any }
586
+
587
+ return (
588
+ <ContextView {...viewProps} borderColor={styles?.borderColor}>
589
+ {content}
590
+ </ContextView>
591
+ )
592
+ } else {
593
+ viewProps = {
594
+ ...viewProps,
595
+ borderWidth: this.props.borderWidth || 'small',
596
+ borderRadius: 'medium',
597
+ ...(color === 'primary-inverse' && { borderColor: 'transparent' })
598
+ } as Partial<ViewProps> & { ref: any }
599
+ return (
600
+ <View
601
+ {...viewProps}
602
+ borderColor={styles?.borderColor}
603
+ borderRadius={styles?.borderRadius}
604
+ >
605
+ {content}
606
+ </View>
607
+ )
608
+ }
609
+ } else {
610
+ return null
611
+ }
612
+ }
613
+
614
+ render() {
615
+ const positionProps = this.positionProps
616
+
617
+ if (this.props.positionTarget) {
618
+ return (
619
+ <span ref={this.handleRef}>
620
+ {this.renderTrigger()}
621
+ <Position
622
+ {...positionProps}
623
+ data-cid={combineDataCid('Popover', this.props)}
624
+ >
625
+ {this.renderContent()}
626
+ </Position>
627
+ </span>
628
+ )
629
+ } else {
630
+ return (
631
+ <Position
632
+ data-cid={combineDataCid('Popover', this.props)}
633
+ {...positionProps}
634
+ renderTarget={this.renderTrigger()}
635
+ elementRef={this.handleRef}
636
+ >
637
+ {this.renderContent()}
638
+ </Position>
639
+ )
640
+ }
641
+ }
642
+ }
643
+
644
+ export default Popover
645
+ export { Popover }