Merge branch 'fix-roving-users' of github.com:KDSBrowne/bigbluebutton
This commit is contained in:
commit
e29b22d893
@ -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>
|
||||
);
|
||||
}
|
||||
|
@ -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)) {
|
||||
|
@ -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() {
|
||||
|
@ -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; }}
|
||||
>
|
||||
|
@ -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';
|
||||
|
Loading…
Reference in New Issue
Block a user