diff --git a/bigbluebutton-html5/imports/ui/components/dropdown/component.jsx b/bigbluebutton-html5/imports/ui/components/dropdown/component.jsx
index fb519b7669..e8066568ab 100644
--- a/bigbluebutton-html5/imports/ui/components/dropdown/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/dropdown/component.jsx
@@ -2,7 +2,7 @@ import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { findDOMNode } from 'react-dom';
import cx from 'classnames';
-import { defineMessages, injectIntl } from 'react-intl';
+import { defineMessages, injectIntl, intlShape } from 'react-intl';
import Button from '/imports/ui/components/button/component';
import screenreaderTrap from 'makeup-screenreader-trap';
import { styles } from './styles';
@@ -26,37 +26,40 @@ const propTypes = {
const children = props[propName];
if (!children || children.length < 2) {
- return new Error(`Invalid prop \`${propName}\` supplied to` +
- ` \`${componentName}\`. Validation failed.`);
+ return new Error(`Invalid prop \`${propName}\` supplied to`
+ + ` \`${componentName}\`. Validation failed.`);
}
const trigger = children.find(x => x.type === DropdownTrigger);
const content = children.find(x => x.type === DropdownContent);
if (!trigger) {
- return new Error(`Invalid prop \`${propName}\` supplied to` +
- ` \`${componentName}\`. Missing \`DropdownTrigger\`. Validation failed.`);
+ return new Error(`Invalid prop \`${propName}\` supplied to`
+ + ` \`${componentName}\`. Missing \`DropdownTrigger\`. Validation failed.`);
}
if (!content) {
- return new Error(`Invalid prop \`${propName}\` supplied to` +
- ` \`${componentName}\`. Missing \`DropdownContent\`. Validation failed.`);
+ return new Error(`Invalid prop \`${propName}\` supplied to`
+ + ` \`${componentName}\`. Missing \`DropdownContent\`. Validation failed.`);
}
return null;
},
isOpen: PropTypes.bool,
+ keepOpen: PropTypes.bool,
onHide: PropTypes.func,
onShow: PropTypes.func,
autoFocus: PropTypes.bool,
+ intl: intlShape.isRequired,
};
const defaultProps = {
children: null,
- keepOpen: null,
onShow: noop,
onHide: noop,
autoFocus: false,
+ isOpen: false,
+ keepOpen: false,
};
class Dropdown extends Component {
@@ -82,6 +85,7 @@ class Dropdown extends Component {
const { isOpen } = this.state;
+
if (isOpen && !prevState.isOpen) { onShow(); }
if (!isOpen && prevState.isOpen) { onHide(); }
@@ -90,6 +94,7 @@ class Dropdown extends Component {
}
handleShow() {
+ Session.set('dropdownOpen', true);
const {
onShow,
} = this.props;
@@ -101,6 +106,7 @@ class Dropdown extends Component {
}
handleHide() {
+ Session.set('dropdownOpen', false);
const { onHide } = this.props;
this.setState({ isOpen: false }, () => {
const { removeEventListener } = window;
@@ -115,28 +121,33 @@ class Dropdown extends Component {
const triggerElement = findDOMNode(this.trigger);
const contentElement = findDOMNode(this.content);
if (!(triggerElement && contentElement)) return;
- if (keepOpen === null) {
- if (triggerElement.contains(event.target)) {
+ if (triggerElement && triggerElement.contains(event.target)) {
+ if (keepOpen) {
+ onHide();
+ return;
+ }
+ if (isOpen) {
+ this.handleHide();
return;
}
}
- if (triggerElement && triggerElement.contains(event.target)) {
- if (keepOpen) return onHide();
- if (isOpen) return this.handleHide();
- }
-
if (keepOpen && isOpen && !contentElement.contains(event.target)) {
+ if (triggerElement) {
+ const { parentElement } = triggerElement;
+ if (parentElement) parentElement.focus();
+ }
onHide();
this.handleHide();
return;
}
-
- if (keepOpen !== null) {
- return;
+
+ if (keepOpen && triggerElement) {
+ const { parentElement } = triggerElement;
+ if (parentElement) parentElement.focus();
}
- this.handleHide();
+ if (!keepOpen) this.handleHide();
}
handleToggle() {
@@ -148,12 +159,11 @@ class Dropdown extends Component {
const {
children,
className,
- style,
intl,
keepOpen,
...otherProps
} = this.props;
-
+
const { isOpen } = this.state;
let trigger = children.find(x => x.type === DropdownTrigger);
@@ -175,31 +185,31 @@ class Dropdown extends Component {
dropdownShow: this.handleShow,
dropdownHide: this.handleHide,
});
-
+
const showCloseBtn = (isOpen && keepOpen) || (isOpen && keepOpen === null);
return (
{ this.dropdown = node; }}
tabIndex={-1}
>
{trigger}
{content}
- {showCloseBtn ?
- : null}
+ {showCloseBtn
+ ? (
+
+ ) : null}
);
}
diff --git a/bigbluebutton-html5/imports/ui/components/user-list/service.js b/bigbluebutton-html5/imports/ui/components/user-list/service.js
index 9cde5a3dbb..64f9079320 100755
--- a/bigbluebutton-html5/imports/ui/components/user-list/service.js
+++ b/bigbluebutton-html5/imports/ui/components/user-list/service.js
@@ -394,8 +394,11 @@ const muteAllExceptPresenter = (userId) => { makeCall('muteAllExceptPresenter',
const changeRole = (userId, role) => { makeCall('changeRole', userId, role); };
-const roving = (event, itemCount, changeState) => {
- if (document.activeElement.getAttribute('data-isopen') === 'true') {
+const roving = (event, changeState, elementsList, element) => {
+ this.selectedElement = element;
+ const menuOpen = Session.get('dropdownOpen') || false;
+
+ if (menuOpen) {
const menuChildren = document.activeElement.getElementsByTagName('li');
if ([KEY_CODES.ESCAPE, KEY_CODES.ARROW_LEFT].includes(event.keyCode)) {
@@ -418,34 +421,23 @@ const roving = (event, itemCount, changeState) => {
return;
}
- if (this.selectedIndex === undefined) {
- this.selectedIndex = -1;
- }
-
if ([KEY_CODES.ESCAPE, KEY_CODES.TAB].includes(event.keyCode)) {
document.activeElement.blur();
- this.selectedIndex = -1;
- changeState(this.selectedIndex);
+ changeState(null);
}
if (event.keyCode === KEY_CODES.ARROW_DOWN) {
- this.selectedIndex += 1;
-
- if (this.selectedIndex === itemCount) {
- this.selectedIndex = 0;
- }
-
- changeState(this.selectedIndex);
+ const firstElement = elementsList.firstChild;
+ let elRef = element ? element.nextSibling : firstElement;
+ elRef = elRef || firstElement;
+ changeState(elRef);
}
if (event.keyCode === KEY_CODES.ARROW_UP) {
- this.selectedIndex -= 1;
-
- if (this.selectedIndex < 0) {
- this.selectedIndex = itemCount - 1;
- }
-
- changeState(this.selectedIndex);
+ const lastElement = elementsList.lastChild;
+ let elRef = element ? element.previousSibling : lastElement;
+ elRef = elRef || lastElement;
+ changeState(elRef);
}
if ([KEY_CODES.ARROW_RIGHT, KEY_CODES.SPACE, KEY_CODES.ENTER].includes(event.keyCode)) {
diff --git a/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-messages/component.jsx b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-messages/component.jsx
index 4fe15a6347..48694f9e55 100644
--- a/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-messages/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-messages/component.jsx
@@ -4,6 +4,7 @@ import PropTypes from 'prop-types';
import { defineMessages } from 'react-intl';
import cx from 'classnames';
import { styles } from '/imports/ui/components/user-list/user-list-content/styles';
+import { findDOMNode } from 'react-dom';
import ChatListItemContainer from '../../chat-list-item/container';
const propTypes = {
@@ -41,38 +42,31 @@ class UserMessages extends PureComponent {
super();
this.state = {
- index: -1,
+ selectedChat: null,
};
this.activeChatRefs = [];
- this.selectedIndex = -1;
- this.focusActiveChatItem = this.focusActiveChatItem.bind(this);
this.changeState = this.changeState.bind(this);
+ this.rove = this.rove.bind(this);
}
componentDidMount() {
- const { compact, roving, activeChats } = this.props;
+ const { compact } = this.props;
if (!compact) {
this._msgsList.addEventListener(
'keydown',
- event => roving(
- event,
- activeChats.length,
- this.changeState,
- ),
+ this.rove,
);
}
}
componentDidUpdate(prevProps, prevState) {
- const { index } = this.state;
- if (index === -1) {
- return;
- }
+ const { selectedChat } = this.state;
- if (index !== prevState.index) {
- this.focusActiveChatItem(index);
+ if (selectedChat && selectedChat !== prevState.selectedChat) {
+ const { firstChild } = selectedChat;
+ if (firstChild) firstChild.focus();
}
}
@@ -108,16 +102,15 @@ class UserMessages extends PureComponent {
));
}
- changeState(newIndex) {
- this.setState({ index: newIndex });
+ changeState(ref) {
+ this.setState({ selectedChat: ref });
}
- focusActiveChatItem(index) {
- if (!this.activeChatRefs[index]) {
- return;
- }
-
- this.activeChatRefs[index].firstChild.focus();
+ rove(event) {
+ const { roving } = this.props;
+ const { selectedChat } = this.state;
+ const msgItemsRef = findDOMNode(this._msgItems);
+ roving(event, this.changeState, msgItemsRef, selectedChat);
}
render() {
diff --git a/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/component.jsx b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/component.jsx
index cf4e37a004..30b247865a 100755
--- a/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/component.jsx
@@ -5,6 +5,7 @@ import PropTypes from 'prop-types';
import cx from 'classnames';
import { styles } from '/imports/ui/components/user-list/user-list-content/styles';
import _ from 'lodash';
+import { findDOMNode } from 'react-dom';
import UserListItemContainer from './user-list-item/container';
import UserOptionsContainer from './user-options/container';
@@ -62,46 +63,43 @@ class UserParticipants extends Component {
super();
this.state = {
- index: -1,
+ selectedUser: null,
};
this.userRefs = [];
- this.selectedIndex = -1;
this.getScrollContainerRef = this.getScrollContainerRef.bind(this);
- this.focusUserItem = this.focusUserItem.bind(this);
+ this.rove = this.rove.bind(this);
this.changeState = this.changeState.bind(this);
this.getUsers = this.getUsers.bind(this);
}
componentDidMount() {
- const { compact, roving, users } = this.props;
+ const { compact } = this.props;
if (!compact) {
this.refScrollContainer.addEventListener(
'keydown',
- event => roving(
- event,
- users.length,
- this.changeState,
- ),
+ this.rove,
);
}
}
+ componentWillUnmount() {
+ this.refScrollContainer.removeEventListener('keydown', this.rove);
+ }
+
shouldComponentUpdate(nextProps, nextState) {
const isPropsEqual = _.isEqual(this.props, nextProps);
const isStateEqual = _.isEqual(this.state, nextState);
return !isPropsEqual || !isStateEqual;
}
- componentDidUpdate(prevProps, prevState) {
- const { index } = this.state;
- if (index === -1) {
- return;
- }
+ componentDidUpdate() {
+ const { selectedUser } = this.state;
- if (index !== prevState.index) {
- this.focusUserItem(index);
+ if (selectedUser) {
+ const { firstChild } = selectedUser;
+ if (firstChild) firstChild.focus();
}
}
@@ -177,14 +175,15 @@ class UserParticipants extends Component {
));
}
- focusUserItem(index) {
- if (!this.userRefs[index]) return;
-
- this.userRefs[index].firstChild.focus();
+ rove(event) {
+ const { roving } = this.props;
+ const { selectedUser } = this.state;
+ const usersItemsRef = findDOMNode(this.refScrollItems);
+ roving(event, this.changeState, usersItemsRef, selectedUser);
}
- changeState(newIndex) {
- this.setState({ index: newIndex });
+ changeState(ref) {
+ this.setState({ selectedUser: ref });
}
render() {
@@ -230,7 +229,6 @@ class UserParticipants extends Component {
}
{ this.refScrollContainer = ref; }}
>
diff --git a/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-list-item/user-dropdown/component.jsx b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-list-item/user-dropdown/component.jsx
index 391e84967f..a4eb8dd8e9 100755
--- a/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-list-item/user-dropdown/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-list-item/user-dropdown/component.jsx
@@ -153,10 +153,13 @@ class UserDropdown extends PureComponent {
}
componentDidUpdate() {
+ const { dropdownVisible } = this.props;
+ if (!dropdownVisible) document.activeElement.blur();
this.checkDropdownDirection();
}
onActionsShow() {
+ Session.set('dropdownOpen', true);
const { getScrollContainerRef } = this.props;
const dropdown = this.getDropdownMenuParent();
const scrollContainer = getScrollContainerRef();
@@ -193,6 +196,8 @@ class UserDropdown extends PureComponent {
if (callback) {
return callback;
}
+
+ return Session.set('dropdownOpen', false);
}
getUsersActions() {
@@ -251,7 +256,12 @@ class UserDropdown extends PureComponent {
actions.push(this.makeDropdownItem(
'back',
intl.formatMessage(messages.backTriggerLabel),
- () => this.setState({ showNestedOptions: false, isActionsOpen: true }),
+ () => this.setState(
+ {
+ showNestedOptions: false,
+ isActionsOpen: true,
+ }, Session.set('dropdownOpen', true),
+ ),
'left_arrow',
));
}
@@ -273,7 +283,12 @@ class UserDropdown extends PureComponent {
actions.push(this.makeDropdownItem(
'setstatus',
intl.formatMessage(messages.statusTriggerLabel),
- () => this.setState({ showNestedOptions: true, isActionsOpen: true }),
+ () => this.setState(
+ {
+ showNestedOptions: true,
+ isActionsOpen: true,
+ }, Session.set('dropdownOpen', true),
+ ),
'user',
'right_arrow',
));
@@ -440,7 +455,8 @@ class UserDropdown extends PureComponent {
);
if (!isDropdownVisible) {
- const offsetPageTop = (dropdownTrigger.offsetTop + dropdownTrigger.offsetHeight) - scrollContainer.scrollTop;
+ const { offsetTop, offsetHeight } = dropdownTrigger;
+ const offsetPageTop = (offsetTop + offsetHeight) - scrollContainer.scrollTop;
nextState.dropdownOffset = window.innerHeight - offsetPageTop;
nextState.dropdownDirection = 'bottom';