diff --git a/bzGraflow.js b/bzGraflow.js index 289278a..02fb486 100644 --- a/bzGraflow.js +++ b/bzGraflow.js @@ -505,15 +505,86 @@ class BZgraflow extends Buildoz{ else this.currentOrientation = 'vertical' } } - if(forceAutoplace) this.autoPlace(this.currentOrientation, parseInt(this.getBZAttribute('gapx')) || 80, parseInt(this.getBZAttribute('gapy')) || 80) - this.fireEvent('refreshed', { }) - if(this.hasAttribute('autofit')){ - const autofitAttr = this.getAttribute('autofit') - const autofitPercent = (autofitAttr !== null && autofitAttr !== '' && !Number.isNaN(parseFloat(autofitAttr))) - ? parseFloat(autofitAttr) - : undefined - this.autofit(autofitPercent) + const gapx = parseInt(this.getBZAttribute('gapx')) || 80 + const gapy = parseInt(this.getBZAttribute('gapy')) || 80 + const finishRefresh = () => { + this.fireEvent('refreshed', { }) + if(this.hasAttribute('autofit')){ + const autofitAttr = this.getAttribute('autofit') + const autofitPercent = (autofitAttr !== null && autofitAttr !== '' && !Number.isNaN(parseFloat(autofitAttr))) + ? parseFloat(autofitAttr) + : undefined + if(this._canRunAutoPlace()) this.autofit(autofitPercent) + else this._scheduleLayoutWhenReady(() => this.autofit(autofitPercent)) + } } + if(forceAutoplace) this._scheduleAutoPlaceWhenReady(this.currentOrientation, gapx, gapy, finishRefresh) + else finishRefresh() + } + + disconnectedCallback(){ + this._disconnectLayoutObserver() + super.disconnectedCallback?.() + } + + _disconnectLayoutObserver(){ + if(this._layoutObserver) { + this._layoutObserver.disconnect() + this._layoutObserver = null + } + } + + _canRunAutoPlace(){ + let nodesHaveLayoutSize = false + const ids = Object.keys(this.stagedNodes || {}) + if(ids.length === 0) return(true) + for(const nid of ids){ + if(nid.startsWith('longLinkPlaceHolder_')) continue + const el = this.stagedNodes[nid] + if(el && (el.offsetWidth > 0 || el.offsetHeight > 0)) { + nodesHaveLayoutSize=true + break + } + } + return((this.clientWidth > 0 && this.clientHeight > 0) && nodesHaveLayoutSize) + } + + /** + * autoPlace uses offsetWidth/offsetHeight; when the host is hidden (display:none / off-screen view) + * those are 0 and layout is wrong. Defer until the element is measurable. + */ + _scheduleLayoutWhenReady(callback){ + this._layoutScheduleToken = (this._layoutScheduleToken || 0) + 1 + const token = this._layoutScheduleToken + this._disconnectLayoutObserver() + + const attempt = () => { + if(token !== this._layoutScheduleToken) return(true) + if(!this._canRunAutoPlace()) return(false) + this._disconnectLayoutObserver() + callback() + return(true) + } + + if(attempt()) return + + this._layoutObserver = new ResizeObserver(() => { attempt() }) + this._layoutObserver.observe(this) + + let frames = 0 + const rafPoll = () => { + if(token !== this._layoutScheduleToken) return + if(attempt()) return + if(++frames < 12) requestAnimationFrame(rafPoll) + } + requestAnimationFrame(rafPoll) + } + + _scheduleAutoPlaceWhenReady(orientation, gapx, gapy, onDone){ + this._scheduleLayoutWhenReady(() => { + this.autoPlace(orientation, gapx, gapy) + if(typeof onDone === 'function') onDone() + }) } // Convert viewport (client) coordinates to this instance's SVG local coordinates.