summaryrefslogtreecommitdiffstats
path: root/afb-client/bower_components/tether/coffee/tether.coffee
diff options
context:
space:
mode:
authorStephane Desneux <stephane.desneux@iot.bzh>2016-05-31 18:16:48 +0200
committerStephane Desneux <stephane.desneux@iot.bzh>2016-05-31 18:16:48 +0200
commit5b1e6cc132f44262a873fa8296a2a3e1017b0278 (patch)
tree43b2cd54e2e300b399ff3f2af4458a2c4ed8a144 /afb-client/bower_components/tether/coffee/tether.coffee
parentf7d2f9ac4168ee5064580c666d508667a73cefc0 (diff)
parent85ace9c1ce9a98e9b8a22f045c7dd752b38d9129 (diff)
Merge afb-client
Diffstat (limited to 'afb-client/bower_components/tether/coffee/tether.coffee')
-rw-r--r--afb-client/bower_components/tether/coffee/tether.coffee573
1 files changed, 573 insertions, 0 deletions
diff --git a/afb-client/bower_components/tether/coffee/tether.coffee b/afb-client/bower_components/tether/coffee/tether.coffee
new file mode 100644
index 0000000..3df246f
--- /dev/null
+++ b/afb-client/bower_components/tether/coffee/tether.coffee
@@ -0,0 +1,573 @@
+if not @Tether?
+ throw new Error "You must include the utils.js file before tether.js"
+
+Tether = @Tether
+
+{getScrollParent, getSize, getOuterSize, getBounds, getOffsetParent, extend, addClass, removeClass, updateClasses, defer, flush, getScrollBarSize} = Tether.Utils
+
+within = (a, b, diff=1) ->
+ a + diff >= b >= a - diff
+
+transformKey = do ->
+ el = document.createElement 'div'
+
+ for key in ['transform', 'webkitTransform', 'OTransform', 'MozTransform', 'msTransform']
+ if el.style[key] isnt undefined
+ return key
+
+tethers = []
+
+position = ->
+ for tether in tethers
+ tether.position(false)
+
+ flush()
+
+now = ->
+ performance?.now?() ? +new Date
+
+do ->
+ lastCall = null
+ lastDuration = null
+ pendingTimeout = null
+
+ tick = ->
+ if lastDuration? and lastDuration > 16
+ # We voluntarily throttle ourselves if we can't manage 60fps
+ lastDuration = Math.min(lastDuration - 16, 250)
+
+ # Just in case this is the last event, remember to position just once more
+ pendingTimeout = setTimeout tick, 250
+ return
+
+ if lastCall? and (now() - lastCall) < 10
+ # Some browsers call events a little too frequently, refuse to run more than is reasonable
+ return
+
+ if pendingTimeout?
+ clearTimeout pendingTimeout
+ pendingTimeout = null
+
+ lastCall = now()
+
+ position()
+
+ lastDuration = now() - lastCall
+
+ for event in ['resize', 'scroll', 'touchmove']
+ window.addEventListener event, tick
+
+MIRROR_LR =
+ center: 'center'
+ left: 'right'
+ right: 'left'
+
+MIRROR_TB =
+ middle: 'middle'
+ top: 'bottom'
+ bottom: 'top'
+
+OFFSET_MAP =
+ top: 0
+ left: 0
+ middle: '50%'
+ center: '50%'
+ bottom: '100%'
+ right: '100%'
+
+autoToFixedAttachment = (attachment, relativeToAttachment) ->
+ {left, top} = attachment
+
+ if left is 'auto'
+ left = MIRROR_LR[relativeToAttachment.left]
+
+ if top is 'auto'
+ top = MIRROR_TB[relativeToAttachment.top]
+
+ {left, top}
+
+attachmentToOffset = (attachment) ->
+ return {
+ left: OFFSET_MAP[attachment.left] ? attachment.left
+ top: OFFSET_MAP[attachment.top] ? attachment.top
+ }
+
+addOffset = (offsets...) ->
+ out = {top: 0, left: 0}
+
+ for {top, left} in offsets
+ if typeof top is 'string'
+ top = parseFloat(top, 10)
+ if typeof left is 'string'
+ left = parseFloat(left, 10)
+
+ out.top += top
+ out.left += left
+
+ out
+
+offsetToPx = (offset, size) ->
+ if typeof offset.left is 'string' and offset.left.indexOf('%') isnt -1
+ offset.left = parseFloat(offset.left, 10) / 100 * size.width
+ if typeof offset.top is 'string' and offset.top.indexOf('%') isnt -1
+ offset.top = parseFloat(offset.top, 10) / 100 * size.height
+
+ offset
+
+parseAttachment = parseOffset = (value) ->
+ [top, left] = value.split(' ')
+
+ {top, left}
+
+class _Tether
+ @modules: []
+
+ constructor: (options) ->
+ tethers.push @
+
+ @history = []
+
+ @setOptions options, false
+
+ for module in Tether.modules
+ module.initialize?.call(@)
+
+ @position()
+
+ getClass: (key) ->
+ if @options.classes?[key]
+ @options.classes[key]
+ else if @options.classes?[key] isnt false
+ if @options.classPrefix
+ "#{ @options.classPrefix }-#{ key }"
+ else
+ key
+ else
+ ''
+
+ setOptions: (@options, position=true) ->
+ defaults =
+ offset: '0 0'
+ targetOffset: '0 0'
+ targetAttachment: 'auto auto'
+ classPrefix: 'tether'
+
+ @options = extend defaults, @options
+
+ {@element, @target, @targetModifier} = @options
+
+ if @target is 'viewport'
+ @target = document.body
+ @targetModifier = 'visible'
+ else if @target is 'scroll-handle'
+ @target = document.body
+ @targetModifier = 'scroll-handle'
+
+ for key in ['element', 'target']
+ if not @[key]?
+ throw new Error "Tether Error: Both element and target must be defined"
+
+ if @[key].jquery?
+ @[key] = @[key][0]
+ else if typeof @[key] is 'string'
+ @[key] = document.querySelector @[key]
+
+ addClass @element, @getClass 'element'
+ addClass @target, @getClass 'target'
+
+ if not @options.attachment
+ throw new Error "Tether Error: You must provide an attachment"
+
+ @targetAttachment = parseAttachment @options.targetAttachment
+ @attachment = parseAttachment @options.attachment
+ @offset = parseOffset @options.offset
+ @targetOffset = parseOffset @options.targetOffset
+
+ if @scrollParent?
+ @disable()
+
+ if @targetModifier is 'scroll-handle'
+ @scrollParent = @target
+ else
+ @scrollParent = getScrollParent @target
+
+ unless @options.enabled is false
+ @enable(position)
+
+ getTargetBounds: ->
+ if @targetModifier?
+ switch @targetModifier
+ when 'visible'
+ if @target is document.body
+ {top: pageYOffset, left: pageXOffset, height: innerHeight, width: innerWidth}
+ else
+ bounds = getBounds @target
+
+ out =
+ height: bounds.height
+ width: bounds.width
+ top: bounds.top
+ left: bounds.left
+
+ out.height = Math.min(out.height, bounds.height - (pageYOffset - bounds.top))
+ out.height = Math.min(out.height, bounds.height - ((bounds.top + bounds.height) - (pageYOffset + innerHeight)))
+ out.height = Math.min(innerHeight, out.height)
+ out.height -= 2
+
+ out.width = Math.min(out.width, bounds.width - (pageXOffset - bounds.left))
+ out.width = Math.min(out.width, bounds.width - ((bounds.left + bounds.width) - (pageXOffset + innerWidth)))
+ out.width = Math.min(innerWidth, out.width)
+ out.width -= 2
+
+ if out.top < pageYOffset
+ out.top = pageYOffset
+ if out.left < pageXOffset
+ out.left = pageXOffset
+
+ out
+
+ when 'scroll-handle'
+ target = @target
+ if target is document.body
+ target = document.documentElement
+
+ bounds =
+ left: pageXOffset
+ top: pageYOffset
+ height: innerHeight
+ width: innerWidth
+ else
+ bounds = getBounds target
+
+ style = getComputedStyle target
+
+ hasBottomScroll = target.scrollWidth > target.clientWidth or 'scroll' is [style.overflow, style.overflowX] or @target isnt document.body
+
+ scrollBottom = 0
+ if hasBottomScroll
+ scrollBottom = 15
+
+ height = bounds.height - parseFloat(style.borderTopWidth) - parseFloat(style.borderBottomWidth) - scrollBottom
+
+ out =
+ width: 15
+ height: height * 0.975 * (height / target.scrollHeight)
+ left: bounds.left + bounds.width - parseFloat(style.borderLeftWidth) - 15
+
+ fitAdj = 0
+ if height < 408 and @target is document.body
+ fitAdj = -0.00011 * Math.pow(height, 2) - 0.00727 * height + 22.58
+
+ if @target isnt document.body
+ out.height = Math.max out.height, 24
+
+ scrollPercentage = @target.scrollTop / (target.scrollHeight - height)
+ out.top = scrollPercentage * (height - out.height - fitAdj) + bounds.top + parseFloat(style.borderTopWidth)
+
+ if @target is document.body
+ out.height = Math.max out.height, 24
+
+ out
+ else
+ getBounds @target
+
+ clearCache: ->
+ @_cache = {}
+
+ cache: (k, getter) ->
+ # More than one module will often need the same DOM info, so
+ # we keep a cache which is cleared on each position call
+ @_cache ?= {}
+
+ if not @_cache[k]?
+ @_cache[k] = getter.call(@)
+
+ @_cache[k]
+
+ enable: (position=true) ->
+ addClass @target, @getClass 'enabled'
+ addClass @element, @getClass 'enabled'
+ @enabled = true
+
+ if @scrollParent isnt document
+ @scrollParent.addEventListener 'scroll', @position
+
+ if position
+ @position()
+
+ disable: ->
+ removeClass @target, @getClass 'enabled'
+ removeClass @element, @getClass 'enabled'
+ @enabled = false
+
+ if @scrollParent?
+ @scrollParent.removeEventListener 'scroll', @position
+
+ destroy: ->
+ @disable()
+
+ for tether, i in tethers
+ if tether is @
+ tethers.splice i, 1
+ break
+
+ updateAttachClasses: (elementAttach=@attachment, targetAttach=@targetAttachment) ->
+ sides = ['left', 'top', 'bottom', 'right', 'middle', 'center']
+
+ if @_addAttachClasses?.length
+ # updateAttachClasses can be called more than once in a position call, so
+ # we need to clean up after ourselves such that when the last defer gets
+ # ran it doesn't add any extra classes from previous calls.
+ @_addAttachClasses.splice 0, @_addAttachClasses.length
+
+ add = @_addAttachClasses ?= []
+ add.push "#{ @getClass('element-attached') }-#{ elementAttach.top }" if elementAttach.top
+ add.push "#{ @getClass('element-attached') }-#{ elementAttach.left }" if elementAttach.left
+ add.push "#{ @getClass('target-attached') }-#{ targetAttach.top }" if targetAttach.top
+ add.push "#{ @getClass('target-attached') }-#{ targetAttach.left }" if targetAttach.left
+
+ all = []
+ all.push "#{ @getClass('element-attached') }-#{ side }" for side in sides
+ all.push "#{ @getClass('target-attached') }-#{ side }" for side in sides
+
+ defer =>
+ return unless @_addAttachClasses?
+
+ updateClasses @element, @_addAttachClasses, all
+ updateClasses @target, @_addAttachClasses, all
+
+ @_addAttachClasses = undefined
+
+ position: (flushChanges=true) =>
+ # flushChanges commits the changes immediately, leave true unless you are positioning multiple
+ # tethers (in which case call Tether.Utils.flush yourself when you're done)
+
+ return unless @enabled
+
+ @clearCache()
+
+ # Turn 'auto' attachments into the appropriate corner or edge
+ targetAttachment = autoToFixedAttachment(@targetAttachment, @attachment)
+
+ @updateAttachClasses @attachment, targetAttachment
+
+ elementPos = @cache 'element-bounds', => getBounds @element
+ {width, height} = elementPos
+
+ if width is 0 and height is 0 and @lastSize?
+ # We cache the height and width to make it possible to position elements that are
+ # getting hidden.
+ {width, height} = @lastSize
+ else
+ @lastSize = {width, height}
+
+ targetSize = targetPos = @cache 'target-bounds', => @getTargetBounds()
+
+ # Get an actual px offset from the attachment
+ offset = offsetToPx attachmentToOffset(@attachment), {width, height}
+ targetOffset = offsetToPx attachmentToOffset(targetAttachment), targetSize
+
+ manualOffset = offsetToPx(@offset, {width, height})
+ manualTargetOffset = offsetToPx(@targetOffset, targetSize)
+
+ # Add the manually provided offset
+ offset = addOffset offset, manualOffset
+ targetOffset = addOffset targetOffset, manualTargetOffset
+
+ # It's now our goal to make (element position + offset) == (target position + target offset)
+ left = targetPos.left + targetOffset.left - offset.left
+ top = targetPos.top + targetOffset.top - offset.top
+
+ for module in Tether.modules
+ ret = module.position.call(@, {left, top, targetAttachment, targetPos, @attachment, elementPos, offset, targetOffset, manualOffset, manualTargetOffset, scrollbarSize})
+
+ if not ret? or typeof ret isnt 'object'
+ continue
+ else if ret is false
+ return false
+ else
+ {top, left} = ret
+
+ # We describe the position three different ways to give the optimizer
+ # a chance to decide the best possible way to position the element
+ # with the fewest repaints.
+ next = {
+ # It's position relative to the page (absolute positioning when
+ # the element is a child of the body)
+ page:
+ top: top
+ left: left
+
+ # It's position relative to the viewport (fixed positioning)
+ viewport:
+ top: top - pageYOffset
+ bottom: pageYOffset - top - height + innerHeight
+ left: left - pageXOffset
+ right: pageXOffset - left - width + innerWidth
+ }
+
+ if document.body.scrollWidth > window.innerWidth
+ scrollbarSize = @cache 'scrollbar-size', getScrollBarSize
+ next.viewport.bottom -= scrollbarSize.height
+
+ if document.body.scrollHeight > window.innerHeight
+ scrollbarSize = @cache 'scrollbar-size', getScrollBarSize
+ next.viewport.right -= scrollbarSize.width
+
+ if document.body.style.position not in ['', 'static'] or document.body.parentElement.style.position not in ['', 'static']
+ # Absolute positioning in the body will be relative to the page, not the 'initial containing block'
+ next.page.bottom = document.body.scrollHeight - top - height
+ next.page.right = document.body.scrollWidth - left - width
+
+ if @options.optimizations?.moveElement isnt false and not @targetModifier?
+ offsetParent = @cache 'target-offsetparent', => getOffsetParent @target
+ offsetPosition = @cache 'target-offsetparent-bounds', -> getBounds offsetParent
+ offsetParentStyle = getComputedStyle offsetParent
+ elementStyle = getComputedStyle @element
+ offsetParentSize = offsetPosition
+
+ offsetBorder = {}
+ for side in ['Top', 'Left', 'Bottom', 'Right']
+ offsetBorder[side.toLowerCase()] = parseFloat offsetParentStyle["border#{ side }Width"]
+
+ offsetPosition.right = document.body.scrollWidth - offsetPosition.left - offsetParentSize.width + offsetBorder.right
+ offsetPosition.bottom = document.body.scrollHeight - offsetPosition.top - offsetParentSize.height + offsetBorder.bottom
+
+ if next.page.top >= (offsetPosition.top + offsetBorder.top) and next.page.bottom >= offsetPosition.bottom
+ if next.page.left >= (offsetPosition.left + offsetBorder.left) and next.page.right >= offsetPosition.right
+ # We're within the visible part of the target's scroll parent
+
+ scrollTop = offsetParent.scrollTop
+ scrollLeft = offsetParent.scrollLeft
+
+ # It's position relative to the target's offset parent (absolute positioning when
+ # the element is moved to be a child of the target's offset parent).
+ next.offset =
+ top: next.page.top - offsetPosition.top + scrollTop - offsetBorder.top
+ left: next.page.left - offsetPosition.left + scrollLeft - offsetBorder.left
+
+
+ # We could also travel up the DOM and try each containing context, rather than only
+ # looking at the body, but we're gonna get diminishing returns.
+
+ @move next
+
+ @history.unshift next
+
+ if @history.length > 3
+ @history.pop()
+
+ if flushChanges
+ flush()
+
+ true
+
+ move: (position) ->
+ return if not @element.parentNode?
+
+ same = {}
+
+ for type of position
+ same[type] = {}
+
+ for key of position[type]
+ found = false
+
+ for point in @history
+ unless within(point[type]?[key], position[type][key])
+ found = true
+ break
+
+ if not found
+ same[type][key] = true
+
+ css = {top: '', left: '', right: '', bottom: ''}
+
+ transcribe = (same, pos) =>
+ if @options.optimizations?.gpu isnt false
+ if same.top
+ css.top = 0
+ yPos = pos.top
+ else
+ css.bottom = 0
+ yPos = -pos.bottom
+
+ if same.left
+ css.left = 0
+ xPos = pos.left
+ else
+ css.right = 0
+ xPos = -pos.right
+
+
+ css[transformKey] = "translateX(#{ Math.round xPos }px) translateY(#{ Math.round yPos }px)"
+
+ if transformKey isnt 'msTransform'
+ # The Z transform will keep this in the GPU (faster, and prevents artifacts),
+ # but IE9 doesn't support 3d transforms and will choke.
+ css[transformKey] += " translateZ(0)"
+
+ else
+ if same.top
+ css.top = "#{ pos.top }px"
+ else
+ css.bottom = "#{ pos.bottom }px"
+
+ if same.left
+ css.left = "#{ pos.left }px"
+ else
+ css.right = "#{ pos.right }px"
+
+ moved = false
+ if (same.page.top or same.page.bottom) and (same.page.left or same.page.right)
+ css.position = 'absolute'
+ transcribe same.page, position.page
+
+ else if (same.viewport.top or same.viewport.bottom) and (same.viewport.left or same.viewport.right)
+ css.position = 'fixed'
+
+ transcribe same.viewport, position.viewport
+
+ else if same.offset? and same.offset.top and same.offset.left
+ css.position = 'absolute'
+
+ offsetParent = @cache 'target-offsetparent', => getOffsetParent @target
+
+ if getOffsetParent(@element) isnt offsetParent
+ defer =>
+ @element.parentNode.removeChild @element
+ offsetParent.appendChild @element
+
+ transcribe same.offset, position.offset
+
+ moved = true
+
+ else
+ css.position = 'absolute'
+ transcribe {top: true, left: true}, position.page
+
+ if not moved and @element.parentNode.tagName isnt 'BODY'
+ @element.parentNode.removeChild @element
+ document.body.appendChild @element
+
+ # Any css change will trigger a repaint, so let's avoid one if nothing changed
+ writeCSS = {}
+ write = false
+ for key, val of css
+ elVal = @element.style[key]
+
+ if elVal isnt '' and val isnt '' and key in ['top', 'left', 'bottom', 'right']
+ elVal = parseFloat elVal
+ val = parseFloat val
+
+ if elVal isnt val
+ write = true
+ writeCSS[key] = css[key]
+
+ if write
+ defer =>
+ extend @element.style, writeCSS
+
+Tether.position = position
+
+@Tether = extend _Tether, Tether