Merge branch 'fix-roving-users' of github.com:KDSBrowne/bigbluebutton

This commit is contained in:
Anton Georgiev 2019-08-15 15:16:47 -04:00
commit e29b22d893
5 changed files with 112 additions and 103 deletions

View File

@ -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 (
<div
style={style}
className={cx(styles.dropdown, className)}
aria-live={otherProps['aria-live']}
aria-relevant={otherProps['aria-relevant']}
aria-haspopup={otherProps['aria-haspopup']}
aria-label={otherProps['aria-label']}
data-isopen={this.state.isOpen}
ref={(node) => { this.dropdown = node; }}
tabIndex={-1}
>
{trigger}
{content}
{showCloseBtn ?
<Button
className={styles.close}
label={intl.formatMessage(intlMessages.close)}
size="lg"
color="default"
onClick={this.handleHide}
/> : null}
{showCloseBtn
? (
<Button
className={styles.close}
label={intl.formatMessage(intlMessages.close)}
size="lg"
color="default"
onClick={this.handleHide}
/>
) : null}
</div>
);
}

View File

@ -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)) {

View File

@ -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() {

View File

@ -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 {
}
<div
className={styles.scrollableList}
role="list"
tabIndex={0}
ref={(ref) => { this.refScrollContainer = ref; }}
>

View File

@ -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';