@sanity/ui 2.8.0 → 2.8.1
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.
- package/dist/index.d.mts +25 -53
- package/dist/index.d.ts +25 -53
- package/dist/index.esm.js +46 -6
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +45 -5
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +46 -6
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/core/hooks/index.ts +1 -0
- package/src/core/hooks/useClickOutside.test.tsx +0 -109
- package/src/core/hooks/useClickOutside.ts +70 -103
- package/src/core/hooks/useClickOutsideEvent.test.tsx +116 -0
- package/src/core/hooks/useClickOutsideEvent.ts +72 -0
- package/src/core/primitives/popover/__workshop__/AlignedStory.tsx +2 -2
package/package.json
CHANGED
package/src/core/hooks/index.ts
CHANGED
|
@@ -8,115 +8,6 @@ import {useEffect, useRef, useState} from 'react'
|
|
|
8
8
|
import {useClickOutside} from './useClickOutside'
|
|
9
9
|
|
|
10
10
|
describe('useClickOutside', () => {
|
|
11
|
-
describe('current API', () => {
|
|
12
|
-
/**
|
|
13
|
-
* This suite demonstrates the new API for `useClickOutside` that were introduced in v2.8.0
|
|
14
|
-
*/
|
|
15
|
-
|
|
16
|
-
it('calls the handler when clicking outside of the array of elements', async () => {
|
|
17
|
-
const user = userEvent.setup()
|
|
18
|
-
const handler = jest.fn()
|
|
19
|
-
|
|
20
|
-
const TestComponent = () => {
|
|
21
|
-
const buttonRef = useRef<HTMLButtonElement | null>(null)
|
|
22
|
-
const popoverRef = useRef<HTMLDivElement | null>(null)
|
|
23
|
-
|
|
24
|
-
useClickOutside(handler, () => [buttonRef.current, popoverRef.current])
|
|
25
|
-
|
|
26
|
-
return (
|
|
27
|
-
<>
|
|
28
|
-
<button data-testid="button" ref={buttonRef} />
|
|
29
|
-
<div data-testid="popover" ref={popoverRef} />
|
|
30
|
-
<div data-testid="outside" />
|
|
31
|
-
</>
|
|
32
|
-
)
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
render(<TestComponent />)
|
|
36
|
-
|
|
37
|
-
await user.click(screen.getByTestId('button'))
|
|
38
|
-
await user.click(screen.getByTestId('popover'))
|
|
39
|
-
expect(handler).not.toHaveBeenCalled()
|
|
40
|
-
|
|
41
|
-
await user.click(screen.getByTestId('outside'))
|
|
42
|
-
expect(handler).toHaveBeenCalledTimes(1)
|
|
43
|
-
})
|
|
44
|
-
|
|
45
|
-
it('the elements array flattens nested arrays one level deep', async () => {
|
|
46
|
-
const user = userEvent.setup()
|
|
47
|
-
const handler = jest.fn()
|
|
48
|
-
|
|
49
|
-
const TestComponent = () => {
|
|
50
|
-
const buttonRef = useRef<HTMLButtonElement | null>(null)
|
|
51
|
-
const popoverRef = useRef<HTMLDivElement | null>(null)
|
|
52
|
-
|
|
53
|
-
useClickOutside(handler, () => [
|
|
54
|
-
null,
|
|
55
|
-
[null, buttonRef.current],
|
|
56
|
-
[popoverRef.current, null],
|
|
57
|
-
null,
|
|
58
|
-
])
|
|
59
|
-
|
|
60
|
-
return (
|
|
61
|
-
<>
|
|
62
|
-
<button data-testid="button" ref={buttonRef} />
|
|
63
|
-
<div data-testid="popover" ref={popoverRef} />
|
|
64
|
-
<div data-testid="outside" />
|
|
65
|
-
</>
|
|
66
|
-
)
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
render(<TestComponent />)
|
|
70
|
-
|
|
71
|
-
await user.click(screen.getByTestId('button'))
|
|
72
|
-
await user.click(screen.getByTestId('popover'))
|
|
73
|
-
expect(handler).not.toHaveBeenCalled()
|
|
74
|
-
|
|
75
|
-
await user.click(screen.getByTestId('outside'))
|
|
76
|
-
expect(handler).toHaveBeenCalledTimes(1)
|
|
77
|
-
})
|
|
78
|
-
|
|
79
|
-
it('it can set a boundary to scope outside click events', async () => {
|
|
80
|
-
const user = userEvent.setup()
|
|
81
|
-
const handler = jest.fn()
|
|
82
|
-
|
|
83
|
-
const TestComponent = () => {
|
|
84
|
-
const buttonRef = useRef<HTMLButtonElement | null>(null)
|
|
85
|
-
const popoverRef = useRef<HTMLDivElement | null>(null)
|
|
86
|
-
const boundaryRef = useRef<HTMLDivElement | null>(null)
|
|
87
|
-
|
|
88
|
-
useClickOutside(
|
|
89
|
-
handler,
|
|
90
|
-
() => [buttonRef.current, popoverRef.current],
|
|
91
|
-
() => boundaryRef.current,
|
|
92
|
-
)
|
|
93
|
-
|
|
94
|
-
return (
|
|
95
|
-
<>
|
|
96
|
-
<div ref={boundaryRef}>
|
|
97
|
-
<button data-testid="button" ref={buttonRef} />
|
|
98
|
-
<div data-testid="popover" ref={popoverRef} />
|
|
99
|
-
<div data-testid="inside" />
|
|
100
|
-
</div>
|
|
101
|
-
<div data-testid="outside" />
|
|
102
|
-
</>
|
|
103
|
-
)
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
render(<TestComponent />)
|
|
107
|
-
|
|
108
|
-
await user.click(screen.getByTestId('button'))
|
|
109
|
-
await user.click(screen.getByTestId('popover'))
|
|
110
|
-
// Since it's outside the boundary it should be ignored
|
|
111
|
-
await user.click(screen.getByTestId('outside'))
|
|
112
|
-
expect(handler).not.toHaveBeenCalled()
|
|
113
|
-
|
|
114
|
-
await user.click(screen.getByTestId('inside'))
|
|
115
|
-
expect(handler).toHaveBeenCalledTimes(1)
|
|
116
|
-
})
|
|
117
|
-
})
|
|
118
|
-
})
|
|
119
|
-
describe('legacy API', () => {
|
|
120
11
|
/**
|
|
121
12
|
* Specifying the elements array directly:
|
|
122
13
|
* useClickOutside(handler, [buttonElement, popoverElement])
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import {useEffect, useState} from 'react'
|
|
2
|
-
import {useEffectEvent} from 'use-effect-event'
|
|
1
|
+
import {useEffect, useRef, useState} from 'react'
|
|
3
2
|
import {EMPTY_ARRAY} from '../constants'
|
|
4
3
|
|
|
5
4
|
/**
|
|
@@ -12,138 +11,106 @@ export type ClickOutsideListener = (event: MouseEvent) => void
|
|
|
12
11
|
*/
|
|
13
12
|
export type ClickOutsideElements = (HTMLElement | null | (HTMLElement | null)[])[]
|
|
14
13
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
* @deprecated - use your own `const [element, setElement] = useState(null)` logic instead
|
|
18
|
-
*/
|
|
19
|
-
export type UseClickOutsideSetElement = (el: HTMLElement | null) => void
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* @public
|
|
23
|
-
*/
|
|
24
|
-
export function useClickOutside(
|
|
25
|
-
listener: ClickOutsideListener | false | undefined,
|
|
26
|
-
elementsArg: () => ClickOutsideElements,
|
|
27
|
-
boundaryElement?: () => HTMLElement | null,
|
|
28
|
-
): void
|
|
29
|
-
/**
|
|
30
|
-
* @public
|
|
31
|
-
* @deprecated - change `useClickOutside(handler, () => [...], boundary)` to `useClickOutside(handler, () => [...], () => boundary)`
|
|
32
|
-
*/
|
|
33
|
-
export function useClickOutside(
|
|
34
|
-
listener: ClickOutsideListener | false | undefined,
|
|
35
|
-
elementsArg: () => ClickOutsideElements,
|
|
36
|
-
boundaryElement: HTMLElement | null,
|
|
37
|
-
): void
|
|
38
|
-
/**
|
|
39
|
-
* @public
|
|
40
|
-
* @deprecated - change `useClickOutside(handler, [...], () => boundary)` to `useClickOutside(handler, () => [...], () => boundary)`
|
|
41
|
-
*/
|
|
42
|
-
export function useClickOutside(
|
|
43
|
-
listener: ClickOutsideListener | false | undefined,
|
|
14
|
+
function _getElements(
|
|
15
|
+
element: HTMLElement | null,
|
|
44
16
|
elementsArg: ClickOutsideElements,
|
|
45
|
-
|
|
46
|
-
|
|
17
|
+
): HTMLElement[] {
|
|
18
|
+
const ret = [element]
|
|
19
|
+
|
|
20
|
+
for (const el of elementsArg) {
|
|
21
|
+
if (Array.isArray(el)) {
|
|
22
|
+
ret.push(...el)
|
|
23
|
+
} else {
|
|
24
|
+
ret.push(el)
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return ret.filter(Boolean) as HTMLElement[]
|
|
29
|
+
}
|
|
30
|
+
|
|
47
31
|
/**
|
|
48
32
|
* @public
|
|
49
|
-
* @deprecated
|
|
50
|
-
* Instead of:
|
|
33
|
+
* @deprecated replaced by the new `useClickOutsideEvent` hook, instead of:
|
|
51
34
|
* ```tsx
|
|
52
|
-
* const
|
|
53
|
-
*
|
|
54
|
-
* return
|
|
55
|
-
* <>
|
|
56
|
-
* <button ref={buttonRef} />
|
|
57
|
-
* {open && <div ref={setElement} />}
|
|
58
|
-
* </>
|
|
59
|
-
* )
|
|
35
|
+
* const [button, setButtonElement] = useState(null)
|
|
36
|
+
* useClickOutside((event) => {}, [button])
|
|
37
|
+
* return <button ref={setButtonElement} />
|
|
60
38
|
* ```
|
|
61
|
-
*
|
|
39
|
+
* do:
|
|
62
40
|
* ```tsx
|
|
63
41
|
* const buttonRef = useRef()
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
* return (
|
|
67
|
-
* <>
|
|
68
|
-
* <button ref={buttonRef} />
|
|
69
|
-
* {open && <div ref={setElement} />}
|
|
70
|
-
* </>
|
|
71
|
-
* )
|
|
42
|
+
* useClickOutsideEvent((event) => {}, () => [buttonRef.current])
|
|
43
|
+
* return <button ref={buttonRef} />
|
|
72
44
|
* ```
|
|
73
45
|
*/
|
|
74
46
|
export function useClickOutside(
|
|
75
|
-
listener: ClickOutsideListener
|
|
76
|
-
elementsArg: ClickOutsideElements,
|
|
47
|
+
listener: ClickOutsideListener,
|
|
48
|
+
elementsArg: ClickOutsideElements = EMPTY_ARRAY,
|
|
77
49
|
boundaryElement?: HTMLElement | null,
|
|
78
|
-
):
|
|
50
|
+
): (el: HTMLElement | null) => void {
|
|
51
|
+
const [element, setElement] = useState<HTMLElement | null>(null)
|
|
52
|
+
const [elements, setElements] = useState(() => _getElements(element, elementsArg))
|
|
53
|
+
const elementsRef = useRef(elements)
|
|
79
54
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
export function useClickOutside(
|
|
84
|
-
listener: ClickOutsideListener | false | undefined,
|
|
85
|
-
elementsArg: ClickOutsideElements | (() => ClickOutsideElements) = EMPTY_ARRAY,
|
|
86
|
-
boundaryElement?: HTMLElement | null | (() => HTMLElement | null),
|
|
87
|
-
): UseClickOutsideSetElement | void {
|
|
88
|
-
/** @deprecated */
|
|
89
|
-
const [legacyElement, setLegacyElement] = useState<HTMLElement | null>(null)
|
|
90
|
-
|
|
91
|
-
/**
|
|
92
|
-
* The `useEffectEvent` hook allow us to always see the latest value of `listener`, `elementsArg` and `boundaryElement` without needing to
|
|
93
|
-
* juggle `useState`, `useRef` and `useState` to make sure the `mousedown` event listener isn't constantly being added and removed.
|
|
94
|
-
*/
|
|
95
|
-
const onEvent = useEffectEvent((evt: MouseEvent) => {
|
|
96
|
-
if (!listener) {
|
|
97
|
-
return
|
|
98
|
-
}
|
|
55
|
+
useEffect(() => {
|
|
56
|
+
const prevElements = elementsRef.current
|
|
57
|
+
const nextElements = _getElements(element, elementsArg)
|
|
99
58
|
|
|
100
|
-
|
|
59
|
+
if (prevElements.length !== nextElements.length) {
|
|
60
|
+
setElements(nextElements)
|
|
61
|
+
elementsRef.current = nextElements
|
|
101
62
|
|
|
102
|
-
if (!(target instanceof Node)) {
|
|
103
63
|
return
|
|
104
64
|
}
|
|
105
65
|
|
|
106
|
-
const
|
|
107
|
-
|
|
66
|
+
for (const el of prevElements) {
|
|
67
|
+
if (!nextElements.includes(el)) {
|
|
68
|
+
setElements(nextElements)
|
|
69
|
+
elementsRef.current = nextElements
|
|
108
70
|
|
|
109
|
-
|
|
110
|
-
|
|
71
|
+
return
|
|
72
|
+
}
|
|
111
73
|
}
|
|
112
74
|
|
|
113
|
-
const
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
for (const el of elements) {
|
|
119
|
-
if (!el) continue
|
|
75
|
+
for (const el of nextElements) {
|
|
76
|
+
if (!prevElements.includes(el)) {
|
|
77
|
+
setElements(nextElements)
|
|
78
|
+
elementsRef.current = nextElements
|
|
120
79
|
|
|
121
|
-
if (target === el || el.contains(target)) {
|
|
122
80
|
return
|
|
123
81
|
}
|
|
124
82
|
}
|
|
83
|
+
}, [element, elementsArg])
|
|
125
84
|
|
|
126
|
-
|
|
127
|
-
|
|
85
|
+
useEffect(() => {
|
|
86
|
+
if (!listener) return undefined
|
|
128
87
|
|
|
129
|
-
|
|
88
|
+
const handleWindowMouseDown = (evt: MouseEvent) => {
|
|
89
|
+
const target = evt.target
|
|
130
90
|
|
|
131
|
-
|
|
132
|
-
|
|
91
|
+
if (!(target instanceof Node)) {
|
|
92
|
+
return
|
|
93
|
+
}
|
|
133
94
|
|
|
134
|
-
|
|
95
|
+
if (boundaryElement && !boundaryElement.contains(target)) {
|
|
96
|
+
return
|
|
97
|
+
}
|
|
135
98
|
|
|
136
|
-
|
|
99
|
+
for (const el of elements) {
|
|
100
|
+
if (target === el || el.contains(target)) {
|
|
101
|
+
return
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
listener(evt)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
window.addEventListener('mousedown', handleWindowMouseDown)
|
|
137
109
|
|
|
138
110
|
return () => {
|
|
139
|
-
|
|
111
|
+
window.removeEventListener('mousedown', handleWindowMouseDown)
|
|
140
112
|
}
|
|
141
|
-
}, [
|
|
113
|
+
}, [boundaryElement, listener, elements])
|
|
142
114
|
|
|
143
|
-
|
|
144
|
-
* Only return the legacy setElement function if the elementsArg is an array
|
|
145
|
-
*/
|
|
146
|
-
if (Array.isArray(elementsArg)) {
|
|
147
|
-
return setLegacyElement as UseClickOutsideSetElement
|
|
148
|
-
}
|
|
115
|
+
return setElement
|
|
149
116
|
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/** @jest-environment jsdom */
|
|
2
|
+
/* eslint-disable padding-line-between-statements, react/display-name */
|
|
3
|
+
|
|
4
|
+
import {render, screen} from '@testing-library/react'
|
|
5
|
+
import userEvent from '@testing-library/user-event'
|
|
6
|
+
import {useRef} from 'react'
|
|
7
|
+
|
|
8
|
+
import {useClickOutsideEvent} from './useClickOutsideEvent'
|
|
9
|
+
|
|
10
|
+
describe('useClickOutsideEvent', () => {
|
|
11
|
+
/**
|
|
12
|
+
* This suite demonstrates the new hook `useClickOutsideEvent` that replaces `useClickOutside`
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
it('calls the handler when clicking outside of the array of elements', async () => {
|
|
16
|
+
const user = userEvent.setup()
|
|
17
|
+
const handler = jest.fn()
|
|
18
|
+
|
|
19
|
+
const TestComponent = () => {
|
|
20
|
+
const buttonRef = useRef<HTMLButtonElement | null>(null)
|
|
21
|
+
const popoverRef = useRef<HTMLDivElement | null>(null)
|
|
22
|
+
|
|
23
|
+
useClickOutsideEvent(handler, () => [buttonRef.current, popoverRef.current])
|
|
24
|
+
|
|
25
|
+
return (
|
|
26
|
+
<>
|
|
27
|
+
<button data-testid="button" ref={buttonRef} />
|
|
28
|
+
<div data-testid="popover" ref={popoverRef} />
|
|
29
|
+
<div data-testid="outside" />
|
|
30
|
+
</>
|
|
31
|
+
)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
render(<TestComponent />)
|
|
35
|
+
|
|
36
|
+
await user.click(screen.getByTestId('button'))
|
|
37
|
+
await user.click(screen.getByTestId('popover'))
|
|
38
|
+
expect(handler).not.toHaveBeenCalled()
|
|
39
|
+
|
|
40
|
+
await user.click(screen.getByTestId('outside'))
|
|
41
|
+
expect(handler).toHaveBeenCalledTimes(1)
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
it('the elements array flattens nested arrays one level deep', async () => {
|
|
45
|
+
const user = userEvent.setup()
|
|
46
|
+
const handler = jest.fn()
|
|
47
|
+
|
|
48
|
+
const TestComponent = () => {
|
|
49
|
+
const buttonRef = useRef<HTMLButtonElement | null>(null)
|
|
50
|
+
const popoverRef = useRef<HTMLDivElement | null>(null)
|
|
51
|
+
|
|
52
|
+
useClickOutsideEvent(handler, () => [
|
|
53
|
+
null,
|
|
54
|
+
[null, buttonRef.current],
|
|
55
|
+
[popoverRef.current, null],
|
|
56
|
+
null,
|
|
57
|
+
])
|
|
58
|
+
|
|
59
|
+
return (
|
|
60
|
+
<>
|
|
61
|
+
<button data-testid="button" ref={buttonRef} />
|
|
62
|
+
<div data-testid="popover" ref={popoverRef} />
|
|
63
|
+
<div data-testid="outside" />
|
|
64
|
+
</>
|
|
65
|
+
)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
render(<TestComponent />)
|
|
69
|
+
|
|
70
|
+
await user.click(screen.getByTestId('button'))
|
|
71
|
+
await user.click(screen.getByTestId('popover'))
|
|
72
|
+
expect(handler).not.toHaveBeenCalled()
|
|
73
|
+
|
|
74
|
+
await user.click(screen.getByTestId('outside'))
|
|
75
|
+
expect(handler).toHaveBeenCalledTimes(1)
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
it('it can set a boundary to scope outside click events', async () => {
|
|
79
|
+
const user = userEvent.setup()
|
|
80
|
+
const handler = jest.fn()
|
|
81
|
+
|
|
82
|
+
const TestComponent = () => {
|
|
83
|
+
const buttonRef = useRef<HTMLButtonElement | null>(null)
|
|
84
|
+
const popoverRef = useRef<HTMLDivElement | null>(null)
|
|
85
|
+
const boundaryRef = useRef<HTMLDivElement | null>(null)
|
|
86
|
+
|
|
87
|
+
useClickOutsideEvent(
|
|
88
|
+
handler,
|
|
89
|
+
() => [buttonRef.current, popoverRef.current],
|
|
90
|
+
() => boundaryRef.current,
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
return (
|
|
94
|
+
<>
|
|
95
|
+
<div ref={boundaryRef}>
|
|
96
|
+
<button data-testid="button" ref={buttonRef} />
|
|
97
|
+
<div data-testid="popover" ref={popoverRef} />
|
|
98
|
+
<div data-testid="inside" />
|
|
99
|
+
</div>
|
|
100
|
+
<div data-testid="outside" />
|
|
101
|
+
</>
|
|
102
|
+
)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
render(<TestComponent />)
|
|
106
|
+
|
|
107
|
+
await user.click(screen.getByTestId('button'))
|
|
108
|
+
await user.click(screen.getByTestId('popover'))
|
|
109
|
+
// Since it's outside the boundary it should be ignored
|
|
110
|
+
await user.click(screen.getByTestId('outside'))
|
|
111
|
+
expect(handler).not.toHaveBeenCalled()
|
|
112
|
+
|
|
113
|
+
await user.click(screen.getByTestId('inside'))
|
|
114
|
+
expect(handler).toHaveBeenCalledTimes(1)
|
|
115
|
+
})
|
|
116
|
+
})
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import {useEffect} from 'react'
|
|
2
|
+
import {useEffectEvent} from 'use-effect-event'
|
|
3
|
+
import {EMPTY_ARRAY} from '../constants'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @public
|
|
7
|
+
*/
|
|
8
|
+
export type ClickOutsideEventListener = (event: MouseEvent) => void
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* @public
|
|
12
|
+
*/
|
|
13
|
+
export type ClickOutsideEventElements = (HTMLElement | null | (HTMLElement | null)[])[]
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* @public
|
|
17
|
+
*/
|
|
18
|
+
export function useClickOutsideEvent(
|
|
19
|
+
listener: ClickOutsideEventListener | false | undefined,
|
|
20
|
+
elementsArg: () => ClickOutsideEventElements = () => EMPTY_ARRAY,
|
|
21
|
+
boundaryElement?: () => HTMLElement | null,
|
|
22
|
+
): void {
|
|
23
|
+
/**
|
|
24
|
+
* The `useEffectEvent` hook allow us to always see the latest value of `listener`, `elementsArg` and `boundaryElement` without needing to
|
|
25
|
+
* juggle `useState`, `useRef` and `useState` to make sure the `mousedown` event listener isn't constantly being added and removed.
|
|
26
|
+
*/
|
|
27
|
+
const onEvent = useEffectEvent((evt: MouseEvent) => {
|
|
28
|
+
if (!listener) {
|
|
29
|
+
return
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const target = evt.target
|
|
33
|
+
|
|
34
|
+
if (!(target instanceof Node)) {
|
|
35
|
+
return
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const resolvedBoundaryElement =
|
|
39
|
+
typeof boundaryElement === 'function' ? boundaryElement() : boundaryElement
|
|
40
|
+
|
|
41
|
+
if (resolvedBoundaryElement && !resolvedBoundaryElement.contains(target)) {
|
|
42
|
+
return
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const resolvedElements = Array.isArray(elementsArg) ? elementsArg : elementsArg()
|
|
46
|
+
const elements = resolvedElements.flat()
|
|
47
|
+
|
|
48
|
+
for (const el of elements) {
|
|
49
|
+
if (!el) continue
|
|
50
|
+
|
|
51
|
+
if (target === el || el.contains(target)) {
|
|
52
|
+
return
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
listener(evt)
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
const hasListener = Boolean(listener)
|
|
60
|
+
|
|
61
|
+
useEffect(() => {
|
|
62
|
+
if (!hasListener) return undefined
|
|
63
|
+
|
|
64
|
+
const handleEvent = (evt: MouseEvent) => onEvent(evt)
|
|
65
|
+
|
|
66
|
+
document.addEventListener('mousedown', handleEvent)
|
|
67
|
+
|
|
68
|
+
return () => {
|
|
69
|
+
document.removeEventListener('mousedown', handleEvent)
|
|
70
|
+
}
|
|
71
|
+
}, [hasListener, onEvent])
|
|
72
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import {EllipsisVerticalIcon} from '@sanity/icons'
|
|
2
|
-
import {Button, Card, Flex, Popover, Text,
|
|
2
|
+
import {Button, Card, Flex, Popover, Text, useClickOutsideEvent} from '@sanity/ui'
|
|
3
3
|
import {useBoolean, useSelect} from '@sanity/ui-workshop'
|
|
4
4
|
import {useCallback, useRef, useState} from 'react'
|
|
5
5
|
import {
|
|
@@ -40,7 +40,7 @@ export default function AlignedStory() {
|
|
|
40
40
|
|
|
41
41
|
const handleToggleOpen = useCallback(() => setOpen((v) => !v), [])
|
|
42
42
|
|
|
43
|
-
|
|
43
|
+
useClickOutsideEvent(
|
|
44
44
|
() => setOpen(false),
|
|
45
45
|
() => [buttonElementRef.current, popoverElementRef.current],
|
|
46
46
|
)
|