From fc9becaab6dc7ad20796f4015dedd587284f7362 Mon Sep 17 00:00:00 2001 From: Mirko Pecora Date: Mon, 27 Apr 2026 10:47:23 +0200 Subject: [PATCH 1/3] fix: update node styles to support white-space wrapping --- src/dom-renderer/domRenderer.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/dom-renderer/domRenderer.ts b/src/dom-renderer/domRenderer.ts index 334632e..7c03071 100644 --- a/src/dom-renderer/domRenderer.ts +++ b/src/dom-renderer/domRenderer.ts @@ -327,6 +327,8 @@ function updateNodeStyles(node: DOMNode | DOMText) { break; } + style += `white-space: pre-wrap;`; + if (maxLines !== Infinity) { // https://stackoverflow.com/a/13924997 style += `display: -webkit-box; @@ -628,6 +630,7 @@ function updateNodeStyles(node: DOMNode | DOMText) { if (!node.imgEl) { node.imgEl = document.createElement('img'); node.imgEl.alt = ''; + node.imgEl.crossOrigin = 'anonymous'; node.imgEl.setAttribute('aria-hidden', 'true'); node.imgEl.setAttribute('loading', 'lazy'); node.imgEl.removeAttribute('src'); From 9333be0cb04685c85499fcc2652a5b17dd698837 Mon Sep 17 00:00:00 2001 From: Mirko Pecora Date: Mon, 27 Apr 2026 12:51:46 +0200 Subject: [PATCH 2/3] fix: improve image loading handling with opacity transition Co-authored-by: Copilot --- src/dom-renderer/domRenderer.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/dom-renderer/domRenderer.ts b/src/dom-renderer/domRenderer.ts index 7c03071..ec35454 100644 --- a/src/dom-renderer/domRenderer.ts +++ b/src/dom-renderer/domRenderer.ts @@ -421,6 +421,8 @@ function updateNodeStyles(node: DOMNode | DOMText) { 'bottom: 0', 'display: block', 'pointer-events: none', + `opacity: ${node.imageLoading ? 0 : 1}`, + 'transition: opacity 100ms linear', ]; if (props.textureOptions.resizeMode?.type) { @@ -663,10 +665,17 @@ function updateNodeStyles(node: DOMNode | DOMText) { supportsObjectFit, supportsObjectPosition, ); + + // Reveal only after final fit/positioning is applied + if (node.imgEl) { + node.imageLoading = false; + node.imgEl.style.opacity = '1'; + } node.emit('loaded', payload); }); node.imgEl.addEventListener('error', () => { + node.imageLoading = false; if (node.imgEl) { node.imgEl.removeAttribute('src'); node.imgEl.style.display = 'none'; @@ -1055,6 +1064,7 @@ export class DOMNode extends EventEmitter implements IRendererNode { divBg: HTMLElement | undefined; divBorder: HTMLElement | undefined; imgEl: HTMLImageElement | undefined; + imageLoading = false; lazyImagePendingSrc: string | null = null; lazyImageSubTextureProps: | InstanceType['props'] @@ -1163,6 +1173,9 @@ export class DOMNode extends EventEmitter implements IRendererNode { const pendingSrc = this.lazyImagePendingSrc; if (!pendingSrc) return; if (this.imgEl.dataset.rawSrc === pendingSrc) return; + // Hide transient frame while source is loading and being fitted. + this.imageLoading = true; + this.imgEl.style.opacity = '0'; this.imgEl.style.display = ''; this.imgEl.dataset.pendingSrc = pendingSrc; this.imgEl.src = pendingSrc; From d55dc797a98fe90513057359bea7414d87a50618 Mon Sep 17 00:00:00 2001 From: Mirko Pecora Date: Mon, 27 Apr 2026 15:46:10 +0200 Subject: [PATCH 3/3] fix: enhance image loading handling with background layer visibility Co-authored-by: Copilot --- src/dom-renderer/domRenderer.ts | 36 +++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/src/dom-renderer/domRenderer.ts b/src/dom-renderer/domRenderer.ts index ec35454..dd21065 100644 --- a/src/dom-renderer/domRenderer.ts +++ b/src/dom-renderer/domRenderer.ts @@ -617,6 +617,16 @@ function updateNodeStyles(node: DOMNode | DOMText) { node.div.insertBefore(node.divBg, node.div.firstChild); } + const isSyncSubtextureUpdate = + rawImgSrc != null && + srcPos != null && + !!node.imgEl && + node.imgEl.complete && + node.imgEl.dataset.rawSrc === rawImgSrc; + if (isSyncSubtextureUpdate) { + node.imageLoading = true; + } + let bgLayerStyle = 'position: absolute; top:0; left:0; right:0; bottom:0; z-index: -1; pointer-events: none; overflow: hidden;'; if (bgStyle) { @@ -625,6 +635,9 @@ function updateNodeStyles(node: DOMNode | DOMText) { if (maskStyle) { bgLayerStyle += maskStyle; } + if (hasDivBgTint && srcPos != null && node.imageLoading) { + bgLayerStyle += 'opacity: 0;'; + } node.divBg.setAttribute('style', bgLayerStyle + radiusStyle); @@ -671,11 +684,13 @@ function updateNodeStyles(node: DOMNode | DOMText) { node.imageLoading = false; node.imgEl.style.opacity = '1'; } + node.showBackgroundLayer(); node.emit('loaded', payload); }); node.imgEl.addEventListener('error', () => { node.imageLoading = false; + node.showBackgroundLayer(); if (node.imgEl) { node.imgEl.removeAttribute('src'); node.imgEl.style.display = 'none'; @@ -719,6 +734,11 @@ function updateNodeStyles(node: DOMNode | DOMText) { node.imgEl.dataset.rawSrc === rawImgSrc ) { applySubTextureScaling(node, node.imgEl, srcPos); + if (node.imageLoading) { + node.imageLoading = false; + node.imgEl.style.opacity = '1'; + node.showBackgroundLayer(); + } } if ( !srcPos && @@ -1168,6 +1188,21 @@ export class DOMNode extends EventEmitter implements IRendererNode { } } + showBackgroundLayer() { + if (this.divBg) { + this.divBg.style.opacity = '1'; + } + } + + hideMaskedBackgroundLayer() { + if ( + this.divBg && + (this.divBg.style.maskImage || this.divBg.style.webkitMaskImage) + ) { + this.divBg.style.opacity = '0'; + } + } + applyPendingImageSrc() { if (!this.imgEl) return; const pendingSrc = this.lazyImagePendingSrc; @@ -1176,6 +1211,7 @@ export class DOMNode extends EventEmitter implements IRendererNode { // Hide transient frame while source is loading and being fitted. this.imageLoading = true; this.imgEl.style.opacity = '0'; + this.hideMaskedBackgroundLayer(); this.imgEl.style.display = ''; this.imgEl.dataset.pendingSrc = pendingSrc; this.imgEl.src = pendingSrc;