pivotgrid-js 0.1.3 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -926,6 +926,7 @@ class PivotGrid {
926
926
 
927
927
  this._applyResult(result);
928
928
  this._mount();
929
+ this._baseHeight = this.container.offsetHeight;
929
930
  this._renderVisible();
930
931
  this._bindScroll();
931
932
  }
@@ -1581,6 +1582,36 @@ class PivotGrid {
1581
1582
 
1582
1583
  // ── Public API ──────────────────────────────────────────────────────────
1583
1584
 
1585
+ /** Grows the grid container's height by one base-height increment. */
1586
+ growHeight() {
1587
+ const current = this.container.offsetHeight;
1588
+ this.container.style.flex = '0 0 auto';
1589
+ this.container.style.height = (current + this._baseHeight) + 'px';
1590
+ this._renderVisible();
1591
+ }
1592
+
1593
+ /**
1594
+ * Shrinks the grid container's height by one base-height increment.
1595
+ * Restores the original flex:1 sizing once back at the base height.
1596
+ * @returns {boolean} true if back to the original flex:1 size
1597
+ */
1598
+ shrinkHeight() {
1599
+ const current = this.container.offsetHeight;
1600
+ const next = current - this._baseHeight;
1601
+
1602
+ if (next <= this._baseHeight) {
1603
+ this.container.style.flex = '1'; // вернули как было изначально
1604
+ this.container.style.height = '';
1605
+ this._renderVisible();
1606
+ return true;
1607
+ }
1608
+
1609
+ this.container.style.flex = '0 0 auto';
1610
+ this.container.style.height = next + 'px';
1611
+ this._renderVisible();
1612
+ return false;
1613
+ }
1614
+
1584
1615
  /**
1585
1616
  * Binds mousedown drag on the resize handle to adjust the row-label column width.
1586
1617
  * @param {Element} handle
@@ -2533,6 +2564,8 @@ const I18N = {
2533
2564
  ce_confirmDelete: 'Удалить конфиг «{name}»?',
2534
2565
  ce_deleteOk: 'Конфиг «{name}» удалён',
2535
2566
  ce_deleteFailed: 'Ошибка удаления: ',
2567
+ gridGrow: '⤓ Увеличить',
2568
+ gridShrink: '⤒ Уменьшить',
2536
2569
  },
2537
2570
  en: {
2538
2571
  loading: 'Loading...',
@@ -2617,6 +2650,8 @@ const I18N = {
2617
2650
  ce_confirmDelete: 'Delete config "{name}"?',
2618
2651
  ce_deleteOk: 'Config "{name}" deleted',
2619
2652
  ce_deleteFailed: 'Delete error: ',
2653
+ gridGrow: '⤓ Increase',
2654
+ gridShrink: '⤒ Decrease',
2620
2655
  },
2621
2656
  };
2622
2657
 
@@ -926,6 +926,7 @@ class PivotGrid {
926
926
 
927
927
  this._applyResult(result);
928
928
  this._mount();
929
+ this._baseHeight = this.container.offsetHeight;
929
930
  this._renderVisible();
930
931
  this._bindScroll();
931
932
  }
@@ -1581,6 +1582,36 @@ class PivotGrid {
1581
1582
 
1582
1583
  // ── Public API ──────────────────────────────────────────────────────────
1583
1584
 
1585
+ /** Grows the grid container's height by one base-height increment. */
1586
+ growHeight() {
1587
+ const current = this.container.offsetHeight;
1588
+ this.container.style.flex = '0 0 auto';
1589
+ this.container.style.height = (current + this._baseHeight) + 'px';
1590
+ this._renderVisible();
1591
+ }
1592
+
1593
+ /**
1594
+ * Shrinks the grid container's height by one base-height increment.
1595
+ * Restores the original flex:1 sizing once back at the base height.
1596
+ * @returns {boolean} true if back to the original flex:1 size
1597
+ */
1598
+ shrinkHeight() {
1599
+ const current = this.container.offsetHeight;
1600
+ const next = current - this._baseHeight;
1601
+
1602
+ if (next <= this._baseHeight) {
1603
+ this.container.style.flex = '1'; // вернули как было изначально
1604
+ this.container.style.height = '';
1605
+ this._renderVisible();
1606
+ return true;
1607
+ }
1608
+
1609
+ this.container.style.flex = '0 0 auto';
1610
+ this.container.style.height = next + 'px';
1611
+ this._renderVisible();
1612
+ return false;
1613
+ }
1614
+
1584
1615
  /**
1585
1616
  * Binds mousedown drag on the resize handle to adjust the row-label column width.
1586
1617
  * @param {Element} handle
@@ -2533,6 +2564,8 @@ const I18N = {
2533
2564
  ce_confirmDelete: 'Удалить конфиг «{name}»?',
2534
2565
  ce_deleteOk: 'Конфиг «{name}» удалён',
2535
2566
  ce_deleteFailed: 'Ошибка удаления: ',
2567
+ gridGrow: '⤓ Увеличить',
2568
+ gridShrink: '⤒ Уменьшить',
2536
2569
  },
2537
2570
  en: {
2538
2571
  loading: 'Loading...',
@@ -2617,6 +2650,8 @@ const I18N = {
2617
2650
  ce_confirmDelete: 'Delete config "{name}"?',
2618
2651
  ce_deleteOk: 'Config "{name}" deleted',
2619
2652
  ce_deleteFailed: 'Delete error: ',
2653
+ gridGrow: '⤓ Increase',
2654
+ gridShrink: '⤒ Decrease',
2620
2655
  },
2621
2656
  };
2622
2657
 
package/dist/pivotgrid.js CHANGED
@@ -926,6 +926,7 @@ class PivotGrid {
926
926
 
927
927
  this._applyResult(result);
928
928
  this._mount();
929
+ this._baseHeight = this.container.offsetHeight;
929
930
  this._renderVisible();
930
931
  this._bindScroll();
931
932
  }
@@ -1581,6 +1582,36 @@ class PivotGrid {
1581
1582
 
1582
1583
  // ── Public API ──────────────────────────────────────────────────────────
1583
1584
 
1585
+ /** Grows the grid container's height by one base-height increment. */
1586
+ growHeight() {
1587
+ const current = this.container.offsetHeight;
1588
+ this.container.style.flex = '0 0 auto';
1589
+ this.container.style.height = (current + this._baseHeight) + 'px';
1590
+ this._renderVisible();
1591
+ }
1592
+
1593
+ /**
1594
+ * Shrinks the grid container's height by one base-height increment.
1595
+ * Restores the original flex:1 sizing once back at the base height.
1596
+ * @returns {boolean} true if back to the original flex:1 size
1597
+ */
1598
+ shrinkHeight() {
1599
+ const current = this.container.offsetHeight;
1600
+ const next = current - this._baseHeight;
1601
+
1602
+ if (next <= this._baseHeight) {
1603
+ this.container.style.flex = '1'; // вернули как было изначально
1604
+ this.container.style.height = '';
1605
+ this._renderVisible();
1606
+ return true;
1607
+ }
1608
+
1609
+ this.container.style.flex = '0 0 auto';
1610
+ this.container.style.height = next + 'px';
1611
+ this._renderVisible();
1612
+ return false;
1613
+ }
1614
+
1584
1615
  /**
1585
1616
  * Binds mousedown drag on the resize handle to adjust the row-label column width.
1586
1617
  * @param {Element} handle
@@ -2533,6 +2564,8 @@ const I18N = {
2533
2564
  ce_confirmDelete: 'Удалить конфиг «{name}»?',
2534
2565
  ce_deleteOk: 'Конфиг «{name}» удалён',
2535
2566
  ce_deleteFailed: 'Ошибка удаления: ',
2567
+ gridGrow: '⤓ Увеличить',
2568
+ gridShrink: '⤒ Уменьшить',
2536
2569
  },
2537
2570
  en: {
2538
2571
  loading: 'Loading...',
@@ -2617,6 +2650,8 @@ const I18N = {
2617
2650
  ce_confirmDelete: 'Delete config "{name}"?',
2618
2651
  ce_deleteOk: 'Config "{name}" deleted',
2619
2652
  ce_deleteFailed: 'Delete error: ',
2653
+ gridGrow: '⤓ Increase',
2654
+ gridShrink: '⤒ Decrease',
2620
2655
  },
2621
2656
  };
2622
2657
 
@@ -1,18 +1,18 @@
1
- var PivotGridLib=(()=>{class r{static ROW_HEIGHT=24;static HEADER_HEIGHT=32;static COL_HEADER_W=200;static COL_W=150;static INDENT=16;static BUFFER=5;constructor({container:e,result:t,rows:s,columns:l,measure:o,fieldDefs:i={},labels:n={}}){this.container=e,this.rows=s,this.columns=l,this.measure=o,this.fieldDefs=i,this._labels=n,this._measureKey=o+"_sum",this._colHeaderW=r.COL_HEADER_W,this._hideSubtotals=!1,this.collapsed=new Set,this.collapsedCols=new Set,this.rowPool=[],this.rendered=new Map,this._applyResult(t),this._mount(),this._renderVisible(),this._bindScroll()}_applyResult(e){this.cells=e.cells,this.colTree=e.colTree,this.colKeys=e.colKeys,this.tree=e.tree,this.grandTotal=e.grandTotal,e.measureKey&&(this._measureKey=e.measureKey),this._buildFlatCols(),this._buildFlatRows()}_buildFlatCols(){if(!this.colTree||!this.colTree.length){this.flatCols=[];return}const e=[],t=this.columns&&this.columns.length>1,s=l=>{for(const o of l)o.children?this.collapsedCols.has(o.code)?e.push({code:o.code,label:o.value,isSubtotal:!0,collapsed:!0}):(s(o.children),t&&!this._hideSubtotals&&e.push({code:o.code,label:"\u2211",isSubtotal:!0,collapsed:!1})):e.push({code:o.code,label:o.value,isSubtotal:!1})};s(this.colTree),this.flatCols=e}_getGroupSpan(e){if(!e.children||this.collapsedCols.has(e.code))return 1;let s=this.columns&&this.columns.length>1&&!this._hideSubtotals?1:0;for(const l of e.children)s+=this._getGroupSpan(l);return s}_colTreeDepth(){if(!this.colTree||!this.colTree.length)return 1;const e=t=>{let s=0;for(const l of t)l.children&&!this.collapsedCols.has(l.code)&&(s=Math.max(s,1+e(l.children)));return s};return 1+e(this.colTree)}_buildFlatRows(){this.flatRows=[];const e=t=>{for(const s of t)this.flatRows.push(s),s.children&&!this.collapsed.has(s.code)&&e(s.children)};this.tree&&e(this.tree),this.flatRows.push({isGrandTotal:!0})}get _headerHeight(){return r.HEADER_HEIGHT*this._colTreeDepth()}_mount(){this.container.innerHTML="",this.container.classList.add("pg-root");const e=this.flatCols.length?this.flatCols:this.colKeys;this.totalWidth=this._colHeaderW+(e.length+1)*r.COL_W,this._mountColHeader(),this._mountScrollArea()}_mountColHeader(){const e=r.HEADER_HEIGHT,t=this._colHeaderW,s=r.COL_W,l=this._colTreeDepth(),o=e*l;this.headerEl=document.createElement("div"),this.headerEl.className="pg-col-header",this.headerEl.style.cssText=`
1
+ var PivotGridLib=(()=>{class r{static ROW_HEIGHT=24;static HEADER_HEIGHT=32;static COL_HEADER_W=200;static COL_W=150;static INDENT=16;static BUFFER=5;constructor({container:e,result:t,rows:s,columns:o,measure:i,fieldDefs:l={},labels:n={}}){this.container=e,this.rows=s,this.columns=o,this.measure=i,this.fieldDefs=l,this._labels=n,this._measureKey=i+"_sum",this._colHeaderW=r.COL_HEADER_W,this._hideSubtotals=!1,this.collapsed=new Set,this.collapsedCols=new Set,this.rowPool=[],this.rendered=new Map,this._applyResult(t),this._mount(),this._baseHeight=this.container.offsetHeight,this._renderVisible(),this._bindScroll()}_applyResult(e){this.cells=e.cells,this.colTree=e.colTree,this.colKeys=e.colKeys,this.tree=e.tree,this.grandTotal=e.grandTotal,e.measureKey&&(this._measureKey=e.measureKey),this._buildFlatCols(),this._buildFlatRows()}_buildFlatCols(){if(!this.colTree||!this.colTree.length){this.flatCols=[];return}const e=[],t=this.columns&&this.columns.length>1,s=o=>{for(const i of o)i.children?this.collapsedCols.has(i.code)?e.push({code:i.code,label:i.value,isSubtotal:!0,collapsed:!0}):(s(i.children),t&&!this._hideSubtotals&&e.push({code:i.code,label:"\u2211",isSubtotal:!0,collapsed:!1})):e.push({code:i.code,label:i.value,isSubtotal:!1})};s(this.colTree),this.flatCols=e}_getGroupSpan(e){if(!e.children||this.collapsedCols.has(e.code))return 1;let s=this.columns&&this.columns.length>1&&!this._hideSubtotals?1:0;for(const o of e.children)s+=this._getGroupSpan(o);return s}_colTreeDepth(){if(!this.colTree||!this.colTree.length)return 1;const e=t=>{let s=0;for(const o of t)o.children&&!this.collapsedCols.has(o.code)&&(s=Math.max(s,1+e(o.children)));return s};return 1+e(this.colTree)}_buildFlatRows(){this.flatRows=[];const e=t=>{for(const s of t)this.flatRows.push(s),s.children&&!this.collapsed.has(s.code)&&e(s.children)};this.tree&&e(this.tree),this.flatRows.push({isGrandTotal:!0})}get _headerHeight(){return r.HEADER_HEIGHT*this._colTreeDepth()}_mount(){this.container.innerHTML="",this.container.classList.add("pg-root");const e=this.flatCols.length?this.flatCols:this.colKeys;this.totalWidth=this._colHeaderW+(e.length+1)*r.COL_W,this._mountColHeader(),this._mountScrollArea()}_mountColHeader(){const e=r.HEADER_HEIGHT,t=this._colHeaderW,s=r.COL_W,o=this._colTreeDepth(),i=e*o;this.headerEl=document.createElement("div"),this.headerEl.className="pg-col-header",this.headerEl.style.cssText=`
2
2
  position: absolute; top: 0; left: 0;
3
- width: ${this.totalWidth}px; height: ${o}px;
3
+ width: ${this.totalWidth}px; height: ${i}px;
4
4
  background: #fafafa; border-bottom: 1px solid #d0d0d0; z-index: 10;
5
- `;const i=this._absCell({x:0,y:0,w:t,h:o,text:"",cls:"row-label"});this.rows.forEach((a,d)=>{const h=document.createElement("span"),u=(this.fieldDefs||{})[a]||{};if(h.textContent=u.title||u.label||a,h.style.cssText="cursor:pointer; padding: 0 2px;",h.title=`Expand to "${a}"`,d<this.rows.length-1?h.addEventListener("click",()=>this.expandToDepth(d+1)):h.style.cursor="default",d>0){const f=document.createElement("span");f.textContent=" \u203A ",f.style.color="#ccc",i.appendChild(f)}i.appendChild(h)});const n=document.createElement("div");if(n.className="pg-col-resize-handle",n.style.cssText=`
5
+ `;const l=this._absCell({x:0,y:0,w:t,h:i,text:"",cls:"row-label"});this.rows.forEach((a,d)=>{const h=document.createElement("span"),u=(this.fieldDefs||{})[a]||{};if(h.textContent=u.title||u.label||a,h.style.cssText="cursor:pointer; padding: 0 2px;",h.title=`Expand to "${a}"`,d<this.rows.length-1?h.addEventListener("click",()=>this.expandToDepth(d+1)):h.style.cursor="default",d>0){const f=document.createElement("span");f.textContent=" \u203A ",f.style.color="#ccc",l.appendChild(f)}l.appendChild(h)});const n=document.createElement("div");if(n.className="pg-col-resize-handle",n.style.cssText=`
6
6
  position: absolute; top: 0; left: ${t-4}px;
7
- width: 8px; height: ${o}px;
7
+ width: 8px; height: ${i}px;
8
8
  cursor: col-resize; z-index: 20;
9
- `,this.headerEl.appendChild(n),this._bindResizeHandle(n),this.colTree&&this.colTree.length){let a=0;for(const d of this.colTree)a=this._renderColNode(d,0,a,l)}const c=this.flatCols.length?this.flatCols:this.colKeys;this._absCell({x:t+c.length*s,y:0,w:s,h:o,text:this._labels.total||"Total",cls:"total-col"}),this.container.appendChild(this.headerEl)}_renderColNode(e,t,s,l){const o=r.HEADER_HEIGHT,i=this._colHeaderW,n=r.COL_W,c=this.collapsedCols.has(e.code),a=!e.children,d=this._getGroupSpan(e),h=a||c?(l-t)*o:o,u=c?"subtotal-col":a?"":"pg-col-header-group",f=this._absCell({x:i+s*n,y:t*o,w:d*n,h,text:e.value,cls:u});if(e.children){const p=document.createElement("span");p.className="pg-toggle"+(c?" collapsed":""),p.textContent="\u25BE",p.addEventListener("click",_=>{_.stopPropagation(),this._toggleColCollapse(e.code)}),f.insertBefore(p,f.firstChild)}if(!a&&!c){let p=s;for(const _ of e.children)p=this._renderColNode(_,t+1,p,l);if(this.columns&&this.columns.length>1&&!this._hideSubtotals){const _=(l-t-1)*o;_>0&&this._absCell({x:i+(s+d-1)*n,y:(t+1)*o,w:n,h:_,text:"\u2211",cls:"subtotal-col"})}}return s+d}_absCell({x:e,y:t,w:s,h:l,text:o,cls:i}){const n=document.createElement("div");return n.className="pg-col-header-cell"+(i?" "+i:""),n.style.cssText=`
9
+ `,this.headerEl.appendChild(n),this._bindResizeHandle(n),this.colTree&&this.colTree.length){let a=0;for(const d of this.colTree)a=this._renderColNode(d,0,a,o)}const c=this.flatCols.length?this.flatCols:this.colKeys;this._absCell({x:t+c.length*s,y:0,w:s,h:i,text:this._labels.total||"Total",cls:"total-col"}),this.container.appendChild(this.headerEl)}_renderColNode(e,t,s,o){const i=r.HEADER_HEIGHT,l=this._colHeaderW,n=r.COL_W,c=this.collapsedCols.has(e.code),a=!e.children,d=this._getGroupSpan(e),h=a||c?(o-t)*i:i,u=c?"subtotal-col":a?"":"pg-col-header-group",f=this._absCell({x:l+s*n,y:t*i,w:d*n,h,text:e.value,cls:u});if(e.children){const p=document.createElement("span");p.className="pg-toggle"+(c?" collapsed":""),p.textContent="\u25BE",p.addEventListener("click",_=>{_.stopPropagation(),this._toggleColCollapse(e.code)}),f.insertBefore(p,f.firstChild)}if(!a&&!c){let p=s;for(const _ of e.children)p=this._renderColNode(_,t+1,p,o);if(this.columns&&this.columns.length>1&&!this._hideSubtotals){const _=(o-t-1)*i;_>0&&this._absCell({x:l+(s+d-1)*n,y:(t+1)*i,w:n,h:_,text:"\u2211",cls:"subtotal-col"})}}return s+d}_absCell({x:e,y:t,w:s,h:o,text:i,cls:l}){const n=document.createElement("div");return n.className="pg-col-header-cell"+(l?" "+l:""),n.style.cssText=`
10
10
  position: absolute;
11
11
  left: ${e}px; top: ${t}px;
12
- width: ${s}px; height: ${l}px;
12
+ width: ${s}px; height: ${o}px;
13
13
  box-sizing: border-box;
14
- `,n.textContent=o,this.headerEl.appendChild(n),n}_mountScrollArea(){const e=this._headerHeight;this.scrollArea=document.createElement("div"),this.scrollArea.className="pg-scroll",this.scrollArea.style.top=e+"px",this.container.appendChild(this.scrollArea),this.virtualSpace=document.createElement("div"),this.virtualSpace.style.cssText=`
14
+ `,n.textContent=i,this.headerEl.appendChild(n),n}_mountScrollArea(){const e=this._headerHeight;this.scrollArea=document.createElement("div"),this.scrollArea.className="pg-scroll",this.scrollArea.style.top=e+"px",this.container.appendChild(this.scrollArea),this.virtualSpace=document.createElement("div"),this.virtualSpace.style.cssText=`
15
15
  position: relative;
16
16
  width: ${this.totalWidth}px;
17
17
  height: ${this.flatRows.length*r.ROW_HEIGHT}px;
18
- `,this.scrollArea.appendChild(this.virtualSpace)}_renderVisible(){const e=this.scrollArea.clientHeight,t=this.scrollArea.scrollTop,s=r.ROW_HEIGHT,l=r.BUFFER,o=Math.max(0,Math.floor(t/s)-l),i=Math.min(this.flatRows.length-1,Math.ceil((t+e)/s)+l);for(const[n,c]of this.rendered)(n<o||n>i)&&(this.virtualSpace.removeChild(c),this._recycleRow(c),this.rendered.delete(n));for(let n=o;n<=i;n++){if(this.rendered.has(n))continue;const c=this._acquireRow();this._fillRow(c,this.flatRows[n],n),this.virtualSpace.appendChild(c),this.rendered.set(n,c)}}_acquireRow(){if(this.rowPool.length){const t=this.rowPool.pop();return t.className="pg-row",t.removeAttribute("style"),t.innerHTML="",t}const e=document.createElement("div");return e.className="pg-row",e}_recycleRow(e){this.rowPool.push(e)}_fillRow(e,t,s){const l=r.ROW_HEIGHT;if(e.style.top=s*l+"px",e.style.width=this.totalWidth+"px",e.style.height=l+"px",t.isGrandTotal){e.classList.add("grand-total"),this._fillGrandTotalRow(e);return}e.style.background=s%2===0?"#ffffff":"#fcfcfc",this._fillHeaderCell(e,t),this._fillValueCells(e,t)}_fillHeaderCell(e,t){const s=r.ROW_HEIGHT,l=this._colHeaderW,o=r.INDENT,i=document.createElement("div");if(i.className="pg-cell-header",i.style.cssText=`width:${l}px;height:${s}px;padding-left:${8+t.depth*o}px`,t.children){const c=document.createElement("span");c.className="pg-toggle"+(this.collapsed.has(t.code)?" collapsed":""),c.textContent="\u25BE",c.addEventListener("click",a=>{a.stopPropagation(),this._toggleCollapse(t.code)}),i.appendChild(c)}else{const c=document.createElement("span");c.className="pg-toggle-spacer",i.appendChild(c)}const n=document.createElement("span");n.className=`pg-label depth-${Math.min(t.depth,2)}`,n.textContent=t.value,i.appendChild(n),e.appendChild(i)}_fillValueCells(e,t){const s=r.ROW_HEIGHT,l=r.COL_W,o=this.flatCols.length?this.flatCols:this.colKeys;for(const a of o){const d=t.code+"||"+a.code,h=this.cells.get(d),u=document.createElement("div");u.className="pg-cell"+(h==null?" empty":"")+(a.isSubtotal?" subtotal":""),u.style.cssText=`width:${l}px;height:${s}px`,u.textContent=h!=null?this._fmt(h):"\u2014",h!=null&&u.addEventListener("click",()=>this._emitDrillthrough(t,a.code,h)),e.appendChild(u)}const i=t.code+"||__total__",n=this.cells.get(i)||0,c=document.createElement("div");c.className="pg-cell total",c.style.cssText=`width:${l}px;height:${s}px`,c.textContent=this._fmt(n),c.addEventListener("click",()=>this._emitDrillthrough(t,"__total__",n)),e.appendChild(c)}_fillGrandTotalRow(e){const t=r.ROW_HEIGHT,s=this._colHeaderW,l=r.COL_W,o=this.flatCols.length?this.flatCols:this.colKeys,i=document.createElement("div");i.className="pg-cell-header",i.style.cssText=`width:${s}px;height:${t}px;padding-left:8px`;const n=document.createElement("span");n.className="pg-toggle-spacer",i.appendChild(n);const c=document.createElement("span");c.className="pg-label depth-0",c.textContent=this._labels.total||"Total",i.appendChild(c),e.appendChild(i);for(const d of o){const h="__grand__||"+d.code,u=this.cells.get(h)||0,f=document.createElement("div");f.className="pg-cell total"+(d.isSubtotal?" subtotal":""),f.style.cssText=`width:${l}px;height:${t}px`,f.textContent=this._fmt(u),f.addEventListener("click",()=>this._emitDrillthrough({isGrandTotal:!0},d.code,u)),e.appendChild(f)}const a=document.createElement("div");a.className="pg-cell total grand-total-val",a.style.cssText=`width:${l}px;height:${t}px`,a.textContent=this._fmt(this.grandTotal||0),a.addEventListener("click",()=>this._emitDrillthrough({isGrandTotal:!0},"__total__",this.grandTotal)),e.appendChild(a)}_toggleColCollapse(e){if(this.collapsedCols.has(e)){this.collapsedCols.delete(e);const t=this._findColNode(e);if(t?.children)for(const s of t.children)s.children&&this.collapsedCols.add(s.code)}else this.collapsedCols.add(e);this._rebuildCols()}_findColNode(e,t=this.colTree){if(!t)return null;for(const s of t){if(s.code===e)return s;const l=this._findColNode(e,s.children);if(l)return l}return null}toggleSubtotals(e){this._hideSubtotals=!e,this._rebuildCols()}_rebuildCols(){this._buildFlatCols();const e=this.flatCols.length?this.flatCols:this.colKeys;this.totalWidth=this._colHeaderW+(e.length+1)*r.COL_W,this.virtualSpace.style.width=this.totalWidth+"px",this.scrollArea.style.top=this._headerHeight+"px",this.headerEl.remove(),this._mountColHeader(),this.headerEl.style.transform=`translateX(-${this.scrollArea.scrollLeft}px)`,this._redraw()}_redraw(){this.virtualSpace.style.height=this.flatRows.length*r.ROW_HEIGHT+"px";for(const[,e]of this.rendered)this.virtualSpace.removeChild(e),this._recycleRow(e);this.rendered.clear(),this._renderVisible()}_bindScroll(){let e=!1;this.scrollArea.addEventListener("scroll",()=>{this.headerEl.style.transform=`translateX(-${this.scrollArea.scrollLeft}px)`,e||(requestAnimationFrame(()=>{this._renderVisible(),e=!1}),e=!0)})}_emitDrillthrough(e,t,s){const l={};if(!e.isGrandTotal){const o=this._getNodeChain(e);for(let i=0;i<o.length;i++)l[this.rows[i]]=o[i].value}if(t!=="__total__"){const o=t.split("\u2192");for(let i=0;i<o.length;i++)this.columns[i]&&(l[this.columns[i]]=o[i])}this.container.dispatchEvent(new CustomEvent("drillthrough",{bubbles:!0,detail:{context:l,value:s}}))}_getNodeChain(e){const t=[e];if(e.depth===0)return t;const s=this.flatRows.indexOf(e);for(let l=s-1;l>=0;l--){const o=this.flatRows[l];if(!o.isGrandTotal&&o.depth===e.depth-1){if(t.unshift(o),o.depth===0)break;e=o}}return t}_fmt(e){return new Intl.NumberFormat("ru-RU",{maximumFractionDigits:0}).format(e)}_bindResizeHandle(e){e.addEventListener("mousedown",t=>{t.preventDefault();const s=t.clientX,l=this._colHeaderW,o=n=>{const c=Math.max(r.COL_HEADER_W,l+n.clientX-s);this._colHeaderW=c,this._rebuild()},i=()=>{document.removeEventListener("mousemove",o),document.removeEventListener("mouseup",i)};document.addEventListener("mousemove",o),document.addEventListener("mouseup",i)})}_rebuild(){this.headerEl?.remove(),this.headerEl=null,this._buildFlatCols(),this._mountColHeader();for(const[,e]of this.rendered)this._recycleRow(e);this.rendered.clear(),this._renderVisible()}setResult(e,{rows:t,columns:s,measure:l,fieldDefs:o}={}){if(t&&(this.rows=t),s&&(this.columns=s),l&&(this.measure=l),o&&(this.fieldDefs=o),this.collapsedCols.clear(),this._applyResult(e),this.colTree){for(const n of this.colTree)n.children&&this.collapsedCols.add(n.code);this._buildFlatCols()}const i=this.flatCols.length?this.flatCols:this.colKeys;this.totalWidth=this._colHeaderW+(i.length+1)*r.COL_W,this.virtualSpace.style.width=this.totalWidth+"px",this.scrollArea.style.top=this._headerHeight+"px",this.headerEl.remove(),this._mountColHeader(),this._redraw()}collapseAll(){const e=t=>{if(t)for(const s of t)s.children&&(this.collapsed.add(s.code),e(s.children))};e(this.tree),this._buildFlatRows(),this._redraw()}static _detectMaxHeight(){const e=document.createElement("div");e.style.cssText="position:fixed;visibility:hidden;",document.body.appendChild(e);let t=1e6;for(;t<1e8&&(e.style.height=t+"px",!(e.offsetHeight<t));)t*=2;return e.remove(),t/2}static MAX_FLAT_ROWS=Math.floor(r._detectMaxHeight()/r.ROW_HEIGHT);_confirmLargeExpand(e,t,s){const l=(e/1e6).toFixed(1),o=(this._labels.confirmLargeExpand||"Too many rows (~{millions}M). Click OK to expand anyway.").replace("{millions}",l);window.confirm(o)?t():s?.()}_toggleCollapse(e){const t=this.collapsed.has(e);if(t?this.collapsed.delete(e):this.collapsed.add(e),this._buildFlatRows(),t&&this.flatRows.length>r.MAX_FLAT_ROWS){this._confirmLargeExpand(this.flatRows.length,()=>this._redraw(),()=>{this.collapsed.add(e),this._buildFlatRows()});return}this._redraw()}expandToDepth(e){const t=[],s=o=>{for(const i of o)i.children&&(i.depth<e-1?(this.collapsed.delete(i.code),s(i.children)):i.depth===e-1&&t.push(i))};s(this.tree);const l=t.some(o=>!this.collapsed.has(o.code));for(const o of t)l?this.collapsed.add(o.code):this.collapsed.delete(o.code);this._buildFlatRows(),this._redraw()}expandAll(){if(this.collapsed.clear(),this._buildFlatRows(),this.flatRows.length>r.MAX_FLAT_ROWS){this._confirmLargeExpand(this.flatRows.length,()=>this._redraw());return}this._redraw()}expandAllCols(){this.collapsedCols.clear(),this._rebuildCols()}collapseAllCols(){const e=t=>{if(t)for(const s of t)s.children&&(this.collapsedCols.add(s.code),e(s.children))};e(this.colTree),this._rebuildCols()}}})();
18
+ `,this.scrollArea.appendChild(this.virtualSpace)}_renderVisible(){const e=this.scrollArea.clientHeight,t=this.scrollArea.scrollTop,s=r.ROW_HEIGHT,o=r.BUFFER,i=Math.max(0,Math.floor(t/s)-o),l=Math.min(this.flatRows.length-1,Math.ceil((t+e)/s)+o);for(const[n,c]of this.rendered)(n<i||n>l)&&(this.virtualSpace.removeChild(c),this._recycleRow(c),this.rendered.delete(n));for(let n=i;n<=l;n++){if(this.rendered.has(n))continue;const c=this._acquireRow();this._fillRow(c,this.flatRows[n],n),this.virtualSpace.appendChild(c),this.rendered.set(n,c)}}_acquireRow(){if(this.rowPool.length){const t=this.rowPool.pop();return t.className="pg-row",t.removeAttribute("style"),t.innerHTML="",t}const e=document.createElement("div");return e.className="pg-row",e}_recycleRow(e){this.rowPool.push(e)}_fillRow(e,t,s){const o=r.ROW_HEIGHT;if(e.style.top=s*o+"px",e.style.width=this.totalWidth+"px",e.style.height=o+"px",t.isGrandTotal){e.classList.add("grand-total"),this._fillGrandTotalRow(e);return}e.style.background=s%2===0?"#ffffff":"#fcfcfc",this._fillHeaderCell(e,t),this._fillValueCells(e,t)}_fillHeaderCell(e,t){const s=r.ROW_HEIGHT,o=this._colHeaderW,i=r.INDENT,l=document.createElement("div");if(l.className="pg-cell-header",l.style.cssText=`width:${o}px;height:${s}px;padding-left:${8+t.depth*i}px`,t.children){const c=document.createElement("span");c.className="pg-toggle"+(this.collapsed.has(t.code)?" collapsed":""),c.textContent="\u25BE",c.addEventListener("click",a=>{a.stopPropagation(),this._toggleCollapse(t.code)}),l.appendChild(c)}else{const c=document.createElement("span");c.className="pg-toggle-spacer",l.appendChild(c)}const n=document.createElement("span");n.className=`pg-label depth-${Math.min(t.depth,2)}`,n.textContent=t.value,l.appendChild(n),e.appendChild(l)}_fillValueCells(e,t){const s=r.ROW_HEIGHT,o=r.COL_W,i=this.flatCols.length?this.flatCols:this.colKeys;for(const a of i){const d=t.code+"||"+a.code,h=this.cells.get(d),u=document.createElement("div");u.className="pg-cell"+(h==null?" empty":"")+(a.isSubtotal?" subtotal":""),u.style.cssText=`width:${o}px;height:${s}px`,u.textContent=h!=null?this._fmt(h):"\u2014",h!=null&&u.addEventListener("click",()=>this._emitDrillthrough(t,a.code,h)),e.appendChild(u)}const l=t.code+"||__total__",n=this.cells.get(l)||0,c=document.createElement("div");c.className="pg-cell total",c.style.cssText=`width:${o}px;height:${s}px`,c.textContent=this._fmt(n),c.addEventListener("click",()=>this._emitDrillthrough(t,"__total__",n)),e.appendChild(c)}_fillGrandTotalRow(e){const t=r.ROW_HEIGHT,s=this._colHeaderW,o=r.COL_W,i=this.flatCols.length?this.flatCols:this.colKeys,l=document.createElement("div");l.className="pg-cell-header",l.style.cssText=`width:${s}px;height:${t}px;padding-left:8px`;const n=document.createElement("span");n.className="pg-toggle-spacer",l.appendChild(n);const c=document.createElement("span");c.className="pg-label depth-0",c.textContent=this._labels.total||"Total",l.appendChild(c),e.appendChild(l);for(const d of i){const h="__grand__||"+d.code,u=this.cells.get(h)||0,f=document.createElement("div");f.className="pg-cell total"+(d.isSubtotal?" subtotal":""),f.style.cssText=`width:${o}px;height:${t}px`,f.textContent=this._fmt(u),f.addEventListener("click",()=>this._emitDrillthrough({isGrandTotal:!0},d.code,u)),e.appendChild(f)}const a=document.createElement("div");a.className="pg-cell total grand-total-val",a.style.cssText=`width:${o}px;height:${t}px`,a.textContent=this._fmt(this.grandTotal||0),a.addEventListener("click",()=>this._emitDrillthrough({isGrandTotal:!0},"__total__",this.grandTotal)),e.appendChild(a)}_toggleColCollapse(e){if(this.collapsedCols.has(e)){this.collapsedCols.delete(e);const t=this._findColNode(e);if(t?.children)for(const s of t.children)s.children&&this.collapsedCols.add(s.code)}else this.collapsedCols.add(e);this._rebuildCols()}_findColNode(e,t=this.colTree){if(!t)return null;for(const s of t){if(s.code===e)return s;const o=this._findColNode(e,s.children);if(o)return o}return null}toggleSubtotals(e){this._hideSubtotals=!e,this._rebuildCols()}_rebuildCols(){this._buildFlatCols();const e=this.flatCols.length?this.flatCols:this.colKeys;this.totalWidth=this._colHeaderW+(e.length+1)*r.COL_W,this.virtualSpace.style.width=this.totalWidth+"px",this.scrollArea.style.top=this._headerHeight+"px",this.headerEl.remove(),this._mountColHeader(),this.headerEl.style.transform=`translateX(-${this.scrollArea.scrollLeft}px)`,this._redraw()}_redraw(){this.virtualSpace.style.height=this.flatRows.length*r.ROW_HEIGHT+"px";for(const[,e]of this.rendered)this.virtualSpace.removeChild(e),this._recycleRow(e);this.rendered.clear(),this._renderVisible()}_bindScroll(){let e=!1;this.scrollArea.addEventListener("scroll",()=>{this.headerEl.style.transform=`translateX(-${this.scrollArea.scrollLeft}px)`,e||(requestAnimationFrame(()=>{this._renderVisible(),e=!1}),e=!0)})}_emitDrillthrough(e,t,s){const o={};if(!e.isGrandTotal){const i=this._getNodeChain(e);for(let l=0;l<i.length;l++)o[this.rows[l]]=i[l].value}if(t!=="__total__"){const i=t.split("\u2192");for(let l=0;l<i.length;l++)this.columns[l]&&(o[this.columns[l]]=i[l])}this.container.dispatchEvent(new CustomEvent("drillthrough",{bubbles:!0,detail:{context:o,value:s}}))}_getNodeChain(e){const t=[e];if(e.depth===0)return t;const s=this.flatRows.indexOf(e);for(let o=s-1;o>=0;o--){const i=this.flatRows[o];if(!i.isGrandTotal&&i.depth===e.depth-1){if(t.unshift(i),i.depth===0)break;e=i}}return t}_fmt(e){return new Intl.NumberFormat("ru-RU",{maximumFractionDigits:0}).format(e)}growHeight(){const e=this.container.offsetHeight;this.container.style.flex="0 0 auto",this.container.style.height=e+this._baseHeight+"px",this._renderVisible()}shrinkHeight(){const t=this.container.offsetHeight-this._baseHeight;return t<=this._baseHeight?(this.container.style.flex="1",this.container.style.height="",this._renderVisible(),!0):(this.container.style.flex="0 0 auto",this.container.style.height=t+"px",this._renderVisible(),!1)}_bindResizeHandle(e){e.addEventListener("mousedown",t=>{t.preventDefault();const s=t.clientX,o=this._colHeaderW,i=n=>{const c=Math.max(r.COL_HEADER_W,o+n.clientX-s);this._colHeaderW=c,this._rebuild()},l=()=>{document.removeEventListener("mousemove",i),document.removeEventListener("mouseup",l)};document.addEventListener("mousemove",i),document.addEventListener("mouseup",l)})}_rebuild(){this.headerEl?.remove(),this.headerEl=null,this._buildFlatCols(),this._mountColHeader();for(const[,e]of this.rendered)this._recycleRow(e);this.rendered.clear(),this._renderVisible()}setResult(e,{rows:t,columns:s,measure:o,fieldDefs:i}={}){if(t&&(this.rows=t),s&&(this.columns=s),o&&(this.measure=o),i&&(this.fieldDefs=i),this.collapsedCols.clear(),this._applyResult(e),this.colTree){for(const n of this.colTree)n.children&&this.collapsedCols.add(n.code);this._buildFlatCols()}const l=this.flatCols.length?this.flatCols:this.colKeys;this.totalWidth=this._colHeaderW+(l.length+1)*r.COL_W,this.virtualSpace.style.width=this.totalWidth+"px",this.scrollArea.style.top=this._headerHeight+"px",this.headerEl.remove(),this._mountColHeader(),this._redraw()}collapseAll(){const e=t=>{if(t)for(const s of t)s.children&&(this.collapsed.add(s.code),e(s.children))};e(this.tree),this._buildFlatRows(),this._redraw()}static _detectMaxHeight(){const e=document.createElement("div");e.style.cssText="position:fixed;visibility:hidden;",document.body.appendChild(e);let t=1e6;for(;t<1e8&&(e.style.height=t+"px",!(e.offsetHeight<t));)t*=2;return e.remove(),t/2}static MAX_FLAT_ROWS=Math.floor(r._detectMaxHeight()/r.ROW_HEIGHT);_confirmLargeExpand(e,t,s){const o=(e/1e6).toFixed(1),i=(this._labels.confirmLargeExpand||"Too many rows (~{millions}M). Click OK to expand anyway.").replace("{millions}",o);window.confirm(i)?t():s?.()}_toggleCollapse(e){const t=this.collapsed.has(e);if(t?this.collapsed.delete(e):this.collapsed.add(e),this._buildFlatRows(),t&&this.flatRows.length>r.MAX_FLAT_ROWS){this._confirmLargeExpand(this.flatRows.length,()=>this._redraw(),()=>{this.collapsed.add(e),this._buildFlatRows()});return}this._redraw()}expandToDepth(e){const t=[],s=i=>{for(const l of i)l.children&&(l.depth<e-1?(this.collapsed.delete(l.code),s(l.children)):l.depth===e-1&&t.push(l))};s(this.tree);const o=t.some(i=>!this.collapsed.has(i.code));for(const i of t)o?this.collapsed.add(i.code):this.collapsed.delete(i.code);this._buildFlatRows(),this._redraw()}expandAll(){if(this.collapsed.clear(),this._buildFlatRows(),this.flatRows.length>r.MAX_FLAT_ROWS){this._confirmLargeExpand(this.flatRows.length,()=>this._redraw());return}this._redraw()}expandAllCols(){this.collapsedCols.clear(),this._rebuildCols()}collapseAllCols(){const e=t=>{if(t)for(const s of t)s.children&&(this.collapsedCols.add(s.code),e(s.children))};e(this.colTree),this._rebuildCols()}}})();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pivotgrid-js",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
4
4
  "description": "Vanilla JS pivot table — no dependencies, no frameworks",
5
5
  "author": "Aleksandr Korolev <korolevalexa@gmail.com>",
6
6
  "license": "SEE LICENSE IN LICENSE",
package/src/pivot.js CHANGED
@@ -40,6 +40,7 @@ class PivotGrid {
40
40
 
41
41
  this._applyResult(result);
42
42
  this._mount();
43
+ this._baseHeight = this.container.offsetHeight;
43
44
  this._renderVisible();
44
45
  this._bindScroll();
45
46
  }
@@ -695,6 +696,36 @@ class PivotGrid {
695
696
 
696
697
  // ── Public API ──────────────────────────────────────────────────────────
697
698
 
699
+ /** Grows the grid container's height by one base-height increment. */
700
+ growHeight() {
701
+ const current = this.container.offsetHeight;
702
+ this.container.style.flex = '0 0 auto';
703
+ this.container.style.height = (current + this._baseHeight) + 'px';
704
+ this._renderVisible();
705
+ }
706
+
707
+ /**
708
+ * Shrinks the grid container's height by one base-height increment.
709
+ * Restores the original flex:1 sizing once back at the base height.
710
+ * @returns {boolean} true if back to the original flex:1 size
711
+ */
712
+ shrinkHeight() {
713
+ const current = this.container.offsetHeight;
714
+ const next = current - this._baseHeight;
715
+
716
+ if (next <= this._baseHeight) {
717
+ this.container.style.flex = '1'; // вернули как было изначально
718
+ this.container.style.height = '';
719
+ this._renderVisible();
720
+ return true;
721
+ }
722
+
723
+ this.container.style.flex = '0 0 auto';
724
+ this.container.style.height = next + 'px';
725
+ this._renderVisible();
726
+ return false;
727
+ }
728
+
698
729
  /**
699
730
  * Binds mousedown drag on the resize handle to adjust the row-label column width.
700
731
  * @param {Element} handle
package/widget/i18n.js CHANGED
@@ -91,6 +91,8 @@ const I18N = {
91
91
  ce_confirmDelete: 'Удалить конфиг «{name}»?',
92
92
  ce_deleteOk: 'Конфиг «{name}» удалён',
93
93
  ce_deleteFailed: 'Ошибка удаления: ',
94
+ gridGrow: '⤓ Увеличить',
95
+ gridShrink: '⤒ Уменьшить',
94
96
  },
95
97
  en: {
96
98
  loading: 'Loading...',
@@ -175,5 +177,7 @@ const I18N = {
175
177
  ce_confirmDelete: 'Delete config "{name}"?',
176
178
  ce_deleteOk: 'Config "{name}" deleted',
177
179
  ce_deleteFailed: 'Delete error: ',
180
+ gridGrow: '⤓ Increase',
181
+ gridShrink: '⤒ Decrease',
178
182
  },
179
183
  };
@@ -100,6 +100,9 @@ function buildHTML() {
100
100
  <div class="toolbar-sep"></div>
101
101
  <button class="toolbar-btn toolbar-btn--toggle" id="btn-subtotals">${t('subtotals')}</button>
102
102
  <button class="toolbar-btn" id="btn-export">${t('exportCsv')}</button>
103
+ <div class="toolbar-sep"></div>
104
+ <button class="toolbar-btn" id="btn-grid-grow">${t('gridGrow')}</button>
105
+ <button class="toolbar-btn" id="btn-grid-shrink">${t('gridShrink')}</button>
103
106
  </div>
104
107
 
105
108
  <div id="loading" style="
@@ -146,6 +149,8 @@ if (isEmpty) {
146
149
  pivotEl.innerHTML = buildHTML();
147
150
  }
148
151
 
152
+ const flexWrapper = isEmpty ? pivotEl : document.body;
153
+
149
154
  const gridEl = isEmpty
150
155
  ? document.getElementById('pivot-grid')
151
156
  : pivotEl;
@@ -355,6 +360,20 @@ function initToolbar() {
355
360
  document.getElementById('chk-fields').addEventListener('change', (e) => {
356
361
  document.querySelector('.field-zones').style.display = e.target.checked ? '' : 'none';
357
362
  });
363
+
364
+ document.getElementById('btn-grid-grow').addEventListener('click', () => {
365
+ grid?.growHeight();
366
+ flexWrapper.style.height = ''; // снимаем фиксированную высоту — даём расти
367
+ flexWrapper.style.overflow = 'auto'; // и скроллиться странице
368
+ });
369
+
370
+ document.getElementById('btn-grid-shrink').addEventListener('click', () => {
371
+ const backToOriginal = grid?.shrinkHeight();
372
+ if (backToOriginal) {
373
+ flexWrapper.style.height = '100dvh'; // вернули исходное поведение
374
+ flexWrapper.style.overflow = 'hidden';
375
+ }
376
+ });
358
377
  }
359
378
 
360
379
  // ── CSV export ────────────────────────────────────────────────────────────────