UNPKG

ldx-widgets

Version:

widgets

442 lines (414 loc) 16.3 kB
(function() { var EDGE_BUFFER, NIB_OFFSET, Pvr, React, _, animationMixin, button, div, p, ref, indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; React = require('react'); _ = require('lodash'); animationMixin = require('../mixins/animation_mixin'); ref = React.DOM, div = ref.div, p = ref.p, button = ref.button; NIB_OFFSET = 12; EDGE_BUFFER = 4; /* This class wraps any passed react component in a PVR It is intend to simplify creating any PVr within app, by handling the positioning and direction of the PVR Popover Props @props.element - a react element (not factory) to render inside the pvr @props.direction - string - default 'auto' below, above, right, left detirmines on which side of the anchor element the pvr will appear - in 'auto' mode below, above, right, left is the order they are checked - should the pvr not fit in the direction provided, the oposite sirection will be used - should the pvr not fit in the opposite direction, the other 2 will be checked - should the pvr not fit in any direction, the direction with the most space will be used @props.height - default 100 height of the pvr @props.width default 200 width of the pvr @props.styleMixin object containing any style properties to mixin with and/or overrride the defaults note that width height are passed separately so they can have defaults and auto settings passing width/height in this object could cause issues @props.anchor - DOM element pass then e.currentTarget or e.target element through to enable auto-positioning if this isn't passed, you'll have to manually configure positioning with styleMixin or CSS @props.vAdjust - default 0 for above, below positioning negative value moves the pvr closer to the anchor element, positive value moves it further away for left, right negative value moves pvr higher on the screen, positive value moves it lower @props.vSlide - default 0 leave the nib in the same place, but offsets the pvr body by this number of pixels @props.hAdjust - default 0 for above, below positioning negative value moves the pvr further left, positive value moves it further right for left, right negative value moves the pvr closer to the anchor element, positive value moves it further away @props.hSlide - default 0 leave the nib in the same place, but offsets the pvr body by this number of pixels @props.nibColor - default white the color of the nib pass 'auto' to have the nib attempt to color itself @props.showNib - default yes whether the nib should show @props.borderColor - default rgb(235,235,235) the color of pvr border @props.updateOnKeys - default none any keystrokes that should cause a rerender of the pvr @props.closeOnAnchorLeave - default yes whether or not the pvr should close if the anchor element moves offscreen during a resize or key stroke (of a key in updateOnKeys) */ Pvr = React.createClass({ displayName: 'Pvr', mixins: [animationMixin], enterStateStart: { scale: .9 }, enterStateEnd: { scale: 1 }, enterEasing: 'easeOutElastic', enterDuration: 600, propTypes: { close: React.PropTypes.func.isRequired }, getInitialState: function() { return { autoNibColor: null }; }, getDefaultProps: function() { return { height: 100, width: 200, styleMixin: {}, Adjust: 0, vAdjust: 0, vSlide: 0, hAdjust: 0, hSlide: 0, anchor: { getBoundingClientRect: function() { return { top: 0, right: 0, bottom: 0, left: 0 }; } }, element: null, direction: 'auto', nibColor: 'auto', showNib: true, borderColor: 'rgb(235,235,235)', updateOnKeys: [], closeOnAnchorLeave: true }; }, componentWillMount: function() { var direction; direction = this.props.direction; this.directionCheck = (function() { switch (direction) { case 'left': return ['left', 'right', 'below', 'above']; case 'right': return ['right', 'left', 'below', 'above']; case 'below': return ['below', 'above', 'right', 'left']; case 'above': return ['above', 'below', 'right', 'left']; default: return ['below', 'above', 'right', 'left']; } })(); window.addEventListener("resize", this.handleResize); return window.addEventListener("keydown", this.handleKeyStroke); }, componentWillUnmount: function() { window.removeEventListener("resize", this.handleResize); return window.removeEventListener("keydown", this.handleKeyStroke); }, componentDidMount: function() { var nibColor; nibColor = this.props.nibColor; if (nibColor === 'auto') { return this.colorNibAutomatically(); } }, colorNibAutomatically: function() { var adjacentEl, anchor, autoNibColor, bgColor, buffer, counter, direction, left, nibSize, node, ref1, ref2, top, x, y; anchor = this.props.anchor; ref1 = this.refs.nib.getBoundingClientRect(), top = ref1.top, left = ref1.left; direction = this.calculateOptimalDirection(anchor.getBoundingClientRect()); buffer = 5; nibSize = 14; switch (direction) { case 'left': x = left - buffer; y = top + (nibSize / 2); break; case 'right': x = left + nibSize + buffer; y = top + (nibSize / 2); break; case 'below': x = left + (nibSize / 2); y = top + nibSize + buffer; break; case 'above': x = left + (nibSize / 2); y = top - buffer; } adjacentEl = document.elementFromPoint(x, y); if (adjacentEl == null) { return; } autoNibColor = 'white'; counter = 0; node = adjacentEl; while (autoNibColor === 'white' && (node != null) && counter < 10) { bgColor = (ref2 = getComputedStyle(node, null)) != null ? ref2.getPropertyValue('background-color') : void 0; if (bgColor !== 'rgba(0, 0, 0, 0)' && bgColor !== 'transparent') { autoNibColor = bgColor; } node = node.parentNode; counter++; } return this.setState({ autoNibColor: autoNibColor }); }, handleKeyStroke: function(e) { var close, closeOnAnchorLeave, keyCode, ref1, updateOnKeys; keyCode = e.keyCode; ref1 = this.props, updateOnKeys = ref1.updateOnKeys, closeOnAnchorLeave = ref1.closeOnAnchorLeave, close = ref1.close; if (indexOf.call(updateOnKeys, keyCode) < 0) { return; } if (this.anchorIsOffscreen() && closeOnAnchorLeave) { return close(); } else { return this.forceUpdate(); } }, handleResize: function() { var closeOnAnchorLeave; closeOnAnchorLeave = this.props.closeOnAnchorLeave; if (this.anchorIsOffscreen() && closeOnAnchorLeave) { return close(); } else { return this.forceUpdate(); } }, anchorIsOffscreen: function() { var bottom, left, ref1, right, top; ref1 = this.props.anchor.getBoundingClientRect(), top = ref1.top, left = ref1.left, right = ref1.right, bottom = ref1.bottom; if (top > window.innerHeight || left > window.innerWidth || bottom < 0 || right < 0) { return true; } else { return false; } }, handleClick: function(e) { return e.stopPropagation(); }, calculateOptimalDirection: function(elPos) { var bottom, direction, height, heightNeeded, i, innerHeight, innerWidth, left, len, max, preferredDirection, ref1, ref2, right, top, width, widthNeeded; innerHeight = window.innerHeight, innerWidth = window.innerWidth; ref1 = this.props, preferredDirection = ref1.preferredDirection, width = ref1.width, height = ref1.height; right = elPos.right, left = elPos.left, top = elPos.top, bottom = elPos.bottom; right = innerWidth - right; bottom = innerHeight - bottom; widthNeeded = width + NIB_OFFSET + EDGE_BUFFER; heightNeeded = height + NIB_OFFSET + EDGE_BUFFER; ref2 = this.directionCheck; for (i = 0, len = ref2.length; i < len; i++) { direction = ref2[i]; switch (direction) { case 'left': if (widthNeeded < left) { return 'left'; } break; case 'right': if (widthNeeded < right) { return 'right'; } break; case 'below': if (heightNeeded < bottom) { return 'below'; } break; case 'above': if (heightNeeded < top) { return 'above'; } } } max = _.max([left, top, bottom, right]); switch (max) { case left: return 'left'; case right: return 'right'; case top: return 'above'; case bottom: return 'below'; } }, render: function() { var anchor, anchorHeight, anchorLeft, anchorRight, anchorTop, anchorWidth, anchorbottom, autoNibColor, borderColor, className, direction, elPos, element, hAdjust, hSlide, height, left, leftAdjust, maxNibHorizontalAdjust, maxNibVerticalAdjust, nibBorderClass, nibBorderStyle, nibClass, nibColor, nibPosition, nibStyle, position, ref1, scale, showNib, style, styleMixin, top, topAdjust, vAdjust, vSlide, width; ref1 = this.props, width = ref1.width, height = ref1.height, styleMixin = ref1.styleMixin, position = ref1.position, element = ref1.element, vAdjust = ref1.vAdjust, hAdjust = ref1.hAdjust, anchor = ref1.anchor, direction = ref1.direction, nibColor = ref1.nibColor, showNib = ref1.showNib, borderColor = ref1.borderColor, vSlide = ref1.vSlide, hSlide = ref1.hSlide; autoNibColor = this.state.autoNibColor; if (nibColor === 'auto') { nibColor = autoNibColor != null ? autoNibColor : 'white'; } scale = this.props.scale || this.state.scale; elPos = anchor.getBoundingClientRect(); anchorTop = elPos.top; anchorbottom = elPos.bottom; anchorLeft = elPos.left; anchorRight = elPos.right; anchorWidth = elPos.width; anchorHeight = elPos.height; direction = this.calculateOptimalDirection(elPos); className = "pvr " + direction; nibClass = "nib " + direction; nibBorderClass = "nib border " + direction; nibPosition = NIB_OFFSET / 2; maxNibVerticalAdjust = height / 2 - NIB_OFFSET / 2 - 5; maxNibHorizontalAdjust = width / 2 - NIB_OFFSET / 2 - 5; switch (direction) { case 'left': top = anchorTop + anchorHeight / 2 - height / 2 + vAdjust - vSlide; left = anchorLeft - NIB_OFFSET - width - hAdjust; topAdjust = top + height + EDGE_BUFFER - window.innerHeight; if (topAdjust > 0) { top = top - topAdjust - vSlide; nibPosition = NIB_OFFSET / 2 - Math.min(topAdjust, maxNibVerticalAdjust); } topAdjust = top - EDGE_BUFFER; if (topAdjust < 0) { top = top - topAdjust - vSlide; nibPosition = NIB_OFFSET / 2 - Math.min(topAdjust, maxNibVerticalAdjust); } nibPosition -= vSlide; nibStyle = { borderColor: "transparent transparent transparent " + nibColor, top: "calc(50% - " + nibPosition + "px)" }; nibBorderStyle = { borderColor: "transparent transparent transparent " + borderColor, top: "calc(50% - " + nibPosition + "px)", right: 0 - NIB_OFFSET - 1 }; break; case 'right': top = anchorTop + anchorHeight / 2 - height / 2 + vAdjust - vSlide; left = anchorRight + NIB_OFFSET + hAdjust; topAdjust = top + height + EDGE_BUFFER - window.innerHeight; if (topAdjust > 0) { top = top - topAdjust - vSlide; nibPosition = NIB_OFFSET / 2 - Math.min(topAdjust, maxNibVerticalAdjust); } topAdjust = top - EDGE_BUFFER; if (topAdjust < 0) { top = top - topAdjust - vSlide; nibPosition = NIB_OFFSET / 2 - Math.min(topAdjust, maxNibVerticalAdjust); } nibPosition -= vSlide; nibStyle = { borderColor: "transparent " + nibColor + " transparent transparent", top: "calc(50% - " + nibPosition + "px)" }; nibBorderStyle = { borderColor: "transparent " + borderColor + " transparent transparent", top: "calc(50% - " + nibPosition + "px)", left: 0 - NIB_OFFSET - 1 }; break; case 'below': top = anchorTop + anchorHeight + NIB_OFFSET + vAdjust; left = anchorLeft + anchorWidth / 2 - width / 2 + hAdjust - hSlide; leftAdjust = left + width + EDGE_BUFFER - window.innerWidth; if (leftAdjust > 0) { left = left - leftAdjust - hSlide; nibPosition = NIB_OFFSET / 2 - Math.min(leftAdjust, maxNibHorizontalAdjust); } leftAdjust = left - EDGE_BUFFER; if (leftAdjust < 0) { left = left - leftAdjust - hSlide; nibPosition = NIB_OFFSET / 2 - Math.min(leftAdjust, maxNibHorizontalAdjust); } nibPosition -= hSlide; nibStyle = { borderColor: "transparent transparent " + nibColor + " transparent", left: "calc(50% - " + nibPosition + "px)" }; nibBorderStyle = { borderColor: "transparent transparent " + borderColor + " transparent", left: "calc(50% - " + nibPosition + "px)", top: 0 - NIB_OFFSET - 1 }; break; case 'above': top = anchorTop - NIB_OFFSET - height - vAdjust; left = anchorLeft + anchorWidth / 2 - width / 2 + hAdjust - hSlide; leftAdjust = left + width + EDGE_BUFFER - window.innerWidth; if (leftAdjust > 0) { left = left - leftAdjust - hSlide; nibPosition = NIB_OFFSET / 2 - Math.min(leftAdjust, maxNibHorizontalAdjust); } leftAdjust = left - EDGE_BUFFER; if (leftAdjust < 0) { left = left - leftAdjust - hSlide; nibPosition = NIB_OFFSET / 2 - Math.min(leftAdjust, maxNibHorizontalAdjust); } nibPosition -= hSlide; nibStyle = { borderColor: nibColor + " transparent transparent transparent", left: "calc(50% - " + nibPosition + "px)" }; nibBorderStyle = { borderColor: " " + borderColor + " transparent transparent transparent", left: "calc(50% - " + nibPosition + "px)", bottom: 0 - NIB_OFFSET - 1 }; } element = element || div({ key: 'inner', className: 'inner' }); style = { height: height, width: width, top: top, left: left, borderColor: borderColor, transform: "scale(" + scale + ")", WebkitTransform: "scale(" + scale + ")", msTransform: "scale(" + scale + ")" }; _.assign(style, styleMixin); return div({ className: className, style: style, onClick: this.handleClick }, [ showNib ? div({ key: 'nibBorder', className: nibBorderClass, style: nibBorderStyle }) : void 0, showNib ? div({ key: 'nibInner', className: nibClass, style: nibStyle, ref: 'nib' }) : void 0, element ]); } }); module.exports = Pvr; }).call(this);