Hi! 👋
Firstly, thanks for your work on this project! 🙂
Today I used patch-package to patch @native-html/transient-render-engine@12.0.0 for the project I'm working on.
I ran into an issue with whitespace collapsing around nested inline phrasing nodes.
Problem
Spaces can disappear when they are stored at the boundary of a named inline wrapper such as span, instead of being attached directly to a text node.
For example, this renders without the expected space between foo and bar:
<span><span><strong>foo</strong> </span><span><strong>bar</strong></span></span>
In my real case, the HTML comes from a backend rich-text editor and contains lots of nested inline tags (span, strong, colored text, etc.).
So visually separate words like:
<span style="color:#ffdb80"><strong>Foo</strong> </span>
<span style="color:#000000"><strong>Bar</strong> </span>
<span style="color:#5cce89"><strong>Baz</strong></span>
were rendered as if the spaces between words did not exist.
Expected behavior
Whitespace collapsing should preserve a single visible space between adjacent inline phrasing siblings, even if that space is wrapped in nested inline tags.
So this:
<span><span><strong>foo</strong> </span><span><strong>bar</strong></span></span>
should render like:
foo bar
Actual behavior
The space is trimmed too early during phrasing collapse, so the final output becomes:
foobar
Why this seems to happen
From debugging the transient tree, it looks like TPhrasingCtor.collapseChildren() always calls:
this.trimLeft();
this.trimRight();
after collapsing its children.
That works for anonymous phrasing containers, but for named inline wrappers like span it removes boundary spaces before the parent phrasing node gets a chance to collapse sibling boundaries correctly.
So a structure like:
<span>
<strong>foo</strong>
" "
</span>
<span>
<strong>bar</strong>
</span>
loses the trailing space inside the first span before the outer phrasing container compares the two sibling spans.
Why the patch seems correct
The patch only prevents this eager trimming for named phrasing nodes.
Anonymous phrasing nodes still trim as before.
This preserves boundary spaces for nested inline wrappers while keeping existing whitespace collapsing behavior for normal anonymous phrasing containers.
Validation
After applying this patch, the nested-inline case above works correctly.
I also ran the library's published Jest suites for both:
@native-html/transient-render-engine
@native-html/render
and the existing tests still passed with this change.
Here is the diff that solved my problem:
diff --git a/node_modules/@native-html/render/node_modules/@native-html/transient-render-engine/lib/commonjs/tree/TPhrasingCtor.js b/node_modules/@native-html/render/node_modules/@native-html/transient-render-engine/lib/commonjs/tree/TPhrasingCtor.js
index e735f56..579a4b3 100644
--- a/node_modules/@native-html/render/node_modules/@native-html/transient-render-engine/lib/commonjs/tree/TPhrasingCtor.js
+++ b/node_modules/@native-html/render/node_modules/@native-html/transient-render-engine/lib/commonjs/tree/TPhrasingCtor.js
@@ -35,8 +35,12 @@ TPhrasingCtor.prototype.collapseChildren = function collapseChildren() {
}
previous = childK;
});
- this.trimLeft();
- this.trimRight();
+ // Preserve boundary spaces for named inline wrappers (e.g. styled spans) so
+ // their parent phrasing container can collapse sibling boundaries correctly.
+ if (this.tagName === null) {
+ this.trimLeft();
+ this.trimRight();
+ }
return null;
};
var _default = exports.default = TPhrasingCtor;
diff --git a/node_modules/@native-html/render/node_modules/@native-html/transient-render-engine/src/flow/__tests__/collapse.test.ts b/node_modules/@native-html/render/node_modules/@native-html/transient-render-engine/src/flow/__tests__/collapse.test.ts
index 21bf432..944cf9f 100644
--- a/node_modules/@native-html/render/node_modules/@native-html/transient-render-engine/src/flow/__tests__/collapse.test.ts
+++ b/node_modules/@native-html/render/node_modules/@native-html/transient-render-engine/src/flow/__tests__/collapse.test.ts
@@ -37,6 +37,19 @@ describe('collapse function', () => {
const ttree = makeTTree('<span><span>foo </span><span> bar</span></span>');
expect(ttree).toMatchSnapshot();
});
+ it('should preserve boundary spaces wrapped in nested inline phrasing tags', () => {
+ const ttree = makeTTree(
+ '<span><span><strong>foo</strong> </span><span><strong>bar</strong></span></span>'
+ );
+ const [firstSpan, secondSpan] = ttree.children;
+ expect(firstSpan.tagName).toBe('span');
+ expect(firstSpan.children).toHaveLength(2);
+ expect((firstSpan.children[0] as TTextImpl).data).toBe('foo');
+ expect((firstSpan.children[1] as TTextImpl).data).toBe(' ');
+ expect(secondSpan.tagName).toBe('span');
+ expect(secondSpan.children).toHaveLength(1);
+ expect((secondSpan.children[0] as TTextImpl).data).toBe('bar');
+ });
it('should handle nested anchors', () => {
const ttree = makeTTree(nestedHyperlinksSource);
expect(ttree).toMatchSnapshot();
diff --git a/node_modules/@native-html/render/node_modules/@native-html/transient-render-engine/src/tree/TPhrasingCtor.ts b/node_modules/@native-html/render/node_modules/@native-html/transient-render-engine/src/tree/TPhrasingCtor.ts
index bcf6131..5ad6bac 100644
--- a/node_modules/@native-html/render/node_modules/@native-html/transient-render-engine/src/tree/TPhrasingCtor.ts
+++ b/node_modules/@native-html/render/node_modules/@native-html/transient-render-engine/src/tree/TPhrasingCtor.ts
@@ -48,8 +48,12 @@ TPhrasingCtor.prototype.collapseChildren = function collapseChildren() {
}
previous = childK;
});
- this.trimLeft();
- this.trimRight();
+ // Preserve boundary spaces for named inline wrappers (e.g. styled spans) so
+ // their parent phrasing container can collapse sibling boundaries correctly.
+ if (this.tagName === null) {
+ this.trimLeft();
+ this.trimRight();
+ }
return null;
};
This issue body was partially generated by patch-package.
Hi! 👋
Firstly, thanks for your work on this project! 🙂
Today I used patch-package to patch
@native-html/transient-render-engine@12.0.0for the project I'm working on.I ran into an issue with whitespace collapsing around nested inline phrasing nodes.
Problem
Spaces can disappear when they are stored at the boundary of a named inline wrapper such as
span, instead of being attached directly to a text node.For example, this renders without the expected space between
fooandbar:In my real case, the HTML comes from a backend rich-text editor and contains lots of nested inline tags (span, strong, colored text, etc.).
So visually separate words like:
were rendered as if the spaces between words did not exist.
Expected behavior
Whitespace collapsing should preserve a single visible space between adjacent inline phrasing siblings, even if that space is wrapped in nested inline tags.
So this:
should render like:
foo barActual behavior
The space is trimmed too early during phrasing collapse, so the final output becomes:
foobarWhy this seems to happen
From debugging the transient tree, it looks like
TPhrasingCtor.collapseChildren()always calls:after collapsing its children.
That works for anonymous phrasing containers, but for named inline wrappers like span it removes boundary spaces before the parent phrasing node gets a chance to collapse sibling boundaries correctly.
So a structure like:
loses the trailing space inside the first span before the outer phrasing container compares the two sibling spans.
Why the patch seems correct
The patch only prevents this eager trimming for named phrasing nodes.
Anonymous phrasing nodes still trim as before.
This preserves boundary spaces for nested inline wrappers while keeping existing whitespace collapsing behavior for normal anonymous phrasing containers.
Validation
After applying this patch, the nested-inline case above works correctly.
I also ran the library's published Jest suites for both:
@native-html/transient-render-engine@native-html/renderand the existing tests still passed with this change.
Here is the diff that solved my problem:
This issue body was partially generated by patch-package.