Merge pull request #3224 from matrix-org/bwindels/focus-composer-on-type
Focus composer when typing anywhere in the app
This commit is contained in:
commit
4fa7302f69
8 changed files with 57 additions and 29 deletions
|
@ -106,7 +106,7 @@ const LoggedInView = React.createClass({
|
|||
|
||||
CallMediaHandler.loadDevices();
|
||||
|
||||
document.addEventListener('keydown', this._onKeyDown);
|
||||
document.addEventListener('keydown', this._onNativeKeyDown, false);
|
||||
|
||||
this._sessionStore = sessionStore;
|
||||
this._sessionStoreToken = this._sessionStore.addListener(
|
||||
|
@ -136,7 +136,7 @@ const LoggedInView = React.createClass({
|
|||
},
|
||||
|
||||
componentWillUnmount: function() {
|
||||
document.removeEventListener('keydown', this._onKeyDown);
|
||||
document.removeEventListener('keydown', this._onNativeKeyDown, false);
|
||||
this._matrixClient.removeListener("accountData", this.onAccountData);
|
||||
this._matrixClient.removeListener("sync", this.onSync);
|
||||
this._matrixClient.removeListener("RoomState.events", this.onRoomStateEvents);
|
||||
|
@ -272,6 +272,42 @@ const LoggedInView = React.createClass({
|
|||
});
|
||||
},
|
||||
|
||||
/*
|
||||
SOME HACKERY BELOW:
|
||||
React optimizes event handlers, by always attaching only 1 handler to the document for a given type.
|
||||
It then internally determines the order in which React event handlers should be called,
|
||||
emulating the capture and bubbling phases the DOM also has.
|
||||
|
||||
But, as the native handler for React is always attached on the document,
|
||||
it will always run last for bubbling (first for capturing) handlers,
|
||||
and thus React basically has its own event phases, and will always run
|
||||
after (before for capturing) any native other event handlers (as they tend to be attached last).
|
||||
|
||||
So ideally one wouldn't mix React and native event handlers to have bubbling working as expected,
|
||||
but we do need a native event handler here on the document,
|
||||
to get keydown events when there is no focused element (target=body).
|
||||
|
||||
We also do need bubbling here to give child components a chance to call `stopPropagation()`,
|
||||
for keydown events it can handle itself, and shouldn't be redirected to the composer.
|
||||
|
||||
So we listen with React on this component to get any events on focused elements, and get bubbling working as expected.
|
||||
We also listen with a native listener on the document to get keydown events when no element is focused.
|
||||
Bubbling is irrelevant here as the target is the body element.
|
||||
*/
|
||||
_onReactKeyDown: function(ev) {
|
||||
// events caught while bubbling up on the root element
|
||||
// of this component, so something must be focused.
|
||||
this._onKeyDown(ev);
|
||||
},
|
||||
|
||||
_onNativeKeyDown: function(ev) {
|
||||
// only pass this if there is no focused element.
|
||||
// if there is, _onKeyDown will be called by the
|
||||
// react keydown handler that respects the react bubbling order.
|
||||
if (ev.target === document.body) {
|
||||
this._onKeyDown(ev);
|
||||
}
|
||||
},
|
||||
|
||||
_onKeyDown: function(ev) {
|
||||
/*
|
||||
|
@ -333,6 +369,21 @@ const LoggedInView = React.createClass({
|
|||
if (handled) {
|
||||
ev.stopPropagation();
|
||||
ev.preventDefault();
|
||||
} else {
|
||||
const targetTag = ev.target.tagName;
|
||||
const focusedOnInputControl = targetTag === "INPUT" ||
|
||||
targetTag === "TEXTAREA" ||
|
||||
targetTag === "SELECT" ||
|
||||
!!ev.target.getAttribute("contenteditable");
|
||||
const isClickShortcut = ev.target !== document.body &&
|
||||
(ev.key === "Space" || ev.key === "Enter");
|
||||
|
||||
if (!focusedOnInputControl && !isClickShortcut) {
|
||||
dis.dispatch({action: 'focus_composer'}, true);
|
||||
ev.stopPropagation();
|
||||
// we should *not* preventDefault() here as
|
||||
// that would prevent typing in the now-focussed composer
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -544,7 +595,7 @@ const LoggedInView = React.createClass({
|
|||
}
|
||||
|
||||
return (
|
||||
<div className='mx_MatrixChat_wrapper' aria-hidden={this.props.hideToSRUsers} onMouseDown={this._onMouseDown} onMouseUp={this._onMouseUp}>
|
||||
<div onKeyDown={this._onReactKeyDown} className='mx_MatrixChat_wrapper' aria-hidden={this.props.hideToSRUsers} onMouseDown={this._onMouseDown} onMouseUp={this._onMouseUp}>
|
||||
{ topBar }
|
||||
<DragDropContext onDragEnd={this._onDragEnd}>
|
||||
<div ref={this._setResizeContainerRef} className={bodyClasses}>
|
||||
|
|
|
@ -268,8 +268,6 @@ export default React.createClass({
|
|||
componentDidMount: function() {
|
||||
this.dispatcherRef = dis.register(this.onAction);
|
||||
|
||||
this.focusComposer = false;
|
||||
|
||||
// this can technically be done anywhere but doing this here keeps all
|
||||
// the routing url path logic together.
|
||||
if (this.onAliasClick) {
|
||||
|
@ -362,10 +360,6 @@ export default React.createClass({
|
|||
const durationMs = this.stopPageChangeTimer();
|
||||
Analytics.trackPageChange(durationMs);
|
||||
}
|
||||
if (this.focusComposer) {
|
||||
dis.dispatch({action: 'focus_composer'});
|
||||
this.focusComposer = false;
|
||||
}
|
||||
},
|
||||
|
||||
startPageChangeTimer() {
|
||||
|
@ -793,8 +787,6 @@ export default React.createClass({
|
|||
// that has been passed out-of-band (eg.
|
||||
// room name and avatar from an invite email)
|
||||
_viewRoom: function(roomInfo) {
|
||||
this.focusComposer = true;
|
||||
|
||||
const newState = {
|
||||
view: VIEWS.LOGGED_IN,
|
||||
currentRoomId: roomInfo.room_id || null,
|
||||
|
@ -1368,7 +1360,6 @@ export default React.createClass({
|
|||
self.firstSyncComplete = true;
|
||||
self.firstSyncPromise.resolve();
|
||||
|
||||
dis.dispatch({action: 'focus_composer'});
|
||||
self.setState({
|
||||
ready: true,
|
||||
showNotifierToolbar: Notifier.shouldShowToolbar(),
|
||||
|
|
|
@ -135,12 +135,10 @@ module.exports = React.createClass({
|
|||
|
||||
_onResendAllClick: function() {
|
||||
Resend.resendUnsentEvents(this.props.room);
|
||||
dis.dispatch({action: 'focus_composer'});
|
||||
},
|
||||
|
||||
_onCancelAllClick: function() {
|
||||
Resend.cancelUnsentEvents(this.props.room);
|
||||
dis.dispatch({action: 'focus_composer'});
|
||||
},
|
||||
|
||||
_onShowDevicesClick: function() {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue