Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
C
coopwire-hypothesis
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
孙灵跃 Leon Sun
coopwire-hypothesis
Commits
6b4daba5
Commit
6b4daba5
authored
Nov 07, 2019
by
Lyza Danger Gardner
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Prototype a hook to handle “close” interactions on document
parent
080f0161
Changes
3
Show whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
175 additions
and
50 deletions
+175
-50
use-element-should-close-test.js
...ar/components/hooks/test/use-element-should-close-test.js
+95
-0
use-element-should-close.js
src/sidebar/components/hooks/use-element-should-close.js
+75
-0
menu.js
src/sidebar/components/menu.js
+5
-50
No files found.
src/sidebar/components/hooks/test/use-element-should-close-test.js
0 → 100644
View file @
6b4daba5
'use strict'
;
const
{
createElement
}
=
require
(
'preact'
);
const
{
useRef
}
=
require
(
'preact/hooks'
);
const
propTypes
=
require
(
'prop-types'
);
const
{
act
}
=
require
(
'preact/test-utils'
);
const
{
mount
}
=
require
(
'enzyme'
);
const
useElementShouldClose
=
require
(
'../use-element-should-close'
);
describe
(
'hooks.useElementShouldClose'
,
()
=>
{
let
handleClose
;
let
e
;
const
events
=
[
new
Event
(
'mousedown'
),
new
Event
(
'click'
),
((
e
=
new
Event
(
'keypress'
)),
(
e
.
key
=
'Escape'
),
e
),
new
Event
(
'focus'
),
];
// Create a fake component to mount in tests that uses the hook
function
FakeComponent
({
isOpen
=
true
})
{
const
myRef
=
useRef
();
useElementShouldClose
(
myRef
,
isOpen
,
handleClose
);
return
(
<
div
ref
=
{
myRef
}
>
<
button
>
Hi
<
/button
>
<
/div
>
);
}
FakeComponent
.
propTypes
=
{
isOpen
:
propTypes
.
bool
,
};
function
createComponent
(
props
)
{
return
mount
(
<
FakeComponent
isOpen
=
{
true
}
{...
props
}
/>
)
;
}
beforeEach
(()
=>
{
handleClose
=
sinon
.
stub
();
});
events
.
forEach
(
event
=>
{
it
(
`should invoke close callback once for events outside of element (
${
event
.
type
}
)`
,
()
=>
{
const
wrapper
=
createComponent
();
act
(()
=>
{
document
.
body
.
dispatchEvent
(
event
);
});
wrapper
.
update
();
assert
.
calledOnce
(
handleClose
);
// Update the component to change it and re-execute the hook
wrapper
.
setProps
({
isOpen
:
false
});
act
(()
=>
{
document
.
body
.
dispatchEvent
(
event
);
});
// Cleanup of hook should have removed eventListeners, so the callback
// is not called again
assert
.
calledOnce
(
handleClose
);
});
});
events
.
forEach
(
event
=>
{
it
(
`should not invoke close callback on events outside of element if element closed (
${
event
.
type
}
)`
,
()
=>
{
const
wrapper
=
createComponent
({
isOpen
:
false
});
act
(()
=>
{
document
.
body
.
dispatchEvent
(
event
);
});
wrapper
.
update
();
assert
.
equal
(
handleClose
.
callCount
,
0
);
});
});
events
.
forEach
(
event
=>
{
it
(
`should not invoke close callback on events inside of element (
${
event
.
type
}
)`
,
()
=>
{
const
wrapper
=
createComponent
();
const
button
=
wrapper
.
find
(
'button'
);
act
(()
=>
{
button
.
getDOMNode
().
dispatchEvent
(
event
);
});
wrapper
.
update
();
assert
.
equal
(
handleClose
.
callCount
,
0
);
});
});
});
src/sidebar/components/hooks/use-element-should-close.js
0 → 100644
View file @
6b4daba5
'use strict'
;
const
{
useEffect
}
=
require
(
'preact/hooks'
);
const
{
listen
}
=
require
(
'../../util/dom'
);
/**
* This hook adds appropriate `eventListener`s to the document when a target
* element (`closeableEl`) is open. Events such as `click` and `focus` on
* elements that fall outside of `closeableEl` in the document, or keypress
* events for the `esc` key, will invoke the provided `handleClose` function
* to indicate that `closeableEl` should be closed. This hook also performs
* cleanup to remove `eventListener`s when appropriate.
*
* @param {Object} closeableEl - Preact ref object:
* Reference to a DOM element that should be
* closed when DOM elements external to it are
* interacted with or `Esc` is pressed
* @param {bool} isOpen - Whether the element is currently open. This hook does
* not attach event listeners/do anything if it's not.
* @param {() => void} handleClose - A function that will do the actual closing
* of `closeableEl`
*/
function
useElementShouldClose
(
closeableEl
,
isOpen
,
handleClose
)
{
useEffect
(()
=>
{
if
(
!
isOpen
)
{
return
()
=>
{};
}
// Close element when user presses Escape key, regardless of focus.
const
removeKeypressListener
=
listen
(
document
.
body
,
[
'keypress'
],
event
=>
{
if
(
event
.
key
===
'Escape'
)
{
handleClose
();
}
}
);
// Close element if user focuses an element outside of it via any means
// (key press, programmatic focus change).
const
removeFocusListener
=
listen
(
document
.
body
,
'focus'
,
event
=>
{
if
(
!
closeableEl
.
current
.
contains
(
event
.
target
))
{
handleClose
();
}
},
{
useCapture
:
true
}
);
// Close element if user clicks outside of it, even if on an element which
// does not accept focus.
const
removeClickListener
=
listen
(
document
.
body
,
[
'mousedown'
,
'click'
],
event
=>
{
if
(
!
closeableEl
.
current
.
contains
(
event
.
target
))
{
handleClose
();
}
},
{
useCapture
:
true
}
);
return
()
=>
{
removeKeypressListener
();
removeClickListener
();
removeFocusListener
();
};
},
[
closeableEl
,
isOpen
,
handleClose
]);
}
module
.
exports
=
useElementShouldClose
;
src/sidebar/components/menu.js
View file @
6b4daba5
...
@@ -5,7 +5,7 @@ const { Fragment, createElement } = require('preact');
...
@@ -5,7 +5,7 @@ const { Fragment, createElement } = require('preact');
const
{
useCallback
,
useEffect
,
useRef
,
useState
}
=
require
(
'preact/hooks'
);
const
{
useCallback
,
useEffect
,
useRef
,
useState
}
=
require
(
'preact/hooks'
);
const
propTypes
=
require
(
'prop-types'
);
const
propTypes
=
require
(
'prop-types'
);
const
{
listen
}
=
require
(
'../util/dom
'
);
const
useElementShouldClose
=
require
(
'./hooks/use-element-should-close
'
);
const
SvgIcon
=
require
(
'./svg-icon'
);
const
SvgIcon
=
require
(
'./svg-icon'
);
...
@@ -89,59 +89,14 @@ function Menu({
...
@@ -89,59 +89,14 @@ function Menu({
// These handlers close the menu when the user taps or clicks outside the
// These handlers close the menu when the user taps or clicks outside the
// menu or presses Escape.
// menu or presses Escape.
const
menuRef
=
useRef
();
const
menuRef
=
useRef
();
useEffect
(()
=>
{
if
(
!
isOpen
)
{
return
()
=>
{};
}
// Close menu when user presses Escape key, regardless of focus.
const
removeKeypressListener
=
listen
(
document
.
body
,
[
'keypress'
],
event
=>
{
if
(
event
.
key
===
'Escape'
)
{
closeMenu
();
}
}
);
// Close menu if user focuses an element outside the menu via any means
// (key press, programmatic focus change).
const
removeFocusListener
=
listen
(
document
.
body
,
'focus'
,
event
=>
{
if
(
!
menuRef
.
current
.
contains
(
event
.
target
))
{
closeMenu
();
}
},
{
useCapture
:
true
}
);
// Close menu if user clicks outside menu, even if on an element which
// does not accept focus.
const
removeClickListener
=
listen
(
document
.
body
,
[
'mousedown'
,
'click'
],
event
=>
{
// nb. Mouse events inside the current menu are handled elsewhere.
if
(
!
menuRef
.
current
.
contains
(
event
.
target
))
{
closeMenu
();
}
},
{
useCapture
:
true
}
);
return
()
=>
{
// Menu element should close via `closeMenu` whenever it's open and there
removeKeypressListener
();
// are user interactions outside of it (e.g. clicks) in the document
removeClickListener
();
useElementShouldClose
(
menuRef
,
isOpen
,
closeMenu
);
removeFocusListener
();
};
},
[
closeMenu
,
isOpen
]);
const
stopPropagation
=
e
=>
e
.
stopPropagation
();
const
stopPropagation
=
e
=>
e
.
stopPropagation
();
//
Close menu if
user presses a key which activates menu items.
//
It should also close if the
user presses a key which activates menu items.
const
handleMenuKeyPress
=
event
=>
{
const
handleMenuKeyPress
=
event
=>
{
if
(
event
.
key
===
'Enter'
||
event
.
key
===
' '
)
{
if
(
event
.
key
===
'Enter'
||
event
.
key
===
' '
)
{
closeMenu
();
closeMenu
();
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment