Skip to content

nested grids now support sizeToContent #2454

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Sep 10, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions demo/nested.html
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,9 @@
<div class="container-fluid">
<h1>Nested grids demo</h1>
<p>This example shows v5.x dragging between nested grids (dark yellow) and parent grid (bright yellow.)<br>
Use v9.2 <b>sizeToContent:true</b> on first subgrid item parent to grow/shrink as needed, while leaving leaf green items unchanged.<br>
Uses v3.1 API to load the entire nested grid from JSON.<br>
Nested grids uses new <b>column:'auto'</b> to keep items same size during resize.</p>
Nested grids uses v5 <b>column:'auto'</b> to keep items same size during resize.</p>
<a class="btn btn-primary" onClick="addNested()" href="#">Add Widget</a>
<a class="btn btn-primary" onClick="addNewWidget('.sub1')" href="#">Add Widget Grid1</a>
<a class="btn btn-primary" onClick="addNewWidget('.sub2')" href="#">Add Widget Grid2</a>
Expand All @@ -33,7 +34,7 @@ <h1>Nested grids demo</h1>
<script src="events.js"></script>
<script type="text/javascript">
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}];
let sub2 = [ {x:0, y:0}, {x:0, y:1, w:2}];
let sub2 = [ {x:0, y:0, h:2}, {x:1, y:1, w:2}];
let count = 0;
[...sub1, ...sub2].forEach(d => d.content = String(count++));
let subOptions = {
Expand All @@ -51,7 +52,7 @@ <h1>Nested grids demo</h1>
id: 'main',
children: [
{x:0, y:0, content: 'regular item'},
{x:1, y:0, w:4, h:4, subGridOpts: {children: sub1, id:'sub1_grid', class: 'sub1', ...subOptions}},
{x:1, y:0, w:4, h:4, sizeToContent: true, subGridOpts: {children: sub1, id:'sub1_grid', class: 'sub1', ...subOptions}},
{x:5, y:0, w:3, h:4, subGridOpts: {children: sub2, id:'sub2_grid', class: 'sub2', ...subOptions}},
]
};
Expand Down
5 changes: 3 additions & 2 deletions doc/CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ Change log
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
**Table of Contents** *generated with [DocToc](http://doctoc.herokuapp.com/)*

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

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

## 9.1.1-dev (TBD)
## 9.2.0 (2023-09-10)
* 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!
* fix [#2449](https://github.com/gridstack/gridstack.js/issues/2449) full grid maxRow fix

## 9.1.1 (2023-09-06)
Expand Down
58 changes: 38 additions & 20 deletions src/gridstack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,7 @@ export class GridStack {
protected _extraDragRow = 0;
/** @internal true if nested grid should get column count from our width */
protected _autoColumn?: boolean;
private _skipInitialResize: boolean;

/**
* Construct a grid item from the given element and options
Expand Down Expand Up @@ -654,7 +655,7 @@ export class GridStack {
*
* @example
* see http://gridstackjs.com/demo/serialization.html
**/
*/
public load(layout: GridStackWidget[], addRemove: boolean | AddRemoveFcn = GridStack.addRemoveCB || true): GridStack {
// if passed list has coordinates, use them (insert from end to beginning for conflict resolution) else force widget same order
const haveCoord = layout.some(w => w.x !== undefined || w.y !== undefined);
Expand Down Expand Up @@ -823,7 +824,7 @@ export class GridStack {
* 'compact' might re-order items to fill any empty space
*
* doSort - 'false' to let you do your own sorting ahead in case you need to control a different order. (default to sort)
**/
*/
public compact(layout: CompactOptions = 'compact', doSort = true): GridStack {
this.engine.compact(layout, doSort);
this._triggerChangeEvent();
Expand Down Expand Up @@ -1266,7 +1267,7 @@ export class GridStack {
* Updates widget height to match the content height to avoid v-scrollbar or dead space.
* 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.
* 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
**/
*/
public resizeToContent(el: GridItemHTMLElement, useAttrSize = false) {
el?.classList.remove('size-to-content-max');
if (!el?.clientHeight) return; // 0 when hidden, skip
Expand All @@ -1282,12 +1283,18 @@ export class GridStack {
if (n.resizeToContentParent) item = el.querySelector(n.resizeToContentParent);
if (!item) item = el.querySelector(GridStack.resizeToContentParent);
if (!item) return;
const child = item.firstElementChild;
// NOTE: clientHeight & getBoundingClientRect() is undefined for text and other leaf nodes. use <div> container!
if (!child) { console.log(`Error: resizeToContent() '${GridStack.resizeToContentParent}'.firstElementChild is null, make sure to have a div like container. Skipping sizing.`); return; }
const padding = el.clientHeight - item.clientHeight; // full - available height to our child (minus border, padding...)
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)
const wantedH = child.getBoundingClientRect().height || itemH;
let wantedH: number;
if (n.subGrid) {
// sub-grid - use their actual row count * their cell height
wantedH = n.subGrid.getRow() * n.subGrid.getCellHeight();
} else {
// NOTE: clientHeight & getBoundingClientRect() is undefined for text and other leaf nodes. use <div> container!
const child = item.firstElementChild;
if (!child) { console.log(`Error: resizeToContent() '${GridStack.resizeToContentParent}'.firstElementChild is null, make sure to have a div like container. Skipping sizing.`); return; }
wantedH = child.getBoundingClientRect().height || itemH;
}
if (itemH === wantedH) return;
height += wantedH - itemH;
let h = Math.ceil(height / cell);
Expand Down Expand Up @@ -1495,12 +1502,18 @@ export class GridStack {
this.el.setAttribute('gs-current-row', String(row));
if (row === 0) {
this.el.style.removeProperty('min-height');
return this;
} else {
let cellHeight = this.opts.cellHeight as number;
let unit = this.opts.cellHeightUnit;
if (!cellHeight) return this;
this.el.style.minHeight = row * cellHeight + unit;
}
let cellHeight = this.opts.cellHeight as number;
let unit = this.opts.cellHeightUnit;
if (!cellHeight) return this;
this.el.style.minHeight = row * cellHeight + unit;

// if we're a nested grid inside an sizeToContent item, tell it to resize itself too
if (this.parentGridItem && !this.parentGridItem.grid.engine.batchMode && Utils.shouldSizeToContent(this.parentGridItem)) {
this.parentGridItem.grid.resizeToContentCheck(this.parentGridItem.el);
}

return this;
}

Expand Down Expand Up @@ -1647,7 +1660,9 @@ export class GridStack {
this.engine.nodes.forEach(n => {
if (n.subGrid) n.subGrid.onResize()
});
this.doContentResize(columnChanged); // wait for anim of column changed (DOM reflow before we can size correctly)

if (!this._skipInitialResize) this.doContentResize(columnChanged); // wait for anim of column changed (DOM reflow before we can size correctly)
delete this._skipInitialResize;

this.batchUpdate(false);

Expand All @@ -1660,14 +1675,15 @@ export class GridStack {
setTimeout(() => {
if (n) {
if (Utils.shouldSizeToContent(n)) this.resizeToContentCheck(n.el, useAttr);
} else {
} else if (this.engine.nodes.some(n => Utils.shouldSizeToContent(n))) {
const nodes = [...this.engine.nodes]; // in case order changes while resizing one
this.batchUpdate();
nodes.forEach(n => {
if (Utils.shouldSizeToContent(n)) this.resizeToContentCheck(n.el, useAttr);
});
this.batchUpdate(false);
}
// call this regardless of shouldSizeToContent because widget might need to stretch to take available space after a resize
if (this._gsEventHandler['resizecontent']) this._gsEventHandler['resizecontent'](null, n ? [n] : this.engine.nodes);
}, delay ? 300 + 10 : 0);
}
Expand All @@ -1676,12 +1692,14 @@ export class GridStack {
protected _updateResizeEvent(forceRemove = false): GridStack {
// 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)
// or supporting new sizeToContent option.
const trackSize = !this.parentGridItem && (this._isAutoCellHeight || this.opts.sizeToContent || !this.opts.disableOneColumnMode || this.engine.nodes.find(n => n.sizeToContent));
const trackSize = !this.parentGridItem && (this._isAutoCellHeight || this.opts.sizeToContent || !this.opts.disableOneColumnMode
|| this.engine.nodes.find(n => n.sizeToContent));

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

/** @internal prepares the element for drag&drop **/
/** @internal prepares the element for drag&drop */
protected _prepareDragDropByNode(node: GridStackNode): GridStack {
let el = node.el;
const noMove = node.noMove || this.opts.disableDrag;
Expand Down Expand Up @@ -2270,7 +2288,7 @@ export class GridStack {
return this;
}

/** @internal handles actual drag/resize start **/
/** @internal handles actual drag/resize start */
protected _onStartMoving(el: GridItemHTMLElement, event: Event, ui: DDUIData, node: GridStackNode, cellWidth: number, cellHeight: number): void {
this.engine.cleanNodes()
.beginUpdate(node);
Expand Down Expand Up @@ -2301,7 +2319,7 @@ export class GridStack {
}
}

/** @internal handles actual drag/resize **/
/** @internal handles actual drag/resize */
protected _dragOrResize(el: GridItemHTMLElement, event: MouseEvent, ui: DDUIData, node: GridStackNode, cellWidth: number, cellHeight: number): void {
let p = {...node._orig}; // could be undefined (_isExternal) which is ok (drag only set x,y and w,h will default to node value)
let resizing: boolean;
Expand Down Expand Up @@ -2394,7 +2412,7 @@ export class GridStack {
/** @internal called when item leaving our area by either cursor dropout event
* or shape is outside our boundaries. remove it from us, and mark temporary if this was
* our item to start with else restore prev node values from prev grid it came from.
**/
*/
protected _leave(el: GridItemHTMLElement, helper?: GridItemHTMLElement): void {
let node = el.gridstackNode;
if (!node) return;
Expand Down
8 changes: 4 additions & 4 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -162,10 +162,6 @@ export interface GridStackOptions {
/** the type of engine to create (so you can subclass) default to GridStackEngine */
engineClass?: typeof GridStackEngine;

/** 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.
Note: this is still row based, not pixels, so it will use ceil(getBoundingClientRect().height / getCellHeight()) */
sizeToContent?: boolean;

/** enable floating widgets (default?: false) See example (http://gridstack.github.io/gridstack.js/demo/float.html) */
float?: boolean;

Expand Down Expand Up @@ -245,6 +241,10 @@ export interface GridStackOptions {
*/
rtl?: boolean | 'auto';

/** 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.
Note: this is still row based, not pixels, so it will use ceil(getBoundingClientRect().height / getCellHeight()) */
sizeToContent?: boolean;

/**
* makes grid static (default?: false). If `true` widgets are not movable/resizable.
* You don't even need draggable/resizable. A CSS class
Expand Down