@tamagui/floating 2.0.0-rc.4 → 2.0.0-rc.40

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 (191) hide show
  1. package/dist/cjs/Floating.cjs +7 -5
  2. package/dist/cjs/Floating.native.js +19 -13
  3. package/dist/cjs/Floating.native.js.map +1 -1
  4. package/dist/cjs/index.cjs +46 -13
  5. package/dist/cjs/index.native.js +46 -13
  6. package/dist/cjs/index.native.js.map +1 -1
  7. package/dist/cjs/interactions/PopupTriggerMap.cjs +49 -0
  8. package/dist/cjs/interactions/PopupTriggerMap.native.js +97 -0
  9. package/dist/cjs/interactions/PopupTriggerMap.native.js.map +1 -0
  10. package/dist/cjs/interactions/createFloatingEvents.cjs +50 -0
  11. package/dist/cjs/interactions/createFloatingEvents.native.js +56 -0
  12. package/dist/cjs/interactions/createFloatingEvents.native.js.map +1 -0
  13. package/dist/cjs/interactions/safePolygon.cjs +273 -0
  14. package/dist/cjs/interactions/safePolygon.native.js +284 -0
  15. package/dist/cjs/interactions/safePolygon.native.js.map +1 -0
  16. package/dist/cjs/interactions/types.cjs +18 -0
  17. package/dist/cjs/interactions/types.native.js +21 -0
  18. package/dist/cjs/interactions/types.native.js.map +1 -0
  19. package/dist/cjs/interactions/useClick.cjs +124 -0
  20. package/dist/cjs/interactions/useClick.native.js +132 -0
  21. package/dist/cjs/interactions/useClick.native.js.map +1 -0
  22. package/dist/cjs/interactions/useDelayGroup.cjs +115 -0
  23. package/dist/cjs/interactions/useDelayGroup.native.js +125 -0
  24. package/dist/cjs/interactions/useDelayGroup.native.js.map +1 -0
  25. package/dist/cjs/interactions/useFocus.cjs +130 -0
  26. package/dist/cjs/interactions/useFocus.native.js +139 -0
  27. package/dist/cjs/interactions/useFocus.native.js.map +1 -0
  28. package/dist/cjs/interactions/useHover.cjs +357 -0
  29. package/dist/cjs/interactions/useHover.native.js +373 -0
  30. package/dist/cjs/interactions/useHover.native.js.map +1 -0
  31. package/dist/cjs/interactions/useInnerOffset.cjs +128 -0
  32. package/dist/cjs/interactions/useInnerOffset.native.js +141 -0
  33. package/dist/cjs/interactions/useInnerOffset.native.js.map +1 -0
  34. package/dist/cjs/interactions/useInteractions.cjs +105 -0
  35. package/dist/cjs/interactions/useInteractions.native.js +216 -0
  36. package/dist/cjs/interactions/useInteractions.native.js.map +1 -0
  37. package/dist/cjs/interactions/useListNavigation.cjs +418 -0
  38. package/dist/cjs/interactions/useListNavigation.native.js +433 -0
  39. package/dist/cjs/interactions/useListNavigation.native.js.map +1 -0
  40. package/dist/cjs/interactions/useRole.cjs +122 -0
  41. package/dist/cjs/interactions/useRole.native.js +136 -0
  42. package/dist/cjs/interactions/useRole.native.js.map +1 -0
  43. package/dist/cjs/interactions/useTypeahead.cjs +143 -0
  44. package/dist/cjs/interactions/useTypeahead.native.js +159 -0
  45. package/dist/cjs/interactions/useTypeahead.native.js.map +1 -0
  46. package/dist/cjs/interactions/utils.cjs +208 -0
  47. package/dist/cjs/interactions/utils.native.js +227 -0
  48. package/dist/cjs/interactions/utils.native.js.map +1 -0
  49. package/dist/cjs/middleware/inner.cjs +118 -0
  50. package/dist/cjs/middleware/inner.native.js +130 -0
  51. package/dist/cjs/middleware/inner.native.js.map +1 -0
  52. package/dist/cjs/useFloating.cjs +35 -28
  53. package/dist/cjs/useFloating.native.js +51 -47
  54. package/dist/cjs/useFloating.native.js.map +1 -1
  55. package/dist/esm/Floating.native.js +6 -3
  56. package/dist/esm/Floating.native.js.map +1 -1
  57. package/dist/esm/index.js +17 -34
  58. package/dist/esm/index.js.map +1 -6
  59. package/dist/esm/index.mjs +16 -2
  60. package/dist/esm/index.mjs.map +1 -1
  61. package/dist/esm/index.native.js +16 -2
  62. package/dist/esm/index.native.js.map +1 -1
  63. package/dist/esm/interactions/PopupTriggerMap.mjs +24 -0
  64. package/dist/esm/interactions/PopupTriggerMap.mjs.map +1 -0
  65. package/dist/esm/interactions/PopupTriggerMap.native.js +69 -0
  66. package/dist/esm/interactions/PopupTriggerMap.native.js.map +1 -0
  67. package/dist/esm/interactions/createFloatingEvents.mjs +25 -0
  68. package/dist/esm/interactions/createFloatingEvents.mjs.map +1 -0
  69. package/dist/esm/interactions/createFloatingEvents.native.js +28 -0
  70. package/dist/esm/interactions/createFloatingEvents.native.js.map +1 -0
  71. package/dist/esm/interactions/safePolygon.mjs +248 -0
  72. package/dist/esm/interactions/safePolygon.mjs.map +1 -0
  73. package/dist/esm/interactions/safePolygon.native.js +256 -0
  74. package/dist/esm/interactions/safePolygon.native.js.map +1 -0
  75. package/dist/esm/interactions/types.mjs +2 -0
  76. package/dist/esm/interactions/types.mjs.map +1 -0
  77. package/dist/esm/interactions/types.native.js +2 -0
  78. package/dist/esm/interactions/types.native.js.map +1 -0
  79. package/dist/esm/interactions/useClick.mjs +99 -0
  80. package/dist/esm/interactions/useClick.mjs.map +1 -0
  81. package/dist/esm/interactions/useClick.native.js +104 -0
  82. package/dist/esm/interactions/useClick.native.js.map +1 -0
  83. package/dist/esm/interactions/useDelayGroup.mjs +77 -0
  84. package/dist/esm/interactions/useDelayGroup.mjs.map +1 -0
  85. package/dist/esm/interactions/useDelayGroup.native.js +84 -0
  86. package/dist/esm/interactions/useDelayGroup.native.js.map +1 -0
  87. package/dist/esm/interactions/useFocus.mjs +105 -0
  88. package/dist/esm/interactions/useFocus.mjs.map +1 -0
  89. package/dist/esm/interactions/useFocus.native.js +111 -0
  90. package/dist/esm/interactions/useFocus.native.js.map +1 -0
  91. package/dist/esm/interactions/useHover.mjs +320 -0
  92. package/dist/esm/interactions/useHover.mjs.map +1 -0
  93. package/dist/esm/interactions/useHover.native.js +333 -0
  94. package/dist/esm/interactions/useHover.native.js.map +1 -0
  95. package/dist/esm/interactions/useInnerOffset.mjs +92 -0
  96. package/dist/esm/interactions/useInnerOffset.mjs.map +1 -0
  97. package/dist/esm/interactions/useInnerOffset.native.js +102 -0
  98. package/dist/esm/interactions/useInnerOffset.native.js.map +1 -0
  99. package/dist/esm/interactions/useInteractions.mjs +80 -0
  100. package/dist/esm/interactions/useInteractions.mjs.map +1 -0
  101. package/dist/esm/interactions/useInteractions.native.js +188 -0
  102. package/dist/esm/interactions/useInteractions.native.js.map +1 -0
  103. package/dist/esm/interactions/useListNavigation.mjs +393 -0
  104. package/dist/esm/interactions/useListNavigation.mjs.map +1 -0
  105. package/dist/esm/interactions/useListNavigation.native.js +405 -0
  106. package/dist/esm/interactions/useListNavigation.native.js.map +1 -0
  107. package/dist/esm/interactions/useRole.mjs +86 -0
  108. package/dist/esm/interactions/useRole.mjs.map +1 -0
  109. package/dist/esm/interactions/useRole.native.js +97 -0
  110. package/dist/esm/interactions/useRole.native.js.map +1 -0
  111. package/dist/esm/interactions/useTypeahead.mjs +118 -0
  112. package/dist/esm/interactions/useTypeahead.mjs.map +1 -0
  113. package/dist/esm/interactions/useTypeahead.native.js +131 -0
  114. package/dist/esm/interactions/useTypeahead.native.js.map +1 -0
  115. package/dist/esm/interactions/utils.mjs +162 -0
  116. package/dist/esm/interactions/utils.mjs.map +1 -0
  117. package/dist/esm/interactions/utils.native.js +178 -0
  118. package/dist/esm/interactions/utils.native.js.map +1 -0
  119. package/dist/esm/middleware/inner.mjs +82 -0
  120. package/dist/esm/middleware/inner.mjs.map +1 -0
  121. package/dist/esm/middleware/inner.native.js +91 -0
  122. package/dist/esm/middleware/inner.native.js.map +1 -0
  123. package/dist/esm/useFloating.mjs +8 -3
  124. package/dist/esm/useFloating.mjs.map +1 -1
  125. package/dist/esm/useFloating.native.js +25 -23
  126. package/dist/esm/useFloating.native.js.map +1 -1
  127. package/package.json +8 -10
  128. package/src/Floating.native.tsx +1 -0
  129. package/src/index.ts +49 -0
  130. package/src/interactions/PopupTriggerMap.ts +30 -0
  131. package/src/interactions/createFloatingEvents.ts +34 -0
  132. package/src/interactions/safePolygon.ts +500 -0
  133. package/src/interactions/types.ts +165 -0
  134. package/src/interactions/useClick.ts +148 -0
  135. package/src/interactions/useDelayGroup.ts +114 -0
  136. package/src/interactions/useFocus.ts +164 -0
  137. package/src/interactions/useHover.ts +453 -0
  138. package/src/interactions/useInnerOffset.ts +116 -0
  139. package/src/interactions/useInteractions.ts +101 -0
  140. package/src/interactions/useListNavigation.ts +578 -0
  141. package/src/interactions/useRole.ts +103 -0
  142. package/src/interactions/useTypeahead.ts +173 -0
  143. package/src/interactions/utils.ts +234 -0
  144. package/src/middleware/inner.ts +141 -0
  145. package/src/useFloating.tsx +13 -1
  146. package/types/Floating.native.d.ts +1 -0
  147. package/types/Floating.native.d.ts.map +1 -1
  148. package/types/index.d.ts +17 -2
  149. package/types/index.d.ts.map +1 -1
  150. package/types/interactions/PopupTriggerMap.d.ts +8 -0
  151. package/types/interactions/PopupTriggerMap.d.ts.map +1 -0
  152. package/types/interactions/createFloatingEvents.d.ts +7 -0
  153. package/types/interactions/createFloatingEvents.d.ts.map +1 -0
  154. package/types/interactions/safePolygon.d.ts +4 -0
  155. package/types/interactions/safePolygon.d.ts.map +1 -0
  156. package/types/interactions/types.d.ts +123 -0
  157. package/types/interactions/types.d.ts.map +1 -0
  158. package/types/interactions/useClick.d.ts +3 -0
  159. package/types/interactions/useClick.d.ts.map +1 -0
  160. package/types/interactions/useDelayGroup.d.ts +23 -0
  161. package/types/interactions/useDelayGroup.d.ts.map +1 -0
  162. package/types/interactions/useFocus.d.ts +3 -0
  163. package/types/interactions/useFocus.d.ts.map +1 -0
  164. package/types/interactions/useHover.d.ts +6 -0
  165. package/types/interactions/useHover.d.ts.map +1 -0
  166. package/types/interactions/useInnerOffset.d.ts +3 -0
  167. package/types/interactions/useInnerOffset.d.ts.map +1 -0
  168. package/types/interactions/useInteractions.d.ts +8 -0
  169. package/types/interactions/useInteractions.d.ts.map +1 -0
  170. package/types/interactions/useListNavigation.d.ts +3 -0
  171. package/types/interactions/useListNavigation.d.ts.map +1 -0
  172. package/types/interactions/useRole.d.ts +3 -0
  173. package/types/interactions/useRole.d.ts.map +1 -0
  174. package/types/interactions/useTypeahead.d.ts +3 -0
  175. package/types/interactions/useTypeahead.d.ts.map +1 -0
  176. package/types/interactions/utils.d.ts +46 -0
  177. package/types/interactions/utils.d.ts.map +1 -0
  178. package/types/middleware/inner.d.ts +14 -0
  179. package/types/middleware/inner.d.ts.map +1 -0
  180. package/types/useFloating.d.ts +7 -1
  181. package/types/useFloating.d.ts.map +1 -1
  182. package/dist/cjs/Floating.js +0 -15
  183. package/dist/cjs/Floating.js.map +0 -6
  184. package/dist/cjs/index.js +0 -34
  185. package/dist/cjs/index.js.map +0 -6
  186. package/dist/cjs/useFloating.js +0 -46
  187. package/dist/cjs/useFloating.js.map +0 -6
  188. package/dist/esm/Floating.js +0 -2
  189. package/dist/esm/Floating.js.map +0 -6
  190. package/dist/esm/useFloating.js +0 -23
  191. package/dist/esm/useFloating.js.map +0 -6
@@ -0,0 +1,148 @@
1
+ import { useMemo, useRef } from 'react'
2
+ import { isHTMLElement, isMouseLikePointerType, isTypeableElement } from './utils'
3
+ import type { ElementProps, FloatingInteractionContext, UseClickProps } from './types'
4
+
5
+ function isButtonTarget(event: React.KeyboardEvent<Element>) {
6
+ return isHTMLElement(event.target) && event.target.tagName === 'BUTTON'
7
+ }
8
+
9
+ function isAnchorTarget(event: React.KeyboardEvent<Element>) {
10
+ return isHTMLElement(event.target) && event.target.tagName === 'A'
11
+ }
12
+
13
+ function isSpaceIgnored(element: Element | null) {
14
+ return isTypeableElement(element)
15
+ }
16
+
17
+ // click interaction: toggles open state on click/mousedown
18
+ export function useClick(
19
+ context: FloatingInteractionContext,
20
+ props: UseClickProps = {}
21
+ ): ElementProps {
22
+ const {
23
+ open,
24
+ onOpenChange,
25
+ dataRef,
26
+ elements: { domReference },
27
+ } = context
28
+ const {
29
+ enabled = true,
30
+ event: eventOption = 'click',
31
+ toggle = true,
32
+ ignoreMouse = false,
33
+ keyboardHandlers = true,
34
+ stickIfOpen = true,
35
+ } = props
36
+
37
+ const pointerTypeRef = useRef<'mouse' | 'pen' | 'touch' | undefined>(undefined)
38
+ const didKeyDownRef = useRef(false)
39
+
40
+ const reference: ElementProps['reference'] = useMemo(
41
+ () => ({
42
+ onPointerDown(event: any) {
43
+ pointerTypeRef.current = event.pointerType
44
+ },
45
+ onMouseDown(event: any) {
46
+ const pointerType = pointerTypeRef.current
47
+
48
+ // ignore all buttons except for the "main" button
49
+ if (event.button !== 0) return
50
+ if (eventOption === 'click') return
51
+ if (isMouseLikePointerType(pointerType, true) && ignoreMouse) return
52
+
53
+ if (
54
+ open &&
55
+ toggle &&
56
+ (dataRef.current.openEvent && stickIfOpen
57
+ ? dataRef.current.openEvent.type === 'mousedown'
58
+ : true)
59
+ ) {
60
+ onOpenChange(false, event.nativeEvent || event, 'click')
61
+ } else {
62
+ // prevent stealing focus from the floating element
63
+ event.preventDefault()
64
+ onOpenChange(true, event.nativeEvent || event, 'click')
65
+ }
66
+ },
67
+ onClick(event: any) {
68
+ const pointerType = pointerTypeRef.current
69
+
70
+ if (eventOption === 'mousedown' && pointerTypeRef.current) {
71
+ pointerTypeRef.current = undefined
72
+ return
73
+ }
74
+
75
+ if (isMouseLikePointerType(pointerType, true) && ignoreMouse) return
76
+
77
+ if (
78
+ open &&
79
+ toggle &&
80
+ (dataRef.current.openEvent && stickIfOpen
81
+ ? dataRef.current.openEvent.type === 'click'
82
+ : true)
83
+ ) {
84
+ onOpenChange(false, event.nativeEvent || event, 'click')
85
+ } else {
86
+ onOpenChange(true, event.nativeEvent || event, 'click')
87
+ }
88
+ },
89
+ onKeyDown(event: any) {
90
+ pointerTypeRef.current = undefined
91
+
92
+ if (event.defaultPrevented || !keyboardHandlers || isButtonTarget(event)) {
93
+ return
94
+ }
95
+
96
+ if (event.key === ' ' && !isSpaceIgnored(domReference)) {
97
+ // prevent scrolling
98
+ event.preventDefault()
99
+ didKeyDownRef.current = true
100
+ }
101
+
102
+ if (isAnchorTarget(event)) {
103
+ return
104
+ }
105
+
106
+ if (event.key === 'Enter') {
107
+ if (open && toggle) {
108
+ onOpenChange(false, event.nativeEvent || event, 'click')
109
+ } else {
110
+ onOpenChange(true, event.nativeEvent || event, 'click')
111
+ }
112
+ }
113
+ },
114
+ onKeyUp(event: any) {
115
+ if (
116
+ event.defaultPrevented ||
117
+ !keyboardHandlers ||
118
+ isButtonTarget(event) ||
119
+ isSpaceIgnored(domReference)
120
+ ) {
121
+ return
122
+ }
123
+
124
+ if (event.key === ' ' && didKeyDownRef.current) {
125
+ didKeyDownRef.current = false
126
+ if (open && toggle) {
127
+ onOpenChange(false, event.nativeEvent || event, 'click')
128
+ } else {
129
+ onOpenChange(true, event.nativeEvent || event, 'click')
130
+ }
131
+ }
132
+ },
133
+ }),
134
+ [
135
+ dataRef,
136
+ domReference,
137
+ eventOption,
138
+ ignoreMouse,
139
+ keyboardHandlers,
140
+ onOpenChange,
141
+ open,
142
+ stickIfOpen,
143
+ toggle,
144
+ ]
145
+ )
146
+
147
+ return useMemo(() => (enabled ? { reference } : {}), [enabled, reference])
148
+ }
@@ -0,0 +1,114 @@
1
+ import * as React from 'react'
2
+ import type { Delay, FloatingInteractionContext } from './types'
3
+
4
+ type DelayGroupContextValue = {
5
+ currentId: string | null | undefined
6
+ setCurrentId: (id: string | null | undefined) => void
7
+ delay: Delay
8
+ timeoutMs: number
9
+ initialDelay: Delay
10
+ }
11
+
12
+ const DelayGroupContext = React.createContext<DelayGroupContextValue>({
13
+ currentId: null,
14
+ setCurrentId: () => {},
15
+ delay: 0,
16
+ timeoutMs: 0,
17
+ initialDelay: 0,
18
+ })
19
+
20
+ export function useDelayGroupContext() {
21
+ return React.useContext(DelayGroupContext)
22
+ }
23
+
24
+ // provider for coordinated tooltip delay.
25
+ // when one tooltip is already open (currentId is set), subsequent tooltips
26
+ // in the group open instantly instead of with the configured delay.
27
+ export function FloatingDelayGroup({
28
+ children,
29
+ delay,
30
+ timeoutMs = 0,
31
+ }: {
32
+ children: React.ReactNode
33
+ delay: Delay
34
+ timeoutMs?: number
35
+ }) {
36
+ const [currentId, setCurrentIdRaw] = React.useState<string | null | undefined>(null)
37
+ const timeoutRef = React.useRef<ReturnType<typeof setTimeout>>(undefined)
38
+
39
+ const setCurrentId = React.useCallback(
40
+ (id: string | null | undefined) => {
41
+ clearTimeout(timeoutRef.current)
42
+ if (id == null && timeoutMs > 0) {
43
+ // delay clearing so moving between tooltips stays instant
44
+ timeoutRef.current = setTimeout(() => {
45
+ setCurrentIdRaw(null)
46
+ }, timeoutMs)
47
+ } else {
48
+ setCurrentIdRaw(id)
49
+ }
50
+ },
51
+ [timeoutMs]
52
+ )
53
+
54
+ React.useEffect(() => {
55
+ return () => clearTimeout(timeoutRef.current)
56
+ }, [])
57
+
58
+ const value = React.useMemo(
59
+ () => ({
60
+ currentId,
61
+ setCurrentId,
62
+ delay,
63
+ timeoutMs,
64
+ initialDelay: delay,
65
+ }),
66
+ [currentId, setCurrentId, delay, timeoutMs]
67
+ )
68
+
69
+ return React.createElement(DelayGroupContext.Provider, { value }, children)
70
+ }
71
+
72
+ // registers a tooltip with the delay group.
73
+ // returns coordinated delay: instant open when another tooltip in the group is showing.
74
+ export function useDelayGroup(
75
+ context: FloatingInteractionContext,
76
+ options: { id?: string } = {}
77
+ ): { delay: Delay; currentId: string | null | undefined } {
78
+ const { id } = options
79
+ const groupContext = React.useContext(DelayGroupContext)
80
+
81
+ // when this tooltip closes, start the timeout to clear the group
82
+ React.useEffect(() => {
83
+ if (!context.open && groupContext.currentId === id) {
84
+ groupContext.setCurrentId(null)
85
+ }
86
+ }, [context.open, id])
87
+
88
+ // when another tooltip in the group opens (currentId changed to someone else),
89
+ // close this one so only one tooltip is visible at a time
90
+ React.useEffect(() => {
91
+ if (groupContext.currentId != null && groupContext.currentId !== id && context.open) {
92
+ context.onOpenChange(false)
93
+ }
94
+ }, [groupContext.currentId, id, context.open])
95
+
96
+ // if another tooltip is currently open (currentId is set and not us),
97
+ // use instant delay for opening
98
+ if (groupContext.currentId != null) {
99
+ return {
100
+ delay: { open: 1, close: getClose(groupContext.initialDelay) },
101
+ currentId: groupContext.currentId,
102
+ }
103
+ }
104
+
105
+ return {
106
+ delay: groupContext.initialDelay,
107
+ currentId: groupContext.currentId,
108
+ }
109
+ }
110
+
111
+ function getClose(delay: Delay): number {
112
+ if (typeof delay === 'number') return delay
113
+ return delay?.close ?? 0
114
+ }
@@ -0,0 +1,164 @@
1
+ import { useEffect, useMemo, useRef } from 'react'
2
+
3
+ import type {
4
+ ElementProps,
5
+ FloatingInteractionContext,
6
+ OpenChangeReason,
7
+ UseFocusProps,
8
+ } from './types'
9
+ import {
10
+ activeElement,
11
+ clearTimeoutIfSet,
12
+ contains,
13
+ getDocument,
14
+ getTarget,
15
+ isElement,
16
+ isHTMLElement,
17
+ isMac,
18
+ isSafari,
19
+ isTypeableElement,
20
+ matchesFocusVisible,
21
+ } from './utils'
22
+
23
+ function isMacSafari() {
24
+ return isMac() && isSafari()
25
+ }
26
+
27
+ // focus interaction: opens on focus, closes on blur.
28
+ // ported from floating-ui/react useFocus hook with adaptations for our context.
29
+ export function useFocus(
30
+ context: FloatingInteractionContext,
31
+ props: UseFocusProps = {}
32
+ ): ElementProps {
33
+ const { open, onOpenChange, events, dataRef, elements } = context
34
+ const { enabled = true, visibleOnly = true } = props
35
+
36
+ const blockFocusRef = useRef(false)
37
+ const timeoutRef = useRef(-1)
38
+ const keyboardModalityRef = useRef(true)
39
+
40
+ // if the reference was focused and the user left the tab/window, and the
41
+ // floating element was not open, block focus when they return.
42
+ // also tracks keyboard vs pointer modality on mac safari.
43
+ useEffect(() => {
44
+ if (!enabled) return
45
+
46
+ const win = getDocument(elements.domReference).defaultView || window
47
+
48
+ function onBlur() {
49
+ if (
50
+ !open &&
51
+ isHTMLElement(elements.domReference) &&
52
+ elements.domReference === activeElement(getDocument(elements.domReference))
53
+ ) {
54
+ blockFocusRef.current = true
55
+ }
56
+ }
57
+
58
+ function onKeyDown() {
59
+ keyboardModalityRef.current = true
60
+ }
61
+
62
+ function onPointerDown() {
63
+ keyboardModalityRef.current = false
64
+ }
65
+
66
+ win.addEventListener('blur', onBlur)
67
+
68
+ if (isMacSafari()) {
69
+ win.addEventListener('keydown', onKeyDown, true)
70
+ win.addEventListener('pointerdown', onPointerDown, true)
71
+ }
72
+
73
+ return () => {
74
+ win.removeEventListener('blur', onBlur)
75
+
76
+ if (isMacSafari()) {
77
+ win.removeEventListener('keydown', onKeyDown, true)
78
+ win.removeEventListener('pointerdown', onPointerDown, true)
79
+ }
80
+ }
81
+ }, [elements.domReference, open, enabled])
82
+
83
+ // block focus after reference-press or escape-key close,
84
+ // so that focus returning to the trigger doesn't reopen
85
+ useEffect(() => {
86
+ if (!enabled) return
87
+ if (!events) return
88
+
89
+ function handleOpenChange({ reason }: { reason: OpenChangeReason }) {
90
+ if (reason === 'reference-press' || reason === 'escape-key') {
91
+ blockFocusRef.current = true
92
+ }
93
+ }
94
+
95
+ events.on('openchange', handleOpenChange)
96
+ return () => {
97
+ events.off('openchange', handleOpenChange)
98
+ }
99
+ }, [events, enabled])
100
+
101
+ // cleanup timeout on unmount
102
+ useEffect(() => {
103
+ return () => {
104
+ clearTimeoutIfSet(timeoutRef)
105
+ }
106
+ }, [])
107
+
108
+ const reference: ElementProps['reference'] = useMemo(
109
+ () => ({
110
+ onMouseLeave() {
111
+ blockFocusRef.current = false
112
+ },
113
+ onFocus(event: any) {
114
+ if (blockFocusRef.current) return
115
+
116
+ const target = getTarget(event.nativeEvent)
117
+
118
+ if (visibleOnly && isElement(target)) {
119
+ // safari fails to match :focus-visible if focus was initially
120
+ // outside the document
121
+ if (isMacSafari() && !event.relatedTarget) {
122
+ if (!keyboardModalityRef.current && !isTypeableElement(target)) {
123
+ return
124
+ }
125
+ } else if (!matchesFocusVisible(target)) {
126
+ return
127
+ }
128
+ }
129
+
130
+ onOpenChange(true, event.nativeEvent, 'focus')
131
+ },
132
+ onBlur(event: any) {
133
+ blockFocusRef.current = false
134
+ const relatedTarget = event.relatedTarget as Element | null
135
+ const nativeEvent = event.nativeEvent
136
+
137
+ // wait for the window blur listener to fire
138
+ timeoutRef.current = window.setTimeout(() => {
139
+ const activeEl = activeElement(
140
+ elements.domReference ? elements.domReference.ownerDocument : document
141
+ )
142
+
143
+ // focus left the page, keep it open
144
+ if (!relatedTarget && activeEl === elements.domReference) return
145
+
146
+ // when focusing the reference element then clicking into the floating
147
+ // element, prevent it from hiding. we check activeElement rather than
148
+ // relatedTarget to handle shadow DOM correctly.
149
+ if (
150
+ contains(context.refs.floating.current, activeEl) ||
151
+ contains(elements.domReference, activeEl)
152
+ ) {
153
+ return
154
+ }
155
+
156
+ onOpenChange(false, nativeEvent, 'focus')
157
+ })
158
+ },
159
+ }),
160
+ [context.refs.floating, elements.domReference, onOpenChange, visibleOnly]
161
+ )
162
+
163
+ return useMemo(() => (enabled ? { reference } : {}), [enabled, reference])
164
+ }