grommet
Version:
The most advanced UX framework for enterprise applications.
673 lines (564 loc) • 23 kB
JavaScript
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
var _keys = require('babel-runtime/core-js/object/keys');
var _keys2 = _interopRequireDefault(_keys);
var _defineProperty2 = require('babel-runtime/helpers/defineProperty');
var _defineProperty3 = _interopRequireDefault(_defineProperty2);
var _from = require('babel-runtime/core-js/array/from');
var _from2 = _interopRequireDefault(_from);
var _extends2 = require('babel-runtime/helpers/extends');
var _extends3 = _interopRequireDefault(_extends2);
var _typeof2 = require('babel-runtime/helpers/typeof');
var _typeof3 = _interopRequireDefault(_typeof2);
var _stringify = require('babel-runtime/core-js/json/stringify');
var _stringify2 = _interopRequireDefault(_stringify);
var _getPrototypeOf = require('babel-runtime/core-js/object/get-prototype-of');
var _getPrototypeOf2 = _interopRequireDefault(_getPrototypeOf);
var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck');
var _classCallCheck3 = _interopRequireDefault(_classCallCheck2);
var _createClass2 = require('babel-runtime/helpers/createClass');
var _createClass3 = _interopRequireDefault(_createClass2);
var _possibleConstructorReturn2 = require('babel-runtime/helpers/possibleConstructorReturn');
var _possibleConstructorReturn3 = _interopRequireDefault(_possibleConstructorReturn2);
var _inherits2 = require('babel-runtime/helpers/inherits');
var _inherits3 = _interopRequireDefault(_inherits2);
var _react = require('react');
var _react2 = _interopRequireDefault(_react);
var _reactDom = require('react-dom');
var _classnames2 = require('classnames');
var _classnames3 = _interopRequireDefault(_classnames2);
var _Props = require('../utils/Props');
var _Props2 = _interopRequireDefault(_Props);
var _Box = require('./Box');
var _Box2 = _interopRequireDefault(_Box);
var _Button = require('./Button');
var _Button2 = _interopRequireDefault(_Button);
var _Spinning = require('./icons/Spinning');
var _Spinning2 = _interopRequireDefault(_Spinning);
var _Scroll = require('../utils/Scroll');
var _Scroll2 = _interopRequireDefault(_Scroll);
var _InfiniteScroll = require('../utils/InfiniteScroll');
var _InfiniteScroll2 = _interopRequireDefault(_InfiniteScroll);
var _Selection = require('../utils/Selection');
var _Selection2 = _interopRequireDefault(_Selection);
var _KeyboardAccelerators = require('../utils/KeyboardAccelerators');
var _KeyboardAccelerators2 = _interopRequireDefault(_KeyboardAccelerators);
var _Intl = require('../utils/Intl');
var _Intl2 = _interopRequireDefault(_Intl);
var _Announcer = require('../utils/Announcer');
var _LinkPrevious = require('./icons/base/LinkPrevious');
var _LinkPrevious2 = _interopRequireDefault(_LinkPrevious);
var _LinkNext = require('./icons/base/LinkNext');
var _LinkNext2 = _interopRequireDefault(_LinkNext);
var _CSSClassnames = require('../utils/CSSClassnames');
var _CSSClassnames2 = _interopRequireDefault(_CSSClassnames);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
// (C) Copyright 2014-2016 Hewlett Packard Enterprise Development LP
var CLASS_ROOT = _CSSClassnames2.default.TILES;
var TILE = _CSSClassnames2.default.TILE;
var SELECTED_CLASS = TILE + '--selected';
var ACTIVE_CLASS = TILE + '--active';
var Tiles = function (_Component) {
(0, _inherits3.default)(Tiles, _Component);
function Tiles(props, context) {
(0, _classCallCheck3.default)(this, Tiles);
var _this = (0, _possibleConstructorReturn3.default)(this, (Tiles.__proto__ || (0, _getPrototypeOf2.default)(Tiles)).call(this, props, context));
_this._onLeft = _this._onLeft.bind(_this);
_this._onRight = _this._onRight.bind(_this);
_this._onScrollHorizontal = _this._onScrollHorizontal.bind(_this);
_this._onWheel = _this._onWheel.bind(_this);
_this._onResize = _this._onResize.bind(_this);
_this._layout = _this._layout.bind(_this);
_this._onClick = _this._onClick.bind(_this);
_this._fireClick = _this._fireClick.bind(_this);
_this._announceTile = _this._announceTile.bind(_this);
_this._onPreviousTile = _this._onPreviousTile.bind(_this);
_this._onNextTile = _this._onNextTile.bind(_this);
_this._onEnter = _this._onEnter.bind(_this);
_this.state = {
activeTile: undefined,
mouseActive: false,
overflow: false,
selected: _Selection2.default.normalizeIndexes(props.selected)
};
return _this;
}
(0, _createClass3.default)(Tiles, [{
key: 'componentDidMount',
value: function componentDidMount() {
var _props = this.props,
direction = _props.direction,
onMore = _props.onMore,
selectable = _props.selectable;
this._setSelection();
if (onMore) {
this._scroll = _InfiniteScroll2.default.startListeningForScroll(this.moreRef, onMore);
}
if ('row' === direction) {
window.addEventListener('resize', this._onResize);
document.addEventListener('wheel', this._onWheel, { passive: true });
this._trackHorizontalScroll();
// give browser a chance to stabilize
setTimeout(this._layout, 10);
}
if (selectable) {
// only listen for navigation keys if the tile row can be selected
this._keyboardHandlers = {
left: this._onPreviousTile,
up: this._onPreviousTile,
right: this._onNextTile,
down: this._onNextTile,
enter: this._onEnter,
space: this._onEnter
};
_KeyboardAccelerators2.default.startListeningToKeyboard(this, this._keyboardHandlers);
}
}
}, {
key: 'componentWillReceiveProps',
value: function componentWillReceiveProps(nextProps) {
if (nextProps.selected !== undefined) {
this.setState({
selected: _Selection2.default.normalizeIndexes(nextProps.selected)
});
}
if (this._scroll) {
_InfiniteScroll2.default.stopListeningForScroll(this._scroll);
this._scroll = undefined;
}
}
}, {
key: 'componentDidUpdate',
value: function componentDidUpdate(prevProps, prevState) {
var _props2 = this.props,
direction = _props2.direction,
onMore = _props2.onMore,
selectable = _props2.selectable;
var selected = this.state.selected;
if ((0, _stringify2.default)(selected) !== (0, _stringify2.default)(prevState.selected)) {
this._setSelection();
}
if (onMore && !this._scroll) {
this._scroll = _InfiniteScroll2.default.startListeningForScroll(this.moreRef, onMore);
}
if ('row' === direction) {
this._trackHorizontalScroll();
// give browser a chance to stabilize
setTimeout(this._layout, 10);
}
if (selectable) {
// only listen for navigation keys if the list row can be selected
this._keyboardHandlers = {
left: this._onPreviousTile,
up: this._onPreviousTile,
right: this._onNextTile,
down: this._onNextTile,
enter: this._onEnter,
space: this._onEnter
};
_KeyboardAccelerators2.default.startListeningToKeyboard(this, this._keyboardHandlers);
}
}
}, {
key: 'componentWillUnmount',
value: function componentWillUnmount() {
var _props3 = this.props,
direction = _props3.direction,
selectable = _props3.selectable;
if (this._scroll) {
_InfiniteScroll2.default.stopListeningForScroll(this._scroll);
}
if ('row' === direction) {
window.removeEventListener('resize', this._onResize);
document.removeEventListener('wheel', this._onWheel);
if (this._tracking) {
var tiles = (0, _reactDom.findDOMNode)(this.tilesRef);
tiles.removeEventListener('scroll', this._onScrollHorizontal);
}
}
if (selectable) {
_KeyboardAccelerators2.default.stopListeningToKeyboard(this, this._keyboardHandlers);
}
}
}, {
key: '_announceTile',
value: function _announceTile(label) {
var intl = this.context.intl;
var enterSelectMessage = _Intl2.default.getMessage(intl, 'Enter Select');
// avoid a long text to be read by the screen reader
var labelMessage = label.length > 15 ? label.substring(0, 15) + '...' : label;
(0, _Announcer.announce)(labelMessage + ' ' + enterSelectMessage);
}
}, {
key: '_onPreviousTile',
value: function _onPreviousTile(event) {
var _this2 = this;
if ((0, _reactDom.findDOMNode)(this.tilesRef).contains(document.activeElement)) {
var _ret = function () {
event.preventDefault();
var activeTile = _this2.state.activeTile;
var rows = (0, _reactDom.findDOMNode)(_this2.tilesRef).querySelectorAll('.' + TILE);
if (rows && rows.length > 0) {
if (activeTile === undefined) {
rows[0].classList.add(ACTIVE_CLASS);
_this2.setState({ activeTile: 0 }, function () {
_this2._announceTile(rows[_this2.state.activeTile].innerText);
});
} else if (activeTile - 1 >= 0) {
rows[activeTile].classList.remove(ACTIVE_CLASS);
rows[activeTile - 1].classList.add(ACTIVE_CLASS);
_this2.setState({ activeTile: activeTile - 1 }, function () {
_this2._announceTile(rows[_this2.state.activeTile].innerText);
});
}
}
//stop event propagation
return {
v: true
};
}();
if ((typeof _ret === 'undefined' ? 'undefined' : (0, _typeof3.default)(_ret)) === "object") return _ret.v;
}
}
}, {
key: '_onNextTile',
value: function _onNextTile(event) {
var _this3 = this;
if ((0, _reactDom.findDOMNode)(this.tilesRef).contains(document.activeElement)) {
var _ret2 = function () {
event.preventDefault();
var activeTile = _this3.state.activeTile;
var rows = (0, _reactDom.findDOMNode)(_this3.tilesRef).querySelectorAll('.' + TILE);
if (rows && rows.length > 0) {
if (activeTile === undefined) {
rows[0].classList.add(ACTIVE_CLASS);
_this3.setState({ activeTile: 0 }, function () {
_this3._announceTile(rows[_this3.state.activeTile].innerText);
});
} else if (activeTile + 1 <= rows.length - 1) {
rows[activeTile].classList.remove(ACTIVE_CLASS);
rows[activeTile + 1].classList.add(ACTIVE_CLASS);
_this3.setState({ activeTile: activeTile + 1 }, function () {
_this3._announceTile(rows[_this3.state.activeTile].innerText);
});
}
}
//stop event propagation
return {
v: true
};
}();
if ((typeof _ret2 === 'undefined' ? 'undefined' : (0, _typeof3.default)(_ret2)) === "object") return _ret2.v;
}
}
}, {
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 activeTile = this.state.activeTile;
var intl = this.context.intl;
if ((0, _reactDom.findDOMNode)(this.tilesRef).contains(document.activeElement) && activeTile !== undefined) {
var rows = (0, _reactDom.findDOMNode)(this.tilesRef).querySelectorAll('.' + TILE);
this._fireClick(rows[activeTile], event.shiftKey);
rows[activeTile].classList.remove(ACTIVE_CLASS);
var label = rows[activeTile].innerText;
// avoid a long text to be read by the screen reader
var labelMessage = label.length > 15 ? label.substring(0, 15) + '...' : label;
var selectedMessage = _Intl2.default.getMessage(intl, 'Selected');
(0, _Announcer.announce)(labelMessage + ' ' + selectedMessage);
}
}
}, {
key: '_onLeft',
value: function _onLeft() {
var tiles = (0, _reactDom.findDOMNode)(this.tilesRef);
_Scroll2.default.scrollBy(tiles, 'scrollLeft', -tiles.offsetWidth);
}
}, {
key: '_onRight',
value: function _onRight() {
var tiles = (0, _reactDom.findDOMNode)(this.tilesRef);
_Scroll2.default.scrollBy(tiles, 'scrollLeft', tiles.offsetWidth);
}
}, {
key: '_onScrollHorizontal',
value: function _onScrollHorizontal() {
// debounce
clearTimeout(this._scrollTimer);
this._scrollTimer = setTimeout(this._layout, 50);
}
}, {
key: '_onWheel',
value: function _onWheel(event) {
if (Math.abs(event.deltaX) > 100) {
clearInterval(this._scrollTimer);
} else if (event.deltaX > 5) {
this._onRight();
} else if (event.deltaX < -5) {
this._onLeft();
}
}
}, {
key: '_layout',
value: function _layout() {
var _this4 = this;
var direction = this.props.direction;
if ('row' === direction) {
(function () {
// determine if we have more tiles than room to fit
var tiles = (0, _reactDom.findDOMNode)(_this4.tilesRef);
// 20 is to allow some fuzziness as scrollbars come and go
var newState = {
overflow: tiles.scrollWidth > tiles.offsetWidth + 20,
overflowStart: tiles.scrollLeft <= 20,
overflowEnd: tiles.scrollLeft >= tiles.scrollWidth - tiles.offsetWidth,
scrollWidth: tiles.scrollWidth
};
var state = {
overflow: _this4.state.overflow,
overflowStart: _this4.state.overflowStart,
overflowEnd: _this4.state.overflowEnd,
scrollWidth: _this4.state.scrollWidth
};
// Shallow compare states.
if ((0, _stringify2.default)(newState) !== (0, _stringify2.default)(state)) {
_this4.setState((0, _extends3.default)({}, newState));
}
// mark any tiles that might be clipped
var rect = tiles.getBoundingClientRect();
var children = tiles.querySelectorAll('.' + TILE);
(0, _from2.default)(children).map(function (child, index) {
var childRect = child.getBoundingClientRect();
// 12 accounts for padding
if (childRect.left + 12 < rect.left || childRect.right - 12 > rect.right) {
child.classList.add(TILE + '--eclipsed');
} else {
child.classList.remove(TILE + '--eclipsed');
}
});
})();
}
}
}, {
key: '_renderChild',
value: function _renderChild(element) {
var flush = this.props.flush;
if (element) {
// only clone tile children
if (element.type && element.type.displayName === 'Tile') {
var elementClone = _react2.default.cloneElement(element, {
hoverBorder: !flush
});
return elementClone;
}
return element;
}
return undefined;
}
}, {
key: '_onResize',
value: function _onResize() {
// debounce
clearTimeout(this._resizeTimer);
this._resizeTimer = setTimeout(this._layout, 50);
}
}, {
key: '_trackHorizontalScroll',
value: function _trackHorizontalScroll() {
var overflow = this.state.overflow;
if (overflow && !this._tracking) {
var tiles = (0, _reactDom.findDOMNode)(this.tilesRef);
tiles.addEventListener('scroll', this._onScrollHorizontal);
this._tracking = true;
}
}
}, {
key: '_setSelection',
value: function _setSelection() {
_Selection2.default.setClassFromIndexes({
containerElement: (0, _reactDom.findDOMNode)(this.tilesRef),
childSelector: '.' + TILE,
selectedClass: SELECTED_CLASS,
selectedIndexes: this.state.selected
});
}
}, {
key: '_onClick',
value: function _onClick(event) {
var _props4 = this.props,
onSelect = _props4.onSelect,
selectable = _props4.selectable,
selected = _props4.selected;
var selection = _Selection2.default.onClick(event, {
containerElement: (0, _reactDom.findDOMNode)(this.tilesRef),
childSelector: '.' + TILE,
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);
}
}
// children should be an array of Tile
}, {
key: 'render',
value: function render() {
var _classnames,
_this5 = this;
var _props5 = this.props,
a11yTitle = _props5.a11yTitle,
className = _props5.className,
children = _props5.children,
direction = _props5.direction,
fill = _props5.fill,
flush = _props5.flush,
_onBlur = _props5.onBlur,
_onFocus = _props5.onFocus,
onMore = _props5.onMore,
_onMouseDown = _props5.onMouseDown,
_onMouseUp = _props5.onMouseUp,
selectable = _props5.selectable;
var _state = this.state,
activeTile = _state.activeTile,
focus = _state.focus,
mouseActive = _state.mouseActive,
overflow = _state.overflow,
overflowEnd = _state.overflowEnd,
overflowStart = _state.overflowStart;
var intl = this.context.intl;
var classes = (0, _classnames3.default)(CLASS_ROOT, (_classnames = {}, (0, _defineProperty3.default)(_classnames, CLASS_ROOT + '--fill', fill), (0, _defineProperty3.default)(_classnames, CLASS_ROOT + '--flush', flush), (0, _defineProperty3.default)(_classnames, CLASS_ROOT + '--focus', focus), (0, _defineProperty3.default)(_classnames, CLASS_ROOT + '--selectable', selectable), (0, _defineProperty3.default)(_classnames, CLASS_ROOT + '--moreable', onMore), (0, _defineProperty3.default)(_classnames, CLASS_ROOT + '--overflowed', overflow), _classnames), className);
var other = _Props2.default.pick(this.props, (0, _keys2.default)(_Box2.default.propTypes));
var more = void 0;
if (onMore) {
more = _react2.default.createElement(
'div',
{ ref: function ref(_ref) {
return _this5.moreRef = _ref;
}, className: CLASS_ROOT + '__more' },
_react2.default.createElement(_Spinning2.default, null)
);
}
var tileContents = _react.Children.map(children, function (element) {
return _this5._renderChild(element);
});
var selectableProps = void 0;
if (selectable) {
var multiSelectMessage = selectable === 'multiple' ? '(' + _Intl2.default.getMessage(intl, 'Multi Select') + ')' : '';
var tilesMessage = a11yTitle || _Intl2.default.getMessage(intl, 'Tiles');
var navigationHelpMessage = _Intl2.default.getMessage(intl, 'Navigation Help');
selectableProps = {
'aria-label': tilesMessage + ' ' + multiSelectMessage + ' ' + navigationHelpMessage,
tabIndex: '0',
onClick: this._onClick,
onMouseDown: function onMouseDown(event) {
_this5.setState({ mouseActive: true });
if (_onMouseDown) {
_onMouseDown(event);
}
},
onMouseUp: function onMouseUp(event) {
_this5.setState({ mouseActive: false });
if (_onMouseUp) {
_onMouseUp(event);
}
},
onFocus: function onFocus(event) {
if (mouseActive === false) {
_this5.setState({ focus: true });
}
if (_onFocus) {
_onFocus(event);
}
},
onBlur: function onBlur(event) {
if (activeTile) {
var rows = (0, _reactDom.findDOMNode)(_this5.tilesRef).querySelectorAll('.' + TILE);
rows[activeTile].classList.remove(ACTIVE_CLASS);
}
_this5.setState({ focus: false, activeTile: undefined });
if (_onBlur) {
_onBlur(event);
}
}
};
}
var contents = _react2.default.createElement(
_Box2.default,
(0, _extends3.default)({ ref: function ref(_ref2) {
return _this5.tilesRef = _ref2;
} }, other, {
wrap: direction ? false : true,
direction: direction ? direction : 'row',
className: classes, focusable: false }, selectableProps),
tileContents,
more
);
if (overflow) {
var left = void 0;
var right = void 0;
if (!overflowStart) {
var previousTilesMessage = _Intl2.default.getMessage(intl, 'Previous Tiles');
left = _react2.default.createElement(_Button2.default, { className: CLASS_ROOT + '__left', icon: _react2.default.createElement(_LinkPrevious2.default, null),
a11yTitle: previousTilesMessage, onClick: this._onLeft });
}
if (!overflowEnd) {
var nextTilesMessage = _Intl2.default.getMessage(intl, 'Next Tiles');
right = _react2.default.createElement(_Button2.default, { className: CLASS_ROOT + '__right', icon: _react2.default.createElement(_LinkNext2.default, null),
a11yTitle: nextTilesMessage, onClick: this._onRight });
}
contents = _react2.default.createElement(
'div',
{ className: CLASS_ROOT + '__container' },
left,
contents,
right
);
}
return contents;
}
}]);
return Tiles;
}(_react.Component);
Tiles.displayName = 'Tiles';
exports.default = Tiles;
Tiles.contextTypes = {
intl: _react.PropTypes.object
};
Tiles.propTypes = (0, _extends3.default)({
fill: _react.PropTypes.bool,
flush: _react.PropTypes.bool,
onMore: _react.PropTypes.func,
onSelect: _react.PropTypes.func,
selectable: _react.PropTypes.oneOfType([_react.PropTypes.bool, _react.PropTypes.oneOf(['multiple'])]),
selected: _react.PropTypes.oneOfType([_react.PropTypes.number, _react.PropTypes.arrayOf(_react.PropTypes.number)])
}, _Box2.default.propTypes);
Tiles.defaultProps = {
flush: true,
justify: 'start',
pad: 'small'
};
module.exports = exports['default'];