Skip to content

Commit cb80ee2

Browse files
committed
nested grids now support sizeToContent
* nested grids now support `sizeToContent` to size themselves to how many sub items they contain - Thank you [@helix](https://gridstackjs.slack.com/team/U05QT7G8H7T) for sponsoring this!
1 parent 77936c2 commit cb80ee2

File tree

4 files changed

+49
-29
lines changed

4 files changed

+49
-29
lines changed

demo/nested.html

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,9 @@
1313
<div class="container-fluid">
1414
<h1>Nested grids demo</h1>
1515
<p>This example shows v5.x dragging between nested grids (dark yellow) and parent grid (bright yellow.)<br>
16+
Use v9.2 <b>sizeToContent:true</b> on first subgrid item parent to grow/shrink as needed, while leaving leaf green items unchanged.<br>
1617
Uses v3.1 API to load the entire nested grid from JSON.<br>
17-
Nested grids uses new <b>column:'auto'</b> to keep items same size during resize.</p>
18+
Nested grids uses v5 <b>column:'auto'</b> to keep items same size during resize.</p>
1819
<a class="btn btn-primary" onClick="addNested()" href="#">Add Widget</a>
1920
<a class="btn btn-primary" onClick="addNewWidget('.sub1')" href="#">Add Widget Grid1</a>
2021
<a class="btn btn-primary" onClick="addNewWidget('.sub2')" href="#">Add Widget Grid2</a>
@@ -33,7 +34,7 @@ <h1>Nested grids demo</h1>
3334
<script src="events.js"></script>
3435
<script type="text/javascript">
3536
let sub1 = [ {x:0, y:0}, {x:1, y:0}, {x:2, y:0}, {x:3, y:0}, {x:0, y:1}, {x:1, y:1}];
36-
let sub2 = [ {x:0, y:0}, {x:0, y:1, w:2}];
37+
let sub2 = [ {x:0, y:0, h:2}, {x:1, y:1, w:2}];
3738
let count = 0;
3839
[...sub1, ...sub2].forEach(d => d.content = String(count++));
3940
let subOptions = {
@@ -51,7 +52,7 @@ <h1>Nested grids demo</h1>
5152
id: 'main',
5253
children: [
5354
{x:0, y:0, content: 'regular item'},
54-
{x:1, y:0, w:4, h:4, subGridOpts: {children: sub1, id:'sub1_grid', class: 'sub1', ...subOptions}},
55+
{x:1, y:0, w:4, h:4, sizeToContent: true, subGridOpts: {children: sub1, id:'sub1_grid', class: 'sub1', ...subOptions}},
5556
{x:5, y:0, w:3, h:4, subGridOpts: {children: sub2, id:'sub2_grid', class: 'sub2', ...subOptions}},
5657
]
5758
};

doc/CHANGES.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ Change log
55
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
66
**Table of Contents** *generated with [DocToc](http://doctoc.herokuapp.com/)*
77

8-
- [9.1.1-dev (TBD)](#911-dev-tbd)
8+
- [9.2.0 (2023-09-10)](#920-2023-09-10)
99
- [9.1.1 (2023-09-06)](#911-2023-09-06)
1010
- [9.1.0 (2023-09-04)](#910-2023-09-04)
1111
- [9.0.2 (2023-08-29)](#902-2023-08-29)
@@ -98,7 +98,8 @@ Change log
9898

9999
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
100100

101-
## 9.1.1-dev (TBD)
101+
## 9.2.0 (2023-09-10)
102+
* feat: nested grids now support `sizeToContent` to size themselves to how many sub items they contain - Thank you [@Helix](https://gridstackjs.slack.com/team/U05QT7G8H7T) for sponsoring this!
102103
* fix [#2449](https://github.com/gridstack/gridstack.js/issues/2449) full grid maxRow fix
103104

104105
## 9.1.1 (2023-09-06)

src/gridstack.ts

Lines changed: 38 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,7 @@ export class GridStack {
251251
protected _extraDragRow = 0;
252252
/** @internal true if nested grid should get column count from our width */
253253
protected _autoColumn?: boolean;
254+
private _skipInitialResize: boolean;
254255

255256
/**
256257
* Construct a grid item from the given element and options
@@ -654,7 +655,7 @@ export class GridStack {
654655
*
655656
* @example
656657
* see http://gridstackjs.com/demo/serialization.html
657-
**/
658+
*/
658659
public load(layout: GridStackWidget[], addRemove: boolean | AddRemoveFcn = GridStack.addRemoveCB || true): GridStack {
659660
// if passed list has coordinates, use them (insert from end to beginning for conflict resolution) else force widget same order
660661
const haveCoord = layout.some(w => w.x !== undefined || w.y !== undefined);
@@ -823,7 +824,7 @@ export class GridStack {
823824
* 'compact' might re-order items to fill any empty space
824825
*
825826
* doSort - 'false' to let you do your own sorting ahead in case you need to control a different order. (default to sort)
826-
**/
827+
*/
827828
public compact(layout: CompactOptions = 'compact', doSort = true): GridStack {
828829
this.engine.compact(layout, doSort);
829830
this._triggerChangeEvent();
@@ -1266,7 +1267,7 @@ export class GridStack {
12661267
* Updates widget height to match the content height to avoid v-scrollbar or dead space.
12671268
* Note: this assumes only 1 child under resizeToContentParent='.grid-stack-item-content' (sized to gridItem minus padding) that is at the entire content size wanted.
12681269
* useAttrSize set to true if GridStackNode.h should be used instead of actual container height when we don't need to wait for animation to finish to get actual DOM heights
1269-
**/
1270+
*/
12701271
public resizeToContent(el: GridItemHTMLElement, useAttrSize = false) {
12711272
el?.classList.remove('size-to-content-max');
12721273
if (!el?.clientHeight) return; // 0 when hidden, skip
@@ -1282,12 +1283,18 @@ export class GridStack {
12821283
if (n.resizeToContentParent) item = el.querySelector(n.resizeToContentParent);
12831284
if (!item) item = el.querySelector(GridStack.resizeToContentParent);
12841285
if (!item) return;
1285-
const child = item.firstElementChild;
1286-
// NOTE: clientHeight & getBoundingClientRect() is undefined for text and other leaf nodes. use <div> container!
1287-
if (!child) { console.log(`Error: resizeToContent() '${GridStack.resizeToContentParent}'.firstElementChild is null, make sure to have a div like container. Skipping sizing.`); return; }
12881286
const padding = el.clientHeight - item.clientHeight; // full - available height to our child (minus border, padding...)
12891287
const itemH = useAttrSize && n.h ? n.h * cell - padding : item.clientHeight; // calculated to what cellHeight is or will become (rather than actual to prevent waiting for animation to finish)
1290-
const wantedH = child.getBoundingClientRect().height || itemH;
1288+
let wantedH: number;
1289+
if (n.subGrid) {
1290+
// sub-grid - use their actual row count * their cell height
1291+
wantedH = n.subGrid.getRow() * n.subGrid.getCellHeight();
1292+
} else {
1293+
// NOTE: clientHeight & getBoundingClientRect() is undefined for text and other leaf nodes. use <div> container!
1294+
const child = item.firstElementChild;
1295+
if (!child) { console.log(`Error: resizeToContent() '${GridStack.resizeToContentParent}'.firstElementChild is null, make sure to have a div like container. Skipping sizing.`); return; }
1296+
wantedH = child.getBoundingClientRect().height || itemH;
1297+
}
12911298
if (itemH === wantedH) return;
12921299
height += wantedH - itemH;
12931300
let h = Math.ceil(height / cell);
@@ -1495,12 +1502,18 @@ export class GridStack {
14951502
this.el.setAttribute('gs-current-row', String(row));
14961503
if (row === 0) {
14971504
this.el.style.removeProperty('min-height');
1498-
return this;
1505+
} else {
1506+
let cellHeight = this.opts.cellHeight as number;
1507+
let unit = this.opts.cellHeightUnit;
1508+
if (!cellHeight) return this;
1509+
this.el.style.minHeight = row * cellHeight + unit;
14991510
}
1500-
let cellHeight = this.opts.cellHeight as number;
1501-
let unit = this.opts.cellHeightUnit;
1502-
if (!cellHeight) return this;
1503-
this.el.style.minHeight = row * cellHeight + unit;
1511+
1512+
// if we're a nested grid inside an sizeToContent item, tell it to resize itself too
1513+
if (this.parentGridItem && !this.parentGridItem.grid.engine.batchMode && Utils.shouldSizeToContent(this.parentGridItem)) {
1514+
this.parentGridItem.grid.resizeToContentCheck(this.parentGridItem.el);
1515+
}
1516+
15041517
return this;
15051518
}
15061519

@@ -1647,7 +1660,9 @@ export class GridStack {
16471660
this.engine.nodes.forEach(n => {
16481661
if (n.subGrid) n.subGrid.onResize()
16491662
});
1650-
this.doContentResize(columnChanged); // wait for anim of column changed (DOM reflow before we can size correctly)
1663+
1664+
if (!this._skipInitialResize) this.doContentResize(columnChanged); // wait for anim of column changed (DOM reflow before we can size correctly)
1665+
delete this._skipInitialResize;
16511666

16521667
this.batchUpdate(false);
16531668

@@ -1660,14 +1675,15 @@ export class GridStack {
16601675
setTimeout(() => {
16611676
if (n) {
16621677
if (Utils.shouldSizeToContent(n)) this.resizeToContentCheck(n.el, useAttr);
1663-
} else {
1678+
} else if (this.engine.nodes.some(n => Utils.shouldSizeToContent(n))) {
16641679
const nodes = [...this.engine.nodes]; // in case order changes while resizing one
16651680
this.batchUpdate();
16661681
nodes.forEach(n => {
16671682
if (Utils.shouldSizeToContent(n)) this.resizeToContentCheck(n.el, useAttr);
16681683
});
16691684
this.batchUpdate(false);
16701685
}
1686+
// call this regardless of shouldSizeToContent because widget might need to stretch to take available space after a resize
16711687
if (this._gsEventHandler['resizecontent']) this._gsEventHandler['resizecontent'](null, n ? [n] : this.engine.nodes);
16721688
}, delay ? 300 + 10 : 0);
16731689
}
@@ -1676,12 +1692,14 @@ export class GridStack {
16761692
protected _updateResizeEvent(forceRemove = false): GridStack {
16771693
// only add event if we're not nested (parent will call us) and we're auto sizing cells or supporting oneColumn (i.e. doing work)
16781694
// or supporting new sizeToContent option.
1679-
const trackSize = !this.parentGridItem && (this._isAutoCellHeight || this.opts.sizeToContent || !this.opts.disableOneColumnMode || this.engine.nodes.find(n => n.sizeToContent));
1695+
const trackSize = !this.parentGridItem && (this._isAutoCellHeight || this.opts.sizeToContent || !this.opts.disableOneColumnMode
1696+
|| this.engine.nodes.find(n => n.sizeToContent));
16801697

16811698
if (!forceRemove && trackSize && !this.resizeObserver) {
16821699
this._sizeThrottle = Utils.throttle(() => this.onResize(), this.opts.cellHeightThrottle);
16831700
this.resizeObserver = new ResizeObserver(entries => this._sizeThrottle());
16841701
this.resizeObserver.observe(this.el);
1702+
this._skipInitialResize = true; // makeWidget will originally have called on startup
16851703
} else if ((forceRemove || !trackSize) && this.resizeObserver) {
16861704
this.resizeObserver.disconnect();
16871705
delete this.resizeObserver;
@@ -1784,7 +1802,7 @@ export class GridStack {
17841802
* @param dragIn string selector (ex: '.sidebar .grid-stack-item') or list of dom elements
17851803
* @param dragInOptions options - see DDDragInOpt. (default: {handle: '.grid-stack-item-content', appendTo: 'body'}
17861804
* @param root optional root which defaults to document (for shadow dom pas the parent HTMLDocument)
1787-
**/
1805+
*/
17881806
public static setupDragIn(dragIn?: string | HTMLElement[], dragInOptions?: DDDragInOpt, root: HTMLElement | Document = document): void {
17891807
if (dragInOptions?.pause !== undefined) {
17901808
DDManager.pauseDrag = dragInOptions.pause;
@@ -2165,7 +2183,7 @@ export class GridStack {
21652183
return this;
21662184
}
21672185

2168-
/** @internal prepares the element for drag&drop **/
2186+
/** @internal prepares the element for drag&drop */
21692187
protected _prepareDragDropByNode(node: GridStackNode): GridStack {
21702188
let el = node.el;
21712189
const noMove = node.noMove || this.opts.disableDrag;
@@ -2270,7 +2288,7 @@ export class GridStack {
22702288
return this;
22712289
}
22722290

2273-
/** @internal handles actual drag/resize start **/
2291+
/** @internal handles actual drag/resize start */
22742292
protected _onStartMoving(el: GridItemHTMLElement, event: Event, ui: DDUIData, node: GridStackNode, cellWidth: number, cellHeight: number): void {
22752293
this.engine.cleanNodes()
22762294
.beginUpdate(node);
@@ -2301,7 +2319,7 @@ export class GridStack {
23012319
}
23022320
}
23032321

2304-
/** @internal handles actual drag/resize **/
2322+
/** @internal handles actual drag/resize */
23052323
protected _dragOrResize(el: GridItemHTMLElement, event: MouseEvent, ui: DDUIData, node: GridStackNode, cellWidth: number, cellHeight: number): void {
23062324
let p = {...node._orig}; // could be undefined (_isExternal) which is ok (drag only set x,y and w,h will default to node value)
23072325
let resizing: boolean;
@@ -2394,7 +2412,7 @@ export class GridStack {
23942412
/** @internal called when item leaving our area by either cursor dropout event
23952413
* or shape is outside our boundaries. remove it from us, and mark temporary if this was
23962414
* our item to start with else restore prev node values from prev grid it came from.
2397-
**/
2415+
*/
23982416
protected _leave(el: GridItemHTMLElement, helper?: GridItemHTMLElement): void {
23992417
let node = el.gridstackNode;
24002418
if (!node) return;

src/types.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -162,10 +162,6 @@ export interface GridStackOptions {
162162
/** the type of engine to create (so you can subclass) default to GridStackEngine */
163163
engineClass?: typeof GridStackEngine;
164164

165-
/** set to true if all grid items (by default, but item can also override) height should be based on content size instead of WidgetItem.h to avoid v-scrollbars.
166-
Note: this is still row based, not pixels, so it will use ceil(getBoundingClientRect().height / getCellHeight()) */
167-
sizeToContent?: boolean;
168-
169165
/** enable floating widgets (default?: false) See example (http://gridstack.github.io/gridstack.js/demo/float.html) */
170166
float?: boolean;
171167

@@ -245,6 +241,10 @@ export interface GridStackOptions {
245241
*/
246242
rtl?: boolean | 'auto';
247243

244+
/** set to true if all grid items (by default, but item can also override) height should be based on content size instead of WidgetItem.h to avoid v-scrollbars.
245+
Note: this is still row based, not pixels, so it will use ceil(getBoundingClientRect().height / getCellHeight()) */
246+
sizeToContent?: boolean;
247+
248248
/**
249249
* makes grid static (default?: false). If `true` widgets are not movable/resizable.
250250
* You don't even need draggable/resizable. A CSS class

0 commit comments

Comments
 (0)