Skip to content

Commit f7706eb

Browse files
committed
Remove support for passing a non-Element ref to useElementShouldClose
`useElementShouldClose` allowed passing a ref to a Preact component rather than a ref to a DOM element. Support for this relied on Preact component's having a `base` property, which is going away in future [1] All of the actual usage of `useElementShouldClose` in the app passed an Element ref, so I've simply removed the functionality and associated tests. [1] preactjs/preact#2971
1 parent af7cd28 commit f7706eb

File tree

3 files changed

+54
-124
lines changed

3 files changed

+54
-124
lines changed

src/sidebar/components/TagEditor.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,6 @@ function TagEditor({
4949

5050
// Set up callback to monitor outside click events to close the AutocompleteList
5151
const closeWrapperRef = useRef(/** @type {HTMLElement|null} */ (null));
52-
/** @ts-ignore - TODO: fix useElementShouldClose Ref types */
5352
useElementShouldClose(closeWrapperRef, suggestionsListOpen, () => {
5453
setSuggestionsListOpen(false);
5554
});

src/sidebar/components/hooks/test/use-element-should-close-test.js

Lines changed: 49 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,27 @@ import { mount } from 'enzyme';
22
import { createElement } from 'preact';
33
import { useRef } from 'preact/hooks';
44
import { act } from 'preact/test-utils';
5-
import propTypes from 'prop-types';
65

76
import useElementShouldClose from '../use-element-should-close';
87

9-
describe('hooks.useElementShouldClose', () => {
8+
describe('useElementShouldClose', () => {
109
let handleClose;
11-
let e;
10+
11+
const createEvent = (name, props) => {
12+
const event = new Event(name);
13+
Object.assign(event, props);
14+
return event;
15+
};
16+
1217
const events = [
1318
new Event('mousedown'),
1419
new Event('click'),
15-
((e = new Event('keydown')), (e.key = 'Escape'), e),
20+
createEvent('keydown', { key: 'Escape' }),
1621
new Event('focus'),
1722
];
1823

1924
// Create a fake component to mount in tests that uses the hook
25+
// eslint-disable-next-line react/prop-types
2026
function FakeComponent({ isOpen = true }) {
2127
const myRef = useRef();
2228
useElementShouldClose(myRef, isOpen, handleClose);
@@ -27,103 +33,62 @@ describe('hooks.useElementShouldClose', () => {
2733
);
2834
}
2935

30-
FakeComponent.propTypes = {
31-
isOpen: propTypes.bool,
32-
};
33-
34-
// Tests useElementShouldClose on a custom component directly
35-
function FakeCompoundComponent({ isOpen = true }) {
36-
function FakeCustomComponent() {
37-
return (
38-
<div>
39-
<button>Hi</button>
40-
</div>
41-
);
42-
}
43-
const myRef = useRef();
44-
useElementShouldClose(myRef, isOpen, handleClose);
45-
return <FakeCustomComponent ref={myRef} />;
46-
}
47-
48-
FakeCompoundComponent.propTypes = {
49-
isOpen: propTypes.bool,
50-
};
51-
5236
function createComponent(props) {
5337
return mount(<FakeComponent isOpen={true} {...props} />);
5438
}
5539

56-
function createCompoundComponent(props) {
57-
return mount(<FakeCompoundComponent isOpen={true} {...props} />);
58-
}
59-
6040
beforeEach(() => {
6141
handleClose = sinon.stub();
6242
});
6343

64-
// Run each set of tests twice, once for a regular node and a second
65-
// time for a custom preact component
66-
[
67-
{
68-
createWrapper: createComponent,
69-
description: 'useElementShouldClose attached to a html node',
70-
},
71-
{
72-
createWrapper: createCompoundComponent,
73-
description: 'useElementShouldClose attached to a preact component',
74-
},
75-
].forEach(test => {
76-
context(test.description, () => {
77-
events.forEach(event => {
78-
it(`should invoke close callback once for events outside of element (${event.type})`, () => {
79-
const wrapper = test.createWrapper();
80-
81-
act(() => {
82-
document.body.dispatchEvent(event);
83-
});
84-
wrapper.update();
85-
86-
assert.calledOnce(handleClose);
87-
88-
// Update the component to change it and re-execute the hook
89-
wrapper.setProps({ isOpen: false });
90-
91-
act(() => {
92-
document.body.dispatchEvent(event);
93-
});
94-
95-
// Cleanup of hook should have removed eventListeners, so the callback
96-
// is not called again
97-
assert.calledOnce(handleClose);
98-
});
44+
events.forEach(event => {
45+
it(`should invoke close callback once for events outside of element (${event.type})`, () => {
46+
const wrapper = createComponent();
47+
48+
act(() => {
49+
document.body.dispatchEvent(event);
9950
});
51+
wrapper.update();
10052

101-
events.forEach(event => {
102-
it(`should not invoke close callback on events outside of element if element closed (${event.type})`, () => {
103-
const wrapper = test.createWrapper({ isOpen: false });
53+
assert.calledOnce(handleClose);
10454

105-
act(() => {
106-
document.body.dispatchEvent(event);
107-
});
108-
wrapper.update();
55+
// Update the component to change it and re-execute the hook
56+
wrapper.setProps({ isOpen: false });
10957

110-
assert.equal(handleClose.callCount, 0);
111-
});
58+
act(() => {
59+
document.body.dispatchEvent(event);
11260
});
11361

114-
events.forEach(event => {
115-
it(`should not invoke close callback on events inside of element (${event.type})`, () => {
116-
const wrapper = test.createWrapper();
117-
const button = wrapper.find('button');
62+
// Cleanup of hook should have removed eventListeners, so the callback
63+
// is not called again
64+
assert.calledOnce(handleClose);
65+
});
66+
});
11867

119-
act(() => {
120-
button.getDOMNode().dispatchEvent(event);
121-
});
122-
wrapper.update();
68+
events.forEach(event => {
69+
it(`should not invoke close callback on events outside of element if element closed (${event.type})`, () => {
70+
const wrapper = createComponent({ isOpen: false });
12371

124-
assert.equal(handleClose.callCount, 0);
125-
});
72+
act(() => {
73+
document.body.dispatchEvent(event);
12674
});
75+
wrapper.update();
76+
77+
assert.equal(handleClose.callCount, 0);
78+
});
79+
});
80+
81+
events.forEach(event => {
82+
it(`should not invoke close callback on events inside of element (${event.type})`, () => {
83+
const wrapper = createComponent();
84+
const button = wrapper.find('button');
85+
86+
act(() => {
87+
button.getDOMNode().dispatchEvent(event);
88+
});
89+
wrapper.update();
90+
91+
assert.equal(handleClose.callCount, 0);
12792
});
12893
});
12994
});

src/sidebar/components/hooks/use-element-should-close.js

Lines changed: 5 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,6 @@ import { listen } from '../../util/dom';
88
* @typedef {import("preact/hooks").Ref<T>} Ref
99
*/
1010

11-
/**
12-
* @typedef PreactElement
13-
* @prop {Node} base
14-
*/
15-
16-
/**
17-
* @typedef {Ref<HTMLElement> | Ref<PreactElement>} PreactRef
18-
*/
19-
2011
/**
2112
* This hook adds appropriate `eventListener`s to the document when a target
2213
* element (`closeableEl`) is open. Events such as `click` and `focus` on
@@ -25,13 +16,9 @@ import { listen } from '../../util/dom';
2516
* to indicate that `closeableEl` should be closed. This hook also performs
2617
* cleanup to remove `eventListener`s when appropriate.
2718
*
28-
* Limitation: This will not work when attached to a custom component that has
29-
* more than one element nested under a root <Fragment>
30-
*
31-
* @param {PreactRef} closeableEl - ref object:
32-
* Reference to a DOM element or preat component
33-
* that should be closed when DOM elements external
34-
* to it are interacted with or `Esc` is pressed
19+
* @param {Ref<HTMLElement>} closeableEl -
20+
* Reference to a DOM element that should be closed when DOM elements external
21+
* to it are interacted with or `Esc` is pressed
3522
* @param {boolean} isOpen - Whether the element is currently open. This hook does
3623
* not attach event listeners/do anything if it's not.
3724
* @param {() => void} handleClose - A function that will do the actual closing
@@ -42,25 +29,6 @@ export default function useElementShouldClose(
4229
isOpen,
4330
handleClose
4431
) {
45-
/**
46-
* Helper to return the underlying node object whether
47-
* `closeableEl` is attached to an HTMLNode or Preact component.
48-
*
49-
* @param {PreactRef} closeableEl
50-
* @returns {Node}
51-
*/
52-
53-
const getCurrentNode = closeableEl => {
54-
// if base is present, assume its a preact component
55-
const node = /** @type {PreactElement} */ (closeableEl.current).base
56-
? /** @type {PreactElement} */ (closeableEl.current).base
57-
: closeableEl.current;
58-
if (typeof node !== 'object') {
59-
throw new Error('useElementShouldClose can not find a node reference');
60-
}
61-
return /** @type {Node} */ (node);
62-
};
63-
6432
useEffect(() => {
6533
if (!isOpen) {
6634
return () => {};
@@ -80,8 +48,7 @@ export default function useElementShouldClose(
8048
document.body,
8149
'focus',
8250
event => {
83-
const current = getCurrentNode(closeableEl);
84-
if (!current.contains(/** @type {Node} */ (event.target))) {
51+
if (!closeableEl.current.contains(/** @type {Node} */ (event.target))) {
8552
handleClose();
8653
}
8754
},
@@ -94,8 +61,7 @@ export default function useElementShouldClose(
9461
document.body,
9562
['mousedown', 'click'],
9663
event => {
97-
const current = getCurrentNode(closeableEl);
98-
if (!current.contains(/** @type {Node} */ (event.target))) {
64+
if (!closeableEl.current.contains(/** @type {Node} */ (event.target))) {
9965
handleClose();
10066
}
10167
},

0 commit comments

Comments
 (0)