diff --git a/src/components/ScrollPane/index.vue b/src/components/ScrollPane/index.vue index d4d75aee..d90565d8 100644 --- a/src/components/ScrollPane/index.vue +++ b/src/components/ScrollPane/index.vue @@ -1,37 +1,86 @@ diff --git a/src/vendor/detectElementResize.js b/src/vendor/detectElementResize.js new file mode 100644 index 00000000..85b76b8d --- /dev/null +++ b/src/vendor/detectElementResize.js @@ -0,0 +1,226 @@ +/* eslint-disable */ +export default function createDetectElementResize(nonce) { + // Check `document` and `window` in case of server-side rendering + var _window + if (typeof window !== 'undefined') { + _window = window + } else if (typeof self !== 'undefined') { + _window = self + } else { + _window = global + } + + var attachEvent = typeof document !== 'undefined' && document.attachEvent + + if (!attachEvent) { + var requestFrame = (function() { + var raf = + _window.requestAnimationFrame || + _window.mozRequestAnimationFrame || + _window.webkitRequestAnimationFrame || + function(fn) { + return _window.setTimeout(fn, 20) + } + return function(fn) { + return raf(fn) + } + })() + + var cancelFrame = (function() { + var cancel = + _window.cancelAnimationFrame || + _window.mozCancelAnimationFrame || + _window.webkitCancelAnimationFrame || + _window.clearTimeout + return function(id) { + return cancel(id) + } + })() + + var resetTriggers = function(element) { + var triggers = element.__resizeTriggers__, + expand = triggers.firstElementChild, + contract = triggers.lastElementChild, + expandChild = expand.firstElementChild + contract.scrollLeft = contract.scrollWidth + contract.scrollTop = contract.scrollHeight + expandChild.style.width = expand.offsetWidth + 1 + 'px' + expandChild.style.height = expand.offsetHeight + 1 + 'px' + expand.scrollLeft = expand.scrollWidth + expand.scrollTop = expand.scrollHeight + } + + var checkTriggers = function(element) { + return ( + element.offsetWidth != element.__resizeLast__.width || + element.offsetHeight != element.__resizeLast__.height + ) + } + + var scrollListener = function(e) { + // Don't measure (which forces) reflow for scrolls that happen inside of children! + if ( + e.target.className.indexOf('contract-trigger') < 0 && + e.target.className.indexOf('expand-trigger') < 0 + ) { + return + } + + var element = this + resetTriggers(this) + if (this.__resizeRAF__) { + cancelFrame(this.__resizeRAF__) + } + this.__resizeRAF__ = requestFrame(function() { + if (checkTriggers(element)) { + element.__resizeLast__.width = element.offsetWidth + element.__resizeLast__.height = element.offsetHeight + element.__resizeListeners__.forEach(function(fn) { + fn.call(element, e) + }) + } + }) + } + + /* Detect CSS Animations support to detect element display/re-attach */ + var animation = false, + keyframeprefix = '', + animationstartevent = 'animationstart', + domPrefixes = 'Webkit Moz O ms'.split(' '), + startEvents = 'webkitAnimationStart animationstart oAnimationStart MSAnimationStart'.split( + ' ', + ), + pfx = '' + { + var elm = document.createElement('fakeelement') + if (elm.style.animationName !== undefined) { + animation = true + } + + if (animation === false) { + for (var i = 0; i < domPrefixes.length; i++) { + if (elm.style[domPrefixes[i] + 'AnimationName'] !== undefined) { + pfx = domPrefixes[i] + keyframeprefix = '-' + pfx.toLowerCase() + '-' + animationstartevent = startEvents[i] + animation = true + break + } + } + } + } + + var animationName = 'resizeanim' + var animationKeyframes = + '@' + + keyframeprefix + + 'keyframes ' + + animationName + + ' { from { opacity: 0; } to { opacity: 0; } } ' + var animationStyle = + keyframeprefix + 'animation: 1ms ' + animationName + '; ' + } + + var createStyles = function(doc) { + if (!doc.getElementById('detectElementResize')) { + // opacity:0 works around a chrome bug https://code.google.com/p/chromium/issues/detail?id=286360 + var css = + (animationKeyframes || '') + + '.resize-triggers { ' + + (animationStyle || '') + + 'visibility: hidden; opacity: 0; } ' + + '.resize-triggers, .resize-triggers > div, .contract-trigger:before { content: " "; display: block; position: absolute; top: 0; left: 0; height: 100%; width: 100%; overflow: hidden; z-index: -1; } .resize-triggers > div { background: #eee; overflow: auto; } .contract-trigger:before { width: 200%; height: 200%; }', + head = doc.head || doc.getElementsByTagName('head')[0], + style = doc.createElement('style') + + style.id = 'detectElementResize' + style.type = 'text/css' + + if (nonce != null) { + style.setAttribute('nonce', nonce) + } + + if (style.styleSheet) { + style.styleSheet.cssText = css + } else { + style.appendChild(doc.createTextNode(css)) + } + + head.appendChild(style) + } + } + + var addResizeListener = function(element, fn) { + if (attachEvent) { + element.attachEvent('onresize', fn) + } else { + if (!element.__resizeTriggers__) { + var doc = element.ownerDocument + var elementStyle = _window.getComputedStyle(element) + if (elementStyle && elementStyle.position == 'static') { + element.style.position = 'relative' + } + createStyles(doc) + element.__resizeLast__ = {} + element.__resizeListeners__ = []; + (element.__resizeTriggers__ = doc.createElement('div')).className = + 'resize-triggers' + element.__resizeTriggers__.innerHTML = + '
' + + '
' + element.appendChild(element.__resizeTriggers__) + resetTriggers(element) + element.addEventListener('scroll', scrollListener, true) + + /* Listen for a css animation to detect element display/re-attach */ + if (animationstartevent) { + element.__resizeTriggers__.__animationListener__ = function animationListener( + e, + ) { + if (e.animationName == animationName) { + resetTriggers(element) + } + } + element.__resizeTriggers__.addEventListener( + animationstartevent, + element.__resizeTriggers__.__animationListener__, + ) + } + } + element.__resizeListeners__.push(fn) + } + } + + var removeResizeListener = function(element, fn) { + if (attachEvent) { + element.detachEvent('onresize', fn) + } else { + element.__resizeListeners__.splice( + element.__resizeListeners__.indexOf(fn), + 1, + ) + if (!element.__resizeListeners__.length) { + element.removeEventListener('scroll', scrollListener, true) + if (element.__resizeTriggers__.__animationListener__) { + element.__resizeTriggers__.removeEventListener( + animationstartevent, + element.__resizeTriggers__.__animationListener__, + ) + element.__resizeTriggers__.__animationListener__ = null + } + try { + element.__resizeTriggers__ = !element.removeChild( + element.__resizeTriggers__, + ) + } catch (e) { + // Preact compat; see developit/preact-compat/issues/228 + } + } + } + } + + return { + addResizeListener, + removeResizeListener + } +} diff --git a/src/views/layout/components/TagsView.vue b/src/views/layout/components/TagsView.vue index 2bb83b41..194828b0 100644 --- a/src/views/layout/components/TagsView.vue +++ b/src/views/layout/components/TagsView.vue @@ -1,5 +1,8 @@