react-mega-menu
Version:
react-mega-menu React component
245 lines (244 loc) • 9.57 kB
JavaScript
"use strict";
var __extends = undefined && undefined.__extends || function () {
var _extendStatics = function extendStatics(d, b) {
_extendStatics = Object.setPrototypeOf || { __proto__: [] } instanceof Array && function (d, b) {
d.__proto__ = b;
} || function (d, b) {
for (var p in b) {
if (b.hasOwnProperty(p)) d[p] = b[p];
}
};
return _extendStatics(d, b);
};
return function (d, b) {
_extendStatics(d, b);
function __() {
this.constructor = d;
}
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
}();
var __assign = undefined && undefined.__assign || function () {
__assign = Object.assign || function (t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) {
if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p];
}
}
return t;
};
return __assign.apply(this, arguments);
};
Object.defineProperty(exports, "__esModule", { value: true });
var React = require("react");
var glamor_1 = require("glamor");
/**
* SUB-COMPONENTS
*/
function MenuItem(_a) {
var selected = _a.selected,
label = _a.label,
mouseEntered = _a.mouseEntered,
hasData = _a.hasData,
props = _a.props,
selectedProps = _a.selectedProps;
return React.createElement("li", __assign({}, props, selected ? selectedProps : {}, { onMouseEnter: mouseEntered }), label);
}
exports.MenuItem = MenuItem;
/**
* MAIN COMPONENT
*/
var ReactMegaMenu = /** @class */function (_super) {
__extends(ReactMegaMenu, _super);
function ReactMegaMenu() {
var _this = _super !== null && _super.apply(this, arguments) || this;
_this.state = { activeRow: -1, mouseLocs: [] };
_this.instance = React.createRef();
_this.mouseLeave = function () {
_this.dismissTimeout();
var onExit = _this.props.onExit;
if (onExit) {
onExit();
}
_this.setState({ activeRow: -1 });
};
_this.recordMouse = function (e) {
var x = e.pageX,
y = e.pageY;
_this.setState(function (prevState) {
var mouseLocs = prevState.mouseLocs.slice();
mouseLocs.push({ x: x, y: y });
if (mouseLocs.length > 8) {
mouseLocs.shift();
}
return { mouseLocs: mouseLocs };
});
};
_this.enterSubMenu = function () {
_this.dismissTimeout();
};
return _this;
}
ReactMegaMenu.prototype.mouseEnterRow = function (row) {
var _this = this;
return function () {
_this.dismissTimeout();
_this.possiblyActivate(row);
};
};
ReactMegaMenu.prototype.possiblyActivate = function (row) {
var _this = this;
var delay = this.getActivationDelay();
if (delay) {
var timeoutID = setTimeout(function () {
_this.possiblyActivate(row);
}, delay);
this.setState({ timeoutID: timeoutID });
} else {
this.activate(row);
}
};
ReactMegaMenu.prototype.activate = function (row) {
var activeRow = this.state.activeRow;
if (row === activeRow) {
return;
}
this.setState({ activeRow: row });
};
ReactMegaMenu.prototype.genCoords = function (x, y) {
return { x: x, y: y };
};
ReactMegaMenu.prototype.calcSlope = function (a, b) {
return (b.y - a.y) / (b.x - a.x);
};
ReactMegaMenu.prototype.dismissTimeout = function () {
var timeoutID = this.state.timeoutID;
if (timeoutID) {
clearTimeout(timeoutID);
}
};
/**
* Relies on state & props.
*
* If there is no current active row _OR_
* If the mouse location was not previously recorded _OR_
* If the mouse location comes in from outside of the menu _OR_
* If the mouse hasn't moved since last delay _OR_
* DEFAULT:
* @return 0 delay.
*
* If the slope is smaller than slope to top corner, or bigger than slope to bottom corner
* @return DELAY's value
*
* @memberof ReactMegaMenu
*/
ReactMegaMenu.prototype.getActivationDelay = function () {
var activeRow = this.state.activeRow;
var tolerance = this.props.tolerance;
if (activeRow < 0) {
return 0;
}
var bounds = this.instance.current.getBoundingClientRect();
var upperLeft = this.genCoords(bounds.left, bounds.top - (tolerance || 0)),
upperRight = this.genCoords(bounds.left + bounds.width, upperLeft.y),
lowerLeft = this.genCoords(bounds.left, bounds.top + bounds.height + (tolerance || 0)),
lowerRight = this.genCoords(bounds.left + bounds.width, lowerLeft.y);
var mouseLocs = this.state.mouseLocs;
var loc = mouseLocs[mouseLocs.length - 1];
var prevLoc = mouseLocs[0];
if (!loc) {
return 0;
}
if (!prevLoc) {
prevLoc = loc;
}
if (prevLoc.x < bounds.left || prevLoc.x > lowerRight.x || prevLoc.y < bounds.top || prevLoc.y > lowerRight.y) {
// If the previous mouse location was outside of the entire
// menu's bounds, immediately activate.
return 0;
}
var lastDelayLoc = this.state.lastDelayLoc;
if (lastDelayLoc && loc.x === lastDelayLoc.x && loc.y === lastDelayLoc.y) {
// If the mouse hasn't moved since the last time we checked
// for activation status, immediately activate.
return 0;
}
var decreasingCorner, increasingCorner;
switch (this.props.direction) {
case "LEFT":
default:
{
decreasingCorner = lowerRight;
increasingCorner = upperRight;
}
break;
case "RIGHT":
{
decreasingCorner = upperLeft;
increasingCorner = lowerLeft;
}
break;
}
var decreasingSlope = this.calcSlope(loc, decreasingCorner),
increasingSlope = this.calcSlope(loc, increasingCorner),
prevDecreasingSlope = this.calcSlope(prevLoc, decreasingCorner),
prevIncreasingSlope = this.calcSlope(prevLoc, increasingCorner);
if (decreasingSlope < prevDecreasingSlope && increasingSlope > prevIncreasingSlope) {
// Mouse is moving from previous location towards the
// currently activated submenu. Delay before activating a
// new menu row, because user may be moving into submenu.
this.setState({ lastDelayLoc: loc });
return ReactMegaMenu.DELAY;
}
this.setState({ lastDelayLoc: undefined });
return 0;
};
ReactMegaMenu.prototype.render = function () {
var _this = this;
var activeRow = this.state.activeRow;
var _a = this.props,
data = _a.data,
styleConfig = _a.styleConfig,
direction = _a.direction;
var containerProps = styleConfig.containerProps,
menuProps = styleConfig.menuProps,
contentProps = styleConfig.contentProps,
menuItemSelectedProps = styleConfig.menuItemSelectedProps,
menuItemProps = styleConfig.menuItemProps;
return React.createElement("div", __assign({}, containerProps, { className: (containerProps && containerProps.className || "") + " " + styles.flex + " " + styles["flex" + direction] + " " + styles.menu, onMouseLeave: this.mouseLeave }), React.createElement("ul", __assign({}, menuProps, { className: (menuProps && menuProps.className || "") + " " + styles.menuUl, ref: this.instance, onMouseMove: this.recordMouse }), data.map(function (_a, i) {
var label = _a.label,
key = _a.key,
items = _a.items;
return React.createElement(MenuItem, { selectedProps: menuItemSelectedProps, props: menuItemProps, selected: i === activeRow, hasData: items !== undefined, mouseEntered: _this.mouseEnterRow(i), label: label, key: key });
})), activeRow > -1 && data[activeRow].items && React.createElement("div", __assign({}, contentProps, { onMouseEnter: this.enterSubMenu, className: (contentProps && contentProps.className || "") + " " + styles.contentSubMenu }), React.createElement("div", { style: { width: "100%" } }, data[activeRow].items)));
};
ReactMegaMenu.DELAY = 150;
ReactMegaMenu.defaultProps = {
tolerance: 100,
direction: "RIGHT",
styleConfig: {}
};
return ReactMegaMenu;
}(React.Component);
exports.ReactMegaMenu = ReactMegaMenu;
/**
* css in js
*/
var styles = {
flex: glamor_1.css({ display: "flex" }),
flexLEFT: glamor_1.css({ flexDirection: "row-reverse" }),
flexRIGHT: glamor_1.css({ flexDirection: "row" }),
menu: glamor_1.css({
position: "absolute"
}),
contentSubMenu: glamor_1.css({
position: "relative"
}),
menuUl: glamor_1.css({
listStyle: "none",
paddingLeft: 0
})
};
exports.default = ReactMegaMenu;
//# sourceMappingURL=index.js.map