grommet
Version:
The most advanced UX framework for enterprise applications.
666 lines (576 loc) • 23.9 kB
JavaScript
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
var _react = require('react');
var _react2 = _interopRequireDefault(_react);
var _propTypes = require('prop-types');
var _propTypes2 = _interopRequireDefault(_propTypes);
var _classnames3 = require('classnames');
var _classnames4 = _interopRequireDefault(_classnames3);
var _Spinning = require('./icons/Spinning');
var _Spinning2 = _interopRequireDefault(_Spinning);
var _InfiniteScroll = require('../utils/InfiniteScroll');
var _InfiniteScroll2 = _interopRequireDefault(_InfiniteScroll);
var _Selection = require('../utils/Selection');
var _Selection2 = _interopRequireDefault(_Selection);
var _CSSClassnames = require('../utils/CSSClassnames');
var _CSSClassnames2 = _interopRequireDefault(_CSSClassnames);
var _KeyboardAccelerators = require('../utils/KeyboardAccelerators');
var _KeyboardAccelerators2 = _interopRequireDefault(_KeyboardAccelerators);
var _Responsive = require('../utils/Responsive');
var _Responsive2 = _interopRequireDefault(_Responsive);
var _Intl = require('../utils/Intl');
var _Intl2 = _interopRequireDefault(_Intl);
var _Announcer = require('../utils/Announcer');
var _TableHeader = require('./TableHeader');
var _TableHeader2 = _interopRequireDefault(_TableHeader);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
function _objectWithoutProperties(obj, keys) { var target = {}; for (var i in obj) { if (keys.indexOf(i) >= 0) continue; if (!Object.prototype.hasOwnProperty.call(obj, i)) continue; target[i] = obj[i]; } return target; }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } // (C) Copyright 2014-2016 Hewlett Packard Enterprise Development LP
var CLASS_ROOT = _CSSClassnames2.default.TABLE;
var SELECTED_CLASS = CLASS_ROOT + '-row--selected';
var ACTIVE_CLASS = CLASS_ROOT + '-row--active';
// empirical number describing a minimum cell width for a
// table to be presented in column-mode.
var MIN_CELL_WIDTH = 120;
function getTotalCellCount(cells) {
var cellCount = 0;
[].forEach.call(cells, function (cell) {
var colspan = cell.getAttribute('colspan');
cellCount += colspan ? parseInt(colspan) : 1;
});
return cellCount;
}
// function that filters the items that are not
// an immediate child of its parent
function immediateTableChildOnly(result, tableParent) {
var immediateChild = [];
[].forEach.call(result, function (item) {
var currentParent = item.parentNode;
while (currentParent) {
if (currentParent.tagName.toLowerCase() === 'table') {
if (currentParent === tableParent) {
immediateChild.push(item);
}
break;
}
currentParent = currentParent.parentNode;
}
});
return immediateChild;
}
function findHead(children) {
if (!children) {
return undefined;
}
var childElements = _react.Children.toArray(children);
var head = void 0;
childElements.some(function (child) {
if (child.type && (child.type === 'thead' || child.type === _TableHeader2.default || child.type.displayName === _TableHeader2.default.displayName)) {
head = child;
return true;
} else if (child.props && child.props.children) {
head = findHead(child.props.children);
if (head) {
return true;
}
}
return false;
});
return head;
}
var Table = function (_Component) {
_inherits(Table, _Component);
function Table(props, context) {
_classCallCheck(this, Table);
var _this = _possibleConstructorReturn(this, (Table.__proto__ || Object.getPrototypeOf(Table)).call(this, props, context));
_this._onClick = _this._onClick.bind(_this);
_this._onResize = _this._onResize.bind(_this);
_this._layout = _this._layout.bind(_this);
_this._onResponsive = _this._onResponsive.bind(_this);
_this._onPreviousRow = _this._onPreviousRow.bind(_this);
_this._onNextRow = _this._onNextRow.bind(_this);
_this._onEnter = _this._onEnter.bind(_this);
_this._fireClick = _this._fireClick.bind(_this);
_this._announceRow = _this._announceRow.bind(_this);
_this._onViewPortChange = _this._onViewPortChange.bind(_this);
_this.state = {
activeRow: undefined,
mouseActive: false,
selected: _Selection2.default.normalizeIndexes(props.selected),
columnMode: false,
small: false
};
return _this;
}
_createClass(Table, [{
key: 'componentDidMount',
value: function componentDidMount() {
var _props = this.props,
onMore = _props.onMore,
selectable = _props.selectable,
scrollable = _props.scrollable;
var _state = this.state,
columnMode = _state.columnMode,
small = _state.small;
this._setSelection();
if (scrollable && !columnMode && !small) {
this._alignMirror();
}
if (this.props.onMore) {
this._scroll = _InfiniteScroll2.default.startListeningForScroll(this.moreRef, onMore);
}
this._adjustBodyCells();
setTimeout(this._layout, 50);
window.addEventListener('resize', this._onResize);
if (selectable) {
// only listen for navigation keys if the table row can be selected
this._keyboardHandlers = {
left: this._onPreviousRow,
up: this._onPreviousRow,
right: this._onNextRow,
down: this._onNextRow,
enter: this._onEnter,
space: this._onEnter
};
_KeyboardAccelerators2.default.startListeningToKeyboard(this, this._keyboardHandlers);
}
this._responsive = _Responsive2.default.start(this._onViewPortChange);
}
}, {
key: 'componentWillReceiveProps',
value: function componentWillReceiveProps(nextProps) {
if (this._scroll) {
_InfiniteScroll2.default.stopListeningForScroll(this._scroll);
this._scroll = undefined;
}
if (nextProps.selected !== undefined) {
this.setState({
selected: _Selection2.default.normalizeIndexes(nextProps.selected)
});
}
}
}, {
key: 'componentDidUpdate',
value: function componentDidUpdate(prevProps, prevState) {
var _props2 = this.props,
onMore = _props2.onMore,
selectable = _props2.selectable,
scrollable = _props2.scrollable;
var _state2 = this.state,
columnMode = _state2.columnMode,
selected = _state2.selected,
small = _state2.small;
if (JSON.stringify(selected) !== JSON.stringify(prevState.selected)) {
this._setSelection();
}
if (scrollable && !columnMode && !small) {
this._alignMirror();
}
if (onMore && !this._scroll) {
this._scroll = _InfiniteScroll2.default.startListeningForScroll(this.moreRef, onMore);
}
this._adjustBodyCells();
this._layout();
if (selectable) {
// only listen for navigation keys if the table row can be selected
this._keyboardHandlers = {
left: this._onPreviousRow,
up: this._onPreviousRow,
right: this._onNextRow,
down: this._onNextRow,
enter: this._onEnter,
space: this._onEnter
};
_KeyboardAccelerators2.default.startListeningToKeyboard(this, this._keyboardHandlers);
}
}
}, {
key: 'componentWillUnmount',
value: function componentWillUnmount() {
var selectable = this.props.selectable;
if (this._scroll) {
_InfiniteScroll2.default.stopListeningForScroll(this._scroll);
}
clearTimeout(this._resizeTimer);
window.removeEventListener('resize', this._onResize);
if (selectable) {
_KeyboardAccelerators2.default.stopListeningToKeyboard(this, this._keyboardHandlers);
}
this._responsive.stop();
}
}, {
key: '_onViewPortChange',
value: function _onViewPortChange(small) {
this.setState({ small: small });
}
}, {
key: '_announceRow',
value: function _announceRow(label) {
var intl = this.context.intl;
var enterSelectMessage = _Intl2.default.getMessage(intl, 'Enter Select');
(0, _Announcer.announce)(label + ' ' + enterSelectMessage);
}
}, {
key: '_onResponsive',
value: function _onResponsive() {
var columnMode = this.state.columnMode;
if (this.containerRef && this.tableRef) {
var availableSize = this.containerRef.offsetWidth;
var numberOfCells = getTotalCellCount(immediateTableChildOnly(this.tableRef.querySelectorAll('thead th'), this.tableRef));
if (numberOfCells * MIN_CELL_WIDTH > availableSize) {
if (columnMode === false) {
this.setState({ columnMode: true });
}
} else {
if (columnMode === true) {
this.setState({ columnMode: false });
}
}
}
}
}, {
key: '_container',
value: function _container() {
var containerElement = this.tableRef;
if (containerElement) {
var tableBodies = containerElement.getElementsByTagName('TBODY');
if (tableBodies.length > 0) {
containerElement = tableBodies[0];
}
}
return containerElement;
}
}, {
key: '_setSelection',
value: function _setSelection() {
var selected = this.state.selected;
_Selection2.default.setClassFromIndexes({
containerElement: this._container(),
childSelector: 'tr',
selectedClass: SELECTED_CLASS,
selectedIndexes: selected
});
}
}, {
key: '_onPreviousRow',
value: function _onPreviousRow(event) {
var _this2 = this;
if (this.tableRef.contains(document.activeElement)) {
event.preventDefault();
var activeRow = this.state.activeRow;
var rows = immediateTableChildOnly(this.tableRef.querySelectorAll('tbody tr'), this.tableRef);
if (rows && rows.length > 0) {
if (activeRow === undefined) {
rows[0].classList.add(ACTIVE_CLASS);
this.setState({ activeRow: 0 }, function () {
_this2._announceRow(rows[_this2.state.activeRow].innerText);
});
} else if (activeRow - 1 >= 0) {
rows[activeRow].classList.remove(ACTIVE_CLASS);
rows[activeRow - 1].classList.add(ACTIVE_CLASS);
this.setState({ activeRow: activeRow - 1 }, function () {
_this2._announceRow(rows[_this2.state.activeRow].innerText);
});
}
}
//stop event propagation
return true;
}
}
}, {
key: '_onNextRow',
value: function _onNextRow(event) {
var _this3 = this;
if (this.tableRef.contains(document.activeElement)) {
event.preventDefault();
var activeRow = this.state.activeRow;
var rows = immediateTableChildOnly(this.tableRef.querySelectorAll('tbody tr'), this.tableRef);
if (rows && rows.length > 0) {
if (activeRow === undefined) {
rows[0].classList.add(ACTIVE_CLASS);
this.setState({ activeRow: 0 }, function () {
_this3._announceRow(rows[_this3.state.activeRow].innerText);
});
} else if (activeRow + 1 <= rows.length - 1) {
rows[activeRow].classList.remove(ACTIVE_CLASS);
rows[activeRow + 1].classList.add(ACTIVE_CLASS);
this.setState({ activeRow: activeRow + 1 }, function () {
_this3._announceRow(rows[_this3.state.activeRow].innerText);
});
}
}
//stop event propagation
return true;
}
}
}, {
key: '_fireClick',
value: function _fireClick(element, shiftKey) {
var event = void 0;
try {
event = new MouseEvent('click', {
'bubbles': true,
'cancelable': true,
'shiftKey': shiftKey
});
} catch (e) {
// IE11 workaround.
event = document.createEvent('Event');
event.initEvent('click', true, true);
}
// We use dispatchEvent to have the browser fill out the event fully.
element.dispatchEvent(event);
}
}, {
key: '_onEnter',
value: function _onEnter(event) {
var activeRow = this.state.activeRow;
var intl = this.context.intl;
if (this.tableRef.contains(document.activeElement) && activeRow !== undefined) {
var rows = immediateTableChildOnly(this.tableRef.querySelectorAll('tbody tr'), this.tableRef);
this._fireClick(rows[activeRow], event.shiftKey);
rows[activeRow].classList.remove(ACTIVE_CLASS);
var label = rows[activeRow].innerText;
var selectedMessage = _Intl2.default.getMessage(intl, 'Selected');
(0, _Announcer.announce)(label + ' ' + selectedMessage);
}
}
}, {
key: '_onClick',
value: function _onClick(event) {
var _props3 = this.props,
onSelect = _props3.onSelect,
selectable = _props3.selectable,
selected = _props3.selected;
var selection = _Selection2.default.onClick(event, {
containerElement: this._container(),
childSelector: 'tr',
selectedClass: SELECTED_CLASS,
multiSelect: 'multiple' === selectable,
priorSelectedIndexes: this.state.selected
});
// only set the selected state and classes if the caller isn't managing it.
if (selected === undefined) {
this.setState({ selected: selection }, this._setSelection);
}
if (onSelect) {
onSelect(selection.length === 1 ? selection[0] : selection);
}
}
}, {
key: '_adjustBodyCells',
value: function _adjustBodyCells() {
// adjust table body cells to have link to the header
// so that in responsive mode it displays the text as content in css.
// IMPORTANT: non-text header cells, such as icon, are rendered as empty
// headers.
if (this.tableRef) {
var headerCells = immediateTableChildOnly(this.tableRef.querySelectorAll('thead th'), this.tableRef);
var totalHeaderCells = getTotalCellCount(headerCells);
if (headerCells.length > 0) {
var increments = [];
headerCells.forEach(function (cell) {
var colspan = cell.getAttribute('colspan');
increments.push(colspan ? parseInt(colspan) : 1);
});
var rows = immediateTableChildOnly(this.tableRef.querySelectorAll('tbody tr'), this.tableRef);
rows.forEach(function (row) {
var incrementCount = 0;
var headerIndex = 0;
if (getTotalCellCount(row.cells) !== totalHeaderCells) {
console.error('Table row cells do not match length of header cells.');
}
[].forEach.call(row.cells, function (cell) {
var colspan = cell.getAttribute('colspan');
var cellCount = colspan ? parseInt(colspan) : 1;
if (cellCount < totalHeaderCells) {
// only set the header if the cell colspan is smaller
// than the total header cells
cell.setAttribute('data-th', headerCells[headerIndex].innerText || headerCells[headerIndex].textContent);
}
incrementCount++;
if (incrementCount === increments[headerIndex]) {
incrementCount = 0;
headerIndex++;
}
});
});
}
}
}
}, {
key: '_onResize',
value: function _onResize() {
// debounce
clearTimeout(this._resizeTimer);
this._resizeTimer = setTimeout(this._layout, 50);
}
}, {
key: '_layout',
value: function _layout() {
var scrollable = this.props.scrollable;
var small = this.state.small;
if (scrollable && !small) {
this._alignMirror();
}
this._onResponsive();
}
}, {
key: '_alignMirror',
value: function _alignMirror() {
var mirrorElement = this.mirrorRef;
if (mirrorElement) {
var mirrorCells = immediateTableChildOnly(mirrorElement.querySelectorAll('thead tr th'), mirrorElement);
if (this.mirrorRef && mirrorCells.length > 0) {
var tableElement = this.tableRef;
var cells = immediateTableChildOnly(tableElement.querySelectorAll('thead tr th'), tableElement);
var rect = tableElement.getBoundingClientRect();
mirrorElement.style.width = '' + Math.floor(rect.right - rect.left) + 'px';
var height = 0;
for (var i = 0; i < cells.length; i++) {
rect = cells[i].getBoundingClientRect();
mirrorCells[i].style.width = '' + Math.floor(rect.right - rect.left) + 'px';
mirrorCells[i].style.height = '' + Math.floor(rect.bottom - rect.top) + 'px';
height = Math.max(height, Math.floor(rect.bottom - rect.top));
}
mirrorElement.style.height = '' + height + 'px';
}
}
}
}, {
key: 'render',
value: function render() {
var _classnames,
_this4 = this;
var _props4 = this.props,
a11yTitle = _props4.a11yTitle,
children = _props4.children,
className = _props4.className,
_onBlur = _props4.onBlur,
_onFocus = _props4.onFocus,
onMore = _props4.onMore,
_onMouseDown = _props4.onMouseDown,
_onMouseUp = _props4.onMouseUp,
responsive = _props4.responsive,
scrollable = _props4.scrollable,
selectable = _props4.selectable,
props = _objectWithoutProperties(_props4, ['a11yTitle', 'children', 'className', 'onBlur', 'onFocus', 'onMore', 'onMouseDown', 'onMouseUp', 'responsive', 'scrollable', 'selectable']);
delete props.onSelect;
delete props.selected;
var _state3 = this.state,
activeRow = _state3.activeRow,
columnMode = _state3.columnMode,
focus = _state3.focus,
mouseActive = _state3.mouseActive,
small = _state3.small;
var intl = this.context.intl;
var classes = (0, _classnames4.default)(CLASS_ROOT, (_classnames = {}, _defineProperty(_classnames, CLASS_ROOT + '--small', responsive && columnMode), _defineProperty(_classnames, CLASS_ROOT + '--selectable', selectable), _defineProperty(_classnames, CLASS_ROOT + '--scrollable', scrollable && !small), _classnames), className);
var mirror = void 0;
if (scrollable && !small) {
var head = findHead(children);
mirror = _react2.default.createElement(
'table',
{ ref: function ref(_ref) {
return _this4.mirrorRef = _ref;
},
className: CLASS_ROOT + '__mirror' },
head
);
}
var more = void 0;
if (onMore) {
more = _react2.default.createElement(
'div',
{ ref: function ref(_ref2) {
return _this4.moreRef = _ref2;
}, className: CLASS_ROOT + '__more' },
_react2.default.createElement(_Spinning2.default, null)
);
}
var selectableProps = void 0;
if (selectable) {
var multiSelectMessage = selectable === 'multiple' ? '(' + _Intl2.default.getMessage(intl, 'Multi Select') + ')' : '';
var tableMessage = a11yTitle || _Intl2.default.getMessage(intl, 'Table');
var navigationHelpMessage = _Intl2.default.getMessage(intl, 'Navigation Help');
selectableProps = {
'aria-label': tableMessage + ' ' + multiSelectMessage + ' ' + navigationHelpMessage,
tabIndex: '0',
onClick: this._onClick,
onMouseDown: function onMouseDown(event) {
_this4.setState({ mouseActive: true });
if (_onMouseDown) {
_onMouseDown(event);
}
},
onMouseUp: function onMouseUp(event) {
_this4.setState({ mouseActive: false });
if (_onMouseUp) {
_onMouseUp(event);
}
},
onFocus: function onFocus(event) {
if (mouseActive === false) {
_this4.setState({ focus: true });
}
if (_onFocus) {
_onFocus(event);
}
},
onBlur: function onBlur(event) {
if (activeRow) {
var rows = immediateTableChildOnly(_this4.tableRef.querySelectorAll('tbody tr'), _this4.tableRef);
rows[activeRow].classList.remove(ACTIVE_CLASS);
}
_this4.setState({ focus: false, activeRow: undefined });
if (_onBlur) {
_onBlur(event);
}
}
};
}
var tableClasses = (0, _classnames4.default)(CLASS_ROOT + '__table', _defineProperty({}, CLASS_ROOT + '__table--focus', focus));
return _react2.default.createElement(
'div',
_extends({ ref: function ref(_ref4) {
return _this4.containerRef = _ref4;
} }, props, { className: classes }),
mirror,
_react2.default.createElement(
'table',
_extends({ ref: function ref(_ref3) {
return _this4.tableRef = _ref3;
} }, selectableProps, {
className: tableClasses }),
children
),
more
);
}
}]);
return Table;
}(_react.Component);
Table.displayName = 'Table';
exports.default = Table;
Table.contextTypes = {
intl: _propTypes2.default.object
};
Table.propTypes = {
a11yTitle: _propTypes2.default.string,
onMore: _propTypes2.default.func,
onSelect: _propTypes2.default.func,
responsive: _propTypes2.default.bool,
scrollable: _propTypes2.default.bool,
selectable: _propTypes2.default.oneOfType([_propTypes2.default.bool, _propTypes2.default.oneOf(['multiple'])]),
selected: _propTypes2.default.oneOfType([_propTypes2.default.number, _propTypes2.default.arrayOf(_propTypes2.default.number)])
};
Table.defaultProps = {
responsive: true
};
module.exports = exports['default'];