Merge afb-client
[src/app-framework-demo.git] / afb-client / bower_components / tether / coffee / utils.coffee
diff --git a/afb-client/bower_components/tether/coffee/utils.coffee b/afb-client/bower_components/tether/coffee/utils.coffee
new file mode 100644 (file)
index 0000000..ef1c0ed
--- /dev/null
@@ -0,0 +1,211 @@
+@Tether ?= {modules: []}
+
+getScrollParent = (el) ->
+  position = getComputedStyle(el).position
+
+  if position is 'fixed'
+    return el
+
+  scrollParent = undefined
+
+  parent = el
+  while parent = parent.parentNode
+    try
+      style = getComputedStyle parent
+
+    return parent if not style?
+
+    if /(auto|scroll)/.test(style['overflow'] + style['overflow-y'] + style['overflow-x'])
+      if position isnt 'absolute' or style['position'] in ['relative', 'absolute', 'fixed']
+        return parent
+
+  return document.body
+
+uniqueId = do ->
+  id = 0
+  ->
+    id++
+
+zeroPosCache = {}
+getOrigin = (doc) ->
+  # getBoundingClientRect is unfortunately too accurate.  It introduces a pixel or two of
+  # jitter as the user scrolls that messes with our ability to detect if two positions
+  # are equivilant or not.  We place an element at the top left of the page that will
+  # get the same jitter, so we can cancel the two out.
+  node = doc._tetherZeroElement
+  if not node?
+    node = doc.createElement 'div'
+    node.setAttribute 'data-tether-id', uniqueId()
+    extend node.style,
+      top: 0
+      left: 0
+      position: 'absolute'
+
+    doc.body.appendChild node
+
+    doc._tetherZeroElement = node
+
+  id = node.getAttribute 'data-tether-id'
+  if not zeroPosCache[id]?
+    zeroPosCache[id] = {}
+    for k, v of node.getBoundingClientRect()
+      # Can't use extend, as on IE9, elements don't resolve to be hasOwnProperty
+      zeroPosCache[id][k] = v
+
+    # Clear the cache when this position call is done
+    defer ->
+      zeroPosCache[id] = undefined
+
+  return zeroPosCache[id]
+
+node = null
+getBounds = (el) ->
+  if el is document
+    doc = document
+    el = document.documentElement
+  else
+    doc = el.ownerDocument
+
+  docEl = doc.documentElement
+
+  box = {}
+  # The original object returned by getBoundingClientRect is immutable, so we clone it
+  # We can't use extend because the properties are not considered part of the object by hasOwnProperty in IE9
+  for k, v of el.getBoundingClientRect()
+    box[k] = v
+
+  origin = getOrigin doc
+
+  box.top -= origin.top
+  box.left -= origin.left
+
+  box.width ?= document.body.scrollWidth - box.left - box.right
+  box.height ?= document.body.scrollHeight - box.top - box.bottom
+
+  box.top = box.top - docEl.clientTop
+  box.left = box.left - docEl.clientLeft
+  box.right = doc.body.clientWidth - box.width - box.left
+  box.bottom = doc.body.clientHeight - box.height - box.top
+
+  box
+
+getOffsetParent = (el) ->
+  el.offsetParent or document.documentElement
+
+getScrollBarSize = ->
+  inner = document.createElement 'div'
+  inner.style.width = '100%'
+  inner.style.height = '200px'
+
+  outer = document.createElement 'div'
+  extend outer.style,
+    position: 'absolute'
+    top: 0
+    left: 0
+    pointerEvents: 'none'
+    visibility: 'hidden'
+    width: '200px'
+    height: '150px'
+    overflow: 'hidden'
+
+  outer.appendChild inner
+
+  document.body.appendChild outer
+
+  widthContained = inner.offsetWidth
+  outer.style.overflow = 'scroll'
+  widthScroll = inner.offsetWidth
+
+  if widthContained is widthScroll
+    widthScroll = outer.clientWidth
+
+  document.body.removeChild outer
+
+  width = widthContained - widthScroll
+
+  {width, height: width}
+
+extend = (out={}) ->
+  args = []
+  Array::push.apply(args, arguments)
+
+  for obj in args[1..] when obj
+    for own key, val of obj
+      out[key] = val
+
+  out
+
+removeClass = (el, name) ->
+  if el.classList?
+    el.classList.remove(cls) for cls in name.split(' ') when cls.trim()
+  else
+    el.className = el.className.replace new RegExp("(^| )#{ name.split(' ').join('|') }( |$)", 'gi'), ' '
+
+addClass = (el, name) ->
+  if el.classList?
+    el.classList.add(cls) for cls in name.split(' ') when cls.trim()
+  else
+    removeClass el, name
+    el.className += " #{ name }"
+
+hasClass = (el, name) ->
+  if el.classList?
+    el.classList.contains(name)
+  else
+    new RegExp("(^| )#{ name }( |$)", 'gi').test(el.className)
+
+updateClasses = (el, add, all) ->
+  # Of the set of 'all' classes, we need the 'add' classes, and only the
+  # 'add' classes to be set.
+  for cls in all when cls not in add
+    if hasClass(el, cls)
+      removeClass el, cls
+
+  for cls in add
+    if not hasClass(el, cls)
+      addClass el, cls
+
+deferred = []
+
+defer = (fn) ->
+  deferred.push fn
+
+flush = ->
+  fn() while fn = deferred.pop()
+
+class Evented
+  on: (event, handler, ctx, once=false) ->
+    @bindings ?= {}
+    @bindings[event] ?= []
+    @bindings[event].push {handler, ctx, once}
+
+  once: (event, handler, ctx) ->
+    @on(event, handler, ctx, true)
+
+  off: (event, handler) ->
+    return unless @bindings?[event]?
+
+    if not handler?
+      delete @bindings[event]
+    else
+      i = 0
+      while i < @bindings[event].length
+        if @bindings[event][i].handler is handler
+          @bindings[event].splice i, 1
+        else
+          i++
+
+  trigger: (event, args...) ->
+    if @bindings?[event]
+      i = 0
+      while i < @bindings[event].length
+        {handler, ctx, once} = @bindings[event][i]
+
+        handler.apply(ctx ? @, args)
+
+        if once
+          @bindings[event].splice i, 1
+        else
+          i++
+
+@Tether.Utils = {getScrollParent, getBounds, getOffsetParent, extend, addClass, removeClass, hasClass, updateClasses, defer, flush, uniqueId, Evented, getScrollBarSize}