UNPKG

lucid-ui

Version:

A UI component library from AppNexus.

364 lines 21.2 kB
"use strict"; var __extends = (this && this.__extends) || (function () { var extendStatics = function (d, b) { extendStatics = Object.setPrototypeOf || ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, 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 __makeTemplateObject = (this && this.__makeTemplateObject) || function (cooked, raw) { if (Object.defineProperty) { Object.defineProperty(cooked, "raw", { value: raw }); } else { cooked.raw = raw; } return cooked; }; var __assign = (this && this.__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); }; var __rest = (this && this.__rest) || function (s, e) { var t = {}; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) t[p] = s[p]; if (s != null && typeof Object.getOwnPropertySymbols === "function") for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) t[p[i]] = s[p[i]]; } return t; }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.EnumAlignment = exports.EnumDirection = void 0; /* eslint-disable react/prop-types */ var react_1 = __importDefault(require("react")); var prop_types_1 = __importDefault(require("react-peek/prop-types")); var lodash_1 = __importDefault(require("lodash")); var Portal_1 = __importDefault(require("../Portal/Portal")); var component_types_1 = require("../../util/component-types"); var dom_helpers_1 = require("../../util/dom-helpers"); var style_helpers_1 = require("../../util/style-helpers"); var cx = style_helpers_1.lucidClassNames.bind('&-ContextMenu'); var bool = prop_types_1.default.bool, node = prop_types_1.default.node, func = prop_types_1.default.func, number = prop_types_1.default.number, object = prop_types_1.default.object, oneOf = prop_types_1.default.oneOf, string = prop_types_1.default.string; var ContextMenuTarget = function (_props) { return null; }; ContextMenuTarget.displayName = 'ContextMenu.Target'; ContextMenuTarget.propName = 'Target'; ContextMenuTarget.peek = { description: "Renders an element of `elementType` (defaults to `<span>`)\n\tthat the menu `FlyOut` anchors to.", }; ContextMenuTarget.propTypes = { elementType: string, }; ContextMenuTarget.defaultProps = { elementType: 'span', }; var ContextMenuFlyOut = function (_props) { return null; }; ContextMenuFlyOut.displayName = 'ContextMenu.FlyOut'; ContextMenuFlyOut.propName = 'FlyOut'; ContextMenuFlyOut.peek = { description: "Renders a `<Portal>` anchored to the `Target`.", }; /** These have to be lowercase because: * 1. the key and value have to match * (limitation of TypeScript, see: https://github.com/Microsoft/TypeScript/issues/17198) * 2. the values are currently lowercase in the propTypes * */ var EnumDirection; (function (EnumDirection) { EnumDirection["up"] = "up"; EnumDirection["down"] = "down"; EnumDirection["left"] = "left"; EnumDirection["right"] = "right"; })(EnumDirection = exports.EnumDirection || (exports.EnumDirection = {})); var EnumAlignment; (function (EnumAlignment) { EnumAlignment["start"] = "start"; EnumAlignment["center"] = "center"; EnumAlignment["end"] = "end"; })(EnumAlignment = exports.EnumAlignment || (exports.EnumAlignment = {})); /** default styling hides portal because its position can't be calculated * properly until after 1st render so here we unhide it if the ref exists */ var defaultFlyoutPosition = { opacity: 1, maxHeight: 'none', left: 'auto', top: 'auto', }; var ContextMenu = /** @class */ (function (_super) { __extends(ContextMenu, _super); function ContextMenu() { var _this = _super !== null && _super.apply(this, arguments) || this; _this.targetRef = react_1.default.createRef(); _this.flyOutPortalRef = react_1.default.createRef(); _this.state = { portalId: _this.props.portalId || style_helpers_1.uniqueName('ContextMenu-Portal-'), targetRect: { bottom: 0, top: 0, left: 0, right: 0, height: 0, width: 0, }, flyOutHeight: 0, flyOutWidth: 0, }; // TODO: does this need to be instance property? _this.continueAlignment = false; _this.beginAlignment = function () { _this.continueAlignment = true; window.requestAnimationFrame(_this.handleAlignment); }; _this.endAlignment = function () { _this.continueAlignment = false; }; _this.handleAlignment = function () { if (_this.continueAlignment) { if (_this.props.isExpanded) { _this.alignFlyOut(true); } window.requestAnimationFrame(_this.handleAlignment); } }; _this.handleBodyClick = function (event) { var _a = _this, props = _a.props, onClickOut = _a.props.onClickOut, flyOutPortalRef = _a.flyOutPortalRef, targetRef = _a.targetRef; // in this block, I assert the type of target because EventTarget -> Element -> HtmlElement (from general to specific typing) var eventTarget = event.target; if (onClickOut && flyOutPortalRef.current && targetRef.current && eventTarget && eventTarget.nodeName) { var flyOutEl = flyOutPortalRef.current.portalElement.firstChild; var wasALabelClick = eventTarget.nodeName === 'INPUT' && dom_helpers_1.sharesAncestor(eventTarget, targetRef.current, 'LABEL'); // Attempt to detect <label> click and ignore it if (wasALabelClick) { return; } if (!(flyOutEl.contains(eventTarget) || targetRef.current.contains(eventTarget)) && event.type === 'click') { onClickOut({ props: props, event: event }); } } }; _this.calcAlignmentOffset = function (_a) { var direction = _a.direction, alignment = _a.alignment, getAlignmentOffset = _a.getAlignmentOffset, flyOutHeight = _a.flyOutHeight, flyOutWidth = _a.flyOutWidth; var UP = EnumDirection.up, DOWN = EnumDirection.down; var CENTER = EnumAlignment.center; return !lodash_1.default.isUndefined(_this.props.alignmentOffset) ? _this.props.alignmentOffset : alignment === CENTER ? getAlignmentOffset(lodash_1.default.includes([UP, DOWN], direction) ? flyOutWidth : flyOutHeight) : 0; }; _this.getMatch = function (_a) { var _b, _c, _d, _e, _f; var direction = _a.direction, alignment = _a.alignment, flyOutHeight = _a.flyOutHeight, flyOutWidth = _a.flyOutWidth, clientWidth = _a.clientWidth, directonOffset = _a.directonOffset, alignmentOffset = _a.alignmentOffset, top = _a.top, bottom = _a.bottom, left = _a.left, right = _a.right, width = _a.width, height = _a.height; var UP = EnumDirection.up, DOWN = EnumDirection.down, LEFT = EnumDirection.left, RIGHT = EnumDirection.right; var START = EnumAlignment.start, CENTER = EnumAlignment.center, END = EnumAlignment.end; var options = (_b = {}, _b[UP] = (_c = {}, _c[START] = { top: top - flyOutHeight - directonOffset, left: left - alignmentOffset, }, _c[CENTER] = { top: top - flyOutHeight - directonOffset, left: left + width / 2 - flyOutWidth / 2 + alignmentOffset, }, _c[END] = { top: top - flyOutHeight - directonOffset, right: clientWidth - right - alignmentOffset, }, _c), _b[DOWN] = (_d = {}, _d[START] = { top: bottom + directonOffset, left: left - alignmentOffset, }, _d[CENTER] = { top: bottom + directonOffset, left: left + width / 2 - flyOutWidth / 2 + alignmentOffset, }, _d[END] = { top: bottom + directonOffset, right: clientWidth - right - alignmentOffset, }, _d), _b[LEFT] = (_e = {}, _e[START] = { top: top - alignmentOffset, right: clientWidth - left + directonOffset, }, _e[CENTER] = { top: top - flyOutHeight / 2 + height / 2 + alignmentOffset, right: clientWidth - left + directonOffset, }, _e[END] = { top: top - flyOutHeight + height + alignmentOffset, right: clientWidth - left + directonOffset, }, _e), _b[RIGHT] = (_f = {}, _f[START] = { top: top - alignmentOffset, left: left + width + directonOffset, }, _f[CENTER] = { top: top - flyOutHeight / 2 + height / 2 + alignmentOffset, left: left + width + directonOffset, }, _f[END] = { top: top - flyOutHeight + height + alignmentOffset, left: left + width + directonOffset, }, _f), _b); return __assign(__assign({}, defaultFlyoutPosition), options[direction][alignment]); }; _this.getFlyoutPosition = function () { var _a = _this, _b = _a.props, direction = _b.direction, alignment = _b.alignment, _c = _b.directonOffset, directonOffset = _c === void 0 ? ContextMenu.defaultProps.directonOffset : _c, _d = _b.getAlignmentOffset, getAlignmentOffset = _d === void 0 ? ContextMenu.defaultProps.getAlignmentOffset : _d, _e = _a.state, flyOutHeight = _e.flyOutHeight, flyOutWidth = _e.flyOutWidth, _f = _e.targetRect, bottom = _f.bottom, left = _f.left, right = _f.right, top = _f.top, width = _f.width, height = _f.height, flyOutPortalRef = _a.flyOutPortalRef; var clientWidth = document.body.clientWidth; if (!flyOutPortalRef.current) return {}; if (direction && alignment) { return _this.getMatch({ direction: direction, alignment: alignment, flyOutHeight: flyOutHeight, flyOutWidth: flyOutWidth, clientWidth: clientWidth, directonOffset: directonOffset, alignmentOffset: _this.calcAlignmentOffset({ direction: direction, alignment: alignment, getAlignmentOffset: getAlignmentOffset, flyOutHeight: flyOutHeight, flyOutWidth: flyOutWidth, }), top: top, bottom: bottom, left: left, right: right, width: width, height: height, }); } }; _this.alignFlyOut = function (doRedunancyCheck) { if (doRedunancyCheck === void 0) { doRedunancyCheck = false; } var _a = _this, flyOutPortalRef = _a.flyOutPortalRef, targetRef = _a.targetRef; if (!targetRef.current || !flyOutPortalRef.current) { return; } var targetRect = dom_helpers_1.getAbsoluteBoundingClientRect(targetRef.current); var portalRef = flyOutPortalRef.current; // Don't cause a state-change if target dimensions are the same if (doRedunancyCheck && targetRect.left === _this.state.targetRect.left && targetRect.top === _this.state.targetRect.top && targetRect.height === _this.state.targetRect.height && targetRect.width === _this.state.targetRect.width) { return; } if (portalRef) { var flyOutEl = portalRef.portalElement.firstChild; var _b = flyOutEl.getBoundingClientRect(), height = _b.height, width = _b.width; _this.setState({ targetRect: targetRect, flyOutHeight: height, flyOutWidth: width, }); } }; return _this; } ContextMenu.prototype.UNSAFE_componentWillReceiveProps = function () { var _this = this; lodash_1.default.defer(function () { return _this.alignFlyOut(); }); }; ContextMenu.prototype.componentDidMount = function () { var _this = this; lodash_1.default.defer(function () { return _this.alignFlyOut(); }); this.beginAlignment(); document.body.addEventListener('touchstart', this.handleBodyClick); document.body.addEventListener('click', this.handleBodyClick); }; ContextMenu.prototype.componentWillUnmount = function () { this.endAlignment(); document.body.removeEventListener('click', this.handleBodyClick); }; ContextMenu.prototype.render = function () { var _a = this, _b = _a.props, className = _b.className, direction = _b.direction, isExpanded = _b.isExpanded, style = _b.style, minWidthOffset = _b.minWidthOffset, passThroughs = __rest(_b, ["className", "direction", "isExpanded", "style", "minWidthOffset"]), _c = _a.state, portalId = _c.portalId, targetRect = _c.targetRect; var targetElement = component_types_1.getFirst(this.props, ContextMenu.Target); var targetChildren = lodash_1.default.get(targetElement, 'props.children', null); var TargetElementType = targetElement.props.elementType; var flyoutElement = component_types_1.getFirst(this.props, ContextMenu.FlyOut); var flyProps = lodash_1.default.get(flyoutElement, 'props', {}); return (react_1.default.createElement(TargetElementType, __assign({ ref: this.targetRef }, component_types_1.omitProps(passThroughs, undefined, lodash_1.default.keys(ContextMenu.propTypes)), { className: cx('&', className), style: style }), targetChildren, isExpanded ? (react_1.default.createElement(Portal_1.default, __assign({ ref: this.flyOutPortalRef }, flyProps, { className: cx('&-FlyOut', "&-FlyOut-" + direction, flyProps.className), portalId: portalId, style: __assign(__assign({ minWidth: targetRect.width + minWidthOffset }, this.getFlyoutPosition()), flyProps.style) }), flyProps.children)) : null)); }; ContextMenu.displayName = 'ContextMenu'; ContextMenu.peek = { description: "\n\t\t\tA ContextMenu component is used to render a target and a flyout which\n\t\t\tis positioned relative to the target.\n\t\t", categories: ['utility'], madeFrom: ['Portal'], }; ContextMenu.propTypes = { children: node(templateObject_1 || (templateObject_1 = __makeTemplateObject(["\n\t\t\t`children` should include exactly one ContextMenu.Target and one\n\t\t\tContextMenu.FlyOut.\n\t\t"], ["\n\t\t\t\\`children\\` should include exactly one ContextMenu.Target and one\n\t\t\tContextMenu.FlyOut.\n\t\t"]))), className: string(templateObject_2 || (templateObject_2 = __makeTemplateObject(["\n\t\t\tAppended to the component-specific class names set on the root element.\n\t\t"], ["\n\t\t\tAppended to the component-specific class names set on the root element.\n\t\t"]))), style: object(templateObject_3 || (templateObject_3 = __makeTemplateObject(["\n\t\t\tPassed through to the root element.\n\t\t"], ["\n\t\t\tPassed through to the root element.\n\t\t"]))), direction: oneOf(['down', 'up', 'right', 'left'])(templateObject_4 || (templateObject_4 = __makeTemplateObject(["\n\t\t\tdirection of the FlyOut relative to Target.\n\t\t"], ["\n\t\t\tdirection of the FlyOut relative to Target.\n\t\t"]))), directonOffset: number(templateObject_5 || (templateObject_5 = __makeTemplateObject(["\n\t\t\tthe px offset along the axis of the direction\n\t\t"], ["\n\t\t\tthe px offset along the axis of the direction\n\t\t"]))), alignment: oneOf(['start', 'center', 'end'])(templateObject_6 || (templateObject_6 = __makeTemplateObject(["\n\t\t\talignment of the Flyout relative to Target in the cross axis from\n\t\t\t`direction`.\n\t\t"], ["\n\t\t\talignment of the Flyout relative to Target in the cross axis from\n\t\t\t\\`direction\\`.\n\t\t"]))), alignmentOffset: number(templateObject_7 || (templateObject_7 = __makeTemplateObject(["\n\t\t\tthe px offset along the axis of the alignment\n\t\t"], ["\n\t\t\tthe px offset along the axis of the alignment\n\t\t"]))), getAlignmentOffset: func(templateObject_8 || (templateObject_8 = __makeTemplateObject(["\n\t\t\tan alternative to `alignmentOffset`, a function that is applied with\n\t\t\tthe width/height of the flyout. the result is used as the\n\t\t\t`alignmentOffset`\n\t\t"], ["\n\t\t\tan alternative to \\`alignmentOffset\\`, a function that is applied with\n\t\t\tthe width/height of the flyout. the result is used as the\n\t\t\t\\`alignmentOffset\\`\n\t\t"]))), minWidthOffset: number(templateObject_9 || (templateObject_9 = __makeTemplateObject(["\n\t\t\tThe number of px's to grow or shrink the minWidth of the FlyOut\n\t\t"], ["\n\t\t\tThe number of px's to grow or shrink the minWidth of the FlyOut\n\t\t"]))), isExpanded: bool(templateObject_10 || (templateObject_10 = __makeTemplateObject(["\n\t\t\tIndicates whether the FlyOut will render or not.\n\t\t"], ["\n\t\t\tIndicates whether the FlyOut will render or not.\n\t\t"]))), onClickOut: func(templateObject_11 || (templateObject_11 = __makeTemplateObject(["\n\t\t\tCalled when a click event happenens outside of the ContextMenu, with the\n\t\t\tsignature `({ props, event }) => { ... }`\n\t\t"], ["\n\t\t\tCalled when a click event happenens outside of the ContextMenu, with the\n\t\t\tsignature \\`({ props, event }) => { ... }\\`\n\t\t"]))), portalId: string(templateObject_12 || (templateObject_12 = __makeTemplateObject(["\n\t\t\tThe `id` of the FlyOut portal element that is appended to\n\t\t\t`document.body`. Defaults to a generated `id`.\n\t\t"], ["\n\t\t\tThe \\`id\\` of the FlyOut portal element that is appended to\n\t\t\t\\`document.body\\`. Defaults to a generated \\`id\\`.\n\t\t"]))), FlyOut: node, Target: node, }; // all of these should be removed, but it's a breaking change to do so :( ContextMenu.UP = EnumDirection.up; ContextMenu.DOWN = EnumDirection.down; ContextMenu.LEFT = EnumDirection.left; ContextMenu.RIGHT = EnumDirection.right; ContextMenu.START = EnumAlignment.start; ContextMenu.CENTER = EnumAlignment.center; ContextMenu.END = EnumAlignment.end; ContextMenu.Target = ContextMenuTarget; ContextMenu.FlyOut = ContextMenuFlyOut; ContextMenu.defaultProps = { direction: 'down', directonOffset: 0, minWidthOffset: 0, alignment: 'start', // no default alignmentOffset so it can default to result of `getAlignmentOffset` getAlignmentOffset: lodash_1.default.constant(0), isExpanded: true, onClickOut: null, portalId: null, }; return ContextMenu; }(react_1.default.Component)); exports.default = ContextMenu; var templateObject_1, templateObject_2, templateObject_3, templateObject_4, templateObject_5, templateObject_6, templateObject_7, templateObject_8, templateObject_9, templateObject_10, templateObject_11, templateObject_12; //# sourceMappingURL=ContextMenu.js.map