UNPKG

ldx-widgets

Version:

widgets

365 lines (282 loc) 11.8 kB
React = require 'react' _ = require 'lodash' animationMixin = require '../mixins/animation_mixin' {div, p, button} = React.DOM 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 @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 getDefaultProps: -> { height: 100 width: 200 styleMixin: {} Adjust: 0 vAdjust: 0 vSlide: 0 hAdjust: 0 hSlide: 0 anchor: { getBoundingClientRect: -> top: 0 right: 0 bottom: 0 left: 0 } element: null direction: 'auto' nibColor: 'white' showNib: yes borderColor: 'rgb(235,235,235)' updateOnKeys: [] closeOnAnchorLeave: yes } componentWillMount: -> {direction} = @props @directionCheck = switch direction when 'left' then ['left', 'right', 'below', 'above'] when 'right' then ['right', 'left', 'below', 'above'] when 'below' then ['below', 'above', 'right', 'left'] when 'above' then ['above', 'below', 'right', 'left'] else ['below', 'above', 'right', 'left'] window.addEventListener("resize", @handleResize) window.addEventListener("keydown", @handleKeyStroke) componentWillUnmount: -> window.removeEventListener("resize", @handleResize) window.removeEventListener("keydown", @handleKeyStroke) handleKeyStroke: (e) -> {keyCode} = e {updateOnKeys, closeOnAnchorLeave, close} = @props return unless keyCode in updateOnKeys if @anchorIsOffscreen() and closeOnAnchorLeave then do close else @forceUpdate() handleResize: -> {closeOnAnchorLeave} = @props if @anchorIsOffscreen() and closeOnAnchorLeave then do close else @forceUpdate() anchorIsOffscreen: -> {top, left, right, bottom} = @props.anchor.getBoundingClientRect() if top > window.innerHeight or left > window.innerWidth or bottom < 0 or right < 0 then yes else no handleClick: (e) -> e.stopPropagation() calculateOptimalDirection: (elPos) -> {innerHeight, innerWidth} = window {preferredDirection, width, height} = @props {right, left, top, bottom} = elPos # left and top are already the space to the left and the top of the anchor # transform right and bottom to the space to the right and bottom of the anchor right = innerWidth - right bottom = innerHeight - bottom # Check if the popover fits in the preferred direction widthNeeded = width + NIB_OFFSET + EDGE_BUFFER heightNeeded = height + NIB_OFFSET + EDGE_BUFFER for direction in @directionCheck switch direction when 'left' if widthNeeded < left then return 'left' when 'right' if widthNeeded < right then return 'right' when 'below' if heightNeeded < bottom then return 'below' when 'above' if heightNeeded < top then return 'above' # If the popover will not fit in any of the the preferred directions # return the direction with the most space max = _.max [left, top, bottom, right] switch max when left then 'left' when right then 'right' when top then 'above' when bottom then 'below' render: -> {width, height, styleMixin, position, element, vAdjust, hAdjust, anchor, direction, nibColor, showNib, borderColor, vSlide, hSlide} = @props scale = @props.scale or @state.scale elPos = anchor.getBoundingClientRect() anchorTop = elPos.top anchorbottom = elPos.bottom anchorLeft = elPos.left anchorRight = elPos.right anchorWidth = elPos.width anchorHeight = elPos.height direction = @calculateOptimalDirection elPos className = "pvr #{direction}" nibClass = "nib #{direction}" nibBorderClass = "nib border #{direction}" nibPosition = NIB_OFFSET/2 # When adjusting the nib center point up/down, don't exceed half the height minus border radius maxNibVerticalAdjust = height/2 - NIB_OFFSET/2 - 5 # When adjusting the nib center point left/right, don't exceed half the width minus border radius maxNibHorizontalAdjust = width/2 - NIB_OFFSET/2 - 5 switch direction when 'left' top = anchorTop + anchorHeight/2 - height/2 + vAdjust - vSlide left = anchorLeft - NIB_OFFSET - width - hAdjust # Make sure the pvr is not off the screen to the bottom topAdjust = top + height + EDGE_BUFFER - window.innerHeight if topAdjust > 0 top = top - topAdjust - vSlide nibPosition = NIB_OFFSET/2 - Math.min(topAdjust, maxNibVerticalAdjust) # Make sure the pvr is not off the screen to the top 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 when 'right' top = anchorTop + anchorHeight/2 - height/2 + vAdjust - vSlide left = anchorRight + NIB_OFFSET + hAdjust # Make sure the pvr is not off the screen to the bottom topAdjust = top + height + EDGE_BUFFER - window.innerHeight if topAdjust > 0 top = top - topAdjust - vSlide nibPosition = NIB_OFFSET/2 - Math.min(topAdjust, maxNibVerticalAdjust) # Make sure the pvr is not off the screen to the top 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 when 'below' top = anchorTop + anchorHeight + NIB_OFFSET + vAdjust left = anchorLeft + anchorWidth/2 - width/2 + hAdjust - hSlide # Make sure the pvr is not off the screen to the right leftAdjust = left + width + EDGE_BUFFER - window.innerWidth if leftAdjust > 0 left = left - leftAdjust - hSlide nibPosition = NIB_OFFSET/2 - Math.min(leftAdjust, maxNibHorizontalAdjust) # Make sure the pvr is not off the screen to the left 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 when 'above' top = anchorTop - NIB_OFFSET - height - vAdjust left = anchorLeft + anchorWidth/2 - width/2 + hAdjust - hSlide # Make sure the pvr is not off the screen to the right leftAdjust = left + width + EDGE_BUFFER - window.innerWidth if leftAdjust > 0 left = left - leftAdjust - hSlide nibPosition = NIB_OFFSET/2 - Math.min(leftAdjust, maxNibHorizontalAdjust) # Make sure the pvr is not off the screen to the left 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 or div {key: 'inner', className: 'inner'} # Set the styles style = height: height width: width top: top left: left borderColor: borderColor transform: "scale(#{scale})" WebkitTransform: "scale(#{scale})" msTransform: "scale(#{scale})" _.assign style, styleMixin div { className: className style: style onClick: @handleClick }, [ div { key: 'nibBorder' className: nibBorderClass style: nibBorderStyle } if showNib div { key:'nibInner' className: nibClass style: nibStyle } if showNib element ] module.exports = Pvr