1 @Tether ?= {modules: []}
3 getScrollParent = (el) ->
4 position = getComputedStyle(el).position
9 scrollParent = undefined
12 while parent = parent.parentNode
14 style = getComputedStyle parent
16 return parent if not style?
18 if /(auto|scroll)/.test(style['overflow'] + style['overflow-y'] + style['overflow-x'])
19 if position isnt 'absolute' or style['position'] in ['relative', 'absolute', 'fixed']
31 # getBoundingClientRect is unfortunately too accurate. It introduces a pixel or two of
32 # jitter as the user scrolls that messes with our ability to detect if two positions
33 # are equivilant or not. We place an element at the top left of the page that will
34 # get the same jitter, so we can cancel the two out.
35 node = doc._tetherZeroElement
37 node = doc.createElement 'div'
38 node.setAttribute 'data-tether-id', uniqueId()
44 doc.body.appendChild node
46 doc._tetherZeroElement = node
48 id = node.getAttribute 'data-tether-id'
49 if not zeroPosCache[id]?
51 for k, v of node.getBoundingClientRect()
52 # Can't use extend, as on IE9, elements don't resolve to be hasOwnProperty
53 zeroPosCache[id][k] = v
55 # Clear the cache when this position call is done
57 zeroPosCache[id] = undefined
59 return zeroPosCache[id]
65 el = document.documentElement
67 doc = el.ownerDocument
69 docEl = doc.documentElement
72 # The original object returned by getBoundingClientRect is immutable, so we clone it
73 # We can't use extend because the properties are not considered part of the object by hasOwnProperty in IE9
74 for k, v of el.getBoundingClientRect()
77 origin = getOrigin doc
80 box.left -= origin.left
82 box.width ?= document.body.scrollWidth - box.left - box.right
83 box.height ?= document.body.scrollHeight - box.top - box.bottom
85 box.top = box.top - docEl.clientTop
86 box.left = box.left - docEl.clientLeft
87 box.right = doc.body.clientWidth - box.width - box.left
88 box.bottom = doc.body.clientHeight - box.height - box.top
92 getOffsetParent = (el) ->
93 el.offsetParent or document.documentElement
96 inner = document.createElement 'div'
97 inner.style.width = '100%'
98 inner.style.height = '200px'
100 outer = document.createElement 'div'
105 pointerEvents: 'none'
111 outer.appendChild inner
113 document.body.appendChild outer
115 widthContained = inner.offsetWidth
116 outer.style.overflow = 'scroll'
117 widthScroll = inner.offsetWidth
119 if widthContained is widthScroll
120 widthScroll = outer.clientWidth
122 document.body.removeChild outer
124 width = widthContained - widthScroll
126 {width, height: width}
130 Array::push.apply(args, arguments)
132 for obj in args[1..] when obj
133 for own key, val of obj
138 removeClass = (el, name) ->
140 el.classList.remove(cls) for cls in name.split(' ') when cls.trim()
142 el.className = el.className.replace new RegExp("(^| )#{ name.split(' ').join('|') }( |$)", 'gi'), ' '
144 addClass = (el, name) ->
146 el.classList.add(cls) for cls in name.split(' ') when cls.trim()
149 el.className += " #{ name }"
151 hasClass = (el, name) ->
153 el.classList.contains(name)
155 new RegExp("(^| )#{ name }( |$)", 'gi').test(el.className)
157 updateClasses = (el, add, all) ->
158 # Of the set of 'all' classes, we need the 'add' classes, and only the
159 # 'add' classes to be set.
160 for cls in all when cls not in add
165 if not hasClass(el, cls)
174 fn() while fn = deferred.pop()
177 on: (event, handler, ctx, once=false) ->
179 @bindings[event] ?= []
180 @bindings[event].push {handler, ctx, once}
182 once: (event, handler, ctx) ->
183 @on(event, handler, ctx, true)
185 off: (event, handler) ->
186 return unless @bindings?[event]?
189 delete @bindings[event]
192 while i < @bindings[event].length
193 if @bindings[event][i].handler is handler
194 @bindings[event].splice i, 1
198 trigger: (event, args...) ->
201 while i < @bindings[event].length
202 {handler, ctx, once} = @bindings[event][i]
204 handler.apply(ctx ? @, args)
207 @bindings[event].splice i, 1
211 @Tether.Utils = {getScrollParent, getBounds, getOffsetParent, extend, addClass, removeClass, hasClass, updateClasses, defer, flush, uniqueId, Evented, getScrollBarSize}