diff --git a/doc/CHANGES.md b/doc/CHANGES.md index 0516854b6..c29ed6b7e 100644 --- a/doc/CHANGES.md +++ b/doc/CHANGES.md @@ -99,6 +99,7 @@ Change log ## 9.1.0-dev (TBD) * fix [#2435](https://github.com/gridstack/gridstack.js/issues/2435) directionCollideCoverage() tweaks +* fix resizeToContent() to handle node.h (using when cellHeight changes or we resize) vs DOM sizing (rest of the time) ## 9.1.0 (2023-09-04) * renamed fitToContent to sizeToContent (API BREAK) diff --git a/src/gridstack.scss b/src/gridstack.scss index 426a35e77..a42fb7d90 100644 --- a/src/gridstack.scss +++ b/src/gridstack.scss @@ -51,7 +51,7 @@ $animation_speed: .3s !default; overflow-x: hidden; overflow-y: auto; } - &.size-to-content > .grid-stack-item-content { + &.size-to-content:not(.size-to-content-max) > .grid-stack-item-content { overflow-y: hidden; } } diff --git a/src/gridstack.ts b/src/gridstack.ts index 74c5a71cd..11d4712ac 100644 --- a/src/gridstack.ts +++ b/src/gridstack.ts @@ -798,7 +798,7 @@ export class GridStack { this.opts.cellHeightUnit = data.unit; this.opts.cellHeight = data.h; - this.doContentResize(false); + this.doContentResize(false, true); // no anim wait, but use attributes since we only change row height if (update) { this._updateStyles(true); // true = force re-create for current # of rows @@ -1002,7 +1002,7 @@ export class GridStack { this._updateContainerHeight(); - this.doContentResize(false, node); + this.doContentResize(false, false, node); // see if there is a sub-grid to create if (node.subGridOpts) { @@ -1262,54 +1262,54 @@ export class GridStack { this.engine.endUpdate(); } - /** Updates widget height to match the content height to avoid v-scrollbar or dead space. - Note: this assumes only 1 child under '.grid-stack-item-content' (sized to gridItem minus padding) that is at the entire content size wanted */ - public resizeToContent(els: GridStackElement) { - GridStack.getElements(els).forEach(el => { - if (!el.clientHeight) return; // 0 when hidden, skip - let n = el?.gridstackNode; - if (!n) return; - const grid = n.grid; - if (grid !== this) return grid?.resizeToContent(el); - if (el.parentElement !== this.el) return; // skip if we are not inside a grid - const cell = this.getCellHeight(); - if (!cell) return; - let height = n.h ? n.h * cell : el.clientHeight; // getBoundingClientRect().height seem to flicker back and forth - let item: Element; - 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
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 = 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; - if (itemH === wantedH) return; - height += wantedH - itemH; - let h = Math.ceil(height / cell); - // check for min/max and special sizing - const softMax = Number.isInteger(n.sizeToContent) ? n.sizeToContent as number : 0; - if (softMax) { - if (h > softMax) { - h = softMax; - el.classList.remove('size-to-content'); // get v-scroll back - } else el.classList.add('size-to-content'); - } - if (n.minH && h < n.minH) h = n.minH; - else if (n.maxH && h > n.maxH) h = n.maxH; - if (h !== n.h) { - this._ignoreLayoutsNodeChange = true; - this.moveNode(n, {h}); - delete this._ignoreLayoutsNodeChange; - } - }); + /** + * 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 + let n = el.gridstackNode; + if (!n) return; + const grid = n.grid; + if (!grid) return; + if (el.parentElement !== grid.el) return; // skip if we are not inside a grid + const cell = grid.getCellHeight(); + if (!cell) return; + let height = useAttrSize && n.h ? n.h * cell : el.clientHeight; // getBoundingClientRect().height seem to flicker back and forth + let item: Element; + 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
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; + if (itemH === wantedH) return; + height += wantedH - itemH; + let h = Math.ceil(height / cell); + // check for min/max and special sizing + const softMax = Number.isInteger(n.sizeToContent) ? n.sizeToContent as number : 0; + if (softMax && h > softMax) { + h = softMax; + el.classList.add('size-to-content-max'); // get v-scroll back + } + if (n.minH && h < n.minH) h = n.minH; + else if (n.maxH && h > n.maxH) h = n.maxH; + if (h !== n.h) { + grid._ignoreLayoutsNodeChange = true; + grid.moveNode(n, {h}); + delete grid._ignoreLayoutsNodeChange; + } } /** call the user resize (so we can do extra work) else our build in version */ - protected resizeToContentCheck(el: GridItemHTMLElement) { + protected resizeToContentCheck(el: GridItemHTMLElement, useAttr = false) { if (GridStack.resizeToContentCB) GridStack.resizeToContentCB(el); - else this.resizeToContent(el); + else this.resizeToContent(el, useAttr); } /** @@ -1647,24 +1647,24 @@ export class GridStack { this.engine.nodes.forEach(n => { if (n.subGrid) n.subGrid.onResize() }); - this.doContentResize(columnChanged); + this.doContentResize(columnChanged); // wait for anim of column changed (DOM reflow before we can size correctly) this.batchUpdate(false); return this; } - private doContentResize(delay = true, n: GridStackNode = undefined) { + private doContentResize(delay = true, useAttr = false, n: GridStackNode = undefined) { // update any gridItem height with sizeToContent, but wait for DOM $animation_speed to settle if we changed column count // TODO: is there a way to know what the final (post animation) size of the content will be so we can animate the column width and height together rather than sequentially ? setTimeout(() => { if (n) { - if (Utils.shouldSizeToContent(n)) this.resizeToContentCheck(n.el); + if (Utils.shouldSizeToContent(n)) this.resizeToContentCheck(n.el, useAttr); } else { 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); + if (Utils.shouldSizeToContent(n)) this.resizeToContentCheck(n.el, useAttr); }); this.batchUpdate(false); } @@ -2247,7 +2247,7 @@ export class GridStack { if (event.type === 'resizestop') { if (Number.isInteger(node.sizeToContent)) node.sizeToContent = node.h; // new soft limit - this.doContentResize(false, node); + this.doContentResize(false, true, node); // no amin wait as will use the actual sized coordinate attr } }