"!==l[1]||kb.test(f)?0:h:h.firstChild,e=f&&f.childNodes.length;while(e--)m.nodeName(j=f.childNodes[e],"tbody")&&!j.childNodes.length&&f.removeChild(j)}m.merge(p,h.childNodes),h.textContent="";while(h.firstChild)h.removeChild(h.firstChild);h=o.lastChild}else p.push(b.createTextNode(f));h&&o.removeChild(h),k.appendChecked||m.grep(ub(p,"input"),vb),q=0;while(f=p[q++])if((!d||-1===m.inArray(f,d))&&(g=m.contains(f.ownerDocument,f),h=ub(o.appendChild(f),"script"),g&&zb(h),c)){e=0;while(f=h[e++])ob.test(f.type||"")&&c.push(f)}return h=null,o},cleanData:function(a,b){for(var d,e,f,g,h=0,i=m.expando,j=m.cache,l=k.deleteExpando,n=m.event.special;null!=(d=a[h]);h++)if((b||m.acceptData(d))&&(f=d[i],g=f&&j[f])){if(g.events)for(e in g.events)n[e]?m.event.remove(d,e):m.removeEvent(d,e,g.handle);j[f]&&(delete j[f],l?delete d[i]:typeof d.removeAttribute!==K?d.removeAttribute(i):d[i]=null,c.push(f))}}}),m.fn.extend({text:function(a){return V(this,function(a){return void 0===a?m.text(this):this.empty().append((this[0]&&this[0].ownerDocument||y).createTextNode(a))},null,a,arguments.length)},append:function(){return this.domManip(arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=wb(this,a);b.appendChild(a)}})},prepend:function(){return this.domManip(arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=wb(this,a);b.insertBefore(a,b.firstChild)}})},before:function(){return this.domManip(arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this)})},after:function(){return this.domManip(arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this.nextSibling)})},remove:function(a,b){for(var c,d=a?m.filter(a,this):this,e=0;null!=(c=d[e]);e++)b||1!==c.nodeType||m.cleanData(ub(c)),c.parentNode&&(b&&m.contains(c.ownerDocument,c)&&zb(ub(c,"script")),c.parentNode.removeChild(c));return this},empty:function(){for(var a,b=0;null!=(a=this[b]);b++){1===a.nodeType&&m.cleanData(ub(a,!1));while(a.firstChild)a.removeChild(a.firstChild);a.options&&m.nodeName(a,"select")&&(a.options.length=0)}return this},clone:function(a,b){return a=null==a?!1:a,b=null==b?a:b,this.map(function(){return m.clone(this,a,b)})},html:function(a){return V(this,function(a){var b=this[0]||{},c=0,d=this.length;if(void 0===a)return 1===b.nodeType?b.innerHTML.replace(fb,""):void 0;if(!("string"!=typeof a||mb.test(a)||!k.htmlSerialize&&gb.test(a)||!k.leadingWhitespace&&hb.test(a)||rb[(jb.exec(a)||["",""])[1].toLowerCase()])){a=a.replace(ib,"<$1>$2>");try{for(;d>c;c++)b=this[c]||{},1===b.nodeType&&(m.cleanData(ub(b,!1)),b.innerHTML=a);b=0}catch(e){}}b&&this.empty().append(a)},null,a,arguments.length)},replaceWith:function(){var a=arguments[0];return this.domManip(arguments,function(b){a=this.parentNode,m.cleanData(ub(this)),a&&a.replaceChild(b,this)}),a&&(a.length||a.nodeType)?this:this.remove()},detach:function(a){return this.remove(a,!0)},domManip:function(a,b){a=e.apply([],a);var c,d,f,g,h,i,j=0,l=this.length,n=this,o=l-1,p=a[0],q=m.isFunction(p);if(q||l>1&&"string"==typeof p&&!k.checkClone&&nb.test(p))return this.each(function(c){var d=n.eq(c);q&&(a[0]=p.call(this,c,d.html())),d.domManip(a,b)});if(l&&(i=m.buildFragment(a,this[0].ownerDocument,!1,this),c=i.firstChild,1===i.childNodes.length&&(i=c),c)){for(g=m.map(ub(i,"script"),xb),f=g.length;l>j;j++)d=i,j!==o&&(d=m.clone(d,!0,!0),f&&m.merge(g,ub(d,"script"))),b.call(this[j],d,j);if(f)for(h=g[g.length-1].ownerDocument,m.map(g,yb),j=0;f>j;j++)d=g[j],ob.test(d.type||"")&&!m._data(d,"globalEval")&&m.contains(h,d)&&(d.src?m._evalUrl&&m._evalUrl(d.src):m.globalEval((d.text||d.textContent||d.innerHTML||"").replace(qb,"")));i=c=null}return this}}),m.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){m.fn[a]=function(a){for(var c,d=0,e=[],g=m(a),h=g.length-1;h>=d;d++)c=d===h?this:this.clone(!0),m(g[d])[b](c),f.apply(e,c.get());return this.pushStack(e)}});var Cb,Db={};function Eb(b,c){var d,e=m(c.createElement(b)).appendTo(c.body),f=a.getDefaultComputedStyle&&(d=a.getDefaultComputedStyle(e[0]))?d.display:m.css(e[0],"display");return e.detach(),f}function Fb(a){var b=y,c=Db[a];return c||(c=Eb(a,b),"none"!==c&&c||(Cb=(Cb||m("")).appendTo(b.documentElement),b=(Cb[0].contentWindow||Cb[0].contentDocument).document,b.write(),b.close(),c=Eb(a,b),Cb.detach()),Db[a]=c),c}!function(){var a;k.shrinkWrapBlocks=function(){if(null!=a)return a;a=!1;var b,c,d;return c=y.getElementsByTagName("body")[0],c&&c.style?(b=y.createElement("div"),d=y.createElement("div"),d.style.cssText="position:absolute;border:0;width:0;height:0;top:0;left:-9999px",c.appendChild(d).appendChild(b),typeof b.style.zoom!==K&&(b.style.cssText="-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;display:block;margin:0;border:0;padding:1px;width:1px;zoom:1",b.appendChild(y.createElement("div")).style.width="5px",a=3!==b.offsetWidth),c.removeChild(d),a):void 0}}();var Gb=/^margin/,Hb=new RegExp("^("+S+")(?!px)[a-z%]+$","i"),Ib,Jb,Kb=/^(top|right|bottom|left)$/;a.getComputedStyle?(Ib=function(b){return b.ownerDocument.defaultView.opener?b.ownerDocument.defaultView.getComputedStyle(b,null):a.getComputedStyle(b,null)},Jb=function(a,b,c){var d,e,f,g,h=a.style;return c=c||Ib(a),g=c?c.getPropertyValue(b)||c[b]:void 0,c&&(""!==g||m.contains(a.ownerDocument,a)||(g=m.style(a,b)),Hb.test(g)&&Gb.test(b)&&(d=h.width,e=h.minWidth,f=h.maxWidth,h.minWidth=h.maxWidth=h.width=g,g=c.width,h.width=d,h.minWidth=e,h.maxWidth=f)),void 0===g?g:g+""}):y.documentElement.currentStyle&&(Ib=function(a){return a.currentStyle},Jb=function(a,b,c){var d,e,f,g,h=a.style;return c=c||Ib(a),g=c?c[b]:void 0,null==g&&h&&h[b]&&(g=h[b]),Hb.test(g)&&!Kb.test(b)&&(d=h.left,e=a.runtimeStyle,f=e&&e.left,f&&(e.left=a.currentStyle.left),h.left="fontSize"===b?"1em":g,g=h.pixelLeft+"px",h.left=d,f&&(e.left=f)),void 0===g?g:g+""||"auto"});function Lb(a,b){return{get:function(){var c=a();if(null!=c)return c?void delete this.get:(this.get=b).apply(this,arguments)}}}!function(){var b,c,d,e,f,g,h;if(b=y.createElement("div"),b.innerHTML=" a ",d=b.getElementsByTagName("a")[0],c=d&&d.style){c.cssText="float:left;opacity:.5",k.opacity="0.5"===c.opacity,k.cssFloat=!!c.cssFloat,b.style.backgroundClip="content-box",b.cloneNode(!0).style.backgroundClip="",k.clearCloneStyle="content-box"===b.style.backgroundClip,k.boxSizing=""===c.boxSizing||""===c.MozBoxSizing||""===c.WebkitBoxSizing,m.extend(k,{reliableHiddenOffsets:function(){return null==g&&i(),g},boxSizingReliable:function(){return null==f&&i(),f},pixelPosition:function(){return null==e&&i(),e},reliableMarginRight:function(){return null==h&&i(),h}});function i(){var b,c,d,i;c=y.getElementsByTagName("body")[0],c&&c.style&&(b=y.createElement("div"),d=y.createElement("div"),d.style.cssText="position:absolute;border:0;width:0;height:0;top:0;left:-9999px",c.appendChild(d).appendChild(b),b.style.cssText="-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;display:block;margin-top:1%;top:1%;border:1px;padding:1px;width:4px;position:absolute",e=f=!1,h=!0,a.getComputedStyle&&(e="1%"!==(a.getComputedStyle(b,null)||{}).top,f="4px"===(a.getComputedStyle(b,null)||{width:"4px"}).width,i=b.appendChild(y.createElement("div")),i.style.cssText=b.style.cssText="-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;display:block;margin:0;border:0;padding:0",i.style.marginRight=i.style.width="0",b.style.width="1px",h=!parseFloat((a.getComputedStyle(i,null)||{}).marginRight),b.removeChild(i)),b.innerHTML="",i=b.getElementsByTagName("td"),i[0].style.cssText="margin:0;border:0;padding:0;display:none",g=0===i[0].offsetHeight,g&&(i[0].style.display="",i[1].style.display="none",g=0===i[0].offsetHeight),c.removeChild(d))}}}(),m.swap=function(a,b,c,d){var e,f,g={};for(f in b)g[f]=a.style[f],a.style[f]=b[f];e=c.apply(a,d||[]);for(f in b)a.style[f]=g[f];return e};var Mb=/alpha\([^)]*\)/i,Nb=/opacity\s*=\s*([^)]*)/,Ob=/^(none|table(?!-c[ea]).+)/,Pb=new RegExp("^("+S+")(.*)$","i"),Qb=new RegExp("^([+-])=("+S+")","i"),Rb={position:"absolute",visibility:"hidden",display:"block"},Sb={letterSpacing:"0",fontWeight:"400"},Tb=["Webkit","O","Moz","ms"];function Ub(a,b){if(b in a)return b;var c=b.charAt(0).toUpperCase()+b.slice(1),d=b,e=Tb.length;while(e--)if(b=Tb[e]+c,b in a)return b;return d}function Vb(a,b){for(var c,d,e,f=[],g=0,h=a.length;h>g;g++)d=a[g],d.style&&(f[g]=m._data(d,"olddisplay"),c=d.style.display,b?(f[g]||"none"!==c||(d.style.display=""),""===d.style.display&&U(d)&&(f[g]=m._data(d,"olddisplay",Fb(d.nodeName)))):(e=U(d),(c&&"none"!==c||!e)&&m._data(d,"olddisplay",e?c:m.css(d,"display"))));for(g=0;h>g;g++)d=a[g],d.style&&(b&&"none"!==d.style.display&&""!==d.style.display||(d.style.display=b?f[g]||"":"none"));return a}function Wb(a,b,c){var d=Pb.exec(b);return d?Math.max(0,d[1]-(c||0))+(d[2]||"px"):b}function Xb(a,b,c,d,e){for(var f=c===(d?"border":"content")?4:"width"===b?1:0,g=0;4>f;f+=2)"margin"===c&&(g+=m.css(a,c+T[f],!0,e)),d?("content"===c&&(g-=m.css(a,"padding"+T[f],!0,e)),"margin"!==c&&(g-=m.css(a,"border"+T[f]+"Width",!0,e))):(g+=m.css(a,"padding"+T[f],!0,e),"padding"!==c&&(g+=m.css(a,"border"+T[f]+"Width",!0,e)));return g}function Yb(a,b,c){var d=!0,e="width"===b?a.offsetWidth:a.offsetHeight,f=Ib(a),g=k.boxSizing&&"border-box"===m.css(a,"boxSizing",!1,f);if(0>=e||null==e){if(e=Jb(a,b,f),(0>e||null==e)&&(e=a.style[b]),Hb.test(e))return e;d=g&&(k.boxSizingReliable()||e===a.style[b]),e=parseFloat(e)||0}return e+Xb(a,b,c||(g?"border":"content"),d,f)+"px"}m.extend({cssHooks:{opacity:{get:function(a,b){if(b){var c=Jb(a,"opacity");return""===c?"1":c}}}},cssNumber:{columnCount:!0,fillOpacity:!0,flexGrow:!0,flexShrink:!0,fontWeight:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":k.cssFloat?"cssFloat":"styleFloat"},style:function(a,b,c,d){if(a&&3!==a.nodeType&&8!==a.nodeType&&a.style){var e,f,g,h=m.camelCase(b),i=a.style;if(b=m.cssProps[h]||(m.cssProps[h]=Ub(i,h)),g=m.cssHooks[b]||m.cssHooks[h],void 0===c)return g&&"get"in g&&void 0!==(e=g.get(a,!1,d))?e:i[b];if(f=typeof c,"string"===f&&(e=Qb.exec(c))&&(c=(e[1]+1)*e[2]+parseFloat(m.css(a,b)),f="number"),null!=c&&c===c&&("number"!==f||m.cssNumber[h]||(c+="px"),k.clearCloneStyle||""!==c||0!==b.indexOf("background")||(i[b]="inherit"),!(g&&"set"in g&&void 0===(c=g.set(a,c,d)))))try{i[b]=c}catch(j){}}},css:function(a,b,c,d){var e,f,g,h=m.camelCase(b);return b=m.cssProps[h]||(m.cssProps[h]=Ub(a.style,h)),g=m.cssHooks[b]||m.cssHooks[h],g&&"get"in g&&(f=g.get(a,!0,c)),void 0===f&&(f=Jb(a,b,d)),"normal"===f&&b in Sb&&(f=Sb[b]),""===c||c?(e=parseFloat(f),c===!0||m.isNumeric(e)?e||0:f):f}}),m.each(["height","width"],function(a,b){m.cssHooks[b]={get:function(a,c,d){return c?Ob.test(m.css(a,"display"))&&0===a.offsetWidth?m.swap(a,Rb,function(){return Yb(a,b,d)}):Yb(a,b,d):void 0},set:function(a,c,d){var e=d&&Ib(a);return Wb(a,c,d?Xb(a,b,d,k.boxSizing&&"border-box"===m.css(a,"boxSizing",!1,e),e):0)}}}),k.opacity||(m.cssHooks.opacity={get:function(a,b){return Nb.test((b&&a.currentStyle?a.currentStyle.filter:a.style.filter)||"")?.01*parseFloat(RegExp.$1)+"":b?"1":""},set:function(a,b){var c=a.style,d=a.currentStyle,e=m.isNumeric(b)?"alpha(opacity="+100*b+")":"",f=d&&d.filter||c.filter||"";c.zoom=1,(b>=1||""===b)&&""===m.trim(f.replace(Mb,""))&&c.removeAttribute&&(c.removeAttribute("filter"),""===b||d&&!d.filter)||(c.filter=Mb.test(f)?f.replace(Mb,e):f+" "+e)}}),m.cssHooks.marginRight=Lb(k.reliableMarginRight,function(a,b){return b?m.swap(a,{display:"inline-block"},Jb,[a,"marginRight"]):void 0}),m.each({margin:"",padding:"",border:"Width"},function(a,b){m.cssHooks[a+b]={expand:function(c){for(var d=0,e={},f="string"==typeof c?c.split(" "):[c];4>d;d++)e[a+T[d]+b]=f[d]||f[d-2]||f[0];return e}},Gb.test(a)||(m.cssHooks[a+b].set=Wb)}),m.fn.extend({css:function(a,b){return V(this,function(a,b,c){var d,e,f={},g=0;if(m.isArray(b)){for(d=Ib(a),e=b.length;e>g;g++)f[b[g]]=m.css(a,b[g],!1,d);return f}return void 0!==c?m.style(a,b,c):m.css(a,b)},a,b,arguments.length>1)},show:function(){return Vb(this,!0)},hide:function(){return Vb(this)},toggle:function(a){return"boolean"==typeof a?a?this.show():this.hide():this.each(function(){U(this)?m(this).show():m(this).hide()})}});function Zb(a,b,c,d,e){return new Zb.prototype.init(a,b,c,d,e)
}m.Tween=Zb,Zb.prototype={constructor:Zb,init:function(a,b,c,d,e,f){this.elem=a,this.prop=c,this.easing=e||"swing",this.options=b,this.start=this.now=this.cur(),this.end=d,this.unit=f||(m.cssNumber[c]?"":"px")},cur:function(){var a=Zb.propHooks[this.prop];return a&&a.get?a.get(this):Zb.propHooks._default.get(this)},run:function(a){var b,c=Zb.propHooks[this.prop];return this.pos=b=this.options.duration?m.easing[this.easing](a,this.options.duration*a,0,1,this.options.duration):a,this.now=(this.end-this.start)*b+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),c&&c.set?c.set(this):Zb.propHooks._default.set(this),this}},Zb.prototype.init.prototype=Zb.prototype,Zb.propHooks={_default:{get:function(a){var b;return null==a.elem[a.prop]||a.elem.style&&null!=a.elem.style[a.prop]?(b=m.css(a.elem,a.prop,""),b&&"auto"!==b?b:0):a.elem[a.prop]},set:function(a){m.fx.step[a.prop]?m.fx.step[a.prop](a):a.elem.style&&(null!=a.elem.style[m.cssProps[a.prop]]||m.cssHooks[a.prop])?m.style(a.elem,a.prop,a.now+a.unit):a.elem[a.prop]=a.now}}},Zb.propHooks.scrollTop=Zb.propHooks.scrollLeft={set:function(a){a.elem.nodeType&&a.elem.parentNode&&(a.elem[a.prop]=a.now)}},m.easing={linear:function(a){return a},swing:function(a){return.5-Math.cos(a*Math.PI)/2}},m.fx=Zb.prototype.init,m.fx.step={};var $b,_b,ac=/^(?:toggle|show|hide)$/,bc=new RegExp("^(?:([+-])=|)("+S+")([a-z%]*)$","i"),cc=/queueHooks$/,dc=[ic],ec={"*":[function(a,b){var c=this.createTween(a,b),d=c.cur(),e=bc.exec(b),f=e&&e[3]||(m.cssNumber[a]?"":"px"),g=(m.cssNumber[a]||"px"!==f&&+d)&&bc.exec(m.css(c.elem,a)),h=1,i=20;if(g&&g[3]!==f){f=f||g[3],e=e||[],g=+d||1;do h=h||".5",g/=h,m.style(c.elem,a,g+f);while(h!==(h=c.cur()/d)&&1!==h&&--i)}return e&&(g=c.start=+g||+d||0,c.unit=f,c.end=e[1]?g+(e[1]+1)*e[2]:+e[2]),c}]};function fc(){return setTimeout(function(){$b=void 0}),$b=m.now()}function gc(a,b){var c,d={height:a},e=0;for(b=b?1:0;4>e;e+=2-b)c=T[e],d["margin"+c]=d["padding"+c]=a;return b&&(d.opacity=d.width=a),d}function hc(a,b,c){for(var d,e=(ec[b]||[]).concat(ec["*"]),f=0,g=e.length;g>f;f++)if(d=e[f].call(c,b,a))return d}function ic(a,b,c){var d,e,f,g,h,i,j,l,n=this,o={},p=a.style,q=a.nodeType&&U(a),r=m._data(a,"fxshow");c.queue||(h=m._queueHooks(a,"fx"),null==h.unqueued&&(h.unqueued=0,i=h.empty.fire,h.empty.fire=function(){h.unqueued||i()}),h.unqueued++,n.always(function(){n.always(function(){h.unqueued--,m.queue(a,"fx").length||h.empty.fire()})})),1===a.nodeType&&("height"in b||"width"in b)&&(c.overflow=[p.overflow,p.overflowX,p.overflowY],j=m.css(a,"display"),l="none"===j?m._data(a,"olddisplay")||Fb(a.nodeName):j,"inline"===l&&"none"===m.css(a,"float")&&(k.inlineBlockNeedsLayout&&"inline"!==Fb(a.nodeName)?p.zoom=1:p.display="inline-block")),c.overflow&&(p.overflow="hidden",k.shrinkWrapBlocks()||n.always(function(){p.overflow=c.overflow[0],p.overflowX=c.overflow[1],p.overflowY=c.overflow[2]}));for(d in b)if(e=b[d],ac.exec(e)){if(delete b[d],f=f||"toggle"===e,e===(q?"hide":"show")){if("show"!==e||!r||void 0===r[d])continue;q=!0}o[d]=r&&r[d]||m.style(a,d)}else j=void 0;if(m.isEmptyObject(o))"inline"===("none"===j?Fb(a.nodeName):j)&&(p.display=j);else{r?"hidden"in r&&(q=r.hidden):r=m._data(a,"fxshow",{}),f&&(r.hidden=!q),q?m(a).show():n.done(function(){m(a).hide()}),n.done(function(){var b;m._removeData(a,"fxshow");for(b in o)m.style(a,b,o[b])});for(d in o)g=hc(q?r[d]:0,d,n),d in r||(r[d]=g.start,q&&(g.end=g.start,g.start="width"===d||"height"===d?1:0))}}function jc(a,b){var c,d,e,f,g;for(c in a)if(d=m.camelCase(c),e=b[d],f=a[c],m.isArray(f)&&(e=f[1],f=a[c]=f[0]),c!==d&&(a[d]=f,delete a[c]),g=m.cssHooks[d],g&&"expand"in g){f=g.expand(f),delete a[d];for(c in f)c in a||(a[c]=f[c],b[c]=e)}else b[d]=e}function kc(a,b,c){var d,e,f=0,g=dc.length,h=m.Deferred().always(function(){delete i.elem}),i=function(){if(e)return!1;for(var b=$b||fc(),c=Math.max(0,j.startTime+j.duration-b),d=c/j.duration||0,f=1-d,g=0,i=j.tweens.length;i>g;g++)j.tweens[g].run(f);return h.notifyWith(a,[j,f,c]),1>f&&i?c:(h.resolveWith(a,[j]),!1)},j=h.promise({elem:a,props:m.extend({},b),opts:m.extend(!0,{specialEasing:{}},c),originalProperties:b,originalOptions:c,startTime:$b||fc(),duration:c.duration,tweens:[],createTween:function(b,c){var d=m.Tween(a,j.opts,b,c,j.opts.specialEasing[b]||j.opts.easing);return j.tweens.push(d),d},stop:function(b){var c=0,d=b?j.tweens.length:0;if(e)return this;for(e=!0;d>c;c++)j.tweens[c].run(1);return b?h.resolveWith(a,[j,b]):h.rejectWith(a,[j,b]),this}}),k=j.props;for(jc(k,j.opts.specialEasing);g>f;f++)if(d=dc[f].call(j,a,k,j.opts))return d;return m.map(k,hc,j),m.isFunction(j.opts.start)&&j.opts.start.call(a,j),m.fx.timer(m.extend(i,{elem:a,anim:j,queue:j.opts.queue})),j.progress(j.opts.progress).done(j.opts.done,j.opts.complete).fail(j.opts.fail).always(j.opts.always)}m.Animation=m.extend(kc,{tweener:function(a,b){m.isFunction(a)?(b=a,a=["*"]):a=a.split(" ");for(var c,d=0,e=a.length;e>d;d++)c=a[d],ec[c]=ec[c]||[],ec[c].unshift(b)},prefilter:function(a,b){b?dc.unshift(a):dc.push(a)}}),m.speed=function(a,b,c){var d=a&&"object"==typeof a?m.extend({},a):{complete:c||!c&&b||m.isFunction(a)&&a,duration:a,easing:c&&b||b&&!m.isFunction(b)&&b};return d.duration=m.fx.off?0:"number"==typeof d.duration?d.duration:d.duration in m.fx.speeds?m.fx.speeds[d.duration]:m.fx.speeds._default,(null==d.queue||d.queue===!0)&&(d.queue="fx"),d.old=d.complete,d.complete=function(){m.isFunction(d.old)&&d.old.call(this),d.queue&&m.dequeue(this,d.queue)},d},m.fn.extend({fadeTo:function(a,b,c,d){return this.filter(U).css("opacity",0).show().end().animate({opacity:b},a,c,d)},animate:function(a,b,c,d){var e=m.isEmptyObject(a),f=m.speed(b,c,d),g=function(){var b=kc(this,m.extend({},a),f);(e||m._data(this,"finish"))&&b.stop(!0)};return g.finish=g,e||f.queue===!1?this.each(g):this.queue(f.queue,g)},stop:function(a,b,c){var d=function(a){var b=a.stop;delete a.stop,b(c)};return"string"!=typeof a&&(c=b,b=a,a=void 0),b&&a!==!1&&this.queue(a||"fx",[]),this.each(function(){var b=!0,e=null!=a&&a+"queueHooks",f=m.timers,g=m._data(this);if(e)g[e]&&g[e].stop&&d(g[e]);else for(e in g)g[e]&&g[e].stop&&cc.test(e)&&d(g[e]);for(e=f.length;e--;)f[e].elem!==this||null!=a&&f[e].queue!==a||(f[e].anim.stop(c),b=!1,f.splice(e,1));(b||!c)&&m.dequeue(this,a)})},finish:function(a){return a!==!1&&(a=a||"fx"),this.each(function(){var b,c=m._data(this),d=c[a+"queue"],e=c[a+"queueHooks"],f=m.timers,g=d?d.length:0;for(c.finish=!0,m.queue(this,a,[]),e&&e.stop&&e.stop.call(this,!0),b=f.length;b--;)f[b].elem===this&&f[b].queue===a&&(f[b].anim.stop(!0),f.splice(b,1));for(b=0;g>b;b++)d[b]&&d[b].finish&&d[b].finish.call(this);delete c.finish})}}),m.each(["toggle","show","hide"],function(a,b){var c=m.fn[b];m.fn[b]=function(a,d,e){return null==a||"boolean"==typeof a?c.apply(this,arguments):this.animate(gc(b,!0),a,d,e)}}),m.each({slideDown:gc("show"),slideUp:gc("hide"),slideToggle:gc("toggle"),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"},fadeToggle:{opacity:"toggle"}},function(a,b){m.fn[a]=function(a,c,d){return this.animate(b,a,c,d)}}),m.timers=[],m.fx.tick=function(){var a,b=m.timers,c=0;for($b=m.now();ca ",d=b.getElementsByTagName("a")[0],c=y.createElement("select"),e=c.appendChild(y.createElement("option")),a=b.getElementsByTagName("input")[0],d.style.cssText="top:1px",k.getSetAttribute="t"!==b.className,k.style=/top/.test(d.getAttribute("style")),k.hrefNormalized="/a"===d.getAttribute("href"),k.checkOn=!!a.value,k.optSelected=e.selected,k.enctype=!!y.createElement("form").enctype,c.disabled=!0,k.optDisabled=!e.disabled,a=y.createElement("input"),a.setAttribute("value",""),k.input=""===a.getAttribute("value"),a.value="t",a.setAttribute("type","radio"),k.radioValue="t"===a.value}();var lc=/\r/g;m.fn.extend({val:function(a){var b,c,d,e=this[0];{if(arguments.length)return d=m.isFunction(a),this.each(function(c){var e;1===this.nodeType&&(e=d?a.call(this,c,m(this).val()):a,null==e?e="":"number"==typeof e?e+="":m.isArray(e)&&(e=m.map(e,function(a){return null==a?"":a+""})),b=m.valHooks[this.type]||m.valHooks[this.nodeName.toLowerCase()],b&&"set"in b&&void 0!==b.set(this,e,"value")||(this.value=e))});if(e)return b=m.valHooks[e.type]||m.valHooks[e.nodeName.toLowerCase()],b&&"get"in b&&void 0!==(c=b.get(e,"value"))?c:(c=e.value,"string"==typeof c?c.replace(lc,""):null==c?"":c)}}}),m.extend({valHooks:{option:{get:function(a){var b=m.find.attr(a,"value");return null!=b?b:m.trim(m.text(a))}},select:{get:function(a){for(var b,c,d=a.options,e=a.selectedIndex,f="select-one"===a.type||0>e,g=f?null:[],h=f?e+1:d.length,i=0>e?h:f?e:0;h>i;i++)if(c=d[i],!(!c.selected&&i!==e||(k.optDisabled?c.disabled:null!==c.getAttribute("disabled"))||c.parentNode.disabled&&m.nodeName(c.parentNode,"optgroup"))){if(b=m(c).val(),f)return b;g.push(b)}return g},set:function(a,b){var c,d,e=a.options,f=m.makeArray(b),g=e.length;while(g--)if(d=e[g],m.inArray(m.valHooks.option.get(d),f)>=0)try{d.selected=c=!0}catch(h){d.scrollHeight}else d.selected=!1;return c||(a.selectedIndex=-1),e}}}}),m.each(["radio","checkbox"],function(){m.valHooks[this]={set:function(a,b){return m.isArray(b)?a.checked=m.inArray(m(a).val(),b)>=0:void 0}},k.checkOn||(m.valHooks[this].get=function(a){return null===a.getAttribute("value")?"on":a.value})});var mc,nc,oc=m.expr.attrHandle,pc=/^(?:checked|selected)$/i,qc=k.getSetAttribute,rc=k.input;m.fn.extend({attr:function(a,b){return V(this,m.attr,a,b,arguments.length>1)},removeAttr:function(a){return this.each(function(){m.removeAttr(this,a)})}}),m.extend({attr:function(a,b,c){var d,e,f=a.nodeType;if(a&&3!==f&&8!==f&&2!==f)return typeof a.getAttribute===K?m.prop(a,b,c):(1===f&&m.isXMLDoc(a)||(b=b.toLowerCase(),d=m.attrHooks[b]||(m.expr.match.bool.test(b)?nc:mc)),void 0===c?d&&"get"in d&&null!==(e=d.get(a,b))?e:(e=m.find.attr(a,b),null==e?void 0:e):null!==c?d&&"set"in d&&void 0!==(e=d.set(a,c,b))?e:(a.setAttribute(b,c+""),c):void m.removeAttr(a,b))},removeAttr:function(a,b){var c,d,e=0,f=b&&b.match(E);if(f&&1===a.nodeType)while(c=f[e++])d=m.propFix[c]||c,m.expr.match.bool.test(c)?rc&&qc||!pc.test(c)?a[d]=!1:a[m.camelCase("default-"+c)]=a[d]=!1:m.attr(a,c,""),a.removeAttribute(qc?c:d)},attrHooks:{type:{set:function(a,b){if(!k.radioValue&&"radio"===b&&m.nodeName(a,"input")){var c=a.value;return a.setAttribute("type",b),c&&(a.value=c),b}}}}}),nc={set:function(a,b,c){return b===!1?m.removeAttr(a,c):rc&&qc||!pc.test(c)?a.setAttribute(!qc&&m.propFix[c]||c,c):a[m.camelCase("default-"+c)]=a[c]=!0,c}},m.each(m.expr.match.bool.source.match(/\w+/g),function(a,b){var c=oc[b]||m.find.attr;oc[b]=rc&&qc||!pc.test(b)?function(a,b,d){var e,f;return d||(f=oc[b],oc[b]=e,e=null!=c(a,b,d)?b.toLowerCase():null,oc[b]=f),e}:function(a,b,c){return c?void 0:a[m.camelCase("default-"+b)]?b.toLowerCase():null}}),rc&&qc||(m.attrHooks.value={set:function(a,b,c){return m.nodeName(a,"input")?void(a.defaultValue=b):mc&&mc.set(a,b,c)}}),qc||(mc={set:function(a,b,c){var d=a.getAttributeNode(c);return d||a.setAttributeNode(d=a.ownerDocument.createAttribute(c)),d.value=b+="","value"===c||b===a.getAttribute(c)?b:void 0}},oc.id=oc.name=oc.coords=function(a,b,c){var d;return c?void 0:(d=a.getAttributeNode(b))&&""!==d.value?d.value:null},m.valHooks.button={get:function(a,b){var c=a.getAttributeNode(b);return c&&c.specified?c.value:void 0},set:mc.set},m.attrHooks.contenteditable={set:function(a,b,c){mc.set(a,""===b?!1:b,c)}},m.each(["width","height"],function(a,b){m.attrHooks[b]={set:function(a,c){return""===c?(a.setAttribute(b,"auto"),c):void 0}}})),k.style||(m.attrHooks.style={get:function(a){return a.style.cssText||void 0},set:function(a,b){return a.style.cssText=b+""}});var sc=/^(?:input|select|textarea|button|object)$/i,tc=/^(?:a|area)$/i;m.fn.extend({prop:function(a,b){return V(this,m.prop,a,b,arguments.length>1)},removeProp:function(a){return a=m.propFix[a]||a,this.each(function(){try{this[a]=void 0,delete this[a]}catch(b){}})}}),m.extend({propFix:{"for":"htmlFor","class":"className"},prop:function(a,b,c){var d,e,f,g=a.nodeType;if(a&&3!==g&&8!==g&&2!==g)return f=1!==g||!m.isXMLDoc(a),f&&(b=m.propFix[b]||b,e=m.propHooks[b]),void 0!==c?e&&"set"in e&&void 0!==(d=e.set(a,c,b))?d:a[b]=c:e&&"get"in e&&null!==(d=e.get(a,b))?d:a[b]},propHooks:{tabIndex:{get:function(a){var b=m.find.attr(a,"tabindex");return b?parseInt(b,10):sc.test(a.nodeName)||tc.test(a.nodeName)&&a.href?0:-1}}}}),k.hrefNormalized||m.each(["href","src"],function(a,b){m.propHooks[b]={get:function(a){return a.getAttribute(b,4)}}}),k.optSelected||(m.propHooks.selected={get:function(a){var b=a.parentNode;return b&&(b.selectedIndex,b.parentNode&&b.parentNode.selectedIndex),null}}),m.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],function(){m.propFix[this.toLowerCase()]=this}),k.enctype||(m.propFix.enctype="encoding");var uc=/[\t\r\n\f]/g;m.fn.extend({addClass:function(a){var b,c,d,e,f,g,h=0,i=this.length,j="string"==typeof a&&a;if(m.isFunction(a))return this.each(function(b){m(this).addClass(a.call(this,b,this.className))});if(j)for(b=(a||"").match(E)||[];i>h;h++)if(c=this[h],d=1===c.nodeType&&(c.className?(" "+c.className+" ").replace(uc," "):" ")){f=0;while(e=b[f++])d.indexOf(" "+e+" ")<0&&(d+=e+" ");g=m.trim(d),c.className!==g&&(c.className=g)}return this},removeClass:function(a){var b,c,d,e,f,g,h=0,i=this.length,j=0===arguments.length||"string"==typeof a&&a;if(m.isFunction(a))return this.each(function(b){m(this).removeClass(a.call(this,b,this.className))});if(j)for(b=(a||"").match(E)||[];i>h;h++)if(c=this[h],d=1===c.nodeType&&(c.className?(" "+c.className+" ").replace(uc," "):"")){f=0;while(e=b[f++])while(d.indexOf(" "+e+" ")>=0)d=d.replace(" "+e+" "," ");g=a?m.trim(d):"",c.className!==g&&(c.className=g)}return this},toggleClass:function(a,b){var c=typeof a;return"boolean"==typeof b&&"string"===c?b?this.addClass(a):this.removeClass(a):this.each(m.isFunction(a)?function(c){m(this).toggleClass(a.call(this,c,this.className,b),b)}:function(){if("string"===c){var b,d=0,e=m(this),f=a.match(E)||[];while(b=f[d++])e.hasClass(b)?e.removeClass(b):e.addClass(b)}else(c===K||"boolean"===c)&&(this.className&&m._data(this,"__className__",this.className),this.className=this.className||a===!1?"":m._data(this,"__className__")||"")})},hasClass:function(a){for(var b=" "+a+" ",c=0,d=this.length;d>c;c++)if(1===this[c].nodeType&&(" "+this[c].className+" ").replace(uc," ").indexOf(b)>=0)return!0;return!1}}),m.each("blur focus focusin focusout load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup error contextmenu".split(" "),function(a,b){m.fn[b]=function(a,c){return arguments.length>0?this.on(b,null,a,c):this.trigger(b)}}),m.fn.extend({hover:function(a,b){return this.mouseenter(a).mouseleave(b||a)},bind:function(a,b,c){return this.on(a,null,b,c)},unbind:function(a,b){return this.off(a,null,b)},delegate:function(a,b,c,d){return this.on(b,a,c,d)},undelegate:function(a,b,c){return 1===arguments.length?this.off(a,"**"):this.off(b,a||"**",c)}});var vc=m.now(),wc=/\?/,xc=/(,)|(\[|{)|(}|])|"(?:[^"\\\r\n]|\\["\\\/bfnrt]|\\u[\da-fA-F]{4})*"\s*:?|true|false|null|-?(?!0\d)\d+(?:\.\d+|)(?:[eE][+-]?\d+|)/g;m.parseJSON=function(b){if(a.JSON&&a.JSON.parse)return a.JSON.parse(b+"");var c,d=null,e=m.trim(b+"");return e&&!m.trim(e.replace(xc,function(a,b,e,f){return c&&b&&(d=0),0===d?a:(c=e||b,d+=!f-!e,"")}))?Function("return "+e)():m.error("Invalid JSON: "+b)},m.parseXML=function(b){var c,d;if(!b||"string"!=typeof b)return null;try{a.DOMParser?(d=new DOMParser,c=d.parseFromString(b,"text/xml")):(c=new ActiveXObject("Microsoft.XMLDOM"),c.async="false",c.loadXML(b))}catch(e){c=void 0}return c&&c.documentElement&&!c.getElementsByTagName("parsererror").length||m.error("Invalid XML: "+b),c};var yc,zc,Ac=/#.*$/,Bc=/([?&])_=[^&]*/,Cc=/^(.*?):[ \t]*([^\r\n]*)\r?$/gm,Dc=/^(?:about|app|app-storage|.+-extension|file|res|widget):$/,Ec=/^(?:GET|HEAD)$/,Fc=/^\/\//,Gc=/^([\w.+-]+:)(?:\/\/(?:[^\/?#]*@|)([^\/?#:]*)(?::(\d+)|)|)/,Hc={},Ic={},Jc="*/".concat("*");try{zc=location.href}catch(Kc){zc=y.createElement("a"),zc.href="",zc=zc.href}yc=Gc.exec(zc.toLowerCase())||[];function Lc(a){return function(b,c){"string"!=typeof b&&(c=b,b="*");var d,e=0,f=b.toLowerCase().match(E)||[];if(m.isFunction(c))while(d=f[e++])"+"===d.charAt(0)?(d=d.slice(1)||"*",(a[d]=a[d]||[]).unshift(c)):(a[d]=a[d]||[]).push(c)}}function Mc(a,b,c,d){var e={},f=a===Ic;function g(h){var i;return e[h]=!0,m.each(a[h]||[],function(a,h){var j=h(b,c,d);return"string"!=typeof j||f||e[j]?f?!(i=j):void 0:(b.dataTypes.unshift(j),g(j),!1)}),i}return g(b.dataTypes[0])||!e["*"]&&g("*")}function Nc(a,b){var c,d,e=m.ajaxSettings.flatOptions||{};for(d in b)void 0!==b[d]&&((e[d]?a:c||(c={}))[d]=b[d]);return c&&m.extend(!0,a,c),a}function Oc(a,b,c){var d,e,f,g,h=a.contents,i=a.dataTypes;while("*"===i[0])i.shift(),void 0===e&&(e=a.mimeType||b.getResponseHeader("Content-Type"));if(e)for(g in h)if(h[g]&&h[g].test(e)){i.unshift(g);break}if(i[0]in c)f=i[0];else{for(g in c){if(!i[0]||a.converters[g+" "+i[0]]){f=g;break}d||(d=g)}f=f||d}return f?(f!==i[0]&&i.unshift(f),c[f]):void 0}function Pc(a,b,c,d){var e,f,g,h,i,j={},k=a.dataTypes.slice();if(k[1])for(g in a.converters)j[g.toLowerCase()]=a.converters[g];f=k.shift();while(f)if(a.responseFields[f]&&(c[a.responseFields[f]]=b),!i&&d&&a.dataFilter&&(b=a.dataFilter(b,a.dataType)),i=f,f=k.shift())if("*"===f)f=i;else if("*"!==i&&i!==f){if(g=j[i+" "+f]||j["* "+f],!g)for(e in j)if(h=e.split(" "),h[1]===f&&(g=j[i+" "+h[0]]||j["* "+h[0]])){g===!0?g=j[e]:j[e]!==!0&&(f=h[0],k.unshift(h[1]));break}if(g!==!0)if(g&&a["throws"])b=g(b);else try{b=g(b)}catch(l){return{state:"parsererror",error:g?l:"No conversion from "+i+" to "+f}}}return{state:"success",data:b}}m.extend({active:0,lastModified:{},etag:{},ajaxSettings:{url:zc,type:"GET",isLocal:Dc.test(yc[1]),global:!0,processData:!0,async:!0,contentType:"application/x-www-form-urlencoded; charset=UTF-8",accepts:{"*":Jc,text:"text/plain",html:"text/html",xml:"application/xml, text/xml",json:"application/json, text/javascript"},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText",json:"responseJSON"},converters:{"* text":String,"text html":!0,"text json":m.parseJSON,"text xml":m.parseXML},flatOptions:{url:!0,context:!0}},ajaxSetup:function(a,b){return b?Nc(Nc(a,m.ajaxSettings),b):Nc(m.ajaxSettings,a)},ajaxPrefilter:Lc(Hc),ajaxTransport:Lc(Ic),ajax:function(a,b){"object"==typeof a&&(b=a,a=void 0),b=b||{};var c,d,e,f,g,h,i,j,k=m.ajaxSetup({},b),l=k.context||k,n=k.context&&(l.nodeType||l.jquery)?m(l):m.event,o=m.Deferred(),p=m.Callbacks("once memory"),q=k.statusCode||{},r={},s={},t=0,u="canceled",v={readyState:0,getResponseHeader:function(a){var b;if(2===t){if(!j){j={};while(b=Cc.exec(f))j[b[1].toLowerCase()]=b[2]}b=j[a.toLowerCase()]}return null==b?null:b},getAllResponseHeaders:function(){return 2===t?f:null},setRequestHeader:function(a,b){var c=a.toLowerCase();return t||(a=s[c]=s[c]||a,r[a]=b),this},overrideMimeType:function(a){return t||(k.mimeType=a),this},statusCode:function(a){var b;if(a)if(2>t)for(b in a)q[b]=[q[b],a[b]];else v.always(a[v.status]);return this},abort:function(a){var b=a||u;return i&&i.abort(b),x(0,b),this}};if(o.promise(v).complete=p.add,v.success=v.done,v.error=v.fail,k.url=((a||k.url||zc)+"").replace(Ac,"").replace(Fc,yc[1]+"//"),k.type=b.method||b.type||k.method||k.type,k.dataTypes=m.trim(k.dataType||"*").toLowerCase().match(E)||[""],null==k.crossDomain&&(c=Gc.exec(k.url.toLowerCase()),k.crossDomain=!(!c||c[1]===yc[1]&&c[2]===yc[2]&&(c[3]||("http:"===c[1]?"80":"443"))===(yc[3]||("http:"===yc[1]?"80":"443")))),k.data&&k.processData&&"string"!=typeof k.data&&(k.data=m.param(k.data,k.traditional)),Mc(Hc,k,b,v),2===t)return v;h=m.event&&k.global,h&&0===m.active++&&m.event.trigger("ajaxStart"),k.type=k.type.toUpperCase(),k.hasContent=!Ec.test(k.type),e=k.url,k.hasContent||(k.data&&(e=k.url+=(wc.test(e)?"&":"?")+k.data,delete k.data),k.cache===!1&&(k.url=Bc.test(e)?e.replace(Bc,"$1_="+vc++):e+(wc.test(e)?"&":"?")+"_="+vc++)),k.ifModified&&(m.lastModified[e]&&v.setRequestHeader("If-Modified-Since",m.lastModified[e]),m.etag[e]&&v.setRequestHeader("If-None-Match",m.etag[e])),(k.data&&k.hasContent&&k.contentType!==!1||b.contentType)&&v.setRequestHeader("Content-Type",k.contentType),v.setRequestHeader("Accept",k.dataTypes[0]&&k.accepts[k.dataTypes[0]]?k.accepts[k.dataTypes[0]]+("*"!==k.dataTypes[0]?", "+Jc+"; q=0.01":""):k.accepts["*"]);for(d in k.headers)v.setRequestHeader(d,k.headers[d]);if(k.beforeSend&&(k.beforeSend.call(l,v,k)===!1||2===t))return v.abort();u="abort";for(d in{success:1,error:1,complete:1})v[d](k[d]);if(i=Mc(Ic,k,b,v)){v.readyState=1,h&&n.trigger("ajaxSend",[v,k]),k.async&&k.timeout>0&&(g=setTimeout(function(){v.abort("timeout")},k.timeout));try{t=1,i.send(r,x)}catch(w){if(!(2>t))throw w;x(-1,w)}}else x(-1,"No Transport");function x(a,b,c,d){var j,r,s,u,w,x=b;2!==t&&(t=2,g&&clearTimeout(g),i=void 0,f=d||"",v.readyState=a>0?4:0,j=a>=200&&300>a||304===a,c&&(u=Oc(k,v,c)),u=Pc(k,u,v,j),j?(k.ifModified&&(w=v.getResponseHeader("Last-Modified"),w&&(m.lastModified[e]=w),w=v.getResponseHeader("etag"),w&&(m.etag[e]=w)),204===a||"HEAD"===k.type?x="nocontent":304===a?x="notmodified":(x=u.state,r=u.data,s=u.error,j=!s)):(s=x,(a||!x)&&(x="error",0>a&&(a=0))),v.status=a,v.statusText=(b||x)+"",j?o.resolveWith(l,[r,x,v]):o.rejectWith(l,[v,x,s]),v.statusCode(q),q=void 0,h&&n.trigger(j?"ajaxSuccess":"ajaxError",[v,k,j?r:s]),p.fireWith(l,[v,x]),h&&(n.trigger("ajaxComplete",[v,k]),--m.active||m.event.trigger("ajaxStop")))}return v},getJSON:function(a,b,c){return m.get(a,b,c,"json")},getScript:function(a,b){return m.get(a,void 0,b,"script")}}),m.each(["get","post"],function(a,b){m[b]=function(a,c,d,e){return m.isFunction(c)&&(e=e||d,d=c,c=void 0),m.ajax({url:a,type:b,dataType:e,data:c,success:d})}}),m._evalUrl=function(a){return m.ajax({url:a,type:"GET",dataType:"script",async:!1,global:!1,"throws":!0})},m.fn.extend({wrapAll:function(a){if(m.isFunction(a))return this.each(function(b){m(this).wrapAll(a.call(this,b))});if(this[0]){var b=m(a,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&b.insertBefore(this[0]),b.map(function(){var a=this;while(a.firstChild&&1===a.firstChild.nodeType)a=a.firstChild;return a}).append(this)}return this},wrapInner:function(a){return this.each(m.isFunction(a)?function(b){m(this).wrapInner(a.call(this,b))}:function(){var b=m(this),c=b.contents();c.length?c.wrapAll(a):b.append(a)})},wrap:function(a){var b=m.isFunction(a);return this.each(function(c){m(this).wrapAll(b?a.call(this,c):a)})},unwrap:function(){return this.parent().each(function(){m.nodeName(this,"body")||m(this).replaceWith(this.childNodes)}).end()}}),m.expr.filters.hidden=function(a){return a.offsetWidth<=0&&a.offsetHeight<=0||!k.reliableHiddenOffsets()&&"none"===(a.style&&a.style.display||m.css(a,"display"))},m.expr.filters.visible=function(a){return!m.expr.filters.hidden(a)};var Qc=/%20/g,Rc=/\[\]$/,Sc=/\r?\n/g,Tc=/^(?:submit|button|image|reset|file)$/i,Uc=/^(?:input|select|textarea|keygen)/i;function Vc(a,b,c,d){var e;if(m.isArray(b))m.each(b,function(b,e){c||Rc.test(a)?d(a,e):Vc(a+"["+("object"==typeof e?b:"")+"]",e,c,d)});else if(c||"object"!==m.type(b))d(a,b);else for(e in b)Vc(a+"["+e+"]",b[e],c,d)}m.param=function(a,b){var c,d=[],e=function(a,b){b=m.isFunction(b)?b():null==b?"":b,d[d.length]=encodeURIComponent(a)+"="+encodeURIComponent(b)};if(void 0===b&&(b=m.ajaxSettings&&m.ajaxSettings.traditional),m.isArray(a)||a.jquery&&!m.isPlainObject(a))m.each(a,function(){e(this.name,this.value)});else for(c in a)Vc(c,a[c],b,e);return d.join("&").replace(Qc,"+")},m.fn.extend({serialize:function(){return m.param(this.serializeArray())},serializeArray:function(){return this.map(function(){var a=m.prop(this,"elements");return a?m.makeArray(a):this}).filter(function(){var a=this.type;return this.name&&!m(this).is(":disabled")&&Uc.test(this.nodeName)&&!Tc.test(a)&&(this.checked||!W.test(a))}).map(function(a,b){var c=m(this).val();return null==c?null:m.isArray(c)?m.map(c,function(a){return{name:b.name,value:a.replace(Sc,"\r\n")}}):{name:b.name,value:c.replace(Sc,"\r\n")}}).get()}}),m.ajaxSettings.xhr=void 0!==a.ActiveXObject?function(){return!this.isLocal&&/^(get|post|head|put|delete|options)$/i.test(this.type)&&Zc()||$c()}:Zc;var Wc=0,Xc={},Yc=m.ajaxSettings.xhr();a.attachEvent&&a.attachEvent("onunload",function(){for(var a in Xc)Xc[a](void 0,!0)}),k.cors=!!Yc&&"withCredentials"in Yc,Yc=k.ajax=!!Yc,Yc&&m.ajaxTransport(function(a){if(!a.crossDomain||k.cors){var b;return{send:function(c,d){var e,f=a.xhr(),g=++Wc;if(f.open(a.type,a.url,a.async,a.username,a.password),a.xhrFields)for(e in a.xhrFields)f[e]=a.xhrFields[e];a.mimeType&&f.overrideMimeType&&f.overrideMimeType(a.mimeType),a.crossDomain||c["X-Requested-With"]||(c["X-Requested-With"]="XMLHttpRequest");for(e in c)void 0!==c[e]&&f.setRequestHeader(e,c[e]+"");f.send(a.hasContent&&a.data||null),b=function(c,e){var h,i,j;if(b&&(e||4===f.readyState))if(delete Xc[g],b=void 0,f.onreadystatechange=m.noop,e)4!==f.readyState&&f.abort();else{j={},h=f.status,"string"==typeof f.responseText&&(j.text=f.responseText);try{i=f.statusText}catch(k){i=""}h||!a.isLocal||a.crossDomain?1223===h&&(h=204):h=j.text?200:404}j&&d(h,i,j,f.getAllResponseHeaders())},a.async?4===f.readyState?setTimeout(b):f.onreadystatechange=Xc[g]=b:b()},abort:function(){b&&b(void 0,!0)}}}});function Zc(){try{return new a.XMLHttpRequest}catch(b){}}function $c(){try{return new a.ActiveXObject("Microsoft.XMLHTTP")}catch(b){}}m.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/(?:java|ecma)script/},converters:{"text script":function(a){return m.globalEval(a),a}}}),m.ajaxPrefilter("script",function(a){void 0===a.cache&&(a.cache=!1),a.crossDomain&&(a.type="GET",a.global=!1)}),m.ajaxTransport("script",function(a){if(a.crossDomain){var b,c=y.head||m("head")[0]||y.documentElement;return{send:function(d,e){b=y.createElement("script"),b.async=!0,a.scriptCharset&&(b.charset=a.scriptCharset),b.src=a.url,b.onload=b.onreadystatechange=function(a,c){(c||!b.readyState||/loaded|complete/.test(b.readyState))&&(b.onload=b.onreadystatechange=null,b.parentNode&&b.parentNode.removeChild(b),b=null,c||e(200,"success"))},c.insertBefore(b,c.firstChild)},abort:function(){b&&b.onload(void 0,!0)}}}});var _c=[],ad=/(=)\?(?=&|$)|\?\?/;m.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var a=_c.pop()||m.expando+"_"+vc++;return this[a]=!0,a}}),m.ajaxPrefilter("json jsonp",function(b,c,d){var e,f,g,h=b.jsonp!==!1&&(ad.test(b.url)?"url":"string"==typeof b.data&&!(b.contentType||"").indexOf("application/x-www-form-urlencoded")&&ad.test(b.data)&&"data");return h||"jsonp"===b.dataTypes[0]?(e=b.jsonpCallback=m.isFunction(b.jsonpCallback)?b.jsonpCallback():b.jsonpCallback,h?b[h]=b[h].replace(ad,"$1"+e):b.jsonp!==!1&&(b.url+=(wc.test(b.url)?"&":"?")+b.jsonp+"="+e),b.converters["script json"]=function(){return g||m.error(e+" was not called"),g[0]},b.dataTypes[0]="json",f=a[e],a[e]=function(){g=arguments},d.always(function(){a[e]=f,b[e]&&(b.jsonpCallback=c.jsonpCallback,_c.push(e)),g&&m.isFunction(f)&&f(g[0]),g=f=void 0}),"script"):void 0}),m.parseHTML=function(a,b,c){if(!a||"string"!=typeof a)return null;"boolean"==typeof b&&(c=b,b=!1),b=b||y;var d=u.exec(a),e=!c&&[];return d?[b.createElement(d[1])]:(d=m.buildFragment([a],b,e),e&&e.length&&m(e).remove(),m.merge([],d.childNodes))};var bd=m.fn.load;m.fn.load=function(a,b,c){if("string"!=typeof a&&bd)return bd.apply(this,arguments);var d,e,f,g=this,h=a.indexOf(" ");return h>=0&&(d=m.trim(a.slice(h,a.length)),a=a.slice(0,h)),m.isFunction(b)?(c=b,b=void 0):b&&"object"==typeof b&&(f="POST"),g.length>0&&m.ajax({url:a,type:f,dataType:"html",data:b}).done(function(a){e=arguments,g.html(d?m("").append(m.parseHTML(a)).find(d):a)}).complete(c&&function(a,b){g.each(c,e||[a.responseText,b,a])}),this},m.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(a,b){m.fn[b]=function(a){return this.on(b,a)}}),m.expr.filters.animated=function(a){return m.grep(m.timers,function(b){return a===b.elem}).length};var cd=a.document.documentElement;function dd(a){return m.isWindow(a)?a:9===a.nodeType?a.defaultView||a.parentWindow:!1}m.offset={setOffset:function(a,b,c){var d,e,f,g,h,i,j,k=m.css(a,"position"),l=m(a),n={};"static"===k&&(a.style.position="relative"),h=l.offset(),f=m.css(a,"top"),i=m.css(a,"left"),j=("absolute"===k||"fixed"===k)&&m.inArray("auto",[f,i])>-1,j?(d=l.position(),g=d.top,e=d.left):(g=parseFloat(f)||0,e=parseFloat(i)||0),m.isFunction(b)&&(b=b.call(a,c,h)),null!=b.top&&(n.top=b.top-h.top+g),null!=b.left&&(n.left=b.left-h.left+e),"using"in b?b.using.call(a,n):l.css(n)}},m.fn.extend({offset:function(a){if(arguments.length)return void 0===a?this:this.each(function(b){m.offset.setOffset(this,a,b)});var b,c,d={top:0,left:0},e=this[0],f=e&&e.ownerDocument;if(f)return b=f.documentElement,m.contains(b,e)?(typeof e.getBoundingClientRect!==K&&(d=e.getBoundingClientRect()),c=dd(f),{top:d.top+(c.pageYOffset||b.scrollTop)-(b.clientTop||0),left:d.left+(c.pageXOffset||b.scrollLeft)-(b.clientLeft||0)}):d},position:function(){if(this[0]){var a,b,c={top:0,left:0},d=this[0];return"fixed"===m.css(d,"position")?b=d.getBoundingClientRect():(a=this.offsetParent(),b=this.offset(),m.nodeName(a[0],"html")||(c=a.offset()),c.top+=m.css(a[0],"borderTopWidth",!0),c.left+=m.css(a[0],"borderLeftWidth",!0)),{top:b.top-c.top-m.css(d,"marginTop",!0),left:b.left-c.left-m.css(d,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var a=this.offsetParent||cd;while(a&&!m.nodeName(a,"html")&&"static"===m.css(a,"position"))a=a.offsetParent;return a||cd})}}),m.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(a,b){var c=/Y/.test(b);m.fn[a]=function(d){return V(this,function(a,d,e){var f=dd(a);return void 0===e?f?b in f?f[b]:f.document.documentElement[d]:a[d]:void(f?f.scrollTo(c?m(f).scrollLeft():e,c?e:m(f).scrollTop()):a[d]=e)},a,d,arguments.length,null)}}),m.each(["top","left"],function(a,b){m.cssHooks[b]=Lb(k.pixelPosition,function(a,c){return c?(c=Jb(a,b),Hb.test(c)?m(a).position()[b]+"px":c):void 0})}),m.each({Height:"height",Width:"width"},function(a,b){m.each({padding:"inner"+a,content:b,"":"outer"+a},function(c,d){m.fn[d]=function(d,e){var f=arguments.length&&(c||"boolean"!=typeof d),g=c||(d===!0||e===!0?"margin":"border");return V(this,function(b,c,d){var e;return m.isWindow(b)?b.document.documentElement["client"+a]:9===b.nodeType?(e=b.documentElement,Math.max(b.body["scroll"+a],e["scroll"+a],b.body["offset"+a],e["offset"+a],e["client"+a])):void 0===d?m.css(b,c,g):m.style(b,c,d,g)},b,f?d:void 0,f,null)}})}),m.fn.size=function(){return this.length},m.fn.andSelf=m.fn.addBack,"function"==typeof define&&define.amd&&define("jquery",[],function(){return m});var ed=a.jQuery,fd=a.$;return m.noConflict=function(b){return a.$===m&&(a.$=fd),b&&a.jQuery===m&&(a.jQuery=ed),m},typeof b===K&&(a.jQuery=a.$=m),m});
diff --git a/packages/react-data-grid-examples/src/docs/markdowns/Canvas.md b/packages/react-data-grid-examples/src/docs/markdowns/Canvas.md
index 480fef641d..97f588ac29 100644
--- a/packages/react-data-grid-examples/src/docs/markdowns/Canvas.md
+++ b/packages/react-data-grid-examples/src/docs/markdowns/Canvas.md
@@ -16,22 +16,22 @@ type: `shaperequire('./PropTypeShapes/CellMetaDataShape')`
type: `string`
-### `colDisplayEnd` (required)
+### `colOverscanEndIdx` (required)
type: `number`
-### `colDisplayStart` (required)
+### `colOverscanStartIdx` (required)
type: `number`
-### `colVisibleEnd` (required)
+### `colVisibleEndIdx` (required)
type: `number`
-### `colVisibleStart` (required)
+### `colVisibleStartIdx` (required)
type: `number`
@@ -46,12 +46,12 @@ type: `union(object|array)`
type: `element`
-### `displayEnd` (required)
+### `rowOverscanEndIdx` (required)
type: `number`
-### `displayStart` (required)
+### `rowOverscanStartIdx` (required)
type: `number`
@@ -145,12 +145,12 @@ type: `string`
type: `union(number|string)`
-### `visibleEnd` (required)
+### `rowVisibleEndIdx` (required)
type: `number`
-### `visibleStart` (required)
+### `rowVisibleStartIdx` (required)
type: `number`
diff --git a/packages/react-data-grid-examples/src/docs/markdowns/Row.md b/packages/react-data-grid-examples/src/docs/markdowns/Row.md
index defa96fd72..e301d2994f 100644
--- a/packages/react-data-grid-examples/src/docs/markdowns/Row.md
+++ b/packages/react-data-grid-examples/src/docs/markdowns/Row.md
@@ -17,22 +17,22 @@ type: `func`
defaultValue: `require('./Cell')`
-### `colDisplayEnd` (required)
+### `colOverscanEndIdx` (required)
type: `number`
-### `colDisplayStart` (required)
+### `colOverscanStartIdx` (required)
type: `number`
-### `colVisibleEnd` (required)
+### `colVisibleEndIdx` (required)
type: `number`
-### `colVisibleStart` (required)
+### `colVisibleStartIdx` (required)
type: `number`
diff --git a/packages/react-data-grid-examples/src/scripts/example03-fixed-cols.js b/packages/react-data-grid-examples/src/scripts/example03-frozen-cols.js
similarity index 82%
rename from packages/react-data-grid-examples/src/scripts/example03-fixed-cols.js
rename to packages/react-data-grid-examples/src/scripts/example03-frozen-cols.js
index 50c42d40e4..c9c7c9d9d6 100644
--- a/packages/react-data-grid-examples/src/scripts/example03-fixed-cols.js
+++ b/packages/react-data-grid-examples/src/scripts/example03-frozen-cols.js
@@ -6,26 +6,31 @@ class Example extends React.Component {
constructor(props, context) {
super(props, context);
this.createRows();
- this._columns = [
+
+ const extraColumns = [...Array(50).keys()].map(i => ({key: `col${i}`, name: `col${i}`}));
+ const columns = [
{
key: 'id',
name: 'ID',
- locked: true
+ frozen: true
},
{
key: 'task',
name: 'Title',
- width: 200
+ width: 200,
+ frozen: true
},
{
key: 'priority',
name: 'Priority',
- width: 200
+ width: 200,
+ frozen: true
},
{
key: 'issueType',
name: 'Issue Type',
- width: 200
+ width: 200,
+ frozen: true
},
{
key: 'complete',
@@ -41,9 +46,10 @@ class Example extends React.Component {
key: 'completeDate',
name: 'Expected Complete',
width: 200
- }
- ];
+ },
+ ...extraColumns];
+ this._columns = columns;
this.state = null;
}
@@ -82,12 +88,12 @@ class Example extends React.Component {
}
}
-const exampleDescription =
To make a given column frozen, set column.locked = true
. In this example, the ID columns has been frozen and will remain in position as you scroll horizontally
+const exampleDescription =
To make a given column frozen, set column.frozen = true
. In this example, the ID columns has been frozen and will remain in position as you scroll horizontally
module.exports = exampleWrapper({
WrappedComponent: Example,
exampleName: 'Frozen Columns Example',
exampleDescription,
- examplePath: './scripts/example03-fixed-cols.js',
+ examplePath: './scripts/example03-frozen-cols.js',
examplePlaygroundLink: 'https://jsfiddle.net/k7tfnw1n/5/'
});
diff --git a/packages/react-data-grid-examples/src/scripts/example08-sortable-cols.js b/packages/react-data-grid-examples/src/scripts/example08-sortable-cols.js
index 178dbb8a73..5ee9322c0e 100644
--- a/packages/react-data-grid-examples/src/scripts/example08-sortable-cols.js
+++ b/packages/react-data-grid-examples/src/scripts/example08-sortable-cols.js
@@ -9,7 +9,7 @@ class Example extends React.Component {
{
key: 'id',
name: 'ID',
- locked: true
+ frozen: true
},
{
key: 'task',
diff --git a/packages/react-data-grid-examples/src/scripts/example18-context-menu.js b/packages/react-data-grid-examples/src/scripts/example18-context-menu.js
index c7e33fee1d..abebcaa2e1 100644
--- a/packages/react-data-grid-examples/src/scripts/example18-context-menu.js
+++ b/packages/react-data-grid-examples/src/scripts/example18-context-menu.js
@@ -1,7 +1,7 @@
const ReactDataGrid = require('react-data-grid');
const exampleWrapper = require('../components/exampleWrapper');
const React = require('react');
-const { Menu: { ContextMenu, MenuItem, SubMenu } } = require('react-data-grid-addons');
+const { Menu: { ContextMenu, MenuItem, SubMenu, ContextMenuTrigger } } = require('react-data-grid-addons');
import PropTypes from 'prop-types';
@@ -63,7 +63,8 @@ class Example extends React.Component {
columns={this._columns}
rowGetter={this.rowGetter}
rowsCount={this.state.rows.length}
- minHeight={500} />
+ minHeight={500}
+ RowsContainer={ContextMenuTrigger} />
);
}
}
diff --git a/packages/react-data-grid-examples/src/scripts/example20-cell-navigation.js b/packages/react-data-grid-examples/src/scripts/example20-cell-navigation.js
index b3c8e89743..d3dab311b7 100644
--- a/packages/react-data-grid-examples/src/scripts/example20-cell-navigation.js
+++ b/packages/react-data-grid-examples/src/scripts/example20-cell-navigation.js
@@ -10,7 +10,7 @@ class Example extends React.Component {
{
key: 'id',
name: 'ID',
- locked: true
+ frozen: true
},
{
key: 'task',
diff --git a/packages/react-data-grid-examples/src/scripts/example21-cell-selection-events.js b/packages/react-data-grid-examples/src/scripts/example21-cell-selection-events.js
index 94ed722d0a..b830a28bdf 100644
--- a/packages/react-data-grid-examples/src/scripts/example21-cell-selection-events.js
+++ b/packages/react-data-grid-examples/src/scripts/example21-cell-selection-events.js
@@ -39,9 +39,7 @@ class Example extends React.Component {
};
onCellDeSelected = ({ rowIdx, idx }) => {
- if (idx === 2) {
- alert('the editor for cell (' + rowIdx + ',' + idx + ') should have just closed');
- }
+ this.setState({alert: `The editor for cell ${idx}, ${rowIdx} should have just closed`});
};
render() {
@@ -49,6 +47,7 @@ class Example extends React.Component {
return (
{this.state.selectedRows.length} {rowText} selected
+ {this.state.alert &&
{this.state.alert}
}
this.grid = node }
rowKey="id"
diff --git a/packages/react-data-grid-examples/src/scripts/example25-tree-view.js b/packages/react-data-grid-examples/src/scripts/example25-tree-view.js
index d8a83288d4..2f3d4b76df 100644
--- a/packages/react-data-grid-examples/src/scripts/example25-tree-view.js
+++ b/packages/react-data-grid-examples/src/scripts/example25-tree-view.js
@@ -28,7 +28,7 @@ let columns = [
{
key: 'id',
name: 'id',
- locked: true
+ frozen: true
},
{
key: 'name',
diff --git a/packages/react-data-grid-examples/src/scripts/example26-tree-view-no-add-delete.js b/packages/react-data-grid-examples/src/scripts/example26-tree-view-no-add-delete.js
index f1498711d4..305f96dcfd 100644
--- a/packages/react-data-grid-examples/src/scripts/example26-tree-view-no-add-delete.js
+++ b/packages/react-data-grid-examples/src/scripts/example26-tree-view-no-add-delete.js
@@ -28,7 +28,7 @@ let columns = [
{
key: 'id',
name: 'id',
- locked: true
+ frozen: true
},
{
key: 'name',
diff --git a/packages/react-data-grid-examples/src/scripts/example29-descendingFirstSortable.js b/packages/react-data-grid-examples/src/scripts/example29-descendingFirstSortable.js
index 94127e7cfc..9edbfe46a9 100644
--- a/packages/react-data-grid-examples/src/scripts/example29-descendingFirstSortable.js
+++ b/packages/react-data-grid-examples/src/scripts/example29-descendingFirstSortable.js
@@ -9,7 +9,7 @@ class Example extends React.Component {
{
key: 'id',
name: 'ID',
- locked: true
+ frozen: true
},
{
key: 'task',
diff --git a/packages/react-data-grid-examples/src/scripts/example31-isScrolling.js b/packages/react-data-grid-examples/src/scripts/example31-isScrolling.js
new file mode 100644
index 0000000000..d506e1eb81
--- /dev/null
+++ b/packages/react-data-grid-examples/src/scripts/example31-isScrolling.js
@@ -0,0 +1,70 @@
+import ReactDataGrid from 'react-data-grid';
+import exampleWrapper from '../components/exampleWrapper';
+import React from 'react';
+import {AreaChart, Area} from 'Recharts';
+
+const getRandom = (min, max) => {
+ min = Math.ceil(min);
+ max = Math.floor(max);
+ return Math.floor(Math.random() * (max - min)) + min; // The maximum is exclusive and the minimum is inclusive
+};
+
+const ExpensiveFormatter = ({isScrolling}) => {
+ if (isScrolling) {
+ return is scrolling
;
+ }
+ const items = [...Array(1000).keys()].map(i => ({name: `Page ${i}`, uv: getRandom(0, 4000), pv: getRandom(0, 4000), amt: getRandom(0, 4000)})).slice(0, 50);
+ return (
+
+
+
+ );
+};
+
+const createColumns = (numberCols) => [...Array(numberCols).keys()].map(i => {
+ const column = {
+ key: `col${i}`,
+ name: `col${i}`
+ };
+ if (i === 3) {
+ column.formatter = ExpensiveFormatter;
+ column.width = 500;
+ }
+ return column;
+});
+
+const createRows = (numberRows) => [...Array(numberRows).keys()].map(i => {
+ return [...Array(numberRows).keys()].reduce((row, j) => ({...row, [`col${j}`]: `row ${i} col ${j}`}), {});
+});
+
+class Example extends React.Component {
+ constructor(props, context) {
+ super(props, context);
+ this.state = {columns: createColumns(6), rows: createRows(200)};
+ }
+
+ rowGetter = (i) => {
+ return this.state.rows[i];
+ };
+
+ render() {
+ return (
+
+
);
+ }
+}
+
+module.exports = exampleWrapper({
+ WrappedComponent: Example,
+ exampleName: 'Efficient windowing demonstration',
+ exampleDescription: 'For formatters that are expensive to render it is possible to render a simple placeholder when scrolling to improve scroll performance',
+ examplePath: './scripts/example30-efficient-windowing.js',
+ examplePlaygroundLink: 'https://jsfiddle.net/f6mbnb8z/1/'
+});
diff --git a/packages/react-data-grid/package.json b/packages/react-data-grid/package.json
index 53efc834ab..679598fa0f 100644
--- a/packages/react-data-grid/package.json
+++ b/packages/react-data-grid/package.json
@@ -1,6 +1,6 @@
{
"name": "react-data-grid",
- "version": "4.0.8",
+ "version": "5.0.0-alpha12",
"description": "Excel-like grid component built with React, with editors, keyboard navigation, copy & paste, and the like",
"scripts": {
"beforepublish": "node ../../ci/publish/replacePackageEntry react-data-grid true",
diff --git a/packages/react-data-grid/src/Canvas.js b/packages/react-data-grid/src/Canvas.js
index e174035356..18a143bbf1 100644
--- a/packages/react-data-grid/src/Canvas.js
+++ b/packages/react-data-grid/src/Canvas.js
@@ -1,401 +1,416 @@
-const React = require('react');
-import PropTypes from 'prop-types';
-const Row = require('./Row');
-const cellMetaDataShape = require('./PropTypeShapes/CellMetaDataShape');
-import * as rowUtils from './RowUtils';
-import RowsContainer from './RowsContainer';
-import RowGroup from './RowGroup';
-import { InteractionMasks } from './masks';
-import { getColumnScrollPosition } from './utils/canvasUtils';
-import { EventTypes } from './constants';
-require('../../../themes/react-data-grid-core.css');
-
-class Canvas extends React.Component {
- static displayName = 'Canvas';
-
- static propTypes = {
- rowRenderer: PropTypes.oneOfType([PropTypes.func, PropTypes.element]),
- rowHeight: PropTypes.number.isRequired,
- height: PropTypes.number.isRequired,
- width: PropTypes.number,
- totalWidth: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
- style: PropTypes.string,
- className: PropTypes.string,
- displayStart: PropTypes.number.isRequired,
- displayEnd: PropTypes.number.isRequired,
- visibleStart: PropTypes.number.isRequired,
- visibleEnd: PropTypes.number.isRequired,
- colVisibleStart: PropTypes.number.isRequired,
- colVisibleEnd: PropTypes.number.isRequired,
- colDisplayStart: PropTypes.number.isRequired,
- colDisplayEnd: PropTypes.number.isRequired,
- rowsCount: PropTypes.number.isRequired,
- rowGetter: PropTypes.oneOfType([
- PropTypes.func.isRequired,
- PropTypes.array.isRequired
- ]),
- expandedRows: PropTypes.array,
- onRows: PropTypes.func,
- onScroll: PropTypes.func,
- columns: PropTypes.oneOfType([PropTypes.object, PropTypes.array]).isRequired,
- cellMetaData: PropTypes.shape(cellMetaDataShape).isRequired,
- selectedRows: PropTypes.array,
- rowKey: PropTypes.string,
- rowScrollTimeout: PropTypes.number,
- scrollToRowIndex: PropTypes.number,
- contextMenu: PropTypes.element,
- getSubRowDetails: PropTypes.func,
- rowSelection: PropTypes.oneOfType([
- PropTypes.shape({
- indexes: PropTypes.arrayOf(PropTypes.number).isRequired
- }),
- PropTypes.shape({
- isSelectedKey: PropTypes.string.isRequired
- }),
- PropTypes.shape({
- keys: PropTypes.shape({
- values: PropTypes.array.isRequired,
- rowKey: PropTypes.string.isRequired
- }).isRequired
- })
- ]),
- rowGroupRenderer: PropTypes.func,
- isScrolling: PropTypes.bool,
- length: PropTypes.number,
- enableCellSelect: PropTypes.bool.isRequired,
- enableCellAutoFocus: PropTypes.bool.isRequired,
- cellNavigationMode: PropTypes.string.isRequired,
- eventBus: PropTypes.object.isRequired,
- onCheckCellIsEditable: PropTypes.func,
- onCellCopyPaste: PropTypes.func,
- onGridRowsUpdated: PropTypes.func.isRequired,
- onDragHandleDoubleClick: PropTypes.func.isRequired,
- onCellSelected: PropTypes.func,
- onCellDeSelected: PropTypes.func,
- onCellRangeSelectionStarted: PropTypes.func,
- onCellRangeSelectionUpdated: PropTypes.func,
- onCellRangeSelectionCompleted: PropTypes.func,
- onCommit: PropTypes.func.isRequired
- };
-
- static defaultProps = {
- rowRenderer: Row,
- onRows: () => { },
- selectedRows: [],
- rowScrollTimeout: 0,
- scrollToRowIndex: 0
- };
-
- state = {
- scrollingTimeout: null
- };
-
- rows = [];
- _currentRowsLength = 0;
- _currentRowsRange = { start: 0, end: 0 };
- _scroll = { scrollTop: 0, scrollLeft: 0 };
-
- componentDidMount() {
- this.unsubscribeScrollToColumn = this.props.eventBus.subscribe(EventTypes.SCROLL_TO_COLUMN, this.scrollToColumn);
- this.onRows();
- }
-
- componentWillUnmount() {
- this._currentRowsLength = 0;
- this._currentRowsRange = { start: 0, end: 0 };
- this._scroll = { scrollTop: 0, scrollLeft: 0 };
- this.rows = [];
- this.unsubscribeScrollToColumn();
- }
-
- componentDidUpdate(prevProps) {
- if (this._scroll.scrollTop !== 0 && this._scroll.scrollLeft !== 0) {
- this.setScrollLeft(this._scroll.scrollLeft);
- }
-
- const { scrollToRowIndex } = this.props;
- if (prevProps.scrollToRowIndex !== scrollToRowIndex && scrollToRowIndex !== 0) {
- this.scrollToRow(scrollToRowIndex);
- }
- this.onRows();
- }
-
- onRows = () => {
- if (this._currentRowsRange !== { start: 0, end: 0 }) {
- this.props.onRows(this._currentRowsRange);
- this._currentRowsRange = { start: 0, end: 0 };
- }
- };
-
- scrollToRow = (scrollToRowIndex) => {
- const { rowHeight, rowsCount, height } = this.props;
- this.canvas.scrollTop = Math.min(
- scrollToRowIndex * rowHeight,
- rowsCount * rowHeight - height
- );
- };
-
- onFocusInteractionMask = (focus) => {
- const { scrollTop, scrollLeft } = this._scroll;
- focus();
- if (this.canvas) {
- this.canvas.scrollTop = scrollTop;
- this.canvas.scrollLeft = scrollLeft;
- }
- };
-
- onScroll = (e) => {
- if (this.canvas !== e.target) {
- return;
- }
- const { scrollLeft, scrollTop } = e.target;
- const scroll = { scrollTop, scrollLeft };
- this._scroll = scroll;
- this.props.onScroll(scroll);
- };
-
- getClientScrollTopOffset(node) {
- const { rowHeight } = this.props;
- const scrollVariation = node.scrollTop % rowHeight;
- return scrollVariation > 0 ? rowHeight - scrollVariation : 0;
- }
-
- onHitBottomCanvas = () => {
- const { rowHeight } = this.props;
- const node = this.canvas;
- node.scrollTop += rowHeight + this.getClientScrollTopOffset(node);
- }
-
- onHitTopCanvas = () => {
- const { rowHeight } = this.props;
- const node = this.canvas;
- node.scrollTop -= (rowHeight - this.getClientScrollTopOffset(node));
- }
-
- scrollToColumn = (idx) => {
- const { scrollLeft, clientWidth } = this.canvas;
- const newScrollLeft = getColumnScrollPosition(this.props.columns, idx, scrollLeft, clientWidth);
-
- if (newScrollLeft != null) {
- this.canvas.scrollLeft = scrollLeft + newScrollLeft;
- }
- }
-
- onHitLeftCanvas = ({ idx }) => {
- this.scrollToColumn(idx);
- }
-
- onHitRightCanvas = ({ idx }) => {
- this.scrollToColumn(idx);
- }
-
- getRows = (displayStart, displayEnd) => {
- this._currentRowsRange = { start: displayStart, end: displayEnd };
- if (Array.isArray(this.props.rowGetter)) {
- return this.props.rowGetter.slice(displayStart, displayEnd);
- }
- let rows = [];
- let i = displayStart;
- while (i < displayEnd) {
- let row = this.props.rowGetter(i);
- let subRowDetails = {};
- if (this.props.getSubRowDetails) {
- subRowDetails = this.props.getSubRowDetails(row);
- }
- rows.push({ row, subRowDetails });
- i++;
- }
- return rows;
- };
-
- // getScrollbarWidth = () => {
- // // Get the scrollbar width
- // const scrollbarWidth = this.canvas.offsetWidth - this.canvas.clientWidth;
- // return scrollbarWidth;
- // };
-
- getScroll = () => {
- const { scrollTop, scrollLeft } = this.canvas;
- return { scrollTop, scrollLeft };
- };
-
- isRowSelected = (idx, row) => {
- // Use selectedRows if set
- if (this.props.selectedRows !== null) {
- let selectedRows = this.props.selectedRows.filter(r => {
- let rowKeyValue = row.get ? row.get(this.props.rowKey) : row[this.props.rowKey];
- return r[this.props.rowKey] === rowKeyValue;
- });
- return selectedRows.length > 0 && selectedRows[0].isSelected;
- }
-
- // Else use new rowSelection props
- if (this.props.rowSelection) {
- let { keys, indexes, isSelectedKey } = this.props.rowSelection;
- return rowUtils.isRowSelected(keys, indexes, isSelectedKey, row, idx);
- }
-
- return false;
- };
-
- setScrollLeft = (scrollLeft) => {
- if (this._currentRowsLength !== 0) {
- if (!this.rows) return;
- for (let i = 0, len = this._currentRowsLength; i < len; i++) {
- if (this.rows[i]) {
- let row = this.getRowByRef(i);
- if (row && row.setScrollLeft) {
- row.setScrollLeft(scrollLeft);
- }
- }
- }
- }
- };
-
- getRowByRef = (i) => {
- // check if wrapped with React DND drop target
- let wrappedRow = this.rows[i].getDecoratedComponentInstance ? this.rows[i].getDecoratedComponentInstance(i) : null;
- if (wrappedRow) {
- return wrappedRow.row;
- }
- return this.rows[i];
- };
-
- setCanvasRef = (canvas) => {
- // It is important to define ref callback as a bound method
- // https://reactjs.org/docs/refs-and-the-dom.html#caveats-with-callback-refs
- this.canvas = canvas;
- };
-
- renderRow = (props) => {
- let row = props.row;
- if (row.__metaData && row.__metaData.getRowRenderer) {
- return row.__metaData.getRowRenderer(this.props, props.idx);
- }
- if (row.__metaData && row.__metaData.isGroup) {
- return ( );
- }
- let RowsRenderer = this.props.rowRenderer;
- if (typeof RowsRenderer === 'function') {
- return ;
- }
-
- if (React.isValidElement(this.props.rowRenderer)) {
- return React.cloneElement(this.props.rowRenderer, props);
- }
- };
-
- renderPlaceholder = (key, height) => {
- // just renders empty cells
- // if we wanted to show gridlines, we'd need classes and position as with renderScrollingPlaceholder
- return (
- {
- this.props.columns.map(
- (column, idx) =>
- )
- }
-
- );
- };
-
- render() {
- const { displayStart, displayEnd, cellMetaData, columns, colDisplayStart, colDisplayEnd, colVisibleStart, colVisibleEnd, expandedRows, rowHeight, rowsCount, width, height, rowGetter } = this.props;
-
- const rows = this.getRows(displayStart, displayEnd)
- .map((r, idx) => this.renderRow({
- key: `row-${displayStart + idx}`,
- ref: (node) => this.rows[idx] = node,
- idx: displayStart + idx,
- visibleStart: this.props.visibleStart,
- visibleEnd: this.props.visibleEnd,
- row: r.row,
- height: rowHeight,
- onMouseOver: this.onMouseOver,
- columns,
- isSelected: this.isRowSelected(displayStart + idx, r.row, displayStart, displayEnd),
- expandedRows,
- cellMetaData,
- subRowDetails: r.subRowDetails,
- colVisibleStart,
- colVisibleEnd,
- colDisplayStart,
- colDisplayEnd,
- isScrolling: this.props.isScrolling
- }));
-
- this._currentRowsLength = rows.length;
-
- if (displayStart > 0) {
- rows.unshift(this.renderPlaceholder('top', displayStart * rowHeight));
- }
-
- if (rowsCount - displayEnd > 0) {
- rows.push(
- this.renderPlaceholder('bottom', (rowsCount - displayEnd) * rowHeight));
- }
-
- const style = {
- position: 'absolute',
- top: 0,
- left: 0,
- overflowX: 'auto',
- overflowY: 'scroll',
- width: this.props.totalWidth,
- height
- };
-
- return (
-
-
-
-
- );
- }
-}
-
-module.exports = Canvas;
+import React from 'react';
+import ReactDOM from 'react-dom';
+import PropTypes from 'prop-types';
+import Row from './Row';
+import cellMetaDataShape from './PropTypeShapes/CellMetaDataShape';
+import * as rowUtils from './RowUtils';
+import RowGroup, { DefaultRowGroupRenderer } from './RowGroup';
+import { InteractionMasks } from './masks';
+import { getColumnScrollPosition } from './utils/canvasUtils';
+import { EventTypes } from './constants';
+require('../../../themes/react-data-grid-core.css');
+
+class Canvas extends React.PureComponent {
+
+ static propTypes = {
+ rowRenderer: PropTypes.oneOfType([PropTypes.func, PropTypes.element]),
+ rowHeight: PropTypes.number.isRequired,
+ height: PropTypes.number.isRequired,
+ width: PropTypes.number,
+ totalWidth: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
+ style: PropTypes.string,
+ className: PropTypes.string,
+ rowOverscanStartIdx: PropTypes.number.isRequired,
+ rowOverscanEndIdx: PropTypes.number.isRequired,
+ rowVisibleStartIdx: PropTypes.number.isRequired,
+ rowVisibleEndIdx: PropTypes.number.isRequired,
+ colVisibleStartIdx: PropTypes.number.isRequired,
+ colVisibleEndIdx: PropTypes.number.isRequired,
+ colOverscanStartIdx: PropTypes.number.isRequired,
+ colOverscanEndIdx: PropTypes.number.isRequired,
+ rowsCount: PropTypes.number.isRequired,
+ rowGetter: PropTypes.oneOfType([
+ PropTypes.func.isRequired,
+ PropTypes.array.isRequired
+ ]),
+ expandedRows: PropTypes.array,
+ onRows: PropTypes.func,
+ onScroll: PropTypes.func,
+ columns: PropTypes.oneOfType([PropTypes.object, PropTypes.array]).isRequired,
+ cellMetaData: PropTypes.shape(cellMetaDataShape).isRequired,
+ selectedRows: PropTypes.array,
+ rowKey: PropTypes.string,
+ rowScrollTimeout: PropTypes.number,
+ scrollToRowIndex: PropTypes.number,
+ contextMenu: PropTypes.element,
+ getSubRowDetails: PropTypes.func,
+ rowSelection: PropTypes.oneOfType([
+ PropTypes.shape({
+ indexes: PropTypes.arrayOf(PropTypes.number).isRequired
+ }),
+ PropTypes.shape({
+ isSelectedKey: PropTypes.string.isRequired
+ }),
+ PropTypes.shape({
+ keys: PropTypes.shape({
+ values: PropTypes.array.isRequired,
+ rowKey: PropTypes.string.isRequired
+ }).isRequired
+ })
+ ]),
+ rowGroupRenderer: PropTypes.func,
+ isScrolling: PropTypes.bool,
+ length: PropTypes.number,
+ enableCellSelect: PropTypes.bool.isRequired,
+ enableCellAutoFocus: PropTypes.bool.isRequired,
+ cellNavigationMode: PropTypes.string.isRequired,
+ eventBus: PropTypes.object.isRequired,
+ onCheckCellIsEditable: PropTypes.func,
+ onCellCopyPaste: PropTypes.func,
+ onGridRowsUpdated: PropTypes.func.isRequired,
+ onDragHandleDoubleClick: PropTypes.func.isRequired,
+ onCellSelected: PropTypes.func,
+ onCellDeSelected: PropTypes.func,
+ onCellRangeSelectionStarted: PropTypes.func,
+ onCellRangeSelectionUpdated: PropTypes.func,
+ onCellRangeSelectionCompleted: PropTypes.func,
+ onCommit: PropTypes.func.isRequired
+ };
+
+ static defaultProps = {
+ rowRenderer: Row,
+ onRows: () => { },
+ selectedRows: [],
+ rowScrollTimeout: 0,
+ scrollToRowIndex: 0,
+ RowsContainer: ({ children }) => children,
+ rowGroupRenderer: DefaultRowGroupRenderer
+ };
+
+ state = {
+ scrollingTimeout: null
+ };
+
+ rows = [];
+ _currentRowsRange = { start: 0, end: 0 };
+ _scroll = { scrollTop: 0, scrollLeft: 0 };
+
+ componentDidMount() {
+ this.unsubscribeScrollToColumn = this.props.eventBus.subscribe(EventTypes.SCROLL_TO_COLUMN, this.scrollToColumn);
+ this.onRows();
+ }
+
+ componentWillUnmount() {
+ this._currentRowsRange = { start: 0, end: 0 };
+ this._scroll = { scrollTop: 0, scrollLeft: 0 };
+ this.rows = [];
+ this.unsubscribeScrollToColumn();
+ }
+
+ componentDidUpdate(prevProps) {
+ const { scrollToRowIndex } = this.props;
+ if (prevProps.scrollToRowIndex !== scrollToRowIndex && scrollToRowIndex !== 0) {
+ this.scrollToRow(scrollToRowIndex);
+ }
+ this.onRows();
+ }
+
+ onRows = () => {
+ if (this._currentRowsRange !== { start: 0, end: 0 }) {
+ this.props.onRows(this._currentRowsRange);
+ this._currentRowsRange = { start: 0, end: 0 };
+ }
+ };
+
+ scrollToRow = (scrollToRowIndex) => {
+ const { rowHeight, rowsCount, height } = this.props;
+ this.canvas.scrollTop = Math.min(
+ scrollToRowIndex * rowHeight,
+ rowsCount * rowHeight - height
+ );
+ };
+
+ onFocusInteractionMask = (focus) => {
+ const { scrollTop, scrollLeft } = this._scroll;
+ focus();
+ if (this.canvas) {
+ this.canvas.scrollTop = scrollTop;
+ this.canvas.scrollLeft = scrollLeft;
+ }
+ };
+
+ onScroll = (e) => {
+ if (this.canvas !== e.target) {
+ return;
+ }
+ const { scrollLeft, scrollTop } = e.target;
+ const scroll = { scrollTop, scrollLeft };
+ this._scroll = scroll;
+ this.props.onScroll(scroll);
+ };
+
+ getClientScrollTopOffset= (node) => {
+ const { rowHeight } = this.props;
+ const scrollVariation = node.scrollTop % rowHeight;
+ return scrollVariation > 0 ? rowHeight - scrollVariation : 0;
+ }
+
+ onHitBottomCanvas = () => {
+ const { rowHeight } = this.props;
+ const node = this.canvas;
+ node.scrollTop += rowHeight + this.getClientScrollTopOffset(node);
+ }
+
+ onHitTopCanvas = () => {
+ const { rowHeight } = this.props;
+ const node = this.canvas;
+ node.scrollTop -= (rowHeight - this.getClientScrollTopOffset(node));
+ }
+
+ scrollToColumn = (idx) => {
+ const { scrollLeft, clientWidth } = this.canvas;
+ const newScrollLeft = getColumnScrollPosition(this.props.columns, idx, scrollLeft, clientWidth);
+
+ if (newScrollLeft != null) {
+ this.canvas.scrollLeft = scrollLeft + newScrollLeft;
+ }
+ }
+
+ onHitLeftCanvas = ({ idx }) => {
+ this.scrollToColumn(idx);
+ }
+
+ onHitRightCanvas = ({ idx }) => {
+ this.scrollToColumn(idx);
+ }
+
+ getRows = (rowOverscanStartIdx, rowOverscanEndIdx) => {
+ this._currentRowsRange = { start: rowOverscanStartIdx, end: rowOverscanEndIdx };
+ if (Array.isArray(this.props.rowGetter)) {
+ return this.props.rowGetter.slice(rowOverscanStartIdx, rowOverscanEndIdx);
+ }
+ let rows = [];
+ let i = rowOverscanStartIdx;
+ while (i < rowOverscanEndIdx) {
+ let row = this.props.rowGetter(i);
+ let subRowDetails = {};
+ if (this.props.getSubRowDetails) {
+ subRowDetails = this.props.getSubRowDetails(row);
+ }
+ rows.push({ row, subRowDetails });
+ i++;
+ }
+ return rows;
+ };
+
+ getScroll = () => {
+ const { scrollTop, scrollLeft } = this.canvas;
+ return { scrollTop, scrollLeft };
+ };
+
+ isRowSelected = (idx, row) => {
+ // Use selectedRows if set
+ if (this.props.selectedRows !== null) {
+ let selectedRows = this.props.selectedRows.filter(r => {
+ let rowKeyValue = row.get ? row.get(this.props.rowKey) : row[this.props.rowKey];
+ return r[this.props.rowKey] === rowKeyValue;
+ });
+ return selectedRows.length > 0 && selectedRows[0].isSelected;
+ }
+
+ // Else use new rowSelection props
+ if (this.props.rowSelection) {
+ let { keys, indexes, isSelectedKey } = this.props.rowSelection;
+ return rowUtils.isRowSelected(keys, indexes, isSelectedKey, row, idx);
+ }
+
+ return false;
+ };
+
+ setScrollLeft = (scrollLeft) => {
+ this.rows.forEach((r, idx) => {
+ if (r) {
+ let row = this.getRowByRef(idx);
+ if (row && row.setScrollLeft) {
+ row.setScrollLeft(scrollLeft);
+ }
+ }
+ });
+ };
+
+ getRowByRef = (i) => {
+ // check if wrapped with React DND drop target
+ let wrappedRow = this.rows[i] && this.rows[i].getDecoratedComponentInstance ? this.rows[i].getDecoratedComponentInstance(i) : null;
+ if (wrappedRow) {
+ return wrappedRow.row;
+ }
+ return this.rows[i];
+ };
+
+ getSelectedRowTop = (rowIdx) => {
+ const row = this.getRowByRef(rowIdx);
+ if (row) {
+ const node = ReactDOM.findDOMNode(row);
+ return node && node.offsetTop;
+ }
+ return this.props.rowHeight * rowIdx;
+ }
+
+ getSelectedRowHeight = (rowIdx) => {
+ const row = this.getRowByRef(rowIdx);
+ if (row) {
+ const node = ReactDOM.findDOMNode(row);
+ return node && node.clientHeight > 0 ? node.clientHeight : this.props.rowHeight;
+ }
+ return this.props.rowHeight;
+ }
+
+ setCanvasRef = (canvas) => {
+ // It is important to define ref callback as a bound method
+ // https://reactjs.org/docs/refs-and-the-dom.html#caveats-with-callback-refs
+ this.canvas = canvas;
+ };
+
+ setRowRef = idx => row => {
+ this.rows[idx] = row;
+ };
+
+ renderRow = (props) => {
+ let row = props.row;
+ if (row.__metaData && row.__metaData.getRowRenderer) {
+ return row.__metaData.getRowRenderer(this.props, props.idx);
+ }
+ if (row.__metaData && row.__metaData.isGroup) {
+ return ( );
+ }
+ let RowsRenderer = this.props.rowRenderer;
+ if (typeof RowsRenderer === 'function') {
+ return ;
+ }
+
+ if (React.isValidElement(this.props.rowRenderer)) {
+ return React.cloneElement(this.props.rowRenderer, props);
+ }
+ };
+
+ renderPlaceholder = (key, height) => {
+ // just renders empty cells
+ // if we wanted to show gridlines, we'd need classes and position as with renderScrollingPlaceholder
+ return (
+ {
+ this.props.columns.map(
+ (column, idx) =>
+ )
+ }
+
+ );
+ };
+
+ render() {
+ const { rowOverscanStartIdx, rowOverscanEndIdx, cellMetaData, columns, colOverscanStartIdx, colOverscanEndIdx, colVisibleStartIdx, colVisibleEndIdx, lastFrozenColumnIndex, expandedRows, rowHeight, rowsCount, totalColumnWidth, totalWidth, height, rowGetter, RowsContainer, contextMenu } = this.props;
+
+ const rows = this.getRows(rowOverscanStartIdx, rowOverscanEndIdx)
+ .map((r, idx) => {
+ const rowIdx = rowOverscanStartIdx + idx;
+ const key = `row-${rowIdx}`;
+ return (this.renderRow({
+ key,
+ ref: this.setRowRef(rowIdx),
+ idx: rowIdx,
+ rowVisibleStartIdx: this.props.rowVisibleStartIdx,
+ rowVisibleEndIdx: this.props.rowVisibleEndIdx,
+ row: r.row,
+ height: rowHeight,
+ onMouseOver: this.onMouseOver,
+ columns,
+ isSelected: this.isRowSelected(rowIdx, r.row, rowOverscanStartIdx, rowOverscanEndIdx),
+ expandedRows,
+ cellMetaData,
+ subRowDetails: r.subRowDetails,
+ colVisibleStartIdx,
+ colVisibleEndIdx,
+ colOverscanStartIdx,
+ colOverscanEndIdx,
+ lastFrozenColumnIndex,
+ isScrolling: this.props.isScrolling,
+ scrollLeft: this._scroll.scrollLeft
+ })
+ );
+ });
+
+ if (rowOverscanStartIdx > 0) {
+ rows.unshift(this.renderPlaceholder('top', rowOverscanStartIdx * rowHeight));
+ }
+
+ if (rowsCount - rowOverscanEndIdx > 0) {
+ rows.push(
+ this.renderPlaceholder('bottom', (rowsCount - rowOverscanEndIdx) * rowHeight));
+ }
+
+ const style = {
+ position: 'absolute',
+ top: 0,
+ left: 0,
+ overflowX: 'auto',
+ overflowY: 'scroll',
+ width: totalWidth,
+ height
+ };
+
+ return (
+
+
+
+
+ );
+ }
+}
+
+module.exports = Canvas;
diff --git a/packages/react-data-grid/src/Cell.js b/packages/react-data-grid/src/Cell.js
index ef512c0e44..ffc89c92bd 100644
--- a/packages/react-data-grid/src/Cell.js
+++ b/packages/react-data-grid/src/Cell.js
@@ -9,12 +9,13 @@ const createObjectWithProperties = require('./createObjectWithProperties');
import CellAction from './CellAction';
import CellExpand from './CellExpand';
import ChildRowDeleteButton from './ChildRowDeleteButton';
+import columnUtils from './ColumnUtils';
require('../../../themes/react-data-grid-cell.css');
// The list of the propTypes that we want to include in the Cell div
const knownDivPropertyKeys = ['height', 'value'];
-class Cell extends React.Component {
+class Cell extends React.PureComponent {
static propTypes = {
rowIdx: PropTypes.number.isRequired,
idx: PropTypes.number.isRequired,
@@ -35,11 +36,13 @@ class Cell extends React.Component {
forceUpdate: PropTypes.bool,
expandableOptions: PropTypes.object.isRequired,
tooltip: PropTypes.string,
+ isScrolling: PropTypes.bool,
isCellValueChanging: PropTypes.func,
children: PropTypes.oneOfType([
PropTypes.arrayOf(PropTypes.node),
PropTypes.node
- ])
+ ]),
+ scrollLeft: PropTypes.number.isRequired
};
static defaultProps = {
@@ -56,12 +59,16 @@ class Cell extends React.Component {
componentWillReceiveProps(nextProps) {
this.setState({
isCellValueChanging: this.props.isCellValueChanging(this.props.value, nextProps.value),
- isLockChanging: this.props.column.locked !== nextProps.column.locked
+ isLockChanging: columnUtils.isFrozen(this.props.column) !== columnUtils.isFrozen(nextProps.column)
});
}
+ componentDidMount() {
+ this.checkScroll();
+ }
+
componentDidUpdate() {
- if (this.state.isLockChanging && !this.props.column.locked) {
+ if (this.state.isLockChanging && !columnUtils.isFrozen(this.props.column)) {
this.removeScroll();
}
}
@@ -149,24 +156,28 @@ class Cell extends React.Component {
getFormatterDependencies = () => {
// convention based method to get corresponding Id or Name of any Name or Id property
if (typeof this.props.column.getRowMetaData === 'function') {
+ if (process.env.NODE_ENV === 'development') {
+ console.warn('getRowMetaData for formatters is deprecated and will be removed in a future version of ReactDataGrid. Instead access row prop of formatter');
+ }
return this.props.column.getRowMetaData(this.getRowData(), this.props.column);
}
};
getCellClass = () => {
+ const {idx, lastFrozenColumnIndex} = this.props;
let className = joinClasses(
this.props.column.cellClass,
'react-grid-Cell',
this.props.className,
- this.props.column.locked ? 'react-grid-Cell--locked' : null
+ columnUtils.isFrozen(this.props.column) ? 'react-grid-Cell--frozen' : null,
+ lastFrozenColumnIndex === idx ? 'rdg-last--frozen' : null
);
let extraClasses = joinClasses({
'row-selected': this.props.isRowSelected,
editing: this.isEditorEnabled(),
'cell-tooltip': this.props.tooltip ? true : false,
'rdg-child-cell': this.props.expandableOptions && this.props.expandableOptions.subRowDetails && this.props.expandableOptions.treeDepth > 0,
- 'last-column': this.props.column.isLastColumn,
- 'will-change': this.props.isSelected || this.props.wasPreviouslySelected
+ 'last-column': this.props.column.isLastColumn
});
return joinClasses(className, extraClasses);
};
@@ -181,15 +192,24 @@ class Cell extends React.Component {
return this.props.isEditorEnabled === true;
};
- setScrollLeft = (scrollLeft) => {
+ checkScroll() {
+ const {scrollLeft, column} = this.props;
const node = this.node;
+ if (columnUtils.isFrozen(column) && node && node.style.transform != null) {
+ this.setScrollLeft(scrollLeft);
+ }
+ }
+
+ setScrollLeft = (scrollLeft: number) => {
+ let node = this.node;
if (node) {
- const transform = `translate3d(${scrollLeft}px, 0px, 0px)`;
+ let transform = `translate3d(${scrollLeft}px, 0px, 0px)`;
node.style.webkitTransform = transform;
node.style.transform = transform;
}
};
+
removeScroll = () => {
const node = this.node;
if (node) {
@@ -272,14 +292,18 @@ class Cell extends React.Component {
return null;
}
+ setCellRef = (node) => {
+ this.node = node;
+ };
+
+
renderCellContent = (props) => {
let CellContent;
let Formatter = this.getFormatter();
if (React.isValidElement(Formatter)) {
- props.dependentValues = this.getFormatterDependencies();
- CellContent = React.cloneElement(Formatter, props);
+ CellContent = React.cloneElement(Formatter, {...props, dependentValues: this.getFormatterDependencies(), row: this.getRowData()});
} else if (isFunction(Formatter)) {
- CellContent = ;
+ CellContent = ;
} else {
CellContent = ;
}
@@ -310,12 +334,13 @@ class Cell extends React.Component {
let className = this.getCellClass();
const cellActionButtons = this.getCellActions();
-
+ const {value, column, rowIdx, isExpanded, isScrolling} = this.props;
const cellContent = this.props.children || this.renderCellContent({
- value: this.props.value,
- column: this.props.column,
- rowIdx: this.props.rowIdx,
- isExpanded: this.props.isExpanded
+ value,
+ column,
+ rowIdx,
+ isExpanded,
+ isScrolling
});
let events = this.getEvents();
@@ -327,7 +352,7 @@ class Cell extends React.Component {
className={className}
style={style}
{...events}
- ref={(node) => { this.node = node; }}
+ ref={this.setCellRef}
>
{cellActionButtons}
{cellContent}
diff --git a/packages/react-data-grid/src/ColumnMetrics.js b/packages/react-data-grid/src/ColumnMetrics.js
index 85ee7e3e44..a62387c7af 100644
--- a/packages/react-data-grid/src/ColumnMetrics.js
+++ b/packages/react-data-grid/src/ColumnMetrics.js
@@ -1,8 +1,8 @@
const shallowCloneObject = require('./shallowCloneObject');
const sameColumn = require('./ColumnComparer');
const ColumnUtils = require('./ColumnUtils');
-const getScrollbarSize = require('./getScrollbarSize');
-const isColumnsImmutable = require('./utils/isColumnsImmutable');
+const getScrollbarSize = require('./getScrollbarSize');
+const isColumnsImmutable = require('./utils/isColumnsImmutable');
type Column = {
key: string;
@@ -11,9 +11,9 @@ type Column = {
};
type ColumnMetricsType = {
- columns: Array;
- totalWidth: number;
- minColumnWidth: number;
+ columns: Array;
+ totalWidth: number;
+ minColumnWidth: number;
};
function setColumnWidths(columns, totalWidth) {
@@ -57,20 +57,15 @@ function setColumnOffsets(columns) {
});
}
-/**
- * Update column metrics calculation.
- *
- * @param {ColumnMetricsType} metrics
- */
-function recalculate(metrics: ColumnMetricsType): ColumnMetricsType {
- // compute width for columns which specify width
+const getTotalColumnWidth = columns => columns.reduce((acc, c) => acc + c.width, 0);
+
+
+function recalculate(metrics) {
+ // compute width for columns which specify width
let columns = setColumnWidths(metrics.columns, metrics.totalWidth);
- let unallocatedWidth = columns.filter(c => c.width).reduce((w, column) => {
- return w - column.width;
- }, metrics.totalWidth);
+ let unallocatedWidth = columns.filter(c => c.width).reduce((w, column) => w - column.width, metrics.totalWidth);
unallocatedWidth -= getScrollbarSize();
-
let width = columns.filter(c => c.width).reduce((w, column) => {
return w + column.width;
}, 0);
@@ -80,11 +75,17 @@ function recalculate(metrics: ColumnMetricsType): ColumnMetricsType {
// compute left offset
columns = setColumnOffsets(columns);
-
+ const frozenColumns = columns.filter(c => ColumnUtils.isFrozen(c));
+ const nonFrozenColumns = columns.filter(c => !ColumnUtils.isFrozen(c));
+ columns = frozenColumns.concat(nonFrozenColumns).map((c, i) => {
+ c.idx = i;
+ return c;
+ });
return {
columns,
width,
totalWidth: metrics.totalWidth,
+ totalColumnWidth: getTotalColumnWidth(columns),
minColumnWidth: metrics.minColumnWidth
};
}
@@ -117,8 +118,8 @@ function compareEachColumn(prevColumns: Array, nextColumns: Array {
- if (this._scrollLeft !== props.scrollLeft) {
- this._scrollLeft = props.scrollLeft;
+ areFrozenColumnsScrolledLeft(scrollLeft) {
+ return scrollLeft > 0 && this.props.columns.some(c => columnUtils.isFrozen(c));
+ }
+
+ onScroll = (scrollState) => {
+ this.props.onScroll(scrollState);
+ const {scrollLeft} = scrollState;
+ if (this._scrollLeft !== scrollLeft || this.areFrozenColumnsScrolledLeft(scrollLeft)) {
+ this._scrollLeft = scrollLeft;
this._onScroll();
}
};
- // TODO: why is this needed?
- // onHeaderScroll = (e) => {
- // let scrollLeft = e.target.scrollLeft;
- // if (this._scrollLeft !== scrollLeft) {
- // this._scrollLeft = scrollLeft;
- // this.header.setScrollLeft(scrollLeft);
- // let canvas = ReactDOM.findDOMNode(this.viewport.canvas);
- // canvas.scrollLeft = scrollLeft;
- // this.viewport.canvas.setScrollLeft(scrollLeft);
- // }
- // };
-
componentDidMount() {
this._scrollLeft = this.viewport ? this.viewport.getScroll().scrollLeft : 0;
this._onScroll();
@@ -166,6 +164,7 @@ class Grid extends React.Component {
onKeyUp={this.props.onViewportKeyup}
>
:
diff --git a/packages/react-data-grid/src/HeaderCell.js b/packages/react-data-grid/src/HeaderCell.js
index fe1c0bc54d..eeb2244af5 100644
--- a/packages/react-data-grid/src/HeaderCell.js
+++ b/packages/react-data-grid/src/HeaderCell.js
@@ -2,6 +2,7 @@ const React = require('react');
const ReactDOM = require('react-dom');
const joinClasses = require('classnames');
const ExcelColumn = require('./PropTypeShapes/ExcelColumn');
+import columnUtils from './ColumnUtils';
const ResizeHandle = require('./ResizeHandle');
require('../../../themes/react-data-grid-header.css');
@@ -56,15 +57,16 @@ class HeaderCell extends React.Component {
return right - left;
};
- getCell = (): ReactComponent => {
- if (React.isValidElement(this.props.renderer)) {
+ getCell = ()=> {
+ const {height, column, renderer} = this.props;
+ if (React.isValidElement(renderer)) {
// if it is a string, it's an HTML element, and column is not a valid property, so only pass height
if (typeof this.props.renderer.type === 'string') {
- return React.cloneElement(this.props.renderer, {height: this.props.height});
+ return React.cloneElement(renderer, {height});
}
- return React.cloneElement(this.props.renderer, {column: this.props.column, height: this.props.height});
+ return React.cloneElement(renderer, {column, height});
}
- return this.props.renderer({column: this.props.column});
+ return this.props.renderer({column});
};
getStyle = (): {width:number; left: number; display: string; position: string; overflow: string; height: number; margin: number; textOverflow: string; whiteSpace: string } => {
@@ -107,7 +109,7 @@ class HeaderCell extends React.Component {
let className = joinClasses({
'react-grid-HeaderCell': true,
'react-grid-HeaderCell--resizing': this.state.resizing,
- 'react-grid-HeaderCell--locked': this.props.column.locked
+ 'react-grid-HeaderCell--frozen': columnUtils.isFrozen(this.props.column)
});
className = joinClasses(className, this.props.className, this.props.column.cellClass);
let cell = this.getCell();
diff --git a/packages/react-data-grid/src/HeaderRow.js b/packages/react-data-grid/src/HeaderRow.js
index e9448ea1b8..e84f428d33 100644
--- a/packages/react-data-grid/src/HeaderRow.js
+++ b/packages/react-data-grid/src/HeaderRow.js
@@ -2,7 +2,6 @@ const React = require('react');
const shallowEqual = require('shallowequal');
const BaseHeaderCell = require('./HeaderCell');
const getScrollbarSize = require('./getScrollbarSize');
-const ExcelColumn = require('./PropTypeShapes/ExcelColumn');
const columnUtils = require('./ColumnUtils');
const SortableHeaderCell = require('./cells/headerCells/SortableHeaderCell');
const FilterableHeaderCell = require('./cells/headerCells/FilterableHeaderCell');
@@ -118,7 +117,7 @@ class HeaderRow extends React.Component {
getCells = (): Array
=> {
let cells = [];
- let lockedCells = [];
+ let frozenCells = [];
for (let i = 0, len = columnUtils.getSize(this.props.columns); i < len; i++) {
let column = Object.assign({ rowType: this.props.rowType }, columnUtils.getColumn(this.props.columns, i));
let _renderer = this.getHeaderRenderer(column);
@@ -139,19 +138,19 @@ class HeaderRow extends React.Component {
onHeaderDrop={this.props.onHeaderDrop}
/>
);
- if (column.locked) {
- lockedCells.push(cell);
+ if (columnUtils.isFrozen(column)) {
+ frozenCells.push(cell);
} else {
cells.push(cell);
}
}
- return cells.concat(lockedCells);
+ return cells.concat(frozenCells);
};
setScrollLeft = (scrollLeft: number) => {
this.props.columns.forEach( (column, i) => {
- if (column.locked) {
+ if (columnUtils.isFrozen(column)) {
this.cells[i].setScrollLeft(scrollLeft);
} else {
if (this.cells[i] && this.cells[i].removeScroll) {
diff --git a/packages/react-data-grid/src/ReactDataGrid.js b/packages/react-data-grid/src/ReactDataGrid.js
index e57eb598a2..60b896b956 100644
--- a/packages/react-data-grid/src/ReactDataGrid.js
+++ b/packages/react-data-grid/src/ReactDataGrid.js
@@ -1,5 +1,6 @@
const React = require('react');
import PropTypes from 'prop-types';
+import {deprecate} from 'react-is-deprecated';
const BaseGrid = require('./Grid');
const CheckboxEditor = require('./editors/CheckboxEditor');
const RowUtils = require('./RowUtils');
@@ -20,6 +21,8 @@ if (!Object.assign) {
Object.assign = require('object-assign');
}
+const deprecationWarning = (propName, alternative) => `${propName} has been deprecated and will be removed in a future version. Please use ${alternative} instead`;
+
class ReactDataGrid extends React.Component {
static displayName = 'ReactDataGrid';
@@ -29,26 +32,26 @@ class ReactDataGrid extends React.Component {
headerFiltersHeight: PropTypes.number,
minHeight: PropTypes.number.isRequired,
minWidth: PropTypes.number,
- enableRowSelect: PropTypes.oneOfType([PropTypes.bool, PropTypes.string]),
- onRowUpdated: PropTypes.func,
+ enableRowSelect: deprecate(PropTypes.func, deprecationWarning('enableRowSelect', 'rowSelection')),
+ onRowUpdated: deprecate(PropTypes.func, deprecationWarning('onRowUpdated', 'onGridRowsUpdated')),
rowGetter: PropTypes.func.isRequired,
rowsCount: PropTypes.number.isRequired,
toolbar: PropTypes.element,
enableCellSelect: PropTypes.bool,
columns: PropTypes.oneOfType([PropTypes.object, PropTypes.array]).isRequired,
onFilter: PropTypes.func,
- onCellCopyPaste: PropTypes.func,
- onCellsDragged: PropTypes.func,
+ onCellCopyPaste: deprecate(PropTypes.func, deprecationWarning('onCellCopyPaste', 'onGridRowsUpdated')),
+ onCellsDragged: deprecate(PropTypes.func, deprecationWarning('onCellsDragged', 'onGridRowsUpdated')),
getCellActions: PropTypes.func,
onAddFilter: PropTypes.func,
onGridSort: PropTypes.func,
sortColumn: PropTypes.string,
sortDirection: PropTypes.oneOf(Object.keys(DEFINE_SORT)),
- onDragHandleDoubleClick: PropTypes.func,
+ onDragHandleDoubleClick: deprecate(PropTypes.func, deprecationWarning('onDragHandleDoubleClick', 'onGridRowsUpdated')),
onGridRowsUpdated: PropTypes.func,
onRowSelect: PropTypes.func,
rowKey: PropTypes.string,
- rowScrollTimeout: PropTypes.number,
+ rowScrollTimeout: deprecate(PropTypes.number),
scrollToRowIndex: PropTypes.number,
onClearFilters: PropTypes.func,
contextMenu: PropTypes.element,
@@ -101,7 +104,8 @@ class ReactDataGrid extends React.Component {
selectAllRenderer: PropTypes.object,
minColumnWidth: PropTypes.number,
columnEquality: PropTypes.func,
- onColumnResize: PropTypes.func
+ onColumnResize: PropTypes.func,
+ onScroll: PropTypes.func
};
static defaultProps = {
@@ -115,10 +119,10 @@ class ReactDataGrid extends React.Component {
scrollToRowIndex: 0,
cellNavigationMode: CellNavigationMode.NONE,
overScan: {
- colsStart: 5,
- colsEnd: 5,
- rowsStart: 5,
- rowsEnd: 5
+ colsStart: 2,
+ colsEnd: 2,
+ rowsStart: 2,
+ rowsEnd: 2
},
enableCellAutoFocus: true,
onBeforeEdit: () => {},
@@ -142,7 +146,9 @@ class ReactDataGrid extends React.Component {
componentDidMount() {
this._mounted = true;
window.addEventListener('resize', this.metricsUpdated);
- window.addEventListener('mouseup', this.onWindowMouseUp);
+ if (this.props.cellRangeSelection) {
+ window.addEventListener('mouseup', this.onWindowMouseUp);
+ }
this.metricsUpdated();
}
@@ -353,6 +359,11 @@ class ReactDataGrid extends React.Component {
onGridRowsUpdated = (cellKey, fromRow, toRow, updated, action, originRow) => {
const { rowGetter, rowKey, onGridRowsUpdated } = this.props;
+ // Deprecated prop
+ // to be removed in next major release
+ if (isFunction(this.props.onRowUpdated)) {
+ this.props.onRowUpdated({updated, rowIdx: fromRow, cellKey, value: updated[cellKey]});
+ }
if (!isFunction(onGridRowsUpdated)) {
return;
}
@@ -373,6 +384,12 @@ class ReactDataGrid extends React.Component {
this.onGridRowsUpdated(commit.cellKey, targetRow, targetRow, commit.updated, AppConstants.UpdateActions.CELL_UPDATE);
};
+ onScroll = (scrollState) => {
+ if (isFunction(this.props.onScroll)) {
+ this.props.onScroll(scrollState);
+ }
+ }
+
handleSort = (columnKey, direction) => {
this.setState({sortDirection: direction, sortColumn: columnKey}, () => {
this.props.onGridSort(columnKey, direction);
@@ -614,7 +631,7 @@ class ReactDataGrid extends React.Component {
filterable: false,
headerRenderer: headerRenderer,
width: 60,
- locked: true,
+ frozen: true,
getRowMetaData: (rowData) => rowData,
cellClass: this.props.rowActionsCell ? 'rdg-row-actions-cell' : ''
};
@@ -644,8 +661,6 @@ class ReactDataGrid extends React.Component {
const cellMetaData = {
rowKey: this.props.rowKey,
onCellClick: this.onCellClick,
- onCellMouseDown: this.onCellMouseDown,
- onCellMouseEnter: this.onCellMouseEnter,
onCellContextMenu: this.onCellContextMenu,
onCellDoubleClick: this.onCellDoubleClick,
onColumnEvent: this.onColumnEvent,
@@ -656,6 +671,10 @@ class ReactDataGrid extends React.Component {
onAddSubRow: this.props.onAddSubRow,
onDragEnter: this.handleDragEnter
};
+ if (this.props.cellRangeSelection) {
+ cellMetaData.onCellMouseDown = this.onCellMouseDown;
+ cellMetaData.onCellMouseEnter = this.onCellMouseEnter;
+ }
const toolbar = this.renderToolbar();
let containerWidth = this.props.minWidth || this.gridWidth();
@@ -715,6 +734,7 @@ class ReactDataGrid extends React.Component {
onCellRangeSelectionUpdated={this.props.cellRangeSelection && this.props.cellRangeSelection.onUpdate}
onCellRangeSelectionCompleted={this.props.cellRangeSelection && this.props.cellRangeSelection.onComplete}
onCommit={this.onCommit}
+ onScroll={this.onScroll}
/>
diff --git a/packages/react-data-grid/src/Row.js b/packages/react-data-grid/src/Row.js
index 24eaedd2c9..b3b4bce7bd 100644
--- a/packages/react-data-grid/src/Row.js
+++ b/packages/react-data-grid/src/Row.js
@@ -1,10 +1,11 @@
import rowComparer from './RowComparer';
-const React = require('react');
+import React from 'react';
import PropTypes from 'prop-types';
-const joinClasses = require('classnames');
+import joinClasses from 'classnames';
import Cell from './Cell';
-const cellMetaDataShape = require('./PropTypeShapes/CellMetaDataShape');
-const createObjectWithProperties = require('./createObjectWithProperties');
+import cellMetaDataShape from './PropTypeShapes/CellMetaDataShape';
+import createObjectWithProperties from './createObjectWithProperties';
+import columnUtils from './ColumnUtils';
require('../../../themes/react-data-grid-row.css');
// The list of the propTypes that we want to include in the Row div
@@ -26,11 +27,13 @@ class Row extends React.Component {
forceUpdate: PropTypes.bool,
subRowDetails: PropTypes.object,
isRowHovered: PropTypes.bool,
- colVisibleStart: PropTypes.number.isRequired,
- colVisibleEnd: PropTypes.number.isRequired,
- colDisplayStart: PropTypes.number.isRequired,
- colDisplayEnd: PropTypes.number.isRequired,
- isScrolling: PropTypes.bool.isRequired
+ colVisibleStartIdx: PropTypes.number.isRequired,
+ colVisibleEndIdx: PropTypes.number.isRequired,
+ colOverscanStartIdx: PropTypes.number.isRequired,
+ colOverscanEndIdx: PropTypes.number.isRequired,
+ isScrolling: PropTypes.bool.isRequired,
+ scrollLeft: PropTypes.number,
+ lastFrozenColumnIndex: PropTypes.number
};
static defaultProps = {
@@ -49,17 +52,16 @@ class Row extends React.Component {
onDragEnter({ overRowIdx: idx });
};
- handleDrop= (e) => {
+ handleDrop = (e) => {
e.preventDefault();
};
getCell = (column, i) => {
const CellRenderer = this.props.cellRenderer;
- const { idx, cellMetaData } = this.props;
+ const { idx, cellMetaData, isScrolling, row, isSelected, scrollLeft, lastFrozenColumnIndex } = this.props;
const { key, formatter } = column;
- const baseCellProps = { key: `${key}-${idx}`, idx: i, rowIdx: idx, height: this.getRowHeight(), column, cellMetaData };
+ const baseCellProps = { key: `${key}-${idx}`, idx: column.idx, rowIdx: idx, height: this.getRowHeight(), column, cellMetaData };
- const { row, isSelected } = this.props;
const cellProps = {
ref: (node) => {
this[key] = node;
@@ -69,31 +71,20 @@ class Row extends React.Component {
isRowSelected: isSelected,
expandableOptions: this.getExpandableOptions(key),
formatter,
- isScrolling: this.props.isScrolling
+ isScrolling,
+ scrollLeft,
+ lastFrozenColumnIndex
};
return ;
};
getCells = () => {
- let cells = [];
- let lockedCells = [];
- let lastColumnIdx = this.props.columns.size - 1;
- if (this.props.columns) {
- this.props.columns.forEach((column, i) => {
- if (i === lastColumnIdx) {
- column.isLastColumn = true;
- }
- let cell = this.getCell(column, i);
- if (column.locked) {
- lockedCells.push(cell);
- } else {
- cells.push(cell);
- }
- });
- }
-
- return cells.concat(lockedCells);
+ const { colOverscanStartIdx, colOverscanEndIdx, columns } = this.props;
+ const frozenColumns = columns.filter(c => columnUtils.isFrozen(c));
+ const nonFrozenColumnsToRender = columns.slice(colOverscanStartIdx, colOverscanEndIdx + 1).filter(c => !columnUtils.isFrozen(c));
+ return frozenColumns.concat(nonFrozenColumnsToRender)
+ .map(column => this.getCell(column));
};
getRowHeight = () => {
@@ -129,7 +120,7 @@ class Row extends React.Component {
setScrollLeft = (scrollLeft) => {
this.props.columns.forEach((column) => {
- if (column.locked) {
+ if (columnUtils.isFrozen(column)) {
if (!this[column.key]) return;
this[column.key].setScrollLeft(scrollLeft);
}
@@ -147,7 +138,8 @@ class Row extends React.Component {
{
'row-selected': this.props.isSelected
},
- this.props.extraClasses
+ this.props.extraClasses,
+ { 'rdg-scrolling': this.props.isScrolling }
);
let style = {
@@ -159,12 +151,12 @@ class Row extends React.Component {
let cells = this.getCells();
return (
+ >
{
React.isValidElement(this.props.row) ?
this.props.row : cells
diff --git a/packages/react-data-grid/src/RowComparer.js b/packages/react-data-grid/src/RowComparer.js
index 5abc0bfb94..af12b2234b 100644
--- a/packages/react-data-grid/src/RowComparer.js
+++ b/packages/react-data-grid/src/RowComparer.js
@@ -1,35 +1,14 @@
import ColumnMetrics from './ColumnMetrics';
-function doesRowContainSelectedCell(props) {
- let selected = props.cellMetaData.selected;
- if (selected && selected.rowIdx === props.idx) {
- return true;
- }
- return false;
-}
-
-function willRowBeDraggedOver(props) {
- let dragged = props.cellMetaData.dragged;
- return dragged != null && (dragged.rowIdx >= 0 || dragged.complete === true);
-}
-
-function hasRowBeenCopied(props) {
- let copied = props.cellMetaData.copied;
- return copied != null && copied.rowIdx === props.idx;
-}
-
export const shouldRowUpdate = (nextProps, currentProps) => {
return !(ColumnMetrics.sameColumns(currentProps.columns, nextProps.columns, ColumnMetrics.sameColumn)) ||
- doesRowContainSelectedCell(currentProps) ||
- doesRowContainSelectedCell(nextProps) ||
- willRowBeDraggedOver(nextProps) ||
nextProps.row !== currentProps.row ||
- currentProps.colDisplayStart !== nextProps.colDisplayStart ||
- currentProps.colDisplayEnd !== nextProps.colDisplayEnd ||
- currentProps.colVisibleStart !== nextProps.colVisibleStart ||
- currentProps.colVisibleEnd !== nextProps.colVisibleEnd ||
- hasRowBeenCopied(currentProps) ||
+ currentProps.colOverscanStartIdx !== nextProps.colOverscanStartIdx ||
+ currentProps.colOverscanEndIdx !== nextProps.colOverscanEndIdx ||
+ currentProps.colVisibleStartIdx !== nextProps.colVisibleStartIdx ||
+ currentProps.colVisibleEndIdx !== nextProps.colVisibleEndIdx ||
currentProps.isSelected !== nextProps.isSelected ||
+ currentProps.isScrolling !== nextProps.isScrolling ||
nextProps.height !== currentProps.height ||
currentProps.isOver !== nextProps.isOver ||
currentProps.expandedRows !== nextProps.expandedRows ||
diff --git a/packages/react-data-grid/src/RowGroup.js b/packages/react-data-grid/src/RowGroup.js
index 563c15e8db..fab9039b01 100644
--- a/packages/react-data-grid/src/RowGroup.js
+++ b/packages/react-data-grid/src/RowGroup.js
@@ -44,7 +44,7 @@ class RowGroup extends Component {
return (
- {this.rowGroupRenderer = node; }} {...this.props} onRowExpandClick={this.onRowExpandClick} onRowExpandToggle={this.onRowExpandToggle}/>
+ {this.rowGroupRenderer = node; }} {...this.props} onRowExpandClick={this.onRowExpandClick} onRowExpandToggle={this.onRowExpandToggle}/>
);
}
@@ -63,10 +63,10 @@ RowGroup.propTypes = {
forceUpdate: PropTypes.bool,
subRowDetails: PropTypes.object,
isRowHovered: PropTypes.bool,
- colVisibleStart: PropTypes.number.isRequired,
- colVisibleEnd: PropTypes.number.isRequired,
- colDisplayStart: PropTypes.number.isRequired,
- colDisplayEnd: PropTypes.number.isRequired,
+ colVisibleStartIdx: PropTypes.number.isRequired,
+ colVisibleEndIdx: PropTypes.number.isRequired,
+ colOverscanStartIdx: PropTypes.number.isRequired,
+ colOverscanEndIdx: PropTypes.number.isRequired,
isScrolling: PropTypes.bool.isRequired,
columnGroupName: PropTypes.string.isRequired,
isExpanded: PropTypes.bool.isRequired,
@@ -81,7 +81,7 @@ const DefaultRowGroupRenderer = (props) => {
let marginLeft = treeDepth * 20;
let style = {
- height: '50px',
+ height: '100px',
border: '1px solid #dddddd',
paddingTop: '15px',
paddingLeft: '5px'
diff --git a/packages/react-data-grid/src/RowsContainer.js b/packages/react-data-grid/src/RowsContainer.js
deleted file mode 100644
index 0d0c8c86a3..0000000000
--- a/packages/react-data-grid/src/RowsContainer.js
+++ /dev/null
@@ -1,37 +0,0 @@
-import React, { isValidElement } from 'react';
-import PropTypes from 'prop-types';
-
-export const DEFAULT_CONTEXT_MENU_ID = 'rgdContextMenu';
-
-class RowsContainer extends React.Component {
- static propTypes = {
- contextMenu: PropTypes.element,
- window: PropTypes.object,
- rows: PropTypes.array
- };
-
- plugins = this.props.window ? this.props.window.ReactDataGridPlugins : window.ReactDataGridPlugins
-
- validatePlugin() {
- if (!this.plugins) {
- throw new Error('You need to include ReactDataGrid UiPlugins in order to initialise context menu');
- }
- }
-
- render() {
- const { contextMenu, rows } = this.props;
- if (isValidElement(contextMenu)) {
- this.validatePlugin();
- const { ContextMenuTrigger } = this.plugins.Menu;
- return (
-
- );
- }
-
- return
{rows}
;
- }
-}
-
-export default RowsContainer;
diff --git a/packages/react-data-grid/src/Viewport.js b/packages/react-data-grid/src/Viewport.js
index 4eab26e86c..4f88d05f23 100644
--- a/packages/react-data-grid/src/Viewport.js
+++ b/packages/react-data-grid/src/Viewport.js
@@ -1,11 +1,19 @@
-const React = require('react');
-const Canvas = require('./Canvas');
-const cellMetaDataShape = require('./PropTypeShapes/CellMetaDataShape');
+const React = require('react');
+const Canvas = require('./Canvas');
+const cellMetaDataShape = require('./PropTypeShapes/CellMetaDataShape');
import PropTypes from 'prop-types';
-import * as columnUtils from './ColumnUtils';
+import columnUtils from './ColumnUtils';
import {
getGridState,
- getNextScrollState
+ getColOverscanEndIdx,
+ getVisibleBoundaries,
+ getScrollDirection,
+ getRowOverscanStartIdx,
+ getRowOverscanEndIdx,
+ getColOverscanStartIdx,
+ getNonFrozenVisibleColStartIdx,
+ getNonFrozenRenderedColumnCount,
+ findLastFrozenColumnIndex
} from './utils/viewportUtils';
class Viewport extends React.Component {
@@ -58,7 +66,8 @@ class Viewport extends React.Component {
onCellRangeSelectionStarted: PropTypes.func,
onCellRangeSelectionUpdated: PropTypes.func,
onCellRangeSelectionCompleted: PropTypes.func,
- onCommit: PropTypes.func.isRequired
+ onCommit: PropTypes.func.isRequired,
+ RowsContainer: PropTypes.node
};
static defaultProps = {
@@ -67,17 +76,18 @@ class Viewport extends React.Component {
state = getGridState(this.props);
- onScroll = (scroll) => {
- this.updateScroll(
- scroll.scrollTop,
- scroll.scrollLeft,
- this.state.height,
- this.props.rowHeight,
- this.props.rowsCount
- );
+ onScroll = ({ scrollTop, scrollLeft }) => {
+ const { rowHeight, rowsCount, onScroll } = this.props;
+ const nextScrollState = this.updateScroll({
+ scrollTop,
+ scrollLeft,
+ height: this.state.height,
+ rowHeight,
+ rowsCount
+ });
- if (this.props.onScroll) {
- this.props.onScroll({scrollTop: scroll.scrollTop, scrollLeft: scroll.scrollLeft});
+ if (onScroll) {
+ onScroll(nextScrollState);
}
};
@@ -99,6 +109,38 @@ class Viewport extends React.Component {
}
};
+ getNextScrollState({ scrollTop, scrollLeft, height, rowHeight, rowsCount}) {
+ const isScrolling = true;
+ const { columns } = this.props.columnMetrics;
+ const scrollDirection = getScrollDirection(this.state, scrollTop, scrollLeft);
+ const { rowVisibleStartIdx, rowVisibleEndIdx } = getVisibleBoundaries(height, rowHeight, scrollTop, rowsCount);
+ const rowOverscanStartIdx = getRowOverscanStartIdx(scrollDirection, rowVisibleStartIdx);
+ const rowOverscanEndIdx = getRowOverscanEndIdx(scrollDirection, rowVisibleEndIdx, rowsCount);
+ const totalNumberColumns = columnUtils.getSize(columns);
+ const lastFrozenColumnIndex = findLastFrozenColumnIndex(columns);
+ const nonFrozenColVisibleStartIdx = getNonFrozenVisibleColStartIdx(columns, scrollLeft);
+ const nonFrozenRenderedColumnCount = getNonFrozenRenderedColumnCount(this.props.columnMetrics, this.getDOMNodeOffsetWidth(), scrollLeft);
+ const colVisibleEndIdx = Math.min(nonFrozenColVisibleStartIdx + nonFrozenRenderedColumnCount, totalNumberColumns);
+ const colOverscanStartIdx = getColOverscanStartIdx(scrollDirection, nonFrozenColVisibleStartIdx, lastFrozenColumnIndex);
+ const colOverscanEndIdx = getColOverscanEndIdx(scrollDirection, colVisibleEndIdx, totalNumberColumns);
+ return {
+ height,
+ scrollTop,
+ scrollLeft,
+ rowVisibleStartIdx,
+ rowVisibleEndIdx,
+ rowOverscanStartIdx,
+ rowOverscanEndIdx,
+ colVisibleStartIdx: nonFrozenColVisibleStartIdx,
+ colVisibleEndIdx,
+ colOverscanStartIdx,
+ colOverscanEndIdx,
+ scrollDirection,
+ lastFrozenColumnIndex,
+ isScrolling
+ };
+ }
+
resetScrollStateAfterDelay = () => {
this.clearScrollTimer();
this.resetScrollStateTimeoutId = setTimeout(
@@ -114,32 +156,27 @@ class Viewport extends React.Component {
});
};
- updateScroll = (
- scrollTop,
- scrollLeft,
- height,
- rowHeight,
- length,
- width,
- ) => {
+ updateScroll = (scrollParams) => {
this.resetScrollStateAfterDelay();
- const nextScrollState = getNextScrollState(this.props, this.getDOMNodeOffsetWidth, scrollTop, scrollLeft, height, rowHeight, length, width);
-
+ const nextScrollState = this.getNextScrollState(scrollParams);
this.setState(nextScrollState);
+ return nextScrollState;
};
metricsUpdated = () => {
let height = this.viewportHeight();
let width = this.viewportWidth();
if (height) {
- this.updateScroll(
- this.state.scrollTop,
- this.state.scrollLeft,
+ const { scrollTop, scrollLeft } = this.state;
+ const { rowHeight, rowsCount } = this.props;
+ this.updateScroll({
+ scrollTop,
+ scrollLeft,
height,
- this.props.rowHeight,
- this.props.rowsCount,
+ rowHeight,
+ rowsCount,
width
- );
+ });
}
};
@@ -152,38 +189,40 @@ class Viewport extends React.Component {
};
componentWillReceiveProps(nextProps) {
+ const { rowHeight, rowsCount } = nextProps;
if (this.props.rowHeight !== nextProps.rowHeight ||
this.props.minHeight !== nextProps.minHeight) {
- const newState = getGridState(nextProps);
- this.updateScroll(
- newState.scrollTop,
- newState.scrollLeft,
- newState.height,
- nextProps.rowHeight,
- nextProps.rowsCount
- );
+ const { scrollTop, scrollLeft, height } = getGridState(nextProps);
+ this.updateScroll({
+ scrollTop,
+ scrollLeft,
+ height,
+ rowHeight,
+ rowsCount
+ });
} else if (columnUtils.getSize(this.props.columnMetrics.columns) !== columnUtils.getSize(nextProps.columnMetrics.columns)) {
this.setState(getGridState(nextProps));
} else if (this.props.rowsCount !== nextProps.rowsCount) {
- this.updateScroll(
- this.state.scrollTop,
- this.state.scrollLeft,
- this.state.height,
- nextProps.rowHeight,
- nextProps.rowsCount
- );
+ const { scrollTop, scrollLeft, height } = this.state;
+ this.updateScroll({
+ scrollTop,
+ scrollLeft,
+ height,
+ rowHeight,
+ rowsCount
+ });
// Added to fix the hiding of the bottom scrollbar when showing the filters.
} else if (this.props.rowOffsetHeight !== nextProps.rowOffsetHeight) {
+ const { scrollTop, scrollLeft } = this.state;
// The value of height can be positive or negative and will be added to the current height to cater for changes in the header height (due to the filer)
- let height = this.props.rowOffsetHeight - nextProps.rowOffsetHeight;
-
- this.updateScroll(
- this.state.scrollTop,
- this.state.scrollLeft,
- this.state.height + height,
- nextProps.rowHeight,
- nextProps.rowsCount
- );
+ const height = this.state.height + this.props.rowOffsetHeight - nextProps.rowOffsetHeight;
+ this.updateScroll({
+ scrollTop,
+ scrollLeft,
+ height,
+ rowHeight,
+ rowsCount
+ });
}
}
@@ -225,20 +264,22 @@ class Viewport extends React.Component {
rowKey={this.props.rowKey}
totalWidth={this.props.totalWidth}
width={this.props.columnMetrics.width}
+ totalColumnWidth={this.props.columnMetrics.totalColumnWidth}
rowGetter={this.props.rowGetter}
rowsCount={this.props.rowsCount}
selectedRows={this.props.selectedRows}
expandedRows={this.props.expandedRows}
columns={this.props.columnMetrics.columns}
rowRenderer={this.props.rowRenderer}
- displayStart={this.state.displayStart}
- displayEnd={this.state.displayEnd}
- visibleStart={this.state.visibleStart}
- visibleEnd={this.state.visibleEnd}
- colVisibleStart={this.state.colVisibleStart}
- colVisibleEnd={this.state.colVisibleEnd}
- colDisplayStart={this.state.colDisplayStart}
- colDisplayEnd={this.state.colDisplayEnd}
+ rowOverscanStartIdx={this.state.rowOverscanStartIdx}
+ rowOverscanEndIdx={this.state.rowOverscanEndIdx}
+ rowVisibleStartIdx={this.state.rowVisibleStartIdx}
+ rowVisibleEndIdx={this.state.rowVisibleEndIdx}
+ colVisibleStartIdx={this.state.colVisibleStartIdx}
+ colVisibleEndIdx={this.state.colVisibleEndIdx}
+ colOverscanStartIdx={this.state.colOverscanStartIdx}
+ colOverscanEndIdx={this.state.colOverscanEndIdx}
+ lastFrozenColumnIndex={this.state.lastFrozenColumnIndex}
cellMetaData={this.props.cellMetaData}
height={this.state.height}
rowHeight={this.props.rowHeight}
@@ -265,6 +306,7 @@ class Viewport extends React.Component {
onCellRangeSelectionUpdated={this.props.onCellRangeSelectionUpdated}
onCellRangeSelectionCompleted={this.props.onCellRangeSelectionCompleted}
onCommit={this.props.onCommit}
+ RowsContainer={this.props.RowsContainer}
/>
);
diff --git a/packages/react-data-grid/src/__tests__/Canvas.spec.js b/packages/react-data-grid/src/__tests__/Canvas.spec.js
index 60c346c2be..afa31311cf 100644
--- a/packages/react-data-grid/src/__tests__/Canvas.spec.js
+++ b/packages/react-data-grid/src/__tests__/Canvas.spec.js
@@ -2,22 +2,24 @@ import React from 'react';
import { shallow } from 'enzyme';
import InteractionMasks from '../masks/InteractionMasks';
-import RowsContainer from '../RowsContainer';
+
import Canvas from '../Canvas';
const noop = () => null;
+const getRows = wrp => wrp.find('RowsContainer').props().children.props.children;
+
let testProps = {
rowHeight: 25,
height: 200,
- displayStart: 1,
- displayEnd: 10,
- visibleStart: 0,
- visibleEnd: 10,
- colVisibleStart: 0,
- colVisibleEnd: 100,
- colDisplayStart: 0,
- colDisplayEnd: 100,
+ rowOverscanStartIdx: 1,
+ rowOverscanEndIdx: 10,
+ rowVisibleStartIdx: 0,
+ rowVisibleEndIdx: 10,
+ colVisibleStartIdx: 0,
+ colVisibleEndIdx: 100,
+ colOverscanStartIdx: 0,
+ colOverscanEndIdx: 100,
rowsCount: 1,
columns: [],
selectedRows: null,
@@ -74,24 +76,24 @@ describe('Canvas Tests', () => {
expect(wrapper.find(InteractionMasks).props()).toEqual(jasmine.objectContaining({
rowHeight: 25,
rowsCount: 1,
- visibleStart: 0,
- visibleEnd: 10,
- colVisibleStart: 0,
- colVisibleEnd: 100
+ rowVisibleStartIdx: 0,
+ rowVisibleEndIdx: 10,
+ colVisibleStartIdx: 0,
+ colVisibleEndIdx: 100
}));
});
- describe('Row Selection', () =>{
+ describe('Row Selection', () => {
let COLUMNS = [{key: 'id', name: 'ID'}];
describe('selectBy index', () => {
it('renders row selected', () => {
let rowGetter = () => { return { id: 1}; };
- let props = { displayStart: 0, displayEnd: 1, COLUMNS, rowGetter, rowsCount: 1, rowSelection: { indexes: [0] } };
+ let props = { rowOverscanStartIdx: 0, rowOverscanEndIdx: 1, COLUMNS, rowGetter, rowsCount: 1, rowSelection: { indexes: [0] } };
wrapper = renderComponent(props);
- const rows = wrapper.find(RowsContainer).props().rows;
+ const rows = getRows(wrapper);
expect(rows[0].props.isSelected).toBe(true);
});
});
@@ -100,10 +102,10 @@ describe('Canvas Tests', () => {
it('renders row selected', () => {
let rowGetter = () => { return {id: 1}; };
- let props = { displayStart: 0, displayEnd: 1, COLUMNS, rowGetter, rowsCount: 1, rowSelection: { keys: { rowKey: 'id', values: [1] } } };
+ let props = { rowOverscanStartIdx: 0, rowOverscanEndIdx: 1, COLUMNS, rowGetter, rowsCount: 1, rowSelection: { keys: { rowKey: 'id', values: [1] } } };
wrapper = renderComponent(props);
- const rows = wrapper.find(RowsContainer).props().rows;
+ const rows = getRows(wrapper);
expect(rows[0].props.isSelected).toBe(true);
});
});
@@ -113,10 +115,10 @@ describe('Canvas Tests', () => {
it('renders row selected', () => {
let rowGetter = (i) => { return i === 0 ? {id: 1, isSelected: true} : null; };
- let props = { displayStart: 0, displayEnd: 1, COLUMNS, rowGetter, rowsCount: 1, rowSelection: { isSelectedKey: 'isSelected'} };
+ let props = { rowOverscanStartIdx: 0, rowOverscanEndIdx: 1, COLUMNS, rowGetter, rowsCount: 1, rowSelection: { isSelectedKey: 'isSelected'} };
wrapper = renderComponent(props);
- const rows = wrapper.find(RowsContainer).props().rows;
+ const rows = getRows(wrapper);
expect(rows[0].props.isSelected).toBe(true);
});
});
@@ -144,9 +146,9 @@ describe('Canvas Tests', () => {
return (
);
};
let rowGetter = () => { return {id: 0, __metaData: {getRowRenderer: EmptyChildRow}}; };
- let props = { displayStart: 0, displayEnd: 1, columns: COLUMNS, rowGetter, rowsCount: 1, getSubRowDetails: getFakeSubRowDetails(1)};
+ let props = { rowOverscanStartIdx: 0, rowOverscanEndIdx: 1, columns: COLUMNS, rowGetter, rowsCount: 1, getSubRowDetails: getFakeSubRowDetails(1)};
wrapper = renderComponent(props);
- const rows = wrapper.find(RowsContainer).props().rows;
+ const rows = getRows(wrapper);
expect(rows[0].props.className).toBe('test-row-renderer');
});
});
diff --git a/packages/react-data-grid/src/__tests__/ColumnMetrics.spec.js b/packages/react-data-grid/src/__tests__/ColumnMetrics.spec.js
index 4d7b1dd938..8ab398140e 100644
--- a/packages/react-data-grid/src/__tests__/ColumnMetrics.spec.js
+++ b/packages/react-data-grid/src/__tests__/ColumnMetrics.spec.js
@@ -1,9 +1,7 @@
import getScrollbarSize from '../getScrollbarSize';
-import GridPropHelpers from '../helpers/test/GridPropHelpers';
const rewire = require('rewire');
const ColumnMetrics = rewire('../ColumnMetrics');
const Immutable = window.Immutable = require('immutable');
-Object.assign = require('object-assign');
const getAvailableWidthPerColumn = (totalWidth, consumedWidth, numberOfcolumns) => {
let availableWidth = totalWidth - getScrollbarSize() - consumedWidth;
@@ -14,9 +12,9 @@ const getAvailableWidthPerColumn = (totalWidth, consumedWidth, numberOfcolumns)
describe('Column Metrics Tests', () => {
describe('Creating metrics', () => {
- describe('When column width not set for all columns', () =>{
+ describe('When column width not set for all columns', () => {
const totalWidth = 300;
- let columns = [{
+ const getInitialColumns = () => [{
key: 'id',
name: 'ID',
width: 60
@@ -29,8 +27,9 @@ describe('Column Metrics Tests', () => {
}];
it('should set the unset column widths based on the total width', () => {
- let metrics = ColumnMetrics.recalculate({ columns, totalWidth, minColumnWidth: 50 });
- let expectedCalculatedWidth = getAvailableWidthPerColumn(totalWidth, columns[0].width, 2);
+ const columns = getInitialColumns();
+ const metrics = ColumnMetrics.recalculate({ columns, totalWidth, minColumnWidth: 50 });
+ const expectedCalculatedWidth = getAvailableWidthPerColumn(totalWidth, columns[0].width, 2);
expect(metrics.columns[0].width).toEqual(60);
expect(metrics.columns[1].width).toEqual(expectedCalculatedWidth);
@@ -38,20 +37,34 @@ describe('Column Metrics Tests', () => {
});
it('should set the column left based on the column widths', () => {
- let metrics = ColumnMetrics.recalculate({ columns, totalWidth, minColumnWidth: 50 });
- let expectedLeftValue = columns[0].width + getAvailableWidthPerColumn(totalWidth, columns[0].width, 2);
+ const columns = getInitialColumns();
+ const metrics = ColumnMetrics.recalculate({ columns, totalWidth, minColumnWidth: 50 });
+ const expectedLeftValue = columns[0].width + getAvailableWidthPerColumn(totalWidth, columns[0].width, 2);
expect(metrics.columns[0].left).toEqual(0);
expect(metrics.columns[1].left).toEqual(columns[0].width);
expect(metrics.columns[2].left).toEqual(expectedLeftValue);
});
+ it('should shift all frozen columns to the start of column metrics array', () => {
+ const firstFrozenColumn = {key: 'frozenColumn1', name: 'frozenColumn1', frozen: true};
+ const secondFrozenColumn = {key: 'frozenColumn2', name: 'frozenColumn2', frozen: true};
+ const thirdFrozenColumn = {key: 'frozenColumn3', name: 'frozenColumn3', frozen: true};
+ const columns = [...getInitialColumns(), secondFrozenColumn, thirdFrozenColumn];
+ columns.splice(2, 0, firstFrozenColumn);
+ const metrics = ColumnMetrics.recalculate({ columns, totalWidth, minColumnWidth: 50 });
+ expect(metrics.columns[0]).toEqual(jasmine.objectContaining(firstFrozenColumn));
+ expect(metrics.columns[1]).toEqual(jasmine.objectContaining(secondFrozenColumn));
+ expect(metrics.columns[2]).toEqual(jasmine.objectContaining(thirdFrozenColumn));
+ });
+
describe('When column data is immutable js object', () => {
- let immutableColumns = new Immutable.List(columns);
+ const immutableColumns = new Immutable.List(getInitialColumns());
it('should set the unset column widths based on the total width', () => {
- let metrics = ColumnMetrics.recalculate({ columns: immutableColumns, totalWidth: 300, minColumnWidth: 50 });
- let expectedCalculatedWidth = getAvailableWidthPerColumn(totalWidth, columns[0].width, 2);
+ const columns = getInitialColumns();
+ const metrics = ColumnMetrics.recalculate({ columns: immutableColumns, totalWidth: 300, minColumnWidth: 50 });
+ const expectedCalculatedWidth = getAvailableWidthPerColumn(totalWidth, columns[0].width, 2);
expect(metrics.columns.get(0).width).toEqual(60);
expect(metrics.columns.get(1).width).toEqual(expectedCalculatedWidth);
@@ -59,8 +72,9 @@ describe('Column Metrics Tests', () => {
});
it('should set the column left based on the column widths', () => {
- let metrics = ColumnMetrics.recalculate({ columns: immutableColumns, totalWidth: 300, minColumnWidth: 50 });
- let expectedLeftValue = columns[0].width + getAvailableWidthPerColumn(totalWidth, columns[0].width, 2);
+ const columns = getInitialColumns();
+ const metrics = ColumnMetrics.recalculate({ columns: immutableColumns, totalWidth: 300, minColumnWidth: 50 });
+ const expectedLeftValue = columns[0].width + getAvailableWidthPerColumn(totalWidth, columns[0].width, 2);
expect(metrics.columns.get(0).left).toEqual(0);
expect(metrics.columns.get(1).left).toEqual(columns[0].width);
@@ -69,77 +83,4 @@ describe('Column Metrics Tests', () => {
});
});
});
-
- describe('Comparing Columns', () => {
- describe('Using array of object literals', () => {
- let prevColumns;
- let nextColumns;
- beforeEach(() => {
- prevColumns = GridPropHelpers.columns;
- nextColumns = GridPropHelpers.columns.map(c => {
- return Object.assign({}, c);
- });
- });
-
- it('columns with same properties should be equal', () => {
- let areColumnsEqual = ColumnMetrics.sameColumns(prevColumns, nextColumns, ColumnMetrics.sameColumn);
- expect(areColumnsEqual).toBeTruthy();
- });
-
- it('changing a single property in one column will make columns unequal', () => {
- nextColumns[0].width = 101;
- let areColumnsEqual = ColumnMetrics.sameColumns(prevColumns, nextColumns, ColumnMetrics.sameColumn);
- expect(areColumnsEqual).toBeFalsy();
- });
-
- it('should call compareEachColumn when comparing columns', () => {
- let compareEachColumnSpy = jasmine.createSpy();
- ColumnMetrics.__set__('compareEachColumn', compareEachColumnSpy);
- ColumnMetrics.sameColumns(prevColumns, nextColumns, ColumnMetrics.sameColumn);
- expect(compareEachColumnSpy).toHaveBeenCalled();
- expect(compareEachColumnSpy.calls.count()).toEqual(1);
- });
- });
-
- describe('Using ImmutableJs Lists', () => {
- let prevColumns;
- let nextColumns;
-
- beforeEach(() => {
- prevColumns = new Immutable.List(GridPropHelpers.columns);
- nextColumns = prevColumns;
- });
-
- it('columns with same memory reference are equal', () => {
- let areColumnsEqual = ColumnMetrics.sameColumns(prevColumns, nextColumns, ColumnMetrics.sameColumn);
- expect(areColumnsEqual).toBeTruthy();
- });
-
- it('columns with same properties are not equal when objects have different memory reference', () => {
- let firstColWidth = prevColumns.get(0).width;
-
- nextColumns = nextColumns.update(0, (c) => {
- c.width = firstColWidth;
- });
- let areColumnsEqual = ColumnMetrics.sameColumns(prevColumns, nextColumns, ColumnMetrics.sameColumn);
- expect(areColumnsEqual).toBeFalsy();
- });
-
- it('changing a single property in one column will make columns unequal', () => {
- nextColumns = nextColumns.update(0, (c) => {
- c.width = 101;
- });
- let areColumnsEqual = ColumnMetrics.sameColumns(prevColumns, nextColumns, ColumnMetrics.sameColumn);
- expect(areColumnsEqual).toBeFalsy();
- });
-
- it('should not call compareEachColumn when comparing columns', () => {
- let compareEachColumnSpy = jasmine.createSpy();
- ColumnMetrics.__set__('compareEachColumn', compareEachColumnSpy);
- ColumnMetrics.sameColumns(prevColumns, nextColumns, ColumnMetrics.sameColumn);
- expect(compareEachColumnSpy).not.toHaveBeenCalled();
- expect(compareEachColumnSpy.calls.count()).toEqual(0);
- });
- });
- });
});
diff --git a/packages/react-data-grid/src/__tests__/Grid.spec.js b/packages/react-data-grid/src/__tests__/Grid.spec.js
index 078ef1cf9a..94d46619bd 100644
--- a/packages/react-data-grid/src/__tests__/Grid.spec.js
+++ b/packages/react-data-grid/src/__tests__/Grid.spec.js
@@ -3,7 +3,7 @@ const ReactDOM = require('react-dom');
const rewire = require('rewire');
const Grid = rewire('../Grid');
const TestUtils = require('react-dom/test-utils');
-const helpers = require('../helpers/test/GridPropHelpers');
+import helpers from '../helpers/test/GridPropHelpers';
const rewireModule = require('../../../../test/rewireModule');
import { shallow } from 'enzyme';
import { ContextMenu } from 'react-contextmenu';
diff --git a/packages/react-data-grid/src/__tests__/Header.spec.js b/packages/react-data-grid/src/__tests__/Header.spec.js
index cef9960bc0..246faa845e 100644
--- a/packages/react-data-grid/src/__tests__/Header.spec.js
+++ b/packages/react-data-grid/src/__tests__/Header.spec.js
@@ -4,7 +4,7 @@ const Header = rewire('../Header');
const TestUtils = require('react-dom/test-utils');
const rewireModule = require('../../../../test/rewireModule');
const StubComponent = require('../../../../test/StubComponent');
-const helpers = require('../helpers/test/GridPropHelpers');
+import helpers from '../helpers/test/GridPropHelpers';
import { shallow } from 'enzyme';
const SCROLL_BAR_SIZE = 20;
diff --git a/packages/react-data-grid/src/__tests__/HeaderRow.spec.js b/packages/react-data-grid/src/__tests__/HeaderRow.spec.js
index 3425fb3fd0..209ecdf2e0 100644
--- a/packages/react-data-grid/src/__tests__/HeaderRow.spec.js
+++ b/packages/react-data-grid/src/__tests__/HeaderRow.spec.js
@@ -3,7 +3,7 @@ const rewire = require('rewire');
const TestUtils = require('react-dom/test-utils');
const rewireModule = require('../../../../test/rewireModule');
const StubComponent = require('../../../../test/StubComponent');
-const helpers = require('../helpers/test/GridPropHelpers');
+import helpers from '../helpers/test/GridPropHelpers';
const HeaderRow = rewire('../HeaderRow');
import { shallow } from 'enzyme';
diff --git a/packages/react-data-grid/src/__tests__/ReactDataGrid.spec.js b/packages/react-data-grid/src/__tests__/ReactDataGrid.spec.js
index a23325a15d..4b97b25845 100644
--- a/packages/react-data-grid/src/__tests__/ReactDataGrid.spec.js
+++ b/packages/react-data-grid/src/__tests__/ReactDataGrid.spec.js
@@ -2,7 +2,7 @@ import React from 'react';
// import ReactDOM from 'react-dom';
import ReactDataGrid from '../ReactDataGrid';
import { shallow } from 'enzyme';
-import * as helpers from '../helpers/test/GridPropHelpers';
+import helpers from '../helpers/test/GridPropHelpers';
function shallowRenderGrid({
enableCellAutoFocus = undefined,
diff --git a/packages/react-data-grid/src/__tests__/Row.spec.js b/packages/react-data-grid/src/__tests__/Row.spec.js
index 9cc7e11f89..e5fceb0457 100644
--- a/packages/react-data-grid/src/__tests__/Row.spec.js
+++ b/packages/react-data-grid/src/__tests__/Row.spec.js
@@ -1,8 +1,9 @@
import React from 'react';
import TestUtils from 'react-dom/test-utils';
import Row from '../Row';
+import Cell from '../Cell';
import { shallow } from 'enzyme';
-import * as helpers from '../helpers/test/GridPropHelpers';
+import {createColumns} from '../__tests__/utils/createColumns';
describe('Row', () => {
let fakeProps = {
@@ -12,10 +13,6 @@ describe('Row', () => {
idx: 0
};
- it('should import Row', () => {
- expect(Row).toBeDefined();
- });
-
it('should create an instance of Row', () => {
let component = TestUtils.renderIntoDocument(
);
expect(component).toBeDefined();
@@ -37,26 +34,29 @@ describe('Row', () => {
});
describe('Rendering Row component', () => {
- const renderComponent = (props) => {
+ const COLUMN_COUNT = 50;
+
+ const setup = (props) => {
const wrapper = shallow(
);
- return wrapper;
+ const cells = wrapper.find(Cell);
+ return {wrapper, cells};
};
const requiredProperties = {
height: 30,
- columns: helpers.columns,
+ columns: createColumns(COLUMN_COUNT),
row: {key: 'value'},
idx: 17,
- colVisibleStart: 1,
- colVisibleEnd: 2,
- colDisplayStart: 3,
- colDisplayEnd: 4,
+ colVisibleStartIdx: 2,
+ colVisibleEndIdx: 20,
+ colOverscanStartIdx: 0,
+ colOverscanEndIdx: 20,
isScrolling: true
};
const allProperties = {
height: 35,
- columns: helpers.columns,
+ columns: createColumns(COLUMN_COUNT),
row: {key: 'value', name: 'name'},
cellRenderer: jasmine.createSpy(),
cellMetaData: {
@@ -79,30 +79,30 @@ describe('Row', () => {
forceUpdate: false,
subRowDetails: {name: 'subrowname'},
isRowHovered: false,
- colVisibleStart: 0,
- colVisibleEnd: 1,
- colDisplayStart: 2,
- colDisplayEnd: 3,
+ colVisibleStartIdx: 0,
+ colVisibleEndIdx: 1,
+ colOverscanStartIdx: 2,
+ colOverscanEndIdx: 3,
isScrolling: false
};
it('passes classname property', () => {
- const wrapper = renderComponent(requiredProperties);
+ const {wrapper} = setup(requiredProperties);
const draggableDiv = wrapper.find('div').at(0);
expect(draggableDiv.hasClass('react-grid-Row'));
});
it('passes style property', () => {
- const wrapper = renderComponent(requiredProperties);
+ const {wrapper} = setup(requiredProperties);
const draggableDiv = wrapper.find('div').at(0);
expect(draggableDiv.props().style).toBeDefined();
});
it('passes height property', () => {
- const wrapper = renderComponent(requiredProperties);
+ const {wrapper} = setup(requiredProperties);
const draggableDiv = wrapper.find('div').at(0);
expect(draggableDiv.props().height).toBe(30);
});
it('does not pass unknown properties to the div', () => {
- const wrapper = renderComponent(allProperties);
+ const {wrapper} = setup(allProperties);
const draggableDiv = wrapper.find('div').at(0);
expect(draggableDiv.props().columns).toBeUndefined();
expect(draggableDiv.props().row).toBeUndefined();
@@ -115,11 +115,59 @@ describe('Row', () => {
expect(draggableDiv.props().forceUpdate).toBeUndefined();
expect(draggableDiv.props().subRowDetails).toBeUndefined();
expect(draggableDiv.props().isRowHovered).toBeUndefined();
- expect(draggableDiv.props().colVisibleStart).toBeUndefined();
- expect(draggableDiv.props().colVisibleEnd).toBeUndefined();
- expect(draggableDiv.props().colDisplayStart).toBeUndefined();
- expect(draggableDiv.props().colDisplayEnd).toBeUndefined();
+ expect(draggableDiv.props().colVisibleStartIdx).toBeUndefined();
+ expect(draggableDiv.props().colVisibleEndIdx).toBeUndefined();
+ expect(draggableDiv.props().colOverscanStartIdx).toBeUndefined();
+ expect(draggableDiv.props().colOverscanEndIdx).toBeUndefined();
expect(draggableDiv.props().isScrolling).toBeUndefined();
});
+
+ describe('Cell rendering', () => {
+ describe('When using frozen columns', () => {
+ const LAST_LOCKED_CELL_IDX = 5;
+
+ const lockColumns = () => createColumns(COLUMN_COUNT).map((c, idx) => {
+ return idx <= LAST_LOCKED_CELL_IDX ? {...c, frozen: true} : c;
+ });
+
+ it('should render all frozen and visible and overscan cells', () => {
+ const columns = lockColumns(LAST_LOCKED_CELL_IDX);
+ const {cells} = setup({...requiredProperties, columns});
+ const {colOverscanStartIdx, colOverscanEndIdx} = requiredProperties;
+ const renderedRange = colOverscanEndIdx - colOverscanStartIdx + 1;
+ expect(cells.length).toBe(renderedRange);
+ });
+
+ it('first rendered cell index should be first frozen cell', () => {
+ const columns = lockColumns(LAST_LOCKED_CELL_IDX);
+ const {cells} = setup({...requiredProperties, columns});
+ const firstFrozenColumn = columns.filter(c => c.frozen === true)[0];
+ expect(cells.first().props().column).toBe(firstFrozenColumn);
+ });
+ });
+
+ describe('When not using frozen columns', () => {
+ it('should render all visible and overscan cells', () => {
+ const {cells} = setup(requiredProperties);
+ const {colOverscanStartIdx, colOverscanEndIdx} = requiredProperties;
+ const renderedRange = colOverscanEndIdx - colOverscanStartIdx + 1;
+ expect(cells.length).toBe(renderedRange);
+ });
+
+ it('first rendered cell index should be colOverscanStartIdx', () => {
+ const {cells} = setup(requiredProperties);
+ const {columns, colOverscanStartIdx} = requiredProperties;
+ const expectedFirstColumn = columns[colOverscanStartIdx];
+ expect(cells.first().props().column).toBe(expectedFirstColumn);
+ });
+
+ it('lasat rendered cell index should be colOverscanEndIdx', () => {
+ const {cells} = setup(requiredProperties);
+ const {columns, colOverscanEndIdx} = requiredProperties;
+ const expectedLastColumn = columns[colOverscanEndIdx];
+ expect(cells.last().props().column).toBe(expectedLastColumn);
+ });
+ });
+ });
});
});
diff --git a/packages/react-data-grid/src/__tests__/RowsContainer.spec.js b/packages/react-data-grid/src/__tests__/RowsContainer.spec.js
deleted file mode 100644
index 7a42705cd5..0000000000
--- a/packages/react-data-grid/src/__tests__/RowsContainer.spec.js
+++ /dev/null
@@ -1,68 +0,0 @@
-import { shallow } from 'enzyme';
-import React from 'react';
-
-import RowsContainer, { DEFAULT_CONTEXT_MENU_ID } from '../RowsContainer';
-
-const FakeContextMenuTrigger = () => ;
-const FakeContextMenu = () =>
;
-const FakeRow = (id) =>
;
-
-const ReactDataGridPlugins = {
- Menu: {
- ContextMenuTrigger: FakeContextMenuTrigger
- }
-};
-
-describe('Rows Container', () => {
- const setup = (propsOverride = {}) => {
- const props = {
- rowIdx: 5,
- idx: 8,
- window: { ReactDataGridPlugins },
- rows: [
- ,
-
- ],
- contextMenu: ,
- ...propsOverride
- };
-
- return shallow( );
- };
-
- describe('with context menu', () => {
- it('should set the correct id for contextMenu', () => {
- const contextMenuId = 'fakeContextMenu';
- const wrapper = setup({ contextMenu: });
- expect(wrapper.find(FakeContextMenuTrigger).props().id).toBe(contextMenuId);
- });
-
- it('should set the default id for contextMenu', () => {
- const wrapper = setup();
- expect(wrapper.find(FakeContextMenuTrigger).props().id).toBe(DEFAULT_CONTEXT_MENU_ID);
- });
-
- it('should render grid rows', () => {
- const wrapper = setup();
- expect(wrapper.find(FakeRow).length).toBe(2);
- });
-
- it('should throw an exception for no context menu plugin if rdg addons are not included', () => {
- expect(
- () => setup({ window: {} })
- ).toThrow();
- });
- });
-
- describe('without context menu', () => {
- it('should render grid rows', () => {
- const wrapper = setup({ contextMenu: undefined });
- expect(wrapper.find(FakeRow).length).toBe(2);
- });
-
- it('should not render context menu', () => {
- const wrapper = setup({ contextMenu: undefined });
- expect(wrapper.find(FakeContextMenuTrigger).length).toBe(0);
- });
- });
-});
diff --git a/packages/react-data-grid/src/__tests__/Viewport.spec.js b/packages/react-data-grid/src/__tests__/Viewport.spec.js
index 8fd0e0d08f..72a6790a99 100644
--- a/packages/react-data-grid/src/__tests__/Viewport.spec.js
+++ b/packages/react-data-grid/src/__tests__/Viewport.spec.js
@@ -2,6 +2,7 @@ import React from 'react';
import Viewport from '../Viewport';
import { shallow} from 'enzyme';
import helpers from '../helpers/test/GridPropHelpers';
+import {SCROLL_DIRECTION} from '../utils/viewportUtils';
let viewportProps = {
rowOffsetHeight: 0,
@@ -10,6 +11,7 @@ let viewportProps = {
columns: helpers.columns,
minColumnWidth: 80,
totalWidth: 2600,
+ totalColumnWidth: 2600,
width: 2600
},
rowGetter: () => { },
@@ -85,24 +87,26 @@ describe(' ', () => {
let Canvas = wrapper.find('Canvas');
Canvas.props().onScroll({ scrollTop, scrollLeft});
expect(wrapper.state()).toEqual({
- colDisplayEnd: 3,
- colDisplayStart: 0,
- colVisibleEnd: 3,
- colVisibleStart: 0,
- displayEnd: 26,
- displayStart: 1,
+ colOverscanEndIdx: helpers.columns.length,
+ colOverscanStartIdx: 0,
+ colVisibleEndIdx: helpers.columns.length,
+ colVisibleStartIdx: 0,
+ rowOverscanEndIdx: 23,
+ rowOverscanStartIdx: 6,
height: viewportProps.minHeight,
scrollLeft: scrollLeft,
scrollTop: scrollTop,
- visibleEnd: 21,
- visibleStart: 6,
- isScrolling: true
+ rowVisibleEndIdx: 21,
+ rowVisibleStartIdx: 6,
+ isScrolling: true,
+ lastFrozenColumnIndex: -1,
+ scrollDirection: SCROLL_DIRECTION.DOWN
});
});
it('should set the max number of columns when column rendered are zeroed', () => {
const wrapper = shallow( );
- expect(wrapper.state().colVisibleEnd).toEqual(helpers.columns.length);
+ expect(wrapper.state().colVisibleEndIdx).toEqual(helpers.columns.length);
});
it('should update when given different number of columns', () => {
@@ -116,17 +120,19 @@ describe(' ', () => {
let newProps = Object.assign({}, viewportProps, {columnMetrics: Object.assign({}, viewportProps.columnMetrics, {columns: updatedColumns})});
wrapper.setProps(newProps);
expect(wrapper.state()).toEqual({
- colDisplayEnd: updatedColumns.length,
- colDisplayStart: 0,
- colVisibleEnd: updatedColumns.length,
- colVisibleStart: 0,
- displayEnd: 50,
- displayStart: 0,
+ colOverscanEndIdx: updatedColumns.length,
+ colOverscanStartIdx: 0,
+ colVisibleEndIdx: updatedColumns.length,
+ colVisibleStartIdx: 0,
+ rowOverscanEndIdx: 28,
+ rowOverscanStartIdx: 0,
height: viewportProps.minHeight,
scrollLeft: 0,
scrollTop: 0,
- visibleEnd: 50,
- visibleStart: 0
+ rowVisibleEndIdx: 14,
+ rowVisibleStartIdx: 0,
+ lastFrozenColumnIndex: 0,
+ isScrolling: false
});
});
@@ -136,18 +142,20 @@ describe(' ', () => {
let newProps = Object.assign({}, viewportProps, {minHeight: newHeight});
wrapper.setProps(newProps);
expect(wrapper.state()).toEqual({
- colDisplayEnd: helpers.columns.length,
- colDisplayStart: 0,
- colVisibleEnd: helpers.columns.length,
- colVisibleStart: 0,
- displayEnd: 34,
- displayStart: 0,
+ colOverscanEndIdx: helpers.columns.length,
+ colOverscanStartIdx: 0,
+ colVisibleEndIdx: helpers.columns.length,
+ colVisibleStartIdx: 0,
+ rowOverscanEndIdx: 29,
+ rowOverscanStartIdx: 0,
height: newHeight,
scrollLeft: 0,
scrollTop: 0,
- visibleEnd: 29,
- visibleStart: 0,
- isScrolling: true
+ rowVisibleEndIdx: 29,
+ rowVisibleStartIdx: 0,
+ isScrolling: true,
+ scrollDirection: SCROLL_DIRECTION.NONE,
+ lastFrozenColumnIndex: -1
});
});
});
diff --git a/packages/react-data-grid/src/constants/zIndexes.js b/packages/react-data-grid/src/constants/zIndexes.js
new file mode 100644
index 0000000000..4e05b8aef5
--- /dev/null
+++ b/packages/react-data-grid/src/constants/zIndexes.js
@@ -0,0 +1,5 @@
+export default {
+ CELL_MASK: 300,
+ LOCKED_CELL_MASK: 400,
+ EDITOR_CONTAINER: 500
+};
diff --git a/packages/react-data-grid/src/editors/EditorContainer.js b/packages/react-data-grid/src/editors/EditorContainer.js
index d81dbfeea3..0e8b0bd2f9 100644
--- a/packages/react-data-grid/src/editors/EditorContainer.js
+++ b/packages/react-data-grid/src/editors/EditorContainer.js
@@ -1,10 +1,11 @@
-const React = require('react');
+import React from 'react';
import PropTypes from 'prop-types';
-const joinClasses = require('classnames');
-const SimpleTextEditor = require('./SimpleTextEditor');
-const isFunction = require('../utils/isFunction');
+import joinClasses from 'classnames';
+import SimpleTextEditor from './SimpleTextEditor';
+import isFunction from'../utils/isFunction';
import { isKeyPrintable, isCtrlKeyHeldDown } from '../utils/keyboardUtils';
-
+import zIndexes from '../constants/zIndexes';
+import columnUtils from '../ColumnUtils';
require('../../../../themes/react-data-grid-core.css');
class EditorContainer extends React.Component {
@@ -22,7 +23,8 @@ class EditorContainer extends React.Component {
firstEditorKeyPress: PropTypes.string,
width: PropTypes.number,
top: PropTypes.number,
- left: PropTypes.number
+ left: PropTypes.number,
+ scrollLeft: PropTypes.number
};
state = {isInvalid: false};
@@ -319,8 +321,9 @@ class EditorContainer extends React.Component {
};
render() {
- const { left, top, width, height } = this.props;
- const style = { position: 'absolute', height, width, zIndex: 1000, transform: `translate(${left}px, ${top}px)` };
+ const { left, top, width, height, column, scrollLeft } = this.props;
+ const editorLeft = columnUtils.isFrozen(column) ? left + scrollLeft : left;
+ const style = { position: 'absolute', height, width, zIndex: zIndexes.EDITOR_CONTAINER, transform: `translate(${editorLeft}px, ${top}px)` };
return (
{this.createEditor()}
diff --git a/packages/react-data-grid/src/helpers/test/GridPropHelpers.js b/packages/react-data-grid/src/helpers/test/GridPropHelpers.js
index 3841fb18df..1f93d31ee7 100644
--- a/packages/react-data-grid/src/helpers/test/GridPropHelpers.js
+++ b/packages/react-data-grid/src/helpers/test/GridPropHelpers.js
@@ -6,7 +6,7 @@ for (let i = 0; i < 1000; i++) {
count: i * 1000
});
}
-module.exports = {
+export default {
columns: [{
key: 'id',
name: 'ID',
@@ -35,3 +35,4 @@ module.exports = {
copied: null
}
};
+
diff --git a/packages/react-data-grid/src/index.js b/packages/react-data-grid/src/index.js
index 4fe7014661..5ee3e39055 100644
--- a/packages/react-data-grid/src/index.js
+++ b/packages/react-data-grid/src/index.js
@@ -1,6 +1,5 @@
const Grid = require('./ReactDataGrid');
import RowComparer from './RowComparer';
-import RowsContainer from './RowsContainer';
import Cell from './Cell';
module.exports = Grid;
module.exports.Row = require('./Row');
@@ -8,7 +7,6 @@ module.exports.Cell = Cell;
module.exports.HeaderCell = require('./HeaderCell');
module.exports.RowComparer = RowComparer;
module.exports.EmptyChildRow = require('./EmptyChildRow');
-module.exports.RowsContainer = RowsContainer;
module.exports.editors = require('./editors');
module.exports.formatters = require('./formatters');
module.exports.utils = require('./utils');
diff --git a/packages/react-data-grid/src/masks/InteractionMasks.js b/packages/react-data-grid/src/masks/InteractionMasks.js
index 2b7a1808ea..44c1086c41 100644
--- a/packages/react-data-grid/src/masks/InteractionMasks.js
+++ b/packages/react-data-grid/src/masks/InteractionMasks.js
@@ -24,14 +24,17 @@ import * as columnUtils from '../ColumnUtils';
import * as keyCodes from '../KeyCodes';
import { CellNavigationMode, EventTypes } from '../constants';
+require('../../../../themes/interaction-masks.css');
+
const SCROLL_CELL_BUFFER = 2;
class InteractionMasks extends React.Component {
static propTypes = {
- colVisibleStart: PropTypes.number.isRequired,
- colVisibleEnd: PropTypes.number.isRequired,
- visibleStart: PropTypes.number.isRequired,
- visibleEnd: PropTypes.number.isRequired,
+ colVisibleStartIdx: PropTypes.number.isRequired,
+ colVisibleEndIdx: PropTypes.number.isRequired,
+ rowVisibleStartIdx: PropTypes.number.isRequired,
+ rowVisibleEndIdx: PropTypes.number.isRequired,
+ rowOverscanStartIdx: PropTypes.number.isRequired,
columns: PropTypes.array,
width: PropTypes.number,
rowHeight: PropTypes.number.isRequired,
@@ -63,7 +66,10 @@ class InteractionMasks extends React.Component {
onCellsDragged: PropTypes.func,
onDragHandleDoubleClick: PropTypes.func.isRequired,
onBeforeFocus: PropTypes.func.isRequired,
- scrollLeft: PropTypes.number.isRequired
+ scrollLeft: PropTypes.number.isRequired,
+ rows: PropTypes.array.isRequired,
+ getSelectedRowHeight: PropTypes.func.isRequired,
+ getSelectedRowTop: PropTypes.func.isRequired
};
state = {
@@ -81,7 +87,7 @@ class InteractionMasks extends React.Component {
},
copiedPosition: null,
draggedPosition: null,
- lockedPosition: null,
+ frozenPosition: null,
isEditorEnabled: false,
firstEditorKeyPress: null
};
@@ -111,7 +117,6 @@ class InteractionMasks extends React.Component {
componentDidMount() {
const { eventBus, enableCellAutoFocus } = this.props;
-
this.unsubscribeSelectCell = eventBus.subscribe(EventTypes.SELECT_CELL, this.selectCell);
this.unsubscribeSelectStart = eventBus.subscribe(EventTypes.SELECT_START, this.onSelectCellRangeStarted);
this.unsubscribeSelectUpdate = eventBus.subscribe(EventTypes.SELECT_UPDATE, this.onSelectCellRangeUpdated);
@@ -267,12 +272,19 @@ class InteractionMasks extends React.Component {
return this.getKeyNavActionFromEvent(e) != null;
}
+ isGroupedRowSelected() {
+ const { rowGetter } = this.props;
+ const { selectedPosition } = this.state;
+ const rowData = getSelectedRow({ selectedPosition, rowGetter });
+ return rowData && rowData.__metaData ? rowData.__metaData.isGroup : false;
+ }
+
getKeyNavActionFromEvent(e) {
- const { visibleEnd, visibleStart, colVisibleEnd, colVisibleStart, onHitBottomBoundary, onHitRightBoundary, onHitLeftBoundary, onHitTopBoundary } = this.props;
- const isCellAtBottomBoundary = cell => cell.rowIdx >= visibleEnd - SCROLL_CELL_BUFFER;
- const isCellAtTopBoundary = cell => cell.rowIdx !== 0 && cell.rowIdx <= visibleStart - 1;
- const isCellAtRightBoundary = cell => cell.idx !== 0 && cell.idx >= colVisibleEnd - 1;
- const isCellAtLeftBoundary = cell => cell.idx !== 0 && cell.idx <= colVisibleStart + 1;
+ const { rowVisibleEndIdx, rowVisibleStartIdx, colVisibleEndIdx, colVisibleStartIdx, onHitBottomBoundary, onHitRightBoundary, onHitLeftBoundary, onHitTopBoundary } = this.props;
+ const isCellAtBottomBoundary = cell => cell.rowIdx >= rowVisibleEndIdx - SCROLL_CELL_BUFFER;
+ const isCellAtTopBoundary = cell => cell.rowIdx !== 0 && cell.rowIdx <= rowVisibleStartIdx - 1;
+ const isCellAtRightBoundary = cell => cell.idx !== 0 && cell.idx >= colVisibleEndIdx - 1;
+ const isCellAtLeftBoundary = cell => cell.idx !== 0 && cell.idx <= colVisibleStartIdx + 1;
const keyNavActions = {
ArrowDown: {
@@ -404,20 +416,23 @@ class InteractionMasks extends React.Component {
};
selectCell = (cell, openEditor) => {
- const callback = openEditor ? this.openEditor : undefined;
- const next = { ...this.state.selectedPosition, ...cell };
- if (this.isCellWithinBounds(next)) {
- this.setState({
- selectedPosition: next,
- selectedRange: {
- topLeft: next,
- bottomRight: next,
- startCell: next,
- cursorCell: next,
- isDragging: false
- }
- }, callback);
- }
+ const callback = openEditor ? this.openEditor : () => null;
+ this.setState(prevState => {
+ const next = { ...prevState.selectedPosition, ...cell };
+ if (this.isCellWithinBounds(next)) {
+ return {
+ selectedPosition: next,
+ selectedRange: {
+ topLeft: next,
+ bottomRight: next,
+ startCell: next,
+ cursorCell: next,
+ isDragging: false
+ }
+ };
+ }
+ return prevState;
+ }, callback);
};
createSingleCellSelectedRange(cellPosition, isDragging) {
@@ -453,12 +468,12 @@ class InteractionMasks extends React.Component {
const startCell = this.state.selectedRange.startCell || this.state.selectedPosition;
const colIdxs = [startCell.idx, cellPosition.idx].sort((a, b) => a - b);
const rowIdxs = [startCell.rowIdx, cellPosition.rowIdx].sort((a, b) => a - b);
- const topLeft = {idx: colIdxs[0], rowIdx: rowIdxs[0]};
- const bottomRight = {idx: colIdxs[1], rowIdx: rowIdxs[1]};
+ const topLeft = { idx: colIdxs[0], rowIdx: rowIdxs[0] };
+ const bottomRight = { idx: colIdxs[1], rowIdx: rowIdxs[1] };
const selectedRange = {
// default the startCell to the selected cell, in case we've just started via keyboard
- ...{startCell: this.state.selectedPosition},
+ ...{ startCell: this.state.selectedPosition },
// assign the previous state (which will override the startCell if we already have one)
...this.state.selectedRange,
// assign the new state - the bounds of the range, and the new cursor cell
@@ -480,8 +495,8 @@ class InteractionMasks extends React.Component {
};
onSelectCellRangeEnded = () => {
- const selectedRange = {...this.state.selectedRange, isDragging: false};
- this.setState({selectedRange}, () => {
+ const selectedRange = { ...this.state.selectedRange, isDragging: false };
+ this.setState({ selectedRange }, () => {
if (isFunction(this.props.onCellRangeSelectionCompleted)) {
this.props.onCellRangeSelectionCompleted(this.state.selectedRange);
}
@@ -559,17 +574,18 @@ class InteractionMasks extends React.Component {
this.closeEditor();
};
- getSingleCellSelectView = (rowData) => {
- const { rowHeight, columns } = this.props;
+ getSingleCellSelectView = () => {
+ const { columns, getSelectedRowHeight, getSelectedRowTop } = this.props;
const { selectedPosition } = this.state;
-
return (
!this.state.isEditorEnabled && this.isGridSelected() && (
{this.dragEnabled() && (
{
- const { rowHeight, columns } = this.props;
+ const { columns, rowHeight, getSelectedRowHeight, getSelectedRowTop } = this.props;
return [
];
};
render() {
- const { rowHeight, rowGetter, columns, contextMenu } = this.props;
+ const { rowGetter, columns, contextMenu, rowHeight } = this.props;
const { isEditorEnabled, firstEditorKeyPress, selectedPosition, draggedPosition, copiedPosition } = this.state;
const rowData = getSelectedRow({ selectedPosition, rowGetter });
return (
@@ -629,7 +648,7 @@ class InteractionMasks extends React.Component {
/>
)}
{selectedRangeIsSingleCell(this.state.selectedRange) ?
- this.getSingleCellSelectView(rowData) :
+ this.getSingleCellSelectView() :
this.getCellRangeSelectView()
}
{isEditorEnabled && }
{isValidElement(contextMenu) && cloneElement(contextMenu, { ...selectedPosition })}
diff --git a/packages/react-data-grid/src/masks/SelectionMask.js b/packages/react-data-grid/src/masks/SelectionMask.js
index c579cca5d6..845e1e44ac 100644
--- a/packages/react-data-grid/src/masks/SelectionMask.js
+++ b/packages/react-data-grid/src/masks/SelectionMask.js
@@ -1,17 +1,33 @@
import React from 'react';
import PropTypes from 'prop-types';
-
-import { getSelectedDimensions } from '../utils/SelectedCellUtils';
import CellMask from './CellMask';
+import * as columnUtils from '../ColumnUtils';
+import zIndexes from '../constants/zIndexes';
+
+const isFrozenColumn = (columns, {idx}) => columnUtils.isFrozen(columnUtils.getColumn(columns, idx));
+
+const getLeftPosition = (isFrozen, scrollLeft, cellLeft) => {
+ if (isFrozen) {
+ return scrollLeft + cellLeft;
+ }
+ return cellLeft;
+};
+
+export const getCellMaskDimensions = ({ selectedPosition, columns, scrollLeft, getSelectedRowHeight, getSelectedRowTop}) => {
+ const column = columnUtils.getColumn(columns, selectedPosition.idx);
+ const height = getSelectedRowHeight(selectedPosition.rowIdx);
+ const top = getSelectedRowTop(selectedPosition.rowIdx);
+ const frozen = isFrozenColumn(columns, selectedPosition);
+ const zIndex = frozen ? zIndexes.LOCKED_CELL_MASK : zIndexes.CELL_MASK;
+ const left = getLeftPosition(frozen, scrollLeft, column.left);
+ return {height, top, width: column.width, left, zIndex};
+};
-function SelectionMask({ selectedPosition, columns, rowHeight, children, isGroupedRow, scrollLeft }) {
- const dimensions = getSelectedDimensions({ selectedPosition, columns, rowHeight });
- const width = isGroupedRow ? '100%' : dimensions.width;
- const left = isGroupedRow ? scrollLeft : dimensions.left;
- const d = {...dimensions, ...{width, left} };
+function SelectionMask({children, ...rest}) {
+ const dimensions = getCellMaskDimensions(rest);
return (
{children}
diff --git a/packages/react-data-grid/src/masks/__tests__/CellMask.spec.js b/packages/react-data-grid/src/masks/__tests__/CellMask.spec.js
index 3776302b5e..900bf3f627 100644
--- a/packages/react-data-grid/src/masks/__tests__/CellMask.spec.js
+++ b/packages/react-data-grid/src/masks/__tests__/CellMask.spec.js
@@ -3,6 +3,7 @@ import { shallow } from 'enzyme';
import { sel } from '../../__tests__/utils';
import CellMask from '../CellMask';
+import zIndexes from '../../constants/zIndexes';
describe('CellMask', () => {
const setup = (children) => {
@@ -11,7 +12,7 @@ describe('CellMask', () => {
width: 50,
left: 5,
top: 10,
- zIndex: 2
+ zIndex: zIndexes.CELL_MASK
};
const wrapper = shallow({children} );
@@ -25,7 +26,7 @@ describe('CellMask', () => {
jasmine.objectContaining({
height: 30,
width: 50,
- zIndex: 2,
+ zIndex: zIndexes.CELL_MASK,
transform: `translate(5px, 10px)`
})
);
diff --git a/packages/react-data-grid/src/masks/__tests__/CopyMask.spec.js b/packages/react-data-grid/src/masks/__tests__/CopyMask.spec.js
index 2ee6afc06d..0d41a1d7f4 100644
--- a/packages/react-data-grid/src/masks/__tests__/CopyMask.spec.js
+++ b/packages/react-data-grid/src/masks/__tests__/CopyMask.spec.js
@@ -3,6 +3,7 @@ import { shallow } from 'enzyme';
import CellMask from '../CellMask';
import CopyMask from '../CopyMask';
+import zIndexes from '../../constants/zIndexes';
describe('CopyMask', () => {
const setup = (propsOverride = {}) => {
@@ -27,7 +28,8 @@ describe('CopyMask', () => {
width: 50,
left: 5,
top: 90, // = rowHeight * rowIdx
- zIndex: 1
+ zIndex: zIndexes.CELL_MASK,
+ className: 'react-grid-cell-copied'
})
);
});
diff --git a/packages/react-data-grid/src/masks/__tests__/DragMask.spec.js b/packages/react-data-grid/src/masks/__tests__/DragMask.spec.js
index 5a22564421..61997fd6e7 100644
--- a/packages/react-data-grid/src/masks/__tests__/DragMask.spec.js
+++ b/packages/react-data-grid/src/masks/__tests__/DragMask.spec.js
@@ -3,6 +3,7 @@ import { shallow } from 'enzyme';
import CellMask from '../CellMask';
import DragMask from '../DragMask';
+import zIndexes from '../../constants/zIndexes';
describe('DragMask', () => {
const setup = (propsOverride = {}) => {
@@ -33,7 +34,7 @@ describe('DragMask', () => {
width: 50,
left: 5,
top: 90, // = rowHeight * rowIdx
- zIndex: 1
+ zIndex: zIndexes.CELL_MASK
})
);
});
@@ -47,7 +48,7 @@ describe('DragMask', () => {
width: 50,
left: 5,
top: 120, // = rowHeight * overRowIdx
- zIndex: 1
+ zIndex: zIndexes.CELL_MASK
})
);
});
diff --git a/packages/react-data-grid/src/masks/__tests__/InteractionMasks.spec.js b/packages/react-data-grid/src/masks/__tests__/InteractionMasks.spec.js
index 890c334d4c..74437f9752 100644
--- a/packages/react-data-grid/src/masks/__tests__/InteractionMasks.spec.js
+++ b/packages/react-data-grid/src/masks/__tests__/InteractionMasks.spec.js
@@ -23,10 +23,10 @@ describe(' ', () => {
const setup = (overrideProps, initialState, render = shallow) => {
const eventBus = new EventBus();
const props = {
- visibleStart: 0,
- visibleEnd: 10,
- colVisibleStart: 0,
- colVisibleEnd: 10,
+ rowVisibleStartIdx: 0,
+ rowVisibleEndIdx: 10,
+ colVisibleStartIdx: 0,
+ colVisibleEndIdx: 10,
columns: createColumns(NUMBER_OF_COLUMNS),
rowHeight: 30,
rowsCount: ROWS_COUNT,
@@ -48,6 +48,8 @@ describe(' ', () => {
cellNavigationMode: CellNavigationMode.NONE,
eventBus,
onBeforeFocus: jasmine.createSpy().and.returnValue(() => null),
+ getSelectedRowHeight: () => 50,
+ getSelectedRowTop: () => 0,
...overrideProps
};
const wrapper = render( , { disableLifecycleMethods: false });
diff --git a/packages/react-data-grid/src/masks/__tests__/SelectionMask.spec.js b/packages/react-data-grid/src/masks/__tests__/SelectionMask.spec.js
index 38b776a274..c5b06f5644 100644
--- a/packages/react-data-grid/src/masks/__tests__/SelectionMask.spec.js
+++ b/packages/react-data-grid/src/masks/__tests__/SelectionMask.spec.js
@@ -3,15 +3,19 @@ import { shallow } from 'enzyme';
import CellMask from '../CellMask';
import SelectionMask from '../SelectionMask';
+import zIndexes from '../../constants/zIndexes';
describe('SelectionMask', () => {
+ const TOP = 45;
+ const ROW_HEIGHT = 50;
const setup = (propsOverride = {}) => {
const props = {
selectedPosition: { idx: 0, rowIdx: 3 },
columns: [
{ width: 50, left: 5 }
],
- rowHeight: 30,
+ getSelectedRowHeight: () => ROW_HEIGHT,
+ getSelectedRowTop: () => TOP,
isGroupedRow: false,
scrollLeft: 0,
...propsOverride
@@ -26,23 +30,14 @@ describe('SelectionMask', () => {
expect(mask.props()).toEqual(
jasmine.objectContaining({
- height: 30, // = rowHeight
+ height: ROW_HEIGHT,
width: 50,
left: 5,
- top: 90, // = rowHeight * rowIdx
- zIndex: 1
+ top: TOP,
+ zIndex: zIndexes.CELL_MASK,
+ className: 'rdg-selected',
+ children: undefined
})
);
});
-
- it('should set cell mask width to be 100% if row is a grouped row', () => {
- const mask = setup({isGroupedRow: true});
- expect(mask.props().width).toBe('100%');
- });
-
- it('should set cell mask left to be ${scrollLeft} if row is a grouped row', () => {
- const scrollLeft = 120;
- const mask = setup({isGroupedRow: true, scrollLeft});
- expect(mask.props().left).toBe(scrollLeft);
- });
});
diff --git a/packages/react-data-grid/src/masks/__tests__/SelectionRangeMask.spec.js b/packages/react-data-grid/src/masks/__tests__/SelectionRangeMask.spec.js
index a9a1db78bd..2e4988d011 100644
--- a/packages/react-data-grid/src/masks/__tests__/SelectionRangeMask.spec.js
+++ b/packages/react-data-grid/src/masks/__tests__/SelectionRangeMask.spec.js
@@ -3,6 +3,7 @@ import { shallow } from 'enzyme';
import CellMask from '../CellMask';
import SelectionRangeMask from '../SelectionRangeMask';
+import zIndexes from '../../constants/zIndexes';
describe('SelectionRangeMask', () => {
const setup = (propsOverride = {}) => {
@@ -36,7 +37,7 @@ describe('SelectionRangeMask', () => {
width: 90, // = columns[0].width + columns[1].width
left: 5, // = left of leftmost column
top: 90, // = rowHeight * rowIdx of topmost column
- zIndex: 1
+ zIndex: zIndexes.CELL_MASK
})
);
});
diff --git a/packages/react-data-grid/src/utils/SelectedCellUtils.js b/packages/react-data-grid/src/utils/SelectedCellUtils.js
index 873d2d1def..8f6d2e8830 100644
--- a/packages/react-data-grid/src/utils/SelectedCellUtils.js
+++ b/packages/react-data-grid/src/utils/SelectedCellUtils.js
@@ -2,6 +2,7 @@ import { CellNavigationMode } from '../constants/';
import isFunction from './isFunction';
import * as rowUtils from '../RowUtils';
import * as columnUtils from '../ColumnUtils';
+import zIndexes from '../constants/zIndexes';
const getRowTop = (rowIdx, rowHeight) => rowIdx * rowHeight;
@@ -14,9 +15,9 @@ export const getSelectedDimensions = ({ selectedPosition, columns, rowHeight })
const { idx, rowIdx } = selectedPosition;
if (idx >= 0) {
const column = columnUtils.getColumn(columns, idx);
- const { width, left, locked } = column;
+ const { width, left, frozen } = column;
const top = getRowTop(rowIdx, rowHeight);
- const zIndex = locked ? 2 : 1;
+ const zIndex = frozen ? zIndexes.LOCKED_CELL_MASK : zIndexes.CELL_MASK;
return { width, left, top, height: rowHeight, zIndex };
}
return { width: 0, left: 0, top: 0, height: rowHeight, zIndex: 1 };
@@ -24,26 +25,26 @@ export const getSelectedDimensions = ({ selectedPosition, columns, rowHeight })
const getColumnRangeProperties = (from, to, columns) => {
let totalWidth = 0;
- let anyColLocked = false;
+ let anyColFrozen = false;
for (let i = from; i <= to; i++) {
const column = columnUtils.getColumn(columns, i);
totalWidth += column.width;
- anyColLocked = anyColLocked || column.locked;
+ anyColFrozen = anyColFrozen || columnUtils.isFrozen(column);
}
- return { totalWidth, anyColLocked, left: columnUtils.getColumn(columns, from).left };
+ return { totalWidth, anyColFrozen, left: columnUtils.getColumn(columns, from).left };
};
export const getSelectedRangeDimensions = ({ selectedRange, columns, rowHeight }) => {
const { topLeft, bottomRight } = selectedRange;
if (topLeft.idx < 0) {
- return { width: 0, left: 0, top: 0, height: rowHeight, zIndex: 1 };
+ return { width: 0, left: 0, top: 0, height: rowHeight, zIndex: zIndexes.CELL_MASK };
}
- const { totalWidth, anyColLocked, left } = getColumnRangeProperties(topLeft.idx, bottomRight.idx, columns);
+ const { totalWidth, anyColFrozen, left } = getColumnRangeProperties(topLeft.idx, bottomRight.idx, columns);
const top = getRowTop(topLeft.rowIdx, rowHeight);
const height = (bottomRight.rowIdx - topLeft.rowIdx + 1) * rowHeight;
- const zIndex = anyColLocked ? 2 : 1;
+ const zIndex = anyColFrozen ? zIndexes.LOCKED_CELL_MASK : zIndexes.CELL_MASK;
return { width: totalWidth, left, top, height, zIndex };
};
diff --git a/packages/react-data-grid/src/utils/__tests__/canvasUtils.spec.js b/packages/react-data-grid/src/utils/__tests__/canvasUtils.spec.js
index ff4a893932..9c1a0b615c 100644
--- a/packages/react-data-grid/src/utils/__tests__/canvasUtils.spec.js
+++ b/packages/react-data-grid/src/utils/__tests__/canvasUtils.spec.js
@@ -9,11 +9,11 @@ describe('canvasUtils', () => {
expect(scrollPosition).toBe(-100);
});
- describe('When columns are locked', () => {
+ describe('When columns are frozen', () => {
it('should calculate the scroll position for the selected column', () => {
const columns = createColumns(10);
- columns[0].locked = true;
- columns[1].locked = true;
+ columns[0].frozen = true;
+ columns[1].frozen = true;
const scrollPosition = getColumnScrollPosition(columns, 4, 500, 100);
expect(scrollPosition).toBe(-300);
});
@@ -27,11 +27,11 @@ describe('canvasUtils', () => {
expect(scrollPosition).toBe(200);
});
- describe('When columns are locked', () => {
+ describe('When columns are frozen', () => {
it('should calculate the scroll position for the selected column', () => {
const columns = createColumns(10);
- columns[0].locked = true;
- columns[1].locked = true;
+ columns[0].frozen = true;
+ columns[1].frozen = true;
const scrollPosition = getColumnScrollPosition(columns, 7, 500, 100);
expect(scrollPosition).toBe(200);
});
@@ -39,8 +39,8 @@ describe('canvasUtils', () => {
it('should not calculate the scroll position for the selected column when client width is greater than the scroll width', () => {
const columns = createColumns(10);
- columns[0].locked = true;
- columns[1].locked = true;
+ columns[0].frozen = true;
+ columns[1].frozen = true;
const scrollPosition = getColumnScrollPosition(columns, 7, 500, 400);
expect(scrollPosition).toBeUndefined();
});
diff --git a/packages/react-data-grid/src/utils/__tests__/viewportUtils.spec.js b/packages/react-data-grid/src/utils/__tests__/viewportUtils.spec.js
index 0d8a664445..e9788d55f4 100644
--- a/packages/react-data-grid/src/utils/__tests__/viewportUtils.spec.js
+++ b/packages/react-data-grid/src/utils/__tests__/viewportUtils.spec.js
@@ -1,6 +1,7 @@
-import { getGridState, getNextScrollState, getRenderedColumnCount, getVisibleBoundaries } from '../viewportUtils';
+import { getGridState, getNonFrozenRenderedColumnCount, getVisibleBoundaries, getNonFrozenVisibleColStartIdx, getScrollDirection, SCROLL_DIRECTION, getRowOverscanStartIdx, getRowOverscanEndIdx, OVERSCAN_ROWS, getColOverscanStartIdx, getColOverscanEndIdx } from '../viewportUtils';
describe('viewportUtils', () => {
+ const getColumns = () => [{ width: 100, left: 0 }, { width: 100, left: 200 }, { width: 100, left: 300 }, { width: 100, left: 400 }, { width: 100, left: 500 }, { width: 100, left: 600 }];
describe('getGridState', () => {
const getState = (propsOverrides = {}) => {
const props = Object.assign({
@@ -27,48 +28,63 @@ describe('viewportUtils', () => {
it('should correctly set visible rows count when rowsCount is greater than the rendered rows count', () => {
const { state } = getState();
- expect(state.displayEnd).toBe(16);
+ expect(state.rowOverscanEndIdx).toBe(8);
});
it('should correctly set visible rows count when rowsCount is less than the rendered rows count', () => {
- const { state } = getState({ rowsCount: 10 });
- expect(state.displayEnd).toBe(10);
+ const { state } = getState({ rowsCount: 8 });
+ expect(state.rowOverscanEndIdx).toBe(8);
});
it('should correctly set visible column count', () => {
const { state, props } = getState();
- expect(state.colVisibleEnd).toBe(props.columnMetrics.columns.length);
+ expect(state.colVisibleEndIdx).toBe(props.columnMetrics.columns.length);
});
});
- describe('getRenderedColumnCount', () => {
- const fakeGetDOMNodeOffsetWidth = jasmine.createSpy('getDOMNodeOffsetWidth').and.returnValue(50);
- const getColumnCount = (width) => {
- return getRenderedColumnCount({
- columnMetrics: {
- columns: [
- { key: 'col1', width: 5 },
- { key: 'col2', width: 12 }
- ],
- totalWidth: 0
- }
- }, fakeGetDOMNodeOffsetWidth, 0, width);
+ describe('getNonFrozenRenderedColumnCount', () => {
+ const viewportWidth = 100;
+
+ const getCols = () => [
+ { key: 'col1', width: 20, left: 0 },
+ { key: 'col2', width: 20, left: 20 },
+ { key: 'col3', width: 20, left: 40 },
+ { key: 'col4', width: 20, left: 60 },
+ { key: 'col5', width: 20, left: 80 },
+ { key: 'col6', width: 1, left: 100 },
+ { key: 'col7', width: 1, left: 101 },
+ { key: 'col8', width: 1, left: 102 },
+ { key: 'col9', width: 1, left: 103 }
+ ];
+
+ const verifyNonFrozenRenderedColumnCount = (width = viewportWidth, columns = getCols(), scrollLeft = 0) => {
+ const columnMetrics = {
+ columns,
+ totalWidth: 0
+ };
+ return getNonFrozenRenderedColumnCount(columnMetrics, width, scrollLeft);
};
- beforeEach(() => {
- fakeGetDOMNodeOffsetWidth.calls.reset();
+ it('correctly set rendered columns count for a set width', () => {
+ const count = verifyNonFrozenRenderedColumnCount();
+ expect(count).toBe(5); // col1, col2, col3, col4, col5
});
- it('correctly set rendered columns count if width is 0', () => {
- const count = getColumnCount(0);
- expect(fakeGetDOMNodeOffsetWidth).toHaveBeenCalled();
- expect(count).toBe(2);
+ it('should return all columns that fit width after last frozen column', () => {
+ const columns = getCols();
+ columns[1].frozen = true;
+ const count = verifyNonFrozenRenderedColumnCount(100, columns);
+ expect(count).toBe(3); // col3, col4, col5
});
- it('correctly set rendered columns count if width is greater than 0', () => {
- const count = getColumnCount(4);
- expect(fakeGetDOMNodeOffsetWidth).not.toHaveBeenCalled();
- expect(count).toBe(1);
+ it('should return all columns that fit width after last frozen column when grid is scrolled', () => {
+ // 10 px of first non frozen column are hidden, with remaining 10 px visible
+ const scrollLeft = 50;
+ // This means that there are 10px of extra space to fill with columns
+ const columns = getCols();
+ columns[1].frozen = true;
+ const count = verifyNonFrozenRenderedColumnCount(100, columns, scrollLeft);
+ expect(count).toBe(5); // col5, col6, col7, col8, col9
});
});
@@ -79,16 +95,16 @@ describe('viewportUtils', () => {
const EXPECTED_NUMBER_VISIBLE_ROWS = 10;
describe('When scroll top is 0', () => {
- it('should set the visibleStart to be 0', () => {
+ it('should set the rowVisibleStartIdx to be 0', () => {
const scrollTop = 0;
- const { visibleStart } = getVisibleBoundaries(GRID_HEIGHT, ROW_HEIGHT, scrollTop, TOTAL_ROWS);
- expect(visibleStart).toBe(0);
+ const { rowVisibleStartIdx } = getVisibleBoundaries(GRID_HEIGHT, ROW_HEIGHT, scrollTop, TOTAL_ROWS);
+ expect(rowVisibleStartIdx).toBe(0);
});
- it('should set the visibleEnd to be last rendered row', () => {
+ it('should set the rowVisibleEndIdx to be last rendered row', () => {
const scrollTop = 0;
- const { visibleEnd } = getVisibleBoundaries(GRID_HEIGHT, ROW_HEIGHT, scrollTop, TOTAL_ROWS);
- expect(visibleEnd).toBe(EXPECTED_NUMBER_VISIBLE_ROWS);
+ const { rowVisibleEndIdx } = getVisibleBoundaries(GRID_HEIGHT, ROW_HEIGHT, scrollTop, TOTAL_ROWS);
+ expect(rowVisibleEndIdx).toBe(EXPECTED_NUMBER_VISIBLE_ROWS);
});
});
@@ -101,79 +117,215 @@ describe('viewportUtils', () => {
}
};
describe('When incrementing scroll by n*rowHeight', () => {
- it('should increase visibleStart by n rows', () => {
+ it('should increase rowVisibleStartIdx by n rows', () => {
const getScrollTop = n => n * ROW_HEIGHT;
- scrollDown(getScrollTop, (n, { visibleStart }) => {
- expect(visibleStart).toBe(n);
+ scrollDown(getScrollTop, (n, { rowVisibleStartIdx }) => {
+ expect(rowVisibleStartIdx).toBe(n);
});
});
- it('should increase visibleEnd by (n + total rendered rows)', () => {
+ it('should increase rowVisibleEndIdx by (n + total rendered rows)', () => {
const getScrollTop = n => n * ROW_HEIGHT;
- scrollDown(getScrollTop, (n, { visibleEnd }) => {
- expect(visibleEnd).toBe(EXPECTED_NUMBER_VISIBLE_ROWS + n);
+ scrollDown(getScrollTop, (n, { rowVisibleEndIdx }) => {
+ expect(rowVisibleEndIdx).toBe(EXPECTED_NUMBER_VISIBLE_ROWS + n);
});
});
});
describe('When incrementing scroll by a decimal number within 0.5 buffer of n*rowHeight', () => {
- it('should increase visibleEnd by (n + total rendered rows)', () => {
+ it('should increase rowVisibleEndIdx by (n + total rendered rows)', () => {
const clientScrollError = 0.5;
const getScrollTop = n => (n * ROW_HEIGHT) - clientScrollError;
- scrollDown(getScrollTop, (n, { visibleEnd }) => {
- expect(visibleEnd).toBe(EXPECTED_NUMBER_VISIBLE_ROWS + n);
+ scrollDown(getScrollTop, (n, { rowVisibleEndIdx }) => {
+ expect(rowVisibleEndIdx).toBe(EXPECTED_NUMBER_VISIBLE_ROWS + n);
});
});
- it('should increase visibleEnd by n rows', () => {
+ it('should increase rowVisibleEndIdx by n rows', () => {
const clientScrollError = 0.5;
const getScrollTop = n => (n * ROW_HEIGHT) - clientScrollError;
- scrollDown(getScrollTop, (n, { visibleStart }) => {
- expect(visibleStart).toBe(n);
+ scrollDown(getScrollTop, (n, { rowVisibleStartIdx }) => {
+ expect(rowVisibleStartIdx).toBe(n);
});
});
});
});
- });
- describe('getNextScrollState', () => {
- it('should correctly set next scroll state', () => {
- const fakeGetDOMNodeOffsetWidth = () => ({});
- const scrollTop = 100;
- const scrollLeft = 100;
- const height = 500;
- const rowHeight = 40;
- const props = {
- columnMetrics: {
- columns: [
- { key: 'col1', width: 50 },
- { key: 'col2', width: 120 }
- ]
- },
- minHeight: 100,
- rowOffsetHeight: 5,
- rowHeight: 20,
- rowsCount: 100,
- overScan: {
- rowsStart: 1,
- rowsEnd: 10,
- colsStart: 1,
- colsEnd: 10
- }
+ describe('getNonFrozenVisibleColStartIdx', () => {
+ it('should return 0 if no frozen columns and grid not scrolled left', () => {
+ const scrollLeft = 0;
+ const colVisibleStartIdx = getNonFrozenVisibleColStartIdx(getColumns(), scrollLeft);
+ expect(colVisibleStartIdx).toBe(0);
+ });
+
+ it('should return first fully visible column when scrolled left', () => {
+ const scrollLeft = 100;
+ const colVisibleStartIdx = getNonFrozenVisibleColStartIdx(getColumns(), scrollLeft);
+ expect(colVisibleStartIdx).toBe(1);
+ });
+
+ it('should return first partially visible column when scrolled left (left bound)', () => {
+ const scrollLeft = 99;
+ const colVisibleStartIdx = getNonFrozenVisibleColStartIdx(getColumns(), scrollLeft);
+ expect(colVisibleStartIdx).toBe(0);
+ });
+
+ it('should return first partially visible column when scrolled left (right bound)', () => {
+ const scrollLeft = 101;
+ const colVisibleStartIdx = getNonFrozenVisibleColStartIdx(getColumns(), scrollLeft);
+ expect(colVisibleStartIdx).toBe(1);
+ });
+
+ const expectIdxWhenColumsFrozen = (scrollLeft) => {
+ const columns = getColumns();
+ columns[1].frozen = true;
+ const colVisibleStartIdx = getNonFrozenVisibleColStartIdx(columns, scrollLeft);
+ return expect(colVisibleStartIdx);
};
- const state = getNextScrollState(props, fakeGetDOMNodeOffsetWidth, scrollTop, scrollLeft, height, rowHeight, 50, 60);
-
- expect(state).toEqual(jasmine.objectContaining({
- isScrolling: true,
- scrollLeft,
- scrollTop,
- visibleStart: 3,
- visibleEnd: 16,
- displayStart: 2,
- displayEnd: 26,
- colDisplayStart: 0,
- colDisplayEnd: 2
- }));
+
+ it('should return first non frozen column that appears after last frozen column', () => {
+ const scrollLeft = 0;
+ expectIdxWhenColumsFrozen(scrollLeft).toBe(2);
+ });
+
+ it('should return first fully visible non frozen column that appears after last frozen column when scrolled left', () => {
+ const scrollLeft = 200;
+ expectIdxWhenColumsFrozen(scrollLeft).toBe(4);
+ });
+
+ it('should return first partially visible non frozen column that appears after last frozen column when scrolled left', () => {
+ const scrollLeft = 201;
+ expectIdxWhenColumsFrozen(scrollLeft).toBe(4);
+ });
+
+ it('should return first partially visible non frozen column that appears after last frozen column when scrolled left', () => {
+ const scrollLeft = 199;
+ expectIdxWhenColumsFrozen(scrollLeft).toBe(3);
+ });
+ });
+
+ describe('getScrollDirection', () => {
+ it('should return SCROLL_DIRECTION.DOWN iF previous scrollTop is less than current scrollTop', () => {
+ const prevScroll = { scrollTop: 100 };
+ const currentScrollTop = 200;
+ const currentScrollLeft = 0;
+ expect(getScrollDirection(prevScroll, currentScrollTop, currentScrollLeft)).toBe(SCROLL_DIRECTION.DOWN);
+ });
+
+ it('should return SCROLL_DIRECTION.UP iF previous scrollTop is greater than current scrollTop', () => {
+ const prevScroll = { scrollTop: 200 };
+ const currentScrollTop = 0;
+ const currentScrollLeft = 0;
+ expect(getScrollDirection(prevScroll, currentScrollTop, currentScrollLeft)).toBe(SCROLL_DIRECTION.UP);
+ });
+
+ it('should return SCROLL_DIRECTION.RIGHT iF previous scrollLeft is less than current scrollLeft', () => {
+ const prevScroll = { scrollLeft: 200 };
+ const currentScrollTop = 0;
+ const currentScrollLeft = 400;
+ expect(getScrollDirection(prevScroll, currentScrollTop, currentScrollLeft)).toBe(SCROLL_DIRECTION.RIGHT);
+ });
+
+ it('should return SCROLL_DIRECTION.LEFT iF previous scrollLeft is greater than current scrollLeft', () => {
+ const prevScroll = { scrollLeft: 200 };
+ const currentScrollTop = 0;
+ const currentScrollLeft = 0;
+ expect(getScrollDirection(prevScroll, currentScrollTop, currentScrollLeft)).toBe(SCROLL_DIRECTION.LEFT);
+ });
+
+ it('should return SCROLL_DIRECTION.NONE if current scroll is equal to previous scroll', () => {
+ const prevScroll = { scrollLeft: 200, scrollTop: 0 };
+ const currentScrollTop = 0;
+ const currentScrollLeft = 200;
+ expect(getScrollDirection(prevScroll, currentScrollTop, currentScrollLeft)).toBe(SCROLL_DIRECTION.NONE);
+ });
+ });
+
+ describe('getRowOverscanStartIdx', () => {
+ const rowVisibleStartIdx = 2;
+ it('should return rowVisibleStartIdx - OVERSCAN_ROWS if scroll direction is upwards', () => {
+ expect(getRowOverscanStartIdx(SCROLL_DIRECTION.UP, rowVisibleStartIdx)).toBe(rowVisibleStartIdx - OVERSCAN_ROWS);
+ });
+
+ it('should return rowVisibleStartIdx if scroll direction is left', () => {
+ expect(getRowOverscanStartIdx(SCROLL_DIRECTION.LEFT, rowVisibleStartIdx)).toBe(rowVisibleStartIdx);
+ });
+
+ it('should return rowVisibleStartIdx if scroll direction is right', () => {
+ expect(getRowOverscanStartIdx(SCROLL_DIRECTION.RIGHT, rowVisibleStartIdx)).toBe(rowVisibleStartIdx);
+ });
+
+ it('should return rowVisibleStartIdx if scroll direction is downwards', () => {
+ expect(getRowOverscanStartIdx(SCROLL_DIRECTION.DOWN, rowVisibleStartIdx)).toBe(rowVisibleStartIdx);
+ });
+
+ it('should return 0 if rowVisibleStartIdx negative', () => {
+ expect(getRowOverscanStartIdx(SCROLL_DIRECTION.DOWN, -1)).toBe(0);
+ });
+ });
+
+ describe('getRowOverscanEndIdx', () => {
+ const rowVisibleEndIdx = 20;
+ const totalNumberOfRows = 30;
+ it('should return rowVisibleStartIdx + OVERSCAN_ROWS if scroll direction is downward', () => {
+ expect(getRowOverscanEndIdx(SCROLL_DIRECTION.DOWN, rowVisibleEndIdx, totalNumberOfRows)).toBe(rowVisibleEndIdx + OVERSCAN_ROWS);
+ });
+
+ it('should return totalNumberOfRows if scroll direction is downward and rowVisibleEndIdx == totalNumberRows', () => {
+ expect(getRowOverscanEndIdx(SCROLL_DIRECTION.DOWN, totalNumberOfRows, totalNumberOfRows)).toBe(totalNumberOfRows);
+ });
+
+ it('should return rowVisibleEndIdx if scroll direction is left', () => {
+ expect(getRowOverscanEndIdx(SCROLL_DIRECTION.LEFT, rowVisibleEndIdx, totalNumberOfRows)).toBe(rowVisibleEndIdx);
+ });
+
+ it('should return rowVisibleEndIdx if scroll direction is right', () => {
+ expect(getRowOverscanEndIdx(SCROLL_DIRECTION.RIGHT, rowVisibleEndIdx, totalNumberOfRows)).toBe(rowVisibleEndIdx);
+ });
+
+ it('should return rowVisibleEndIdx if scroll direction is upwards', () => {
+ expect(getRowOverscanEndIdx(SCROLL_DIRECTION.UP, rowVisibleEndIdx, totalNumberOfRows)).toBe(rowVisibleEndIdx);
+ });
+ });
+
+ describe('getColOverscanStartIdx', () => {
+ const colVisibleStartIdx = 5;
+ const lastFrozenColumnIdx = -1;
+ it('should return colVisibleStartIdx if scroll direction is downward', () => {
+ expect(getColOverscanStartIdx(SCROLL_DIRECTION.DOWN, colVisibleStartIdx, lastFrozenColumnIdx)).toBe(colVisibleStartIdx);
+ });
+
+ it('should return colVisibleStartIdx if scroll direction is upwards', () => {
+ expect(getColOverscanStartIdx(SCROLL_DIRECTION.UP, colVisibleStartIdx, lastFrozenColumnIdx)).toBe(colVisibleStartIdx);
+ });
+
+ it('should return 0 if scroll direction is left', () => {
+ expect(getColOverscanStartIdx(SCROLL_DIRECTION.LEFT, colVisibleStartIdx, lastFrozenColumnIdx)).toBe(0);
+ });
+
+ it('should return 0 if scroll direction is right', () => {
+ expect(getColOverscanStartIdx(SCROLL_DIRECTION.RIGHT, colVisibleStartIdx, lastFrozenColumnIdx)).toBe(0);
+ });
+ });
+
+ describe('getColOverscanEndIdx', () => {
+ const colVisibleEndIdx = 20;
+ const totalNumberOfColumns = 30;
+ it('should return colVisibleStartIdx if scroll direction is downward', () => {
+ expect(getColOverscanEndIdx(SCROLL_DIRECTION.DOWN, colVisibleEndIdx, totalNumberOfColumns)).toBe(colVisibleEndIdx);
+ });
+
+ it('should return colVisibleEndIdx if scroll direction is upwards', () => {
+ expect(getColOverscanEndIdx(SCROLL_DIRECTION.UP, colVisibleEndIdx, totalNumberOfColumns)).toBe(colVisibleEndIdx);
+ });
+
+ it('should return totalNumberOfColumns if scroll direction is left', () => {
+ expect(getColOverscanEndIdx(SCROLL_DIRECTION.LEFT, colVisibleEndIdx, totalNumberOfColumns)).toBe(totalNumberOfColumns);
+ });
+
+ it('should return totalNumberOfColumns if scroll direction is right', () => {
+ expect(getColOverscanEndIdx(SCROLL_DIRECTION.RIGHT, colVisibleEndIdx, totalNumberOfColumns)).toBe(totalNumberOfColumns);
+ });
});
});
});
diff --git a/packages/react-data-grid/src/utils/canvasUtils.js b/packages/react-data-grid/src/utils/canvasUtils.js
index 31cabd67f7..baa4ca3bd4 100644
--- a/packages/react-data-grid/src/utils/canvasUtils.js
+++ b/packages/react-data-grid/src/utils/canvasUtils.js
@@ -1,8 +1,8 @@
-import { getColumn } from '../ColumnUtils';
+import { getColumn, isFrozen } from '../ColumnUtils';
function getColumnScrollPosition(columns, idx, currentScrollLeft, currentClientWidth) {
let left = 0;
- let locked = 0;
+ let frozen = 0;
for (let i = 0; i < idx; i++) {
const column = getColumn(columns, i);
@@ -10,15 +10,15 @@ function getColumnScrollPosition(columns, idx, currentScrollLeft, currentClientW
if (column.width) {
left += column.width;
}
- if (column.locked) {
- locked += column.width;
+ if (isFrozen(column)) {
+ frozen += column.width;
}
}
}
const selectedColumn = getColumn(columns, idx);
if (selectedColumn) {
- const scrollLeft = left - locked - currentScrollLeft;
+ const scrollLeft = left - frozen - currentScrollLeft;
const scrollRight = left + selectedColumn.width - currentScrollLeft;
if (scrollLeft < 0) {
diff --git a/packages/react-data-grid/src/utils/viewportUtils.js b/packages/react-data-grid/src/utils/viewportUtils.js
index d70d906ab0..3c45d0183c 100644
--- a/packages/react-data-grid/src/utils/viewportUtils.js
+++ b/packages/react-data-grid/src/utils/viewportUtils.js
@@ -1,97 +1,132 @@
-import * as columnUtils from '../ColumnUtils';
+import columnUtils from '../ColumnUtils';
+
+export const OVERSCAN_ROWS = 2;
+
+export const SCROLL_DIRECTION = {
+ UP: 'upwards',
+ DOWN: 'downwards',
+ LEFT: 'left',
+ RIGHT: 'right',
+ NONE: 'none'
+};
const min = Math.min;
const max = Math.max;
const ceil = Math.ceil;
-function getGridState(props) {
+export const getGridState = (props) => {
const totalNumberColumns = columnUtils.getSize(props.columnMetrics.columns);
const canvasHeight = props.minHeight - props.rowOffsetHeight;
const renderedRowsCount = ceil((props.minHeight - props.rowHeight) / props.rowHeight);
- const totalRowCount = min(renderedRowsCount * 4, props.rowsCount);
+ const rowOverscanEndIdx = min(props.rowsCount, renderedRowsCount * 2);
return {
- displayStart: 0,
- displayEnd: totalRowCount,
- visibleStart: 0,
- visibleEnd: totalRowCount,
+ rowOverscanStartIdx: 0,
+ rowOverscanEndIdx,
+ rowVisibleStartIdx: 0,
+ rowVisibleEndIdx: renderedRowsCount,
height: canvasHeight,
scrollTop: 0,
scrollLeft: 0,
- colVisibleStart: 0,
- colVisibleEnd: totalNumberColumns,
- colDisplayStart: 0,
- colDisplayEnd: totalNumberColumns
+ colVisibleStartIdx: 0,
+ colVisibleEndIdx: totalNumberColumns,
+ colOverscanStartIdx: 0,
+ colOverscanEndIdx: totalNumberColumns,
+ isScrolling: false,
+ lastFrozenColumnIndex: 0
};
-}
-
-function getRenderedColumnCount(props, getDOMNodeOffsetWidth, displayStart, width) {
- let remainingWidth = width && width > 0 ? width : props.columnMetrics.totalWidth;
- if (remainingWidth === 0) {
- remainingWidth = getDOMNodeOffsetWidth();
- }
- let columnIndex = displayStart;
- let columnCount = 0;
- while (remainingWidth > 0) {
- let column = columnUtils.getColumn(props.columnMetrics.columns, columnIndex);
+};
- if (!column) {
- break;
+// No IE support for Array.findIndex
+export const findLastFrozenColumnIndex = (columns) => {
+ let index = -1;
+ columns.forEach((c, i) => {
+ if (columnUtils.isFrozen(c)) {
+ index = i;
}
+ });
+ return index;
+};
- columnCount++;
- columnIndex++;
- remainingWidth -= column.width;
+const getTotalFrozenColumnWidth = (columns) => {
+ const lastFrozenColumnIndex = findLastFrozenColumnIndex(columns);
+ if (lastFrozenColumnIndex > -1) {
+ const lastFrozenColumn = columnUtils.getColumn(columns, lastFrozenColumnIndex);
+ return lastFrozenColumn.left + lastFrozenColumn.width;
}
- return columnCount;
-}
+ return 0;
+};
+
+const getColumnCountForWidth = (columns, initialWidth, colVisibleStartIdx) => {
+ const initialValue = {width: initialWidth, count: 0};
+ return columns.slice(colVisibleStartIdx).reduce(({width, count}, column) => {
+ const remainingWidth = width - column.width;
+ const columnCount = remainingWidth >= 0 ? count + 1 : count;
+ return {width: remainingWidth, count: columnCount};
+ }, initialValue);
+};
-function getVisibleColStart(props, scrollLeft) {
+export const getNonFrozenVisibleColStartIdx = (columns, scrollLeft) => {
let remainingScroll = scrollLeft;
- let columnIndex = -1;
- while (remainingScroll >= 0) {
+ const lastFrozenColumnIndex = findLastFrozenColumnIndex(columns);
+ const nonFrozenColumns = columns.slice(lastFrozenColumnIndex + 1);
+ let columnIndex = lastFrozenColumnIndex;
+ while (remainingScroll >= 0 && columnIndex < columnUtils.getSize(nonFrozenColumns)) {
columnIndex++;
- remainingScroll -= columnUtils.getColumn(props.columnMetrics.columns, columnIndex).width;
+ const column = columnUtils.getColumn(columns, columnIndex);
+ remainingScroll -= column ? column.width : 0;
}
- return columnIndex;
-}
+ return Math.max(columnIndex, 0);
+};
-export const getVisibleBoundaries = (gridHeight, rowHeight, scrollTop, totalNumberRows) => {
+export const getNonFrozenRenderedColumnCount = (columnMetrics, viewportDomWidth, scrollLeft) => {
+ const {columns} = columnMetrics;
+ if (columnUtils.getSize(columns) === 0) {
+ return 0;
+ }
+ const colVisibleStartIdx = getNonFrozenVisibleColStartIdx(columnMetrics.columns, scrollLeft);
+ const totalFrozenColumnWidth = getTotalFrozenColumnWidth(columnMetrics.columns);
+ const viewportWidth = viewportDomWidth > 0 ? viewportDomWidth : columnMetrics.totalColumnWidth;
+ const firstColumn = columnUtils.getColumn(columnMetrics.columns, colVisibleStartIdx);
+ // calculate the portion width of first column hidden behind frozen columns
+ const scrolledFrozenWidth = totalFrozenColumnWidth + scrollLeft;
+ const firstColumnHiddenWidth = scrolledFrozenWidth > firstColumn.left ? scrolledFrozenWidth - firstColumn.left : 0;
+ const initialWidth = viewportWidth - totalFrozenColumnWidth + firstColumnHiddenWidth;
+ const {count} = getColumnCountForWidth(columnMetrics.columns, initialWidth, colVisibleStartIdx);
+ return count;
+};
+
+export const getVisibleBoundaries = (gridHeight, rowHeight, scrollTop, rowsCount) => {
const renderedRowsCount = ceil(gridHeight / rowHeight);
- const visibleStart = max(0, Math.round(scrollTop / rowHeight));
- const visibleEnd = min(visibleStart + renderedRowsCount, totalNumberRows);
- return { visibleStart, visibleEnd };
+ const rowVisibleStartIdx = max(0, Math.round(scrollTop / rowHeight));
+ const rowVisibleEndIdx = min(rowVisibleStartIdx + renderedRowsCount, rowsCount);
+ return { rowVisibleStartIdx, rowVisibleEndIdx };
};
-function getNextScrollState(props, getDOMNodeOffsetWidth, scrollTop, scrollLeft, height, rowHeight, length, width) {
- const isScrolling = true;
- const { visibleStart, visibleEnd } = getVisibleBoundaries(height, rowHeight, scrollTop, length);
- const displayStart = max(0, visibleStart - props.overScan.rowsStart);
- const displayEnd = min(visibleEnd + props.overScan.rowsEnd, length);
- const totalNumberColumns = columnUtils.getSize(props.columnMetrics.columns);
- const colVisibleStart = (totalNumberColumns > 0) ? max(0, getVisibleColStart(props, scrollLeft)) : 0;
- const renderedColumnCount = getRenderedColumnCount(props, getDOMNodeOffsetWidth, colVisibleStart, width);
- const colVisibleEnd = (renderedColumnCount !== 0) ? colVisibleStart + renderedColumnCount : totalNumberColumns;
- const colDisplayStart = max(0, colVisibleStart - props.overScan.colsStart);
- const colDisplayEnd = min(colVisibleEnd + props.overScan.colsEnd, totalNumberColumns);
- return {
- visibleStart,
- visibleEnd,
- displayStart,
- displayEnd,
- height,
- scrollTop,
- scrollLeft,
- colVisibleStart,
- colVisibleEnd,
- colDisplayStart,
- colDisplayEnd,
- isScrolling
- };
-}
+export const getScrollDirection = (lastScroll, scrollTop, scrollLeft) => {
+ if (scrollTop !== lastScroll.scrollTop && lastScroll.scrollTop !== undefined) {
+ return scrollTop - lastScroll.scrollTop >= 0 ? SCROLL_DIRECTION.DOWN : SCROLL_DIRECTION.UP;
+ }
+ if (scrollLeft !== lastScroll.scrollLeft && lastScroll.scrollLeft !== undefined) {
+ return scrollLeft - lastScroll.scrollLeft >= 0 ? SCROLL_DIRECTION.RIGHT : SCROLL_DIRECTION.LEFT;
+ }
+ return SCROLL_DIRECTION.NONE;
+};
+
+export const getRowOverscanStartIdx = (scrollDirection, rowVisibleStartIdx) => {
+ return scrollDirection === SCROLL_DIRECTION.UP ? max(0, rowVisibleStartIdx - OVERSCAN_ROWS) : max(0, rowVisibleStartIdx);
+};
+
+export const getRowOverscanEndIdx = (scrollDirection, rowVisibleEndIdx, rowsCount) => {
+ const overscanBoundaryIdx = rowVisibleEndIdx + OVERSCAN_ROWS;
+ return scrollDirection === SCROLL_DIRECTION.DOWN ? min(overscanBoundaryIdx, rowsCount) : rowVisibleEndIdx;
+};
+
+export const getColOverscanStartIdx = (scrollDirection, colVisibleStartIdx, lastFrozenColumnIdx) => {
+ const leftMostBoundIdx = lastFrozenColumnIdx > -1 ? lastFrozenColumnIdx + 1 : 0;
+ return (scrollDirection === SCROLL_DIRECTION.LEFT || scrollDirection === SCROLL_DIRECTION.RIGHT) ? leftMostBoundIdx : colVisibleStartIdx;
+};
-export {
- getGridState,
- getNextScrollState,
- getRenderedColumnCount
+export const getColOverscanEndIdx = (scrollDirection, colVisibleEndIdx, totalNumberColumns) => {
+ return (scrollDirection === SCROLL_DIRECTION.LEFT || scrollDirection === SCROLL_DIRECTION.RIGHT) ? totalNumberColumns : colVisibleEndIdx;
};
diff --git a/test/browser/Viewport/scroll.spec.js b/test/browser/Viewport/scroll.spec.js
index d8ee3e7560..023d0ac3fd 100644
--- a/test/browser/Viewport/scroll.spec.js
+++ b/test/browser/Viewport/scroll.spec.js
@@ -70,12 +70,12 @@ describe("Viewport scroll tests", function() {
expect(canvas.scrollLeft).toBe(100);
});
- it("Should scroll the header locked cells horizontally", function() {
+ it("Should scroll the header frozen cells horizontally", function() {
ReactTests.Simulate.scroll(ReactDOM.findDOMNode(this.canvas), {target:{scrollLeft:100, scrollTop:0}});
- expect(ReactDOM.findDOMNode(this.header).getElementsByClassName('react-grid-HeaderCell--locked')[0].style.transform).toBe("translate3d(100px, 0px, 0px)");
+ expect(ReactDOM.findDOMNode(this.header).getElementsByClassName('react-grid-HeaderCell--frozen')[0].style.transform).toBe("translate3d(100px, 0px, 0px)");
});
- xit("Should scroll the canvas locked cells horizontally", function() {
+ xit("Should scroll the canvas frozen cells horizontally", function() {
//This is failing as the canvas calls setScrollLeft in Canvas.componentDidUpdate
//it passes through scrollLeft to the rows and cells
//but this is taken from Canvas.getScroll()
@@ -83,6 +83,6 @@ describe("Viewport scroll tests", function() {
//for some reason - that is 0 (which is what causes tests above to fail)
ReactTests.Simulate.scroll(ReactDOM.findDOMNode(this.canvas), {target:{scrollLeft:100, scrollTop:0}});
- expect(canvas.getElementsByClassName('react-grid-Cell--locked')[0].style.transform).toBe("translate3d(100px, 0px, 0px)");
+ expect(canvas.getElementsByClassName('react-grid-Cell--frozen')[0].style.transform).toBe("translate3d(100px, 0px, 0px)");
});
});
diff --git a/test/browser/helpers.js b/test/browser/helpers.js
index 5e8fdfd13a..6cd8d0b15f 100644
--- a/test/browser/helpers.js
+++ b/test/browser/helpers.js
@@ -25,7 +25,7 @@ var columns = [
name: 'Supplier',
key: 'supplier',
width: 300,
- locked: true,
+ frozen: true,
},
{
idx: 1,
diff --git a/themes/interaction-masks.css b/themes/interaction-masks.css
new file mode 100644
index 0000000000..c8318d92bc
--- /dev/null
+++ b/themes/interaction-masks.css
@@ -0,0 +1,97 @@
+.rdg-selected {
+ border: 2px solid #66afe9;
+}
+
+.rdg-selected .drag-handle {
+ pointer-events: auto;
+ position: absolute;
+ bottom: -5px;
+ right: -4px;
+ background: #66afe9;
+ width: 8px;
+ height: 8px;
+ border: 1px solid #fff;
+ border-right: 0px;
+ border-bottom: 0px;
+ z-index: 8;
+ cursor: crosshair;
+ cursor: -moz-grab;
+ cursor: -webkit-grab;
+ cursor: grab;
+}
+
+.rdg-selected:hover .drag-handle {
+ bottom: -8px;
+ right: -7px;
+ background: white;
+ width: 16px;
+ height: 16px;
+ border: 1px solid #66afe9;
+}
+
+.rdg-selected:hover .drag-handle .glyphicon-arrow-down {
+ display: 'block'
+}
+
+.react-grid-cell-dragged-over-up, .react-grid-cell-dragged-over-down {
+ border: 1px dashed black;
+ background: rgba(0, 0, 255, 0.2) !important;
+}
+
+.react-grid-cell-dragged-over-up {
+ border-bottom-width: 0;
+}
+
+.react-grid-cell-dragged-over-down {
+ border-top-width: 0;
+}
+
+.react-grid-cell-copied {
+ background: rgba(0, 0, 255, 0.2) !important;
+}
+
+.rdg-editor-container input.editor-main, select.editor-main {
+ display: block;
+ width: 100%;
+ height: 34px;
+ padding: 6px 12px;
+ font-size: 14px;
+ line-height: 1.42857143;
+ color: #555555;
+ background-color: #ffffff;
+ background-image: none;
+ border: 1px solid #cccccc;
+ border-radius: 4px;
+ -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+ box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+}
+
+input.editor-main:focus, select.editor-main:focus {
+ border-color: #66afe9;
+ border: 2px solid #66afe9;
+ background: #eee;
+ border-radius: 4px;
+}
+
+.rdg-editor-container input.editor-main::-moz-placeholder, select.editor-main::-moz-placeholder {
+ color: #999999;
+ opacity: 1;
+}
+
+.rdg-editor-container input.editor-main:-ms-input-placeholder, select.editor-main:-ms-input-placeholder {
+ color: #999999;
+}
+
+.rdg-editor-container input.editor-main::-webkit-input-placeholder, select.editor-main::-webkit-input-placeholder {
+ color: #999999;
+}
+
+.rdg-editor-container input.editor-main[disabled], select.editor-main[disabled], .rdg-editor-container input.editor-main[readonly], select.editor-main[readonly], fieldset[disabled] .rdg-editor-container input.editor-main, fieldset[disabled] select.editor-main {
+ cursor: not-allowed;
+ background-color: #eeeeee;
+ opacity: 1;
+}
+
+textarea.rdg-editor-container input.editor-main, textareaselect.editor-main {
+ height: auto;
+}
diff --git a/themes/react-data-grid-cell.css b/themes/react-data-grid-cell.css
index 30a50fc083..db6048154c 100644
--- a/themes/react-data-grid-cell.css
+++ b/themes/react-data-grid-cell.css
@@ -19,36 +19,21 @@
will-change: transform;
}
-.react-grid-Cell--locked {
- z-index: 1;
+.react-grid-Cell--frozen {
+ z-index: 300;
}
-.react-grid-Cell--locked:focus {
- z-index: 100;
+.react-grid-Cell--frozen:focus {
+ z-index: 300;
}
-.rdg-selected .drag-handle {
- pointer-events: auto;
+.rdg-last--frozen {
+ border-right: 1px solid #dddddd;
+ box-shadow: 2px 0 5px -2px rgba(136, 136, 136, .3) !important;
}
.react-contextmenu--visible {
- z-index: 3;
-}
-
-.rdg-selected .drag-handle,
-.react-grid-Cell:focus .drag-handle {
- position: absolute;
- bottom: -5px;
- right: -4px;
- background: #66afe9;
- width: 8px;
- height: 8px;
- border: 1px solid #fff;
- border-right: 0px;
- border-bottom: 0px;
- z-index: 8;
- cursor: crosshair;
- cursor: -webkit-grab;
+ z-index: 1000;
}
.react-grid-Cell:not(.editing) .react-grid-Cell__value {
@@ -72,51 +57,6 @@
background-color : black;
}
-.react-grid-cell-dragged-over-up,
-.react-grid-cell-dragged-over-down {
- border: 1px dashed black;
- background: rgba(0, 0, 255, 0.2) !important;
-}
-
-.react-grid-cell-dragged-over-up {
- border-bottom-width: 0;
-}
-
-.react-grid-cell-dragged-over-down {
- border-top-width: 0;
-}
-
-.react-grid-cell-copied,
-.react-grid-Cell.copied {
- background: rgba(0, 0, 255, 0.2) !important;
-}
-
-.react-grid-Cell--locked:last-of-type {
- border-right: 1px solid #dddddd;
- box-shadow: none;
-}
-
-.rdg-selected:hover .drag-handle .glyphicon-arrow-down,
-.react-grid-Cell:hover:focus .drag-handle .glyphicon-arrow-down {
- display: 'block'
-}
-
-.react-grid-Cell.is-dragged-over-down {
- border-right: 1px dashed black;
- border-left: 1px dashed black;
- border-bottom: 1px dashed black;
-}
-
-.react-grid-Cell.is-dragged-over-up {
- border-right: 1px dashed black;
- border-left: 1px dashed black;
- border-top: 1px dashed black;
-}
-
-.rdg-selected.is-dragged-over-up .drag-handle
-.react-grid-Cell.is-dragged-over-up .drag-handle {
- top: -5px;
-}
.react-grid-Cell:hover {
background: #eee;
@@ -132,24 +72,6 @@
width: 34px;
height: 34px;
}
-.react-grid-Cell.was-dragged-over {
- border-right: 1px dashed black;
- border-left: 1px dashed black;
-}
-
-.rdg-selected:hover .drag-handle,
-.react-grid-Cell:hover:focus .drag-handle {
- position: absolute;
- bottom: -8px;
- right: -7px;
- background: white;
- width: 16px;
- height: 16px;
- border: 1px solid #66afe9;
- z-index: 8;
- cursor: crosshair;
- cursor: -webkit-grab;
-}
.react-grid-Row.row-selected .react-grid-Cell{
background-color: #DBECFA;
@@ -160,7 +82,7 @@
overflow: visible !important;
}
-.react-grid-Cell--locked.editing {
+.react-grid-Cell--frozen.editing {
z-index: 100;
}
.react-grid-Cell.editing .has-error input {
diff --git a/themes/react-data-grid-core.css b/themes/react-data-grid-core.css
index 6854b5cf9e..2f17d37b16 100644
--- a/themes/react-data-grid-core.css
+++ b/themes/react-data-grid-core.css
@@ -26,58 +26,3 @@
.react-grid-Canvas {
background-color: #ffffff;
}
-
-.react-grid-Cell input.editor-main,
-select.editor-main {
- display: block;
- width: 100%;
- height: 34px;
- padding: 6px 12px;
- font-size: 14px;
- line-height: 1.42857143;
- color: #555555;
- background-color: #ffffff;
- background-image: none;
- border: 1px solid #cccccc;
- border-radius: 4px;
- -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
- box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
-}
-input.editor-main:focus,
-select.editor-main:focus {
- border-color: #66afe9;
- border: 2px solid #66afe9;
- background: #eee;
- border-radius: 4px;
-}
-.react-grid-Cell input.editor-main::-moz-placeholder,
-select.editor-main::-moz-placeholder {
- color: #999999;
- opacity: 1;
-}
-.react-grid-Cell input.editor-main:-ms-input-placeholder,
-select.editor-main:-ms-input-placeholder {
- color: #999999;
-}
-.react-grid-Cell input.editor-main::-webkit-input-placeholder,
-select.editor-main::-webkit-input-placeholder {
- color: #999999;
-}
-.react-grid-Cell input.editor-main[disabled],
-select.editor-main[disabled],
-.react-grid-Cell input.editor-main[readonly],
-select.editor-main[readonly],
-fieldset[disabled] .react-grid-Cell input.editor-main,
-fieldset[disabled] select.editor-main {
- cursor: not-allowed;
- background-color: #eeeeee;
- opacity: 1;
-}
-textarea.react-grid-Cell input.editor-main,
-textareaselect.editor-main {
- height: auto;
-}
-
-.react-grid-ScrollShim {
- z-index: 10002;
-}
\ No newline at end of file
diff --git a/themes/react-data-grid-header.css b/themes/react-data-grid-header.css
index 1fcaf2c5d8..65f555bdba 100644
--- a/themes/react-data-grid-header.css
+++ b/themes/react-data-grid-header.css
@@ -34,8 +34,8 @@
cursor: ew-resize;
background: #dddddd;
}
-.react-grid-HeaderCell--locked:last-of-type {
- box-shadow: none;
+.react-grid-HeaderCell--frozen:last-of-type {
+ box-shadow: 2px 0 5px -2px rgba(136, 136, 136, .3);
}
.react-grid-HeaderCell--resizing .react-grid-HeaderCell__resizeHandle {
background: #dddddd;
diff --git a/themes/react-data-grid-row.css b/themes/react-data-grid-row.css
index 84e43ac310..8a9c8613c3 100644
--- a/themes/react-data-grid-row.css
+++ b/themes/react-data-grid-row.css
@@ -68,3 +68,6 @@
border-bottom: 1px solid black
}
+.rdg-scrolling {
+ pointer-events: none;
+}
diff --git a/themes/react-grid-ScrollShim.css b/themes/react-grid-ScrollShim.css
deleted file mode 100644
index 53037d67e0..0000000000
--- a/themes/react-grid-ScrollShim.css
+++ /dev/null
@@ -1,3 +0,0 @@
-.react-grid-ScrollShim {
- z-index: 10002;
-}
\ No newline at end of file