grommet
Version:
The most advanced UX framework for enterprise applications.
611 lines (521 loc) • 20.4 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 _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 _extends2 = require('babel-runtime/helpers/extends');
var _extends3 = _interopRequireDefault(_extends2);
var _objectWithoutProperties2 = require('babel-runtime/helpers/objectWithoutProperties');
var _objectWithoutProperties3 = _interopRequireDefault(_objectWithoutProperties2);
var _react = require('react');
var _react2 = _interopRequireDefault(_react);
var _reactDom = require('react-dom');
var _classnames5 = require('classnames');
var _classnames6 = _interopRequireDefault(_classnames5);
var _Status = require('./icons/Status');
var _Status2 = _interopRequireDefault(_Status);
var _CSSClassnames = require('../utils/CSSClassnames');
var _CSSClassnames2 = _interopRequireDefault(_CSSClassnames);
var _Intl = require('../utils/Intl');
var _Intl2 = _interopRequireDefault(_Intl);
var _Announcer = require('../utils/Announcer');
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
var CLASS_ROOT = _CSSClassnames2.default.TOPOLOGY; // (C) Copyright 2014-2016 Hewlett Packard Enterprise Development LP
var STATUS_ICON = _CSSClassnames2.default.STATUS_ICON;
var COLOR_INDEX = _CSSClassnames2.default.COLOR_INDEX;
var BACKGROUND_COLOR_INDEX = _CSSClassnames2.default.BACKGROUND_COLOR_INDEX;
var Label = function Label(props) {
var children = props.children,
restProps = (0, _objectWithoutProperties3.default)(props, ['children']);
return _react2.default.createElement(
'span',
(0, _extends3.default)({}, restProps, { className: CLASS_ROOT + '__label' }),
children
);
};
var Part = function (_Component) {
(0, _inherits3.default)(Part, _Component);
function Part() {
(0, _classCallCheck3.default)(this, Part);
return (0, _possibleConstructorReturn3.default)(this, (Part.__proto__ || (0, _getPrototypeOf2.default)(Part)).apply(this, arguments));
}
(0, _createClass3.default)(Part, [{
key: 'render',
value: function render() {
var _classnames,
_this2 = this;
var _props = this.props,
a11yTitle = _props.a11yTitle,
align = _props.align,
children = _props.children,
className = _props.className,
demarcate = _props.demarcate,
direction = _props.direction,
justify = _props.justify,
label = _props.label,
reverse = _props.reverse,
status = _props.status,
props = (0, _objectWithoutProperties3.default)(_props, ['a11yTitle', 'align', 'children', 'className', 'demarcate', 'direction', 'justify', 'label', 'reverse', 'status']);
var intl = this.context.intl;
var realChildren = 0;
_react.Children.forEach(children, function (child) {
if (child) {
realChildren += 1;
}
});
var classes = (0, _classnames6.default)(CLASS_ROOT + '__part', (_classnames = {}, (0, _defineProperty3.default)(_classnames, CLASS_ROOT + '__part--direction-' + direction, direction), (0, _defineProperty3.default)(_classnames, CLASS_ROOT + '__part--justify-' + justify, justify), (0, _defineProperty3.default)(_classnames, CLASS_ROOT + '__part--align-' + align, align), (0, _defineProperty3.default)(_classnames, CLASS_ROOT + '__part--demarcate', demarcate), (0, _defineProperty3.default)(_classnames, CLASS_ROOT + '__part--reverse', reverse), (0, _defineProperty3.default)(_classnames, CLASS_ROOT + '__part--empty', !status && !label && realChildren === 0), _classnames), className);
var statusIcon = void 0;
if (status) {
statusIcon = _react2.default.createElement(_Status2.default, { value: status, size: 'small', role: 'presentation',
'aria-hidden': 'true' });
}
var labelLabel = void 0;
if (label) {
var hiddenProps = void 0;
if (status) {
// hide label if status is present and let aria-label handle
// description
hiddenProps = {
role: 'presentation',
'aria-hidden': true
};
}
labelLabel = _react2.default.createElement(
Label,
hiddenProps,
label
);
}
var role = !status && !label ? 'group' : 'row';
var partMessage = a11yTitle || (role === 'group' ? _Intl2.default.getMessage(intl, 'Part') : (status || '') + ' ' + label);
return _react2.default.createElement(
'div',
(0, _extends3.default)({}, props, { ref: function ref(_ref) {
return _this2._partRef = _ref;
}, className: classes,
onMouseEnter: this.props.onMouseEnter,
onMouseLeave: this.props.onMouseLeave,
tabIndex: '-1', role: role, 'aria-label': partMessage,
onFocus: function onFocus() {
if (_this2._partRef) {
var connects = _this2._partRef.getAttribute('data-connects');
if (connects) {
(0, _Announcer.announce)(connects, 'polite');
}
}
} }),
statusIcon,
labelLabel,
children
);
}
}]);
return Part;
}(_react.Component);
Part.displayName = 'Part';
Part.contextTypes = {
intl: _react.PropTypes.object
};
Part.propTypes = {
a11yTitle: _react.PropTypes.string,
align: _react.PropTypes.oneOf(['start', 'center', 'between', 'end', 'stretch']),
demarcate: _react.PropTypes.bool,
direction: _react.PropTypes.oneOf(['row', 'column']).isRequired,
id: _react.PropTypes.string,
justify: _react.PropTypes.oneOf(['start', 'center', 'between', 'end']),
label: _react.PropTypes.string,
reverse: _react.PropTypes.bool,
status: _react.PropTypes.string
};
Part.defaultProps = {
demarcate: true,
direction: 'row',
justify: 'center',
align: 'stretch'
};
var Parts = function (_Component2) {
(0, _inherits3.default)(Parts, _Component2);
function Parts() {
(0, _classCallCheck3.default)(this, Parts);
return (0, _possibleConstructorReturn3.default)(this, (Parts.__proto__ || (0, _getPrototypeOf2.default)(Parts)).apply(this, arguments));
}
(0, _createClass3.default)(Parts, [{
key: 'componentDidMount',
value: function componentDidMount() {
this._makeUniform();
}
}, {
key: 'componentDidUpdate',
value: function componentDidUpdate() {
this._makeUniform();
}
}, {
key: '_makeUniform',
value: function _makeUniform() {
var _props2 = this.props,
direction = _props2.direction,
uniform = _props2.uniform;
if (uniform) {
var parts = this._componentRef.children;
// clear old basis
for (var i = 0; i < parts.length; i += 1) {
parts[i].style.flexBasis = null;
}
// find max
var max = 0;
for (var _i = 0; _i < parts.length; _i += 1) {
if ('column' === direction) {
max = Math.max(max, parts[_i].offsetHeight);
} else {
max = Math.max(max, parts[_i].offsetWidth);
}
}
// set basis
for (var _i2 = 0; _i2 < parts.length; _i2 += 1) {
parts[_i2].style.flexBasis = '' + max + 'px';
}
}
}
}, {
key: 'render',
value: function render() {
var _classnames2,
_this4 = this;
var _props3 = this.props,
a11yTitle = _props3.a11yTitle,
align = _props3.align,
children = _props3.children,
className = _props3.className,
direction = _props3.direction;
var intl = this.context.intl;
var classes = (0, _classnames6.default)(CLASS_ROOT + '__parts', (_classnames2 = {}, (0, _defineProperty3.default)(_classnames2, CLASS_ROOT + '__parts--direction-' + direction, direction), (0, _defineProperty3.default)(_classnames2, CLASS_ROOT + '__part--align-' + align, align), _classnames2), className);
var partsMessage = a11yTitle || _Intl2.default.getMessage(intl, 'Parts');
return _react2.default.createElement(
'div',
{ ref: function ref(_ref2) {
return _this4._componentRef = _ref2;
}, className: classes,
tabIndex: '-1', role: 'rowgroup', 'aria-label': partsMessage },
children
);
}
}]);
return Parts;
}(_react.Component);
Parts.displayName = 'Parts';
Parts.contextTypes = {
intl: _react.PropTypes.object
};
Parts.propTypes = {
align: _react.PropTypes.oneOf(['start', 'center', 'between', 'end', 'stretch']),
direction: _react.PropTypes.oneOf(['row', 'column']).isRequired,
uniform: _react.PropTypes.bool
};
Parts.defaultProps = {
direction: 'column'
};
var Topology = function (_Component3) {
(0, _inherits3.default)(Topology, _Component3);
function Topology(props, context) {
(0, _classCallCheck3.default)(this, Topology);
var _this5 = (0, _possibleConstructorReturn3.default)(this, (Topology.__proto__ || (0, _getPrototypeOf2.default)(Topology)).call(this, props, context));
_this5._layout = _this5._layout.bind(_this5);
_this5._onResize = _this5._onResize.bind(_this5);
_this5._onMouseMove = _this5._onMouseMove.bind(_this5);
_this5._onMouseLeave = _this5._onMouseLeave.bind(_this5);
_this5.state = {
activeIds: {},
height: 100,
mouseActive: false,
paths: [],
width: 100
};
return _this5;
}
(0, _createClass3.default)(Topology, [{
key: 'componentDidMount',
value: function componentDidMount() {
var links = this.props.links;
var intl = this.context.intl;
window.addEventListener('resize', this._onResize);
this._layout();
if (links && links.length > 0) {
(function () {
var connectsMap = {};
links.forEach(function (link) {
var startId = link.ids[0];
var startElement = document.getElementById(startId);
var endId = link.ids[1];
var endElement = document.getElementById(endId);
if (startElement && endElement) {
var startLabel = startElement.getAttribute('aria-label') || startElement.innerText;
var endLabel = endElement.getAttribute('aria-label') || end.innerText;
if (connectsMap[startId]) {
connectsMap[startId].push(endLabel);
} else {
connectsMap[startId] = [endLabel];
}
if (connectsMap[endId]) {
connectsMap[endId].push(startLabel);
} else {
connectsMap[endId] = [startLabel];
}
}
});
(0, _keys2.default)(connectsMap).forEach(function (element) {
var targetElement = document.getElementById(element);
var connectsMessage = _Intl2.default.getMessage(intl, 'Connects With');
targetElement.setAttribute('data-connects', connectsMessage + ': (' + connectsMap[element].join() + ')');
});
})();
}
}
}, {
key: 'componentWillReceiveProps',
value: function componentWillReceiveProps(nextProps) {
this._layout();
}
}, {
key: 'componentWillUnmount',
value: function componentWillUnmount() {
clearTimeout(this._resizeTimer);
window.removeEventListener('resize', this._onResize);
}
}, {
key: '_coords',
value: function _coords(id, containerRect) {
var result;
var element = document.getElementById(id);
if (!element) {
console.log('!!! Topology is unable to find the link target with id:', id);
result = [0, 0];
} else {
var rect = element.getBoundingClientRect();
// see if the element has a status child, use that if it does
var statusElements = element.querySelectorAll('.' + STATUS_ICON);
if (statusElements.length === 1) {
rect = statusElements[0].getBoundingClientRect();
}
result = [rect.left - containerRect.left + rect.width / 2, rect.top - containerRect.top + rect.height / 2];
}
return result;
}
}, {
key: '_buildPaths',
value: function _buildPaths(contents) {
var _this6 = this;
var _props4 = this.props,
linkOffset = _props4.linkOffset,
links = _props4.links;
var activeIds = this.state.activeIds;
var rect = contents.getBoundingClientRect();
var paths = links.map(function (link, linkIndex) {
var _classnames3;
var commands = '';
var active = false;
var p1 = _this6._coords(link.ids[0], rect);
link.ids.forEach(function (id, idIndex) {
if (activeIds[id]) {
active = true;
}
if (idIndex > 0) {
var p2 = _this6._coords(id, rect);
var delta = [Math.abs(p1[0] - p2[0]), Math.abs(p1[1] - p2[1])];
commands += ' M' + p1[0] + ',' + p1[1];
var cp1 = void 0;
var cp2 = void 0;
if (delta[0] > delta[1]) {
// larger X delta
cp1 = [p1[0], Math.min(p1[1], p2[1]) + Math.max(linkOffset, delta[1] / 2) + linkIndex * 2];
cp2 = [p2[0], cp1[1]];
} else {
// larger Y delta or equal
var cp1xDelta = Math.max(linkOffset, delta[0] / 2 + linkIndex * 2);
if (p1[0] > p2[0]) {
cp1 = [Math.min(p2[0] + cp1xDelta, rect.width), p1[1]];
} else {
cp1 = [Math.max(0, p1[0] - cp1xDelta), p1[1]];
}
cp2 = [cp1[0], p2[1]];
}
commands += ' C' + cp1[0] + ',' + cp1[1] + ' ' + cp2[0] + ',' + cp2[1] + ' ' + p2[0] + ',' + p2[1];
p1 = p2;
}
});
var classes = (0, _classnames6.default)(CLASS_ROOT + '__path', (_classnames3 = {}, (0, _defineProperty3.default)(_classnames3, CLASS_ROOT + '__path--active', active), (0, _defineProperty3.default)(_classnames3, COLOR_INDEX + '-' + link.colorIndex, link.colorIndex), _classnames3));
return _react2.default.createElement('path', { key: linkIndex, fill: 'none', className: classes, d: commands });
});
return paths;
}
}, {
key: '_layout',
value: function _layout() {
var contents = (0, _reactDom.findDOMNode)(this._contentsRef);
if (contents) {
this.setState({
width: contents.scrollWidth,
height: contents.scrollHeight,
paths: this._buildPaths(contents)
});
}
}
}, {
key: '_onResize',
value: function _onResize() {
// debounce
clearTimeout(this._resizeTimer);
this._resizeTimer = setTimeout(this._layout, 50);
}
}, {
key: '_activate',
value: function _activate(element) {
var topology = this._topologyRef;
var activeIds = {};
while (element && element !== topology) {
var id = element.getAttribute('id');
if (id) {
activeIds[id] = true;
}
element = element.parentNode;
}
this.setState({ activeIds: activeIds }, this._layout);
}
}, {
key: '_onMouseMove',
value: function _onMouseMove(event) {
// debounce
clearTimeout(this._mouseMoveTimer);
this._mouseMoveTimer = setTimeout(this._activate.bind(this, event.target), 100);
}
}, {
key: '_onMouseLeave',
value: function _onMouseLeave() {
clearTimeout(this._mouseMoveTimer);
this.setState({ activeIds: {} }, this._layout);
}
}, {
key: 'render',
value: function render() {
var _this7 = this;
var _props5 = this.props,
a11yTitle = _props5.a11yTitle,
children = _props5.children,
className = _props5.className,
links = _props5.links,
_onBlur = _props5.onBlur,
_onFocus = _props5.onFocus,
_onMouseDown = _props5.onMouseDown,
_onMouseUp = _props5.onMouseUp,
props = (0, _objectWithoutProperties3.default)(_props5, ['a11yTitle', 'children', 'className', 'links', 'onBlur', 'onFocus', 'onMouseDown', 'onMouseUp']);
delete props.linkOffset;
var _state = this.state,
focus = _state.focus,
height = _state.height,
mouseActive = _state.mouseActive,
paths = _state.paths,
width = _state.width;
var intl = this.context.intl;
var classes = (0, _classnames6.default)(CLASS_ROOT, (0, _defineProperty3.default)({}, CLASS_ROOT + '--focus', focus), className);
var colorKeys = [];
var colors = {};
links.forEach(function (link) {
if (link.colorIndex && !colors[link.colorIndex]) {
colorKeys.push(_react2.default.createElement('div', { key: link.colorIndex,
className: BACKGROUND_COLOR_INDEX + '-' + link.colorIndex }));
colors[link.colorIndex] = true;
}
});
var topologyMessage = a11yTitle || _Intl2.default.getMessage(intl, 'Topology');
return _react2.default.createElement(
'div',
(0, _extends3.default)({ ref: function ref(_ref4) {
return _this7._topologyRef = _ref4;
} }, props, { className: classes,
'aria-label': topologyMessage, tabIndex: '0', role: 'group',
onMouseDown: function onMouseDown(event) {
_this7.setState({ mouseActive: true });
if (_onMouseDown) {
_onMouseDown(event);
}
},
onMouseUp: function onMouseUp(event) {
_this7.setState({ mouseActive: false });
if (_onMouseUp) {
_onMouseUp(event);
}
},
onFocus: function onFocus(event) {
if (mouseActive === false) {
_this7.setState({ focus: true });
}
if (_onFocus) {
_onFocus(event);
}
},
onBlur: function onBlur(event) {
_this7.setState({ focus: false });
if (_onBlur) {
_onBlur(event);
}
} }),
_react2.default.createElement(
'svg',
{ className: CLASS_ROOT + '__links', role: 'presentation',
width: width, height: height, viewBox: '0 0 ' + width + ' ' + height,
preserveAspectRatio: 'xMidYMid meet' },
paths
),
_react2.default.createElement(
'div',
{ ref: function ref(_ref3) {
return _this7._contentsRef = _ref3;
},
className: CLASS_ROOT + '__contents',
onMouseMove: this._onMouseMove,
onMouseLeave: this._onMouseLeave },
children
),
_react2.default.createElement(
'div',
{ className: CLASS_ROOT + '__color-key', role: 'presentation' },
colorKeys
)
);
}
}]);
return Topology;
}(_react.Component);
Topology.displayName = 'Topology';
exports.default = Topology;
Topology.contextTypes = {
intl: _react.PropTypes.object
};
Topology.propTypes = {
a11yTitle: _react.PropTypes.string,
links: _react.PropTypes.arrayOf(_react.PropTypes.shape({
colorIndex: _react.PropTypes.string,
ids: _react.PropTypes.arrayOf(_react.PropTypes.string).isRequired
})),
linkOffset: _react.PropTypes.number
};
Topology.defaultProps = {
links: [],
linkOffset: 18
};
Topology.Parts = Parts;
Topology.Part = Part;
Topology.Label = Label;
module.exports = exports['default'];