--- /dev/null
+/*!\r
+ * iScroll v4.1.4 ~ Copyright (c) 2011 Matteo Spinelli, http://cubiq.org\r
+ * Released under MIT license, http://cubiq.org/license\r
+ */\r
+\r
+(function(){\r
+var m = Math,\r
+ vendor = (/webkit/i).test(navigator.appVersion) ? 'webkit' :\r
+ (/firefox/i).test(navigator.userAgent) ? 'Moz' :\r
+ 'opera' in window ? 'O' : '',\r
+\r
+ // Browser capabilities\r
+ has3d = 'WebKitCSSMatrix' in window && 'm11' in new WebKitCSSMatrix(),\r
+ hasTouch = 'ontouchstart' in window,\r
+ hasTransform = vendor + 'Transform' in document.documentElement.style,\r
+ isAndroid = (/android/gi).test(navigator.appVersion),\r
+ isIDevice = (/iphone|ipad/gi).test(navigator.appVersion),\r
+ isPlaybook = (/playbook/gi).test(navigator.appVersion),\r
+ hasTransitionEnd = (isIDevice || isPlaybook) && 'onwebkittransitionend' in window,\r
+ nextFrame = (function() {\r
+ return window.requestAnimationFrame\r
+ || window.webkitRequestAnimationFrame\r
+ || window.mozRequestAnimationFrame\r
+ || window.oRequestAnimationFrame\r
+ || window.msRequestAnimationFrame\r
+ || function(callback) { return setTimeout(callback, 17); }\r
+ })(),\r
+ cancelFrame = (function () {\r
+ return window.cancelRequestAnimationFrame\r
+ || window.webkitCancelRequestAnimationFrame\r
+ || window.mozCancelRequestAnimationFrame\r
+ || window.oCancelRequestAnimationFrame\r
+ || window.msCancelRequestAnimationFrame\r
+ || clearTimeout\r
+ })(),\r
+\r
+ // Events\r
+ RESIZE_EV = 'onorientationchange' in window ? 'orientationchange' : 'resize',\r
+ START_EV = hasTouch ? 'touchstart' : 'mousedown',\r
+ MOVE_EV = hasTouch ? 'touchmove' : 'mousemove',\r
+ END_EV = hasTouch ? 'touchend' : 'mouseup',\r
+ CANCEL_EV = hasTouch ? 'touchcancel' : 'mouseup',\r
+ WHEEL_EV = vendor == 'Moz' ? 'DOMMouseScroll' : 'mousewheel',\r
+\r
+ // Helpers\r
+ trnOpen = 'translate' + (has3d ? '3d(' : '('),\r
+ trnClose = has3d ? ',0)' : ')',\r
+\r
+ // Constructor\r
+ iScroll = function (el, options) {\r
+ var that = this,\r
+ doc = document,\r
+ i;\r
+\r
+ that.wrapper = typeof el == 'object' ? el : doc.getElementById(el);\r
+ that.wrapper.style.overflow = 'hidden';\r
+ that.scroller = that.wrapper.children[0];\r
+\r
+ // Default options\r
+ that.options = {\r
+ hScroll: true,\r
+ vScroll: true,\r
+ bounce: true,\r
+ bounceLock: false,\r
+ momentum: true,\r
+ lockDirection: true,\r
+ useTransform: true,\r
+ useTransition: false,\r
+\r
+ // Scrollbar\r
+ hScrollbar: true,\r
+ vScrollbar: true,\r
+ fixedScrollbar: isAndroid,\r
+ hideScrollbar: isIDevice,\r
+ fadeScrollbar: isIDevice && has3d,\r
+ scrollbarClass: '',\r
+\r
+ // Zoom\r
+ zoom: false,\r
+ zoomMin: 1,\r
+ zoomMax: 4,\r
+ doubleTapZoom: 2,\r
+\r
+ // Snap\r
+ snap: false,\r
+ snapThreshold: 1,\r
+\r
+ // Events\r
+ onRefresh: null,\r
+ onBeforeScrollStart: function (e) { e.preventDefault(); },\r
+ onScrollStart: null,\r
+ onBeforeScrollMove: null,\r
+ onScrollMove: null,\r
+ onBeforeScrollEnd: null,\r
+ onScrollEnd: null,\r
+ onTouchEnd: null,\r
+ onDestroy: null\r
+ };\r
+\r
+ // User defined options\r
+ for (i in options) that.options[i] = options[i];\r
+\r
+ // Normalize options\r
+ that.options.useTransform = hasTransform ? that.options.useTransform : false;\r
+ that.options.hScrollbar = that.options.hScroll && that.options.hScrollbar;\r
+ that.options.vScrollbar = that.options.vScroll && that.options.vScrollbar;\r
+ that.options.zoom = that.options.useTransform && that.options.zoom;\r
+ that.options.useTransition = hasTransitionEnd && that.options.useTransition;\r
+ \r
+ // Set some default styles\r
+ that.scroller.style[vendor + 'TransitionProperty'] = that.options.useTransform ? '-' + vendor.toLowerCase() + '-transform' : 'top left';\r
+ that.scroller.style[vendor + 'TransitionDuration'] = '0';\r
+ that.scroller.style[vendor + 'TransformOrigin'] = '0 0';\r
+ if (that.options.useTransition) that.scroller.style[vendor + 'TransitionTimingFunction'] = 'cubic-bezier(0.33,0.66,0.66,1)';\r
+ \r
+ if (that.options.useTransform) that.scroller.style[vendor + 'Transform'] = trnOpen + '0,0' + trnClose;\r
+ else that.scroller.style.cssText += ';top:0;left:0';\r
+\r
+ if (that.options.useTransition) that.options.fixedScrollbar = true;\r
+\r
+ that.refresh();\r
+\r
+ that._bind(RESIZE_EV, window);\r
+ that._bind(START_EV);\r
+ if (!hasTouch) {\r
+ that._bind('mouseout', that.wrapper);\r
+ that._bind(WHEEL_EV);\r
+ }\r
+ };\r
+\r
+// Prototype\r
+iScroll.prototype = {\r
+ enabled: true,\r
+ x: 0,\r
+ y: 0,\r
+ steps: [],\r
+ scale: 1,\r
+ currPageX: 0, currPageY: 0,\r
+ pagesX: [], pagesY: [],\r
+ aniTime: null,\r
+ \r
+ handleEvent: function (e) {\r
+ var that = this;\r
+ switch(e.type) {\r
+ case START_EV: that._start(e); break;\r
+ case MOVE_EV: that._move(e); break;\r
+ case END_EV:\r
+ case CANCEL_EV: that._end(e); break;\r
+ case RESIZE_EV: that._resize(); break;\r
+ case WHEEL_EV: that._wheel(e); break;\r
+ case 'mouseout': that._mouseout(e); break;\r
+ case 'webkitTransitionEnd': that._transitionEnd(e); break;\r
+ }\r
+ },\r
+ \r
+ _scrollbar: function (dir) {\r
+ var that = this,\r
+ doc = document,\r
+ bar;\r
+\r
+ if (!that[dir + 'Scrollbar']) {\r
+ if (that[dir + 'ScrollbarWrapper']) {\r
+ if (hasTransform) that[dir + 'ScrollbarIndicator'].style[vendor + 'Transform'] = '';\r
+ that[dir + 'ScrollbarWrapper'].parentNode.removeChild(that[dir + 'ScrollbarWrapper']);\r
+ that[dir + 'ScrollbarWrapper'] = null;\r
+ that[dir + 'ScrollbarIndicator'] = null;\r
+ }\r
+\r
+ return;\r
+ }\r
+\r
+ if (!that[dir + 'ScrollbarWrapper']) {\r
+ // Create the scrollbar wrapper\r
+ bar = doc.createElement('div');\r
+\r
+ if (that.options.scrollbarClass) bar.className = that.options.scrollbarClass + dir.toUpperCase();\r
+ else bar.style.cssText = 'position:absolute;z-index:100;' + (dir == 'h' ? 'height:7px;bottom:1px;left:2px;right:' + (that.vScrollbar ? '7' : '2') + 'px' : 'width:7px;bottom:' + (that.hScrollbar ? '7' : '2') + 'px;top:2px;right:1px');\r
+\r
+ bar.style.cssText += ';pointer-events:none;-' + vendor + '-transition-property:opacity;-' + vendor + '-transition-duration:' + (that.options.fadeScrollbar ? '350ms' : '0') + ';overflow:hidden;opacity:' + (that.options.hideScrollbar ? '0' : '1');\r
+\r
+ that.wrapper.appendChild(bar);\r
+ that[dir + 'ScrollbarWrapper'] = bar;\r
+\r
+ // Create the scrollbar indicator\r
+ bar = doc.createElement('div');\r
+ if (!that.options.scrollbarClass) {\r
+ bar.style.cssText = 'position:absolute;z-index:100;background:rgba(0,0,0,0.5);border:1px solid rgba(255,255,255,0.9);-' + vendor + '-background-clip:padding-box;-' + vendor + '-box-sizing:border-box;' + (dir == 'h' ? 'height:100%' : 'width:100%') + ';-' + vendor + '-border-radius:3px;border-radius:3px';\r
+ }\r
+ bar.style.cssText += ';pointer-events:none;-' + vendor + '-transition-property:-' + vendor + '-transform;-' + vendor + '-transition-timing-function:cubic-bezier(0.33,0.66,0.66,1);-' + vendor + '-transition-duration:0;-' + vendor + '-transform:' + trnOpen + '0,0' + trnClose;\r
+ if (that.options.useTransition) bar.style.cssText += ';-' + vendor + '-transition-timing-function:cubic-bezier(0.33,0.66,0.66,1)';\r
+\r
+ that[dir + 'ScrollbarWrapper'].appendChild(bar);\r
+ that[dir + 'ScrollbarIndicator'] = bar;\r
+ }\r
+\r
+ if (dir == 'h') {\r
+ that.hScrollbarSize = that.hScrollbarWrapper.clientWidth;\r
+ that.hScrollbarIndicatorSize = m.max(m.round(that.hScrollbarSize * that.hScrollbarSize / that.scrollerW), 8);\r
+ that.hScrollbarIndicator.style.width = that.hScrollbarIndicatorSize + 'px';\r
+ that.hScrollbarMaxScroll = that.hScrollbarSize - that.hScrollbarIndicatorSize;\r
+ that.hScrollbarProp = that.hScrollbarMaxScroll / that.maxScrollX;\r
+ } else {\r
+ that.vScrollbarSize = that.vScrollbarWrapper.clientHeight;\r
+ that.vScrollbarIndicatorSize = m.max(m.round(that.vScrollbarSize * that.vScrollbarSize / that.scrollerH), 8);\r
+ that.vScrollbarIndicator.style.height = that.vScrollbarIndicatorSize + 'px';\r
+ that.vScrollbarMaxScroll = that.vScrollbarSize - that.vScrollbarIndicatorSize;\r
+ that.vScrollbarProp = that.vScrollbarMaxScroll / that.maxScrollY;\r
+ }\r
+\r
+ // Reset position\r
+ that._scrollbarPos(dir, true);\r
+ },\r
+ \r
+ _resize: function () {\r
+ this.refresh();\r
+ },\r
+ \r
+ _pos: function (x, y) {\r
+ x = this.hScroll ? x : 0;\r
+ y = this.vScroll ? y : 0;\r
+\r
+ if (this.options.useTransform) {\r
+ this.scroller.style[vendor + 'Transform'] = trnOpen + x + 'px,' + y + 'px' + trnClose + ' scale(' + this.scale + ')';\r
+ } else {\r
+ x = m.round(x);\r
+ y = m.round(y);\r
+ this.scroller.style.left = x + 'px';\r
+ this.scroller.style.top = y + 'px';\r
+ }\r
+\r
+ this.x = x;\r
+ this.y = y;\r
+\r
+ this._scrollbarPos('h');\r
+ this._scrollbarPos('v');\r
+ },\r
+\r
+ _scrollbarPos: function (dir, hidden) {\r
+ var that = this,\r
+ pos = dir == 'h' ? that.x : that.y,\r
+ size;\r
+ \r
+ if (!that[dir + 'Scrollbar']) return;\r
+ \r
+ pos = that[dir + 'ScrollbarProp'] * pos;\r
+ \r
+ if (pos < 0) {\r
+ if (!that.options.fixedScrollbar) {\r
+ size = that[dir + 'ScrollbarIndicatorSize'] + m.round(pos * 3);\r
+ if (size < 8) size = 8;\r
+ that[dir + 'ScrollbarIndicator'].style[dir == 'h' ? 'width' : 'height'] = size + 'px';\r
+ }\r
+ pos = 0;\r
+ } else if (pos > that[dir + 'ScrollbarMaxScroll']) {\r
+ if (!that.options.fixedScrollbar) {\r
+ size = that[dir + 'ScrollbarIndicatorSize'] - m.round((pos - that[dir + 'ScrollbarMaxScroll']) * 3);\r
+ if (size < 8) size = 8;\r
+ that[dir + 'ScrollbarIndicator'].style[dir == 'h' ? 'width' : 'height'] = size + 'px';\r
+ pos = that[dir + 'ScrollbarMaxScroll'] + (that[dir + 'ScrollbarIndicatorSize'] - size);\r
+ } else {\r
+ pos = that[dir + 'ScrollbarMaxScroll'];\r
+ }\r
+ }\r
+\r
+ that[dir + 'ScrollbarWrapper'].style[vendor + 'TransitionDelay'] = '0';\r
+ that[dir + 'ScrollbarWrapper'].style.opacity = hidden && that.options.hideScrollbar ? '0' : '1';\r
+ that[dir + 'ScrollbarIndicator'].style[vendor + 'Transform'] = trnOpen + (dir == 'h' ? pos + 'px,0' : '0,' + pos + 'px') + trnClose;\r
+ },\r
+ \r
+ _start: function (e) {\r
+ var that = this,\r
+ point = hasTouch ? e.touches[0] : e,\r
+ matrix, x, y,\r
+ c1, c2, target;\r
+\r
+ if (!that.enabled) return;\r
+\r
+ if (that.options.onBeforeScrollStart) that.options.onBeforeScrollStart.call(that, e);\r
+\r
+ if (that.options.useTransition) that._transitionTime(0);\r
+\r
+ that.moved = false;\r
+ that.animating = false;\r
+ that.zoomed = false;\r
+ that.distX = 0;\r
+ that.distY = 0;\r
+ that.absDistX = 0;\r
+ that.absDistY = 0;\r
+ that.dirX = 0;\r
+ that.dirY = 0;\r
+\r
+ // Gesture start\r
+ if (that.options.zoom && hasTouch && e.touches.length > 1) {\r
+ c1 = m.abs(e.touches[0].pageX-e.touches[1].pageX);\r
+ c2 = m.abs(e.touches[0].pageY-e.touches[1].pageY);\r
+ that.touchesDistStart = m.sqrt(c1 * c1 + c2 * c2);\r
+\r
+ that.originX = m.abs(e.touches[0].pageX + e.touches[1].pageX - that.wrapperOffsetLeft * 2) / 2 - that.x;\r
+ that.originY = m.abs(e.touches[0].pageY + e.touches[1].pageY - that.wrapperOffsetTop * 2) / 2 - that.y;\r
+ }\r
+\r
+ if (that.options.momentum) {\r
+ if (that.options.useTransform) {\r
+ // Very lame general purpose alternative to CSSMatrix\r
+ matrix = getComputedStyle(that.scroller, null)[vendor + 'Transform'].replace(/[^0-9-.,]/g, '').split(',');\r
+ x = matrix[4] * 1;\r
+ y = matrix[5] * 1;\r
+ } else {\r
+ x = getComputedStyle(that.scroller, null).left.replace(/[^0-9-]/g, '') * 1;\r
+ y = getComputedStyle(that.scroller, null).top.replace(/[^0-9-]/g, '') * 1;\r
+ }\r
+ \r
+ if (x != that.x || y != that.y) {\r
+ if (that.options.useTransition) that._unbind('webkitTransitionEnd');\r
+ else cancelFrame(that.aniTime);\r
+ that.steps = [];\r
+ that._pos(x, y);\r
+ }\r
+ }\r
+\r
+ that.absStartX = that.x; // Needed by snap threshold\r
+ that.absStartY = that.y;\r
+\r
+ that.startX = that.x;\r
+ that.startY = that.y;\r
+ that.pointX = point.pageX;\r
+ that.pointY = point.pageY;\r
+\r
+ that.startTime = e.timeStamp || (new Date()).getTime();\r
+\r
+ if (that.options.onScrollStart) that.options.onScrollStart.call(that, e);\r
+\r
+ that._bind(MOVE_EV);\r
+ that._bind(END_EV);\r
+ that._bind(CANCEL_EV);\r
+ },\r
+ \r
+ _move: function (e) {\r
+ var that = this,\r
+ point = hasTouch ? e.touches[0] : e,\r
+ deltaX = point.pageX - that.pointX,\r
+ deltaY = point.pageY - that.pointY,\r
+ newX = that.x + deltaX,\r
+ newY = that.y + deltaY,\r
+ c1, c2, scale,\r
+ timestamp = e.timeStamp || (new Date()).getTime();\r
+\r
+ if (that.options.onBeforeScrollMove) that.options.onBeforeScrollMove.call(that, e);\r
+\r
+ // Zoom\r
+ if (that.options.zoom && hasTouch && e.touches.length > 1) {\r
+ c1 = m.abs(e.touches[0].pageX - e.touches[1].pageX);\r
+ c2 = m.abs(e.touches[0].pageY - e.touches[1].pageY);\r
+ that.touchesDist = m.sqrt(c1*c1+c2*c2);\r
+\r
+ that.zoomed = true;\r
+\r
+ scale = 1 / that.touchesDistStart * that.touchesDist * this.scale;\r
+ if (scale < 0.5) scale = 0.5;\r
+ else if (scale > that.options.zoomMax) scale = that.options.zoomMax;\r
+ that.lastScale = scale / this.scale;\r
+\r
+ newX = this.originX - this.originX * that.lastScale + this.x,\r
+ newY = this.originY - this.originY * that.lastScale + this.y;\r
+\r
+ this.scroller.style[vendor + 'Transform'] = trnOpen + newX + 'px,' + newY + 'px' + trnClose + ' scale(' + scale + ')';\r
+\r
+ return;\r
+ }\r
+\r
+ that.pointX = point.pageX;\r
+ that.pointY = point.pageY;\r
+\r
+ // Slow down if outside of the boundaries\r
+ if (newX > 0 || newX < that.maxScrollX) {\r
+ newX = that.options.bounce ? that.x + (deltaX / 2) : newX >= 0 || that.maxScrollX >= 0 ? 0 : that.maxScrollX;\r
+ }\r
+ if (newY > 0 || newY < that.maxScrollY) { \r
+ newY = that.options.bounce ? that.y + (deltaY / 2) : newY >= 0 || that.maxScrollY >= 0 ? 0 : that.maxScrollY;\r
+ }\r
+\r
+ if (that.absDistX < 6 && that.absDistY < 6) {\r
+ that.distX += deltaX;\r
+ that.distY += deltaY;\r
+ that.absDistX = m.abs(that.distX);\r
+ that.absDistY = m.abs(that.distY);\r
+\r
+ return;\r
+ }\r
+\r
+ // Lock direction\r
+ if (that.options.lockDirection) {\r
+ if (that.absDistX > that.absDistY + 5) {\r
+ newY = that.y;\r
+ deltaY = 0;\r
+ } else if (that.absDistY > that.absDistX + 5) {\r
+ newX = that.x;\r
+ deltaX = 0;\r
+ }\r
+ }\r
+\r
+ that.moved = true;\r
+ that._pos(newX, newY);\r
+ that.dirX = deltaX > 0 ? -1 : deltaX < 0 ? 1 : 0;\r
+ that.dirY = deltaY > 0 ? -1 : deltaY < 0 ? 1 : 0;\r
+\r
+ if (timestamp - that.startTime > 300) {\r
+ that.startTime = timestamp;\r
+ that.startX = that.x;\r
+ that.startY = that.y;\r
+ }\r
+ \r
+ if (that.options.onScrollMove) that.options.onScrollMove.call(that, e);\r
+ },\r
+ \r
+ _end: function (e) {\r
+ if (hasTouch && e.touches.length != 0) return;\r
+\r
+ var that = this,\r
+ point = hasTouch ? e.changedTouches[0] : e,\r
+ target, ev,\r
+ momentumX = { dist:0, time:0 },\r
+ momentumY = { dist:0, time:0 },\r
+ duration = (e.timeStamp || (new Date()).getTime()) - that.startTime,\r
+ newPosX = that.x,\r
+ newPosY = that.y,\r
+ distX, distY,\r
+ newDuration;\r
+\r
+ that._unbind(MOVE_EV);\r
+ that._unbind(END_EV);\r
+ that._unbind(CANCEL_EV);\r
+\r
+ if (that.options.onBeforeScrollEnd) that.options.onBeforeScrollEnd.call(that, e);\r
+\r
+ if (that.zoomed) {\r
+ that.scale = that.scale * that.lastScale;\r
+ that.x = that.originX - that.originX * that.lastScale + that.x;\r
+ that.y = that.originY - that.originY * that.lastScale + that.y;\r
+\r
+ that.scroller.style.webkitTransform = trnOpen + that.x + 'px,' + that.y + 'px' + trnClose + ' scale(' + that.scale + ')';\r
+\r
+ that.refresh();\r
+ return;\r
+ }\r
+\r
+ if (!that.moved) {\r
+ if (hasTouch) {\r
+ if (that.doubleTapTimer && that.options.zoom) {\r
+ // Double tapped\r
+ clearTimeout(that.doubleTapTimer);\r
+ that.doubleTapTimer = null;\r
+ that.zoom(that.pointX, that.pointY, that.scale == 1 ? that.options.doubleTapZoom : 1);\r
+ } else {\r
+ that.doubleTapTimer = setTimeout(function () {\r
+ that.doubleTapTimer = null;\r
+\r
+ // Find the last touched element\r
+ target = point.target;\r
+ while (target.nodeType != 1) target = target.parentNode;\r
+\r
+ if (target.tagName != 'SELECT' && target.tagName != 'INPUT' && target.tagName != 'TEXTAREA') {\r
+ ev = document.createEvent('MouseEvents');\r
+ ev.initMouseEvent('click', true, true, e.view, 1,\r
+ point.screenX, point.screenY, point.clientX, point.clientY,\r
+ e.ctrlKey, e.altKey, e.shiftKey, e.metaKey,\r
+ 0, null);\r
+ ev._fake = true;\r
+ target.dispatchEvent(ev);\r
+ }\r
+ }, that.options.zoom ? 250 : 0);\r
+ }\r
+ }\r
+\r
+ that._resetPos(200);\r
+\r
+ if (that.options.onTouchEnd) that.options.onTouchEnd.call(that, e);\r
+ return;\r
+ }\r
+\r
+ if (duration < 300 && that.options.momentum) {\r
+ momentumX = newPosX ? that._momentum(newPosX - that.startX, duration, -that.x, that.scrollerW - that.wrapperW + that.x, that.options.bounce ? that.wrapperW : 0) : momentumX;\r
+ momentumY = newPosY ? that._momentum(newPosY - that.startY, duration, -that.y, (that.maxScrollY < 0 ? that.scrollerH - that.wrapperH + that.y : 0), that.options.bounce ? that.wrapperH : 0) : momentumY;\r
+\r
+ newPosX = that.x + momentumX.dist;\r
+ newPosY = that.y + momentumY.dist;\r
+\r
+ if ((that.x > 0 && newPosX > 0) || (that.x < that.maxScrollX && newPosX < that.maxScrollX)) momentumX = { dist:0, time:0 };\r
+ if ((that.y > 0 && newPosY > 0) || (that.y < that.maxScrollY && newPosY < that.maxScrollY)) momentumY = { dist:0, time:0 };\r
+ }\r
+\r
+ if (momentumX.dist || momentumY.dist) {\r
+ newDuration = m.max(m.max(momentumX.time, momentumY.time), 10);\r
+\r
+ // Do we need to snap?\r
+ if (that.options.snap) {\r
+ distX = newPosX - that.absStartX;\r
+ distY = newPosY - that.absStartY;\r
+ if (m.abs(distX) < that.options.snapThreshold && m.abs(distY) < that.options.snapThreshold) { that.scrollTo(that.absStartX, that.absStartY, 200); }\r
+ else {\r
+ snap = that._snap(newPosX, newPosY);\r
+ newPosX = snap.x;\r
+ newPosY = snap.y;\r
+ newDuration = m.max(snap.time, newDuration);\r
+ }\r
+ }\r
+\r
+ that.scrollTo(newPosX, newPosY, newDuration);\r
+\r
+ if (that.options.onTouchEnd) that.options.onTouchEnd.call(that, e);\r
+ return;\r
+ }\r
+\r
+ // Do we need to snap?\r
+ if (that.options.snap) {\r
+ distX = newPosX - that.absStartX;\r
+ distY = newPosY - that.absStartY;\r
+ if (m.abs(distX) < that.options.snapThreshold && m.abs(distY) < that.options.snapThreshold) that.scrollTo(that.absStartX, that.absStartY, 200);\r
+ else {\r
+ snap = that._snap(that.x, that.y);\r
+ if (snap.x != that.x || snap.y != that.y) that.scrollTo(snap.x, snap.y, snap.time);\r
+ }\r
+\r
+ if (that.options.onTouchEnd) that.options.onTouchEnd.call(that, e);\r
+ return;\r
+ }\r
+\r
+ that._resetPos(200);\r
+ if (that.options.onTouchEnd) that.options.onTouchEnd.call(that, e);\r
+ },\r
+ \r
+ _resetPos: function (time) {\r
+ var that = this,\r
+ resetX = that.x >= 0 ? 0 : that.x < that.maxScrollX ? that.maxScrollX : that.x,\r
+ resetY = that.y >= 0 || that.maxScrollY > 0 ? 0 : that.y < that.maxScrollY ? that.maxScrollY : that.y;\r
+\r
+ if (resetX == that.x && resetY == that.y) {\r
+ if (that.moved) {\r
+ that.moved = false;\r
+ if (that.options.onScrollEnd) that.options.onScrollEnd.call(that); // Execute custom code on scroll end\r
+ }\r
+\r
+ if (that.hScrollbar && that.options.hideScrollbar) {\r
+ if (vendor == 'webkit') that.hScrollbarWrapper.style[vendor + 'TransitionDelay'] = '300ms';\r
+ that.hScrollbarWrapper.style.opacity = '0';\r
+ }\r
+ if (that.vScrollbar && that.options.hideScrollbar) {\r
+ if (vendor == 'webkit') that.vScrollbarWrapper.style[vendor + 'TransitionDelay'] = '300ms';\r
+ that.vScrollbarWrapper.style.opacity = '0';\r
+ }\r
+\r
+ return;\r
+ }\r
+\r
+ that.scrollTo(resetX, resetY, time || 0);\r
+ },\r
+\r
+ _wheel: function (e) {\r
+ var that = this,\r
+ deltaX, deltaY;\r
+\r
+ if ('wheelDeltaX' in e) {\r
+ deltaX = that.x + e.wheelDeltaX / 12,\r
+ deltaY = that.y + e.wheelDeltaY / 12;\r
+ } else if ('detail' in e) {\r
+ deltaX = that.x - e.detail * 3,\r
+ deltaY = that.y - e.detail * 3;\r
+ } else {\r
+ deltaX = that.x - e.wheelDelta,\r
+ deltaY = that.y - e.wheelDelta;\r
+ }\r
+\r
+ if (deltaX > 0) deltaX = 0;\r
+ else if (deltaX < that.maxScrollX) deltaX = that.maxScrollX;\r
+\r
+ if (deltaY > 0) deltaY = 0;\r
+ else if (deltaY < that.maxScrollY) deltaY = that.maxScrollY;\r
+\r
+ that.scrollTo(deltaX, deltaY, 0);\r
+ },\r
+ \r
+ _mouseout: function (e) {\r
+ var t = e.relatedTarget;\r
+\r
+ if (!t) {\r
+ this._end(e);\r
+ return;\r
+ }\r
+\r
+ while (t = t.parentNode) if (t == this.wrapper) return;\r
+ \r
+ this._end(e);\r
+ },\r
+\r
+ _transitionEnd: function (e) {\r
+ var that = this;\r
+\r
+ if (e.target != that.scroller) return;\r
+\r
+ that._unbind('webkitTransitionEnd');\r
+ \r
+ that._startAni();\r
+ },\r
+\r
+\r
+ /**\r
+ *\r
+ * Utilities\r
+ *\r
+ */\r
+ _startAni: function () {\r
+ var that = this,\r
+ startX = that.x, startY = that.y,\r
+ startTime = (new Date).getTime(),\r
+ step, easeOut;\r
+\r
+ if (that.animating) return;\r
+ \r
+ if (!that.steps.length) {\r
+ that._resetPos(400);\r
+ return;\r
+ }\r
+ \r
+ step = that.steps.shift();\r
+ \r
+ if (step.x == startX && step.y == startY) step.time = 0;\r
+\r
+ that.animating = true;\r
+ that.moved = true;\r
+ \r
+ if (that.options.useTransition) {\r
+ that._transitionTime(step.time);\r
+ that._pos(step.x, step.y);\r
+ that.animating = false;\r
+ if (step.time) that._bind('webkitTransitionEnd');\r
+ return;\r
+ }\r
+\r
+ (function animate () {\r
+ var now = (new Date).getTime(),\r
+ newX, newY;\r
+\r
+ if (now >= startTime + step.time) {\r
+ that._pos(step.x, step.y);\r
+ that.animating = false;\r
+ if (that.options.onAnimationEnd) that.options.onAnimationEnd.call(that); // Execute custom code on animation end\r
+ that._startAni();\r
+ return;\r
+ }\r
+\r
+ now = (now - startTime) / step.time - 1;\r
+ easeOut = m.sqrt(1 - now * now);\r
+ newX = (step.x - startX) * easeOut + startX;\r
+ newY = (step.y - startY) * easeOut + startY;\r
+ that._pos(newX, newY);\r
+ if (that.animating) that.aniTime = nextFrame(animate);\r
+ })();\r
+ },\r
+\r
+ _transitionTime: function (time) {\r
+ time += 'ms';\r
+ this.scroller.style[vendor + 'TransitionDuration'] = time;\r
+ if (this.hScrollbar) this.hScrollbarIndicator.style[vendor + 'TransitionDuration'] = time;\r
+ if (this.vScrollbar) this.vScrollbarIndicator.style[vendor + 'TransitionDuration'] = time;\r
+ },\r
+\r
+ _momentum: function (dist, time, maxDistUpper, maxDistLower, size) {\r
+ var deceleration = 0.0006,\r
+ speed = m.abs(dist) / time,\r
+ newDist = (speed * speed) / (2 * deceleration),\r
+ newTime = 0, outsideDist = 0;\r
+\r
+ // Proportinally reduce speed if we are outside of the boundaries \r
+ if (dist > 0 && newDist > maxDistUpper) {\r
+ outsideDist = size / (6 / (newDist / speed * deceleration));\r
+ maxDistUpper = maxDistUpper + outsideDist;\r
+ speed = speed * maxDistUpper / newDist;\r
+ newDist = maxDistUpper;\r
+ } else if (dist < 0 && newDist > maxDistLower) {\r
+ outsideDist = size / (6 / (newDist / speed * deceleration));\r
+ maxDistLower = maxDistLower + outsideDist;\r
+ speed = speed * maxDistLower / newDist;\r
+ newDist = maxDistLower;\r
+ }\r
+\r
+ newDist = newDist * (dist < 0 ? -1 : 1);\r
+ newTime = speed / deceleration;\r
+\r
+ return { dist: newDist, time: m.round(newTime) };\r
+ },\r
+\r
+ _offset: function (el) {\r
+ var left = -el.offsetLeft,\r
+ top = -el.offsetTop;\r
+ \r
+ while (el = el.offsetParent) {\r
+ left -= el.offsetLeft;\r
+ top -= el.offsetTop;\r
+ }\r
+ \r
+ if (el != this.wrapper) {\r
+ left *= this.scale;\r
+ top *= this.scale;\r
+ }\r
+\r
+ return { left: left, top: top };\r
+ },\r
+\r
+ _snap: function (x, y) {\r
+ var that = this,\r
+ i, l,\r
+ page, time,\r
+ sizeX, sizeY;\r
+\r
+ // Check page X\r
+ page = that.pagesX.length - 1;\r
+ for (i=0, l=that.pagesX.length; i<l; i++) {\r
+ if (x >= that.pagesX[i]) {\r
+ page = i;\r
+ break;\r
+ }\r
+ }\r
+ if (page == that.currPageX && page > 0 && that.dirX < 0) page--;\r
+ x = that.pagesX[page];\r
+ sizeX = m.abs(x - that.pagesX[that.currPageX]);\r
+ sizeX = sizeX ? m.abs(that.x - x) / sizeX * 500 : 0;\r
+ that.currPageX = page;\r
+\r
+ // Check page Y\r
+ page = that.pagesY.length-1;\r
+ for (i=0; i<page; i++) {\r
+ if (y >= that.pagesY[i]) {\r
+ page = i;\r
+ break;\r
+ }\r
+ }\r
+ if (page == that.currPageY && page > 0 && that.dirY < 0) page--;\r
+ y = that.pagesY[page];\r
+ sizeY = m.abs(y - that.pagesY[that.currPageY]);\r
+ sizeY = sizeY ? m.abs(that.y - y) / sizeY * 500 : 0;\r
+ that.currPageY = page;\r
+\r
+ // Snap with constant speed (proportional duration)\r
+ time = m.round(m.max(sizeX, sizeY)) || 200;\r
+\r
+ return { x: x, y: y, time: time };\r
+ },\r
+\r
+ _bind: function (type, el, bubble) {\r
+ (el || this.scroller).addEventListener(type, this, !!bubble);\r
+ },\r
+\r
+ _unbind: function (type, el, bubble) {\r
+ (el || this.scroller).removeEventListener(type, this, !!bubble);\r
+ },\r
+\r
+\r
+ /**\r
+ *\r
+ * Public methods\r
+ *\r
+ */\r
+ destroy: function () {\r
+ var that = this;\r
+\r
+ that.scroller.style[vendor + 'Transform'] = '';\r
+\r
+ // Remove the scrollbars\r
+ that.hScrollbar = false;\r
+ that.vScrollbar = false;\r
+ that._scrollbar('h');\r
+ that._scrollbar('v');\r
+\r
+ // Remove the event listeners\r
+ that._unbind(RESIZE_EV);\r
+ that._unbind(START_EV);\r
+ that._unbind(MOVE_EV);\r
+ that._unbind(END_EV);\r
+ that._unbind(CANCEL_EV);\r
+ \r
+ if (that.options.hasTouch) {\r
+ that._unbind('mouseout', that.wrapper);\r
+ that._unbind(WHEEL_EV);\r
+ }\r
+ \r
+ if (that.options.useTransition) that._unbind('webkitTransitionEnd');\r
+ \r
+ if (that.options.onDestroy) that.options.onDestroy.call(that);\r
+ },\r
+\r
+ refresh: function () {\r
+ var that = this,\r
+ offset,\r
+ pos = 0,\r
+ page = 0;\r
+\r
+ if (that.scale < that.options.zoomMin) that.scale = that.options.zoomMin;\r
+ that.wrapperW = that.wrapper.clientWidth || 1;\r
+ that.wrapperH = that.wrapper.clientHeight || 1;\r
+\r
+ that.scrollerW = m.round(that.scroller.offsetWidth * that.scale);\r
+ that.scrollerH = m.round(that.scroller.offsetHeight * that.scale);\r
+ that.maxScrollX = that.wrapperW - that.scrollerW;\r
+ that.maxScrollY = that.wrapperH - that.scrollerH;\r
+ that.dirX = 0;\r
+ that.dirY = 0;\r
+\r
+ that.hScroll = that.options.hScroll && that.maxScrollX < 0;\r
+ that.vScroll = that.options.vScroll && (!that.options.bounceLock && !that.hScroll || that.scrollerH > that.wrapperH);\r
+\r
+ that.hScrollbar = that.hScroll && that.options.hScrollbar;\r
+ that.vScrollbar = that.vScroll && that.options.vScrollbar && that.scrollerH > that.wrapperH;\r
+\r
+ offset = that._offset(that.wrapper);\r
+ that.wrapperOffsetLeft = -offset.left;\r
+ that.wrapperOffsetTop = -offset.top;\r
+\r
+ // Prepare snap\r
+ if (typeof that.options.snap == 'string') {\r
+ that.pagesX = [];\r
+ that.pagesY = [];\r
+ els = that.scroller.querySelectorAll(that.options.snap);\r
+ for (i=0, l=els.length; i<l; i++) {\r
+ pos = that._offset(els[i]);\r
+ pos.left += that.wrapperOffsetLeft;\r
+ pos.top += that.wrapperOffsetTop;\r
+ that.pagesX[i] = pos.left < that.maxScrollX ? that.maxScrollX : pos.left * that.scale;\r
+ that.pagesY[i] = pos.top < that.maxScrollY ? that.maxScrollY : pos.top * that.scale;\r
+ }\r
+ } else if (that.options.snap) {\r
+ that.pagesX = [];\r
+ while (pos >= that.maxScrollX) {\r
+ that.pagesX[page] = pos;\r
+ pos = pos - that.wrapperW;\r
+ page++;\r
+ }\r
+ if (that.maxScrollX%that.wrapperW) that.pagesX[that.pagesX.length] = that.maxScrollX - that.pagesX[that.pagesX.length-1] + that.pagesX[that.pagesX.length-1];\r
+\r
+ pos = 0;\r
+ page = 0;\r
+ that.pagesY = [];\r
+ while (pos >= that.maxScrollY) {\r
+ that.pagesY[page] = pos;\r
+ pos = pos - that.wrapperH;\r
+ page++;\r
+ }\r
+ if (that.maxScrollY%that.wrapperH) that.pagesY[that.pagesY.length] = that.maxScrollY - that.pagesY[that.pagesY.length-1] + that.pagesY[that.pagesY.length-1];\r
+ }\r
+\r
+ // Prepare the scrollbars\r
+ that._scrollbar('h');\r
+ that._scrollbar('v');\r
+\r
+ that.scroller.style[vendor + 'TransitionDuration'] = '0';\r
+\r
+ that._resetPos(200);\r
+ },\r
+\r
+ scrollTo: function (x, y, time, relative) {\r
+ var that = this,\r
+ step = x,\r
+ i, l;\r
+\r
+ that.stop();\r
+\r
+ if (!step.length) step = [{ x: x, y: y, time: time, relative: relative }];\r
+ \r
+ for (i=0, l=step.length; i<l; i++) {\r
+ if (step[i].relative) { step[i].x = that.x - step[i].x; step[i].y = that.y - step[i].y; }\r
+ that.steps.push({ x: step[i].x, y: step[i].y, time: step[i].time || 0 });\r
+ }\r
+\r
+ that._startAni();\r
+ },\r
+\r
+ scrollToElement: function (el, time) {\r
+ var that = this, pos;\r
+ el = el.nodeType ? el : that.scroller.querySelector(el);\r
+ if (!el) return;\r
+\r
+ pos = that._offset(el);\r
+ pos.left += that.wrapperOffsetLeft;\r
+ pos.top += that.wrapperOffsetTop;\r
+\r
+ pos.left = pos.left > 0 ? 0 : pos.left < that.maxScrollX ? that.maxScrollX : pos.left;\r
+ pos.top = pos.top > 0 ? 0 : pos.top < that.maxScrollY ? that.maxScrollY : pos.top;\r
+ time = time === undefined ? m.max(m.abs(pos.left)*2, m.abs(pos.top)*2) : time;\r
+\r
+ that.scrollTo(pos.left, pos.top, time);\r
+ },\r
+\r
+ scrollToPage: function (pageX, pageY, time) {\r
+ var that = this, x, y;\r
+ \r
+ if (that.options.snap) {\r
+ pageX = pageX == 'next' ? that.currPageX+1 : pageX == 'prev' ? that.currPageX-1 : pageX;\r
+ pageY = pageY == 'next' ? that.currPageY+1 : pageY == 'prev' ? that.currPageY-1 : pageY;\r
+\r
+ pageX = pageX < 0 ? 0 : pageX > that.pagesX.length-1 ? that.pagesX.length-1 : pageX;\r
+ pageY = pageY < 0 ? 0 : pageY > that.pagesY.length-1 ? that.pagesY.length-1 : pageY;\r
+\r
+ that.currPageX = pageX;\r
+ that.currPageY = pageY;\r
+ x = that.pagesX[pageX];\r
+ y = that.pagesY[pageY];\r
+ } else {\r
+ x = -that.wrapperW * pageX;\r
+ y = -that.wrapperH * pageY;\r
+ if (x < that.maxScrollX) x = that.maxScrollX;\r
+ if (y < that.maxScrollY) y = that.maxScrollY;\r
+ }\r
+\r
+ that.scrollTo(x, y, time || 400);\r
+ },\r
+\r
+ disable: function () {\r
+ this.stop();\r
+ this._resetPos(0);\r
+ this.enabled = false;\r
+\r
+ // If disabled after touchstart we make sure that there are no left over events\r
+ this._unbind(MOVE_EV);\r
+ this._unbind(END_EV);\r
+ this._unbind(CANCEL_EV);\r
+ },\r
+ \r
+ enable: function () {\r
+ this.enabled = true;\r
+ },\r
+ \r
+ stop: function () {\r
+ if (this.options.useTransition) this._unbind('webkitTransitionEnd');\r
+ else cancelFrame(this.aniTime);\r
+ this.steps = [];\r
+ this.moved = false;\r
+ this.animating = false;\r
+ },\r
+ \r
+ zoom: function (x, y, scale, time) {\r
+ var that = this,\r
+ relScale = scale / that.scale;\r
+\r
+ if (!that.options.useTransform) return;\r
+\r
+ time = (time || 200) + 'ms'\r
+ x = x - that.wrapperOffsetLeft - that.x;\r
+ y = y - that.wrapperOffsetTop - that.y;\r
+ that.x = x - x * relScale + that.x;\r
+ that.y = y - y * relScale + that.y;\r
+\r
+ that.scale = scale;\r
+\r
+ that.scroller.style[vendor + 'TransitionDuration'] = time;\r
+ that.scroller.style[vendor + 'Transform'] = trnOpen + that.x + 'px,' + that.y + 'px' + trnClose + ' scale(' + scale + ')';\r
+\r
+ that.refresh();\r
+ }\r
+};\r
+\r
+if (typeof exports !== 'undefined') exports.iScroll = iScroll;\r
+else window.iScroll = iScroll;\r
+\r
+})();
\ No newline at end of file
--- /dev/null
+<?php
+/**
+ * jsmin.php - PHP implementation of Douglas Crockford's JSMin.
+ *
+ * This is pretty much a direct port of jsmin.c to PHP with just a few
+ * PHP-specific performance tweaks. Also, whereas jsmin.c reads from stdin and
+ * outputs to stdout, this library accepts a string as input and returns another
+ * string as output.
+ *
+ * PHP 5 or higher is required.
+ *
+ * Permission is hereby granted to use this version of the library under the
+ * same terms as jsmin.c, which has the following license:
+ *
+ * --
+ * Copyright (c) 2002 Douglas Crockford (www.crockford.com)
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of
+ * this software and associated documentation files (the "Software"), to deal in
+ * the Software without restriction, including without limitation the rights to
+ * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is furnished to do
+ * so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * The Software shall be used for Good, not Evil.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ * --
+ *
+ * @package JSMin
+ * @author Ryan Grove <ryan@wonko.com>
+ * @copyright 2002 Douglas Crockford <douglas@crockford.com> (jsmin.c)
+ * @copyright 2008 Ryan Grove <ryan@wonko.com> (PHP port)
+ * @license http://opensource.org/licenses/mit-license.php MIT License
+ * @version 1.1.1 (2008-03-02)
+ * @link https://github.com/rgrove/jsmin-php/
+ */
+
+class JSMin {
+ const ORD_LF = 10;
+ const ORD_SPACE = 32;
+ const ACTION_KEEP_A = 1;
+ const ACTION_DELETE_A = 2;
+ const ACTION_DELETE_A_B = 3;
+
+ protected $a = '';
+ protected $b = '';
+ protected $input = '';
+ protected $inputIndex = 0;
+ protected $inputLength = 0;
+ protected $lookAhead = null;
+ protected $output = '';
+
+ // -- Public Static Methods --------------------------------------------------
+
+ /**
+ * Minify Javascript
+ *
+ * @uses __construct()
+ * @uses min()
+ * @param string $js Javascript to be minified
+ * @return string
+ */
+ public static function minify($js) {
+ $jsmin = new JSMin($js);
+ return $jsmin->min();
+ }
+
+ // -- Public Instance Methods ------------------------------------------------
+
+ /**
+ * Constructor
+ *
+ * @param string $input Javascript to be minified
+ */
+ public function __construct($input) {
+ $this->input = str_replace("\r\n", "\n", $input);
+ $this->inputLength = strlen($this->input);
+ }
+
+ // -- Protected Instance Methods ---------------------------------------------
+
+ /**
+ * Action -- do something! What to do is determined by the $command argument.
+ *
+ * action treats a string as a single character. Wow!
+ * action recognizes a regular expression if it is preceded by ( or , or =.
+ *
+ * @uses next()
+ * @uses get()
+ * @throws JSMinException If parser errors are found:
+ * - Unterminated string literal
+ * - Unterminated regular expression set in regex literal
+ * - Unterminated regular expression literal
+ * @param int $command One of class constants:
+ * ACTION_KEEP_A Output A. Copy B to A. Get the next B.
+ * ACTION_DELETE_A Copy B to A. Get the next B. (Delete A).
+ * ACTION_DELETE_A_B Get the next B. (Delete B).
+ */
+ protected function action($command) {
+ switch($command) {
+ case self::ACTION_KEEP_A:
+ $this->output .= $this->a;
+
+ case self::ACTION_DELETE_A:
+ $this->a = $this->b;
+
+ if ($this->a === "'" || $this->a === '"') {
+ for (;;) {
+ $this->output .= $this->a;
+ $this->a = $this->get();
+
+ if ($this->a === $this->b) {
+ break;
+ }
+
+ if (ord($this->a) <= self::ORD_LF) {
+ throw new JSMinException('Unterminated string literal.');
+ }
+
+ if ($this->a === '\\') {
+ $this->output .= $this->a;
+ $this->a = $this->get();
+ }
+ }
+ }
+
+ case self::ACTION_DELETE_A_B:
+ $this->b = $this->next();
+
+ if ($this->b === '/' && (
+ $this->a === '(' || $this->a === ',' || $this->a === '=' ||
+ $this->a === ':' || $this->a === '[' || $this->a === '!' ||
+ $this->a === '&' || $this->a === '|' || $this->a === '?' ||
+ $this->a === '{' || $this->a === '}' || $this->a === ';' ||
+ $this->a === "\n" )) {
+
+ $this->output .= $this->a . $this->b;
+
+ for (;;) {
+ $this->a = $this->get();
+
+ if ($this->a === '[') {
+ /*
+ inside a regex [...] set, which MAY contain a '/' itself. Example: mootools Form.Validator near line 460:
+ return Form.Validator.getValidator('IsEmpty').test(element) || (/^(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]\.?){0,63}[a-z0-9!#$%&'*+/=?^_`{|}~-]@(?:(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)*[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?|\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\])$/i).test(element.get('value'));
+ */
+ for (;;) {
+ $this->output .= $this->a;
+ $this->a = $this->get();
+
+ if ($this->a === ']') {
+ break;
+ } elseif ($this->a === '\\') {
+ $this->output .= $this->a;
+ $this->a = $this->get();
+ } elseif (ord($this->a) <= self::ORD_LF) {
+ throw new JSMinException('Unterminated regular expression set in regex literal.');
+ }
+ }
+ } elseif ($this->a === '/') {
+ break;
+ } elseif ($this->a === '\\') {
+ $this->output .= $this->a;
+ $this->a = $this->get();
+ } elseif (ord($this->a) <= self::ORD_LF) {
+ throw new JSMinException('Unterminated regular expression literal.');
+ }
+
+ $this->output .= $this->a;
+ }
+
+ $this->b = $this->next();
+ }
+ }
+ }
+
+ /**
+ * Get next char. Convert ctrl char to space.
+ *
+ * @return string|null
+ */
+ protected function get() {
+ $c = $this->lookAhead;
+ $this->lookAhead = null;
+
+ if ($c === null) {
+ if ($this->inputIndex < $this->inputLength) {
+ $c = substr($this->input, $this->inputIndex, 1);
+ $this->inputIndex += 1;
+ } else {
+ $c = null;
+ }
+ }
+
+ if ($c === "\r") {
+ return "\n";
+ }
+
+ if ($c === null || $c === "\n" || ord($c) >= self::ORD_SPACE) {
+ return $c;
+ }
+
+ return ' ';
+ }
+
+ /**
+ * Is $c a letter, digit, underscore, dollar sign, or non-ASCII character.
+ *
+ * @return bool
+ */
+ protected function isAlphaNum($c) {
+ return ord($c) > 126 || $c === '\\' || preg_match('/^[\w\$]$/', $c) === 1;
+ }
+
+ /**
+ * Perform minification, return result
+ *
+ * @uses action()
+ * @uses isAlphaNum()
+ * @return string
+ */
+ protected function min() {
+ $this->a = "\n";
+ $this->action(self::ACTION_DELETE_A_B);
+
+ while ($this->a !== null) {
+ switch ($this->a) {
+ case ' ':
+ if ($this->isAlphaNum($this->b)) {
+ $this->action(self::ACTION_KEEP_A);
+ } else {
+ $this->action(self::ACTION_DELETE_A);
+ }
+ break;
+
+ case "\n":
+ switch ($this->b) {
+ case '{':
+ case '[':
+ case '(':
+ case '+':
+ case '-':
+ $this->action(self::ACTION_KEEP_A);
+ break;
+
+ case ' ':
+ $this->action(self::ACTION_DELETE_A_B);
+ break;
+
+ default:
+ if ($this->isAlphaNum($this->b)) {
+ $this->action(self::ACTION_KEEP_A);
+ }
+ else {
+ $this->action(self::ACTION_DELETE_A);
+ }
+ }
+ break;
+
+ default:
+ switch ($this->b) {
+ case ' ':
+ if ($this->isAlphaNum($this->a)) {
+ $this->action(self::ACTION_KEEP_A);
+ break;
+ }
+
+ $this->action(self::ACTION_DELETE_A_B);
+ break;
+
+ case "\n":
+ switch ($this->a) {
+ case '}':
+ case ']':
+ case ')':
+ case '+':
+ case '-':
+ case '"':
+ case "'":
+ $this->action(self::ACTION_KEEP_A);
+ break;
+
+ default:
+ if ($this->isAlphaNum($this->a)) {
+ $this->action(self::ACTION_KEEP_A);
+ }
+ else {
+ $this->action(self::ACTION_DELETE_A_B);
+ }
+ }
+ break;
+
+ default:
+ $this->action(self::ACTION_KEEP_A);
+ break;
+ }
+ }
+ }
+
+ return $this->output;
+ }
+
+ /**
+ * Get the next character, skipping over comments. peek() is used to see
+ * if a '/' is followed by a '/' or '*'.
+ *
+ * @uses get()
+ * @uses peek()
+ * @throws JSMinException On unterminated comment.
+ * @return string
+ */
+ protected function next() {
+ $c = $this->get();
+
+ if ($c === '/') {
+ switch($this->peek()) {
+ case '/':
+ for (;;) {
+ $c = $this->get();
+
+ if (ord($c) <= self::ORD_LF) {
+ return $c;
+ }
+ }
+
+ case '*':
+ $this->get();
+
+ for (;;) {
+ switch($this->get()) {
+ case '*':
+ if ($this->peek() === '/') {
+ $this->get();
+ return ' ';
+ }
+ break;
+
+ case null:
+ throw new JSMinException('Unterminated comment.');
+ }
+ }
+
+ default:
+ return $c;
+ }
+ }
+
+ return $c;
+ }
+
+ /**
+ * Get next char. If is ctrl character, translate to a space or newline.
+ *
+ * @uses get()
+ * @return string|null
+ */
+ protected function peek() {
+ $this->lookAhead = $this->get();
+ return $this->lookAhead;
+ }
+}
+
+// -- Exceptions ---------------------------------------------------------------
+class JSMinException extends Exception {}
+?>
\ No newline at end of file