UNPKG

lucid-ui

Version:

A UI component library from Xandr.

439 lines 20.4 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) { if (typeof b !== "function" && b !== null) throw new TypeError("Class extends value " + String(b) + " is not a constructor or null"); extendStatics(d, b); function __() { this.constructor = d; } d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); }; })(); 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 __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; 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("prop-types")); var lodash_1 = __importStar(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', }; /** TODO: Remove this constant when the component is converted to a functional component */ var nonPassThroughs = [ 'children', 'className', 'style', 'direction', 'directonOffset', 'alignment', 'alignmentOffset', 'getAlignmentOffset', 'minWidthOffset', 'isExpanded', 'onClickOut', 'portalId', 'FlyOut', 'Target', ]; 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 || (0, 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 (!(0, lodash_1.isNil)(onClickOut) && flyOutPortalRef.current && targetRef.current && eventTarget && eventTarget.nodeName) { var flyOutEl = flyOutPortalRef.current.portalElement.firstChild; var wasALabelClick = eventTarget.nodeName === 'INPUT' && (0, 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 = (0, 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 = (0, 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 = (0, 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 }, lodash_1.default.omit(passThroughs, nonPassThroughs), { className: cx('&', className), style: style }), targetChildren, isExpanded ? (react_1.default.createElement(Portal_1.default, __assign({ ref: this.flyOutPortalRef }, flyProps, { className: cx('&-FlyOut', "&-FlyOut-".concat(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: "Use a `ContextMenu` to render a target and a flyout positioned relative to the target.", categories: ['utility'], madeFrom: ['Portal'], }; ContextMenu.propTypes = { /** \`children\` should include exactly one ContextMenu.Target and one ContextMenu.FlyOut. */ children: node, /** Appended to the component-specific class names set on the root element. */ className: string, /** Passed through to the root element. */ style: object, /** direction of the FlyOut relative to Target. */ direction: oneOf(['down', 'up', 'right', 'left']), /** the px offset along the axis of the direction */ directonOffset: number, /** alignment of the Flyout relative to Target in the cross axis from \`direction\`. */ alignment: oneOf(['start', 'center', 'end']), /** the px offset along the axis of the alignment */ alignmentOffset: number, /** an alternative to \`alignmentOffset\`, a function that is applied with the width/height of the flyout. the result is used as the \`alignmentOffset\` */ getAlignmentOffset: func, /** The number of px's to grow or shrink the minWidth of the FlyOut */ minWidthOffset: number, /** Indicates whether the FlyOut will render or not. */ isExpanded: bool, /** Called when a click event happenens outside of the ContextMenu, with the signature \`({ props, event }) => { ... }\` */ onClickOut: func, /** The \`id\` of the FlyOut portal element that is appended to \`document.body\`. Defaults to a generated \`id\`. */ portalId: string, 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; //# sourceMappingURL=ContextMenu.js.map