Skip to content

Commit 4c55f08

Browse files
authored
Fix TR background color doesn't fill the whole row (#1049)
1 parent 3c8d470 commit 4c55f08

File tree

8 files changed

+295
-7
lines changed

8 files changed

+295
-7
lines changed

demo_app/test/table/row_color.png

11.6 KB
Loading

packages/core/lib/src/core_data.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ part 'data/build_bits.dart';
1010
part 'data/css.dart';
1111
part 'data/html_style.dart';
1212
part 'data/image.dart';
13+
part 'data/lockable_list.dart';
1314
part 'data/normal_line_height.dart';
1415

1516
/// A collection of style's key and value pairs.

packages/core/lib/src/data/build_bits.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,7 @@ abstract class BuildTree extends BuildBit {
190190
/// - `HtmlWidget.customStylesBuilder`
191191
/// - [BuildOp.defaultStyles]
192192
/// - Attribute `style` of [dom.Element]
193-
Iterable<css.Declaration> get styles;
193+
LockableList<css.Declaration> get styles;
194194

195195
/// Adds an inline style.
196196
@Deprecated('Use BuildOp.defaultStyles instead.')
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
part of '../core_data.dart';
2+
3+
/// A collection of values, that may be locked to prevent modification.
4+
abstract class LockableList<T> extends Iterable<T> {
5+
/// Returns `true` if this list should not be modified.
6+
bool get isLocked;
7+
8+
/// Adds [value] to the end of this list.
9+
void add(T value);
10+
11+
/// Appends all objects of [iterable] to the end of this list.
12+
void addAll(Iterable<T> iterable);
13+
14+
/// Inserts [element] at position [index] in this list.
15+
void insert(int index, T element);
16+
17+
/// Inserts all objects of [iterable] at position [index] in this list.
18+
void insertAll(int index, Iterable<T> iterable);
19+
}

packages/core/lib/src/internal/core_build_tree.dart

Lines changed: 54 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ class CoreBuildTree extends BuildTree {
3030
final _isInlines = <bool>[];
3131
final BuildTree? _parent;
3232
final Iterable<_CoreBuildOp> _parentOps;
33-
final _styles = <css.Declaration>[];
33+
final _styles = _LockableDeclarations();
3434

3535
CoreBuildTree._({
3636
this.customStylesBuilder,
@@ -67,7 +67,7 @@ class CoreBuildTree extends BuildTree {
6767
BuildTree get parent => _parent!;
6868

6969
@override
70-
Iterable<css.Declaration> get styles => _styles;
70+
LockableList<css.Declaration> get styles => _styles;
7171

7272
@override
7373
void operator []=(String key, String value) {
@@ -159,7 +159,12 @@ class CoreBuildTree extends BuildTree {
159159

160160
@override
161161
css.Declaration? getStyle(String property) {
162-
for (final style in _styles.reversed) {
162+
final values = _styles._values;
163+
if (values == null) {
164+
return null;
165+
}
166+
167+
for (final style in values.reversed) {
163168
if (style.property == property) {
164169
// TODO: add support for `!important`
165170
// https://github.com/daohoangson/flutter_widget_from_html/issues/773
@@ -308,8 +313,12 @@ class CoreBuildTree extends BuildTree {
308313
_styles.addAll(elementStyles);
309314
}
310315

311-
for (final style in _styles) {
312-
wf.parseStyle(this, style);
316+
_styles._isLocked = true;
317+
final values = _styles._values;
318+
if (values != null) {
319+
for (final style in values.toList(growable: false)) {
320+
wf.parseStyle(this, style);
321+
}
313322
}
314323

315324
wf.parseStyleDisplay(this, getStyle(kCssDisplay)?.term);
@@ -381,6 +390,46 @@ class _CoreBuildOp {
381390
}
382391
}
383392

393+
class _LockableDeclarations extends LockableList<css.Declaration> {
394+
var _isLocked = false;
395+
List<css.Declaration>? _values;
396+
397+
@override
398+
bool get isLocked => _isLocked;
399+
400+
@override
401+
Iterator<css.Declaration> get iterator =>
402+
_values?.iterator ?? const <css.Declaration>[].iterator;
403+
404+
@override
405+
void add(css.Declaration value) {
406+
assert(!_isLocked, 'Adding after being locked is undeterministic.');
407+
_values ??= [];
408+
_values?.add(value);
409+
}
410+
411+
@override
412+
void addAll(Iterable<css.Declaration> iterable) {
413+
assert(!_isLocked, 'Adding after being locked is undeterministic.');
414+
_values ??= [];
415+
_values?.addAll(iterable);
416+
}
417+
418+
@override
419+
void insert(int index, css.Declaration element) {
420+
assert(!_isLocked, 'Inserting after being locked is undeterministic.');
421+
_values ??= [];
422+
_values?.insert(index, element);
423+
}
424+
425+
@override
426+
void insertAll(int index, Iterable<css.Declaration> iterable) {
427+
assert(!_isLocked, 'Inserting after being locked is undeterministic.');
428+
_values ??= [];
429+
_values?.insertAll(index, iterable);
430+
}
431+
}
432+
384433
class _WidgetPlaceholderDefault extends StatelessWidget
385434
implements
386435
// ignore: avoid_implementing_value_types

packages/core/lib/src/internal/ops/tag_table.dart

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -338,6 +338,7 @@ class _TagTableRow {
338338

339339
_TagTableRow() {
340340
_rowOp = BuildOp.v1(
341+
alwaysRenderBlock: true,
341342
debugLabel: kTagTableRow,
342343
onChild: _onRowChild,
343344
priority: Priority.tagTableRow,
@@ -358,6 +359,11 @@ class _TagTableRow {
358359
return;
359360
}
360361

362+
for (final style in rowTree.styles) {
363+
// forward all row styles to its cells, this is quite dangerous...
364+
cellTree.styles.add(style);
365+
}
366+
361367
_registerCellOp(cellTree);
362368
}
363369

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
import 'package:csslib/parser.dart' as css;
2+
import 'package:csslib/visitor.dart' as css;
3+
import 'package:flutter_test/flutter_test.dart';
4+
import 'package:flutter_widget_from_html_core/flutter_widget_from_html_core.dart';
5+
import 'package:flutter_widget_from_html_core/src/internal/core_build_tree.dart';
6+
import 'package:html/dom.dart' as dom;
7+
import 'package:html/parser.dart' as parser;
8+
9+
void main() {
10+
group('CoreBuildTree', () {
11+
group('styles', () {
12+
test('is unlocked initially', () {
13+
final tree = _newTree();
14+
expect(tree.styles.isLocked, isFalse);
15+
});
16+
17+
test('is unlocked in onChild', () {
18+
final list = <bool>[];
19+
final tree = _newTree(
20+
wf: _WidgetFactory(
21+
onParse: (tree) => tree.register(
22+
BuildOp.v1(
23+
onChild: (_, subTree) => list.add(subTree.styles.isLocked),
24+
),
25+
),
26+
),
27+
);
28+
tree.addBitsFromNodes(_parseHtml('<div><span>Foo</span></div>'));
29+
expect(list, equals([false]));
30+
});
31+
32+
test('is locked in parseStyle', () {
33+
final list = <bool>[];
34+
final tree = _newTree(
35+
wf: _WidgetFactory(
36+
onParseStyle: (tree) => list.add(tree.styles.isLocked),
37+
),
38+
);
39+
tree.addBitsFromNodes(_parseHtml('<div>Foo</div>'));
40+
expect(list, equals([true]));
41+
});
42+
43+
group('modifications', () {
44+
final red = _parseCss('color: red').first;
45+
final green = _parseCss('color: green').first;
46+
final blue = _parseCss('color: blue').first;
47+
48+
test('add value', () {
49+
final tree = _newTree();
50+
tree.styles.add(red);
51+
expect(tree.styles.toList(), [red]);
52+
});
53+
54+
test('add all values', () {
55+
final tree = _newTree();
56+
tree.styles.addAll([red, green]);
57+
expect(tree.styles.toList(), [red, green]);
58+
});
59+
60+
test('insert value', () {
61+
final tree = _newTree();
62+
tree.styles.insert(0, red);
63+
expect(tree.styles.toList(), [red]);
64+
});
65+
66+
test('insert all values', () {
67+
final tree = _newTree();
68+
tree.styles.insertAll(0, [red, green]);
69+
expect(tree.styles.toList(), [red, green]);
70+
});
71+
72+
test('insert into the middle', () {
73+
final tree = _newTree();
74+
tree.styles.addAll([red, blue]);
75+
tree.styles.insert(1, green);
76+
expect(tree.styles.toList(), [red, green, blue]);
77+
});
78+
79+
test('throw error while being locked', () {
80+
Object? addError;
81+
final tree = _newTree(
82+
wf: _WidgetFactory(
83+
onParseStyle: (tree) {
84+
try {
85+
tree.styles.add(red);
86+
} catch (e) {
87+
addError = e;
88+
}
89+
},
90+
),
91+
);
92+
tree.addBitsFromNodes(_parseHtml('<div>Foo</div>'));
93+
expect(addError, isAssertionError);
94+
});
95+
});
96+
});
97+
});
98+
}
99+
100+
CoreBuildTree _newTree({
101+
WidgetFactory? wf,
102+
}) =>
103+
CoreBuildTree.root(
104+
styleBuilder: HtmlStyleBuilder(),
105+
wf: wf ?? _WidgetFactory(),
106+
);
107+
108+
List<css.Declaration> _parseCss(String input) =>
109+
css.parse('*{$input}').collectDeclarations();
110+
111+
dom.NodeList _parseHtml(String html) =>
112+
parser.HtmlParser(html, parseMeta: false).parseFragment().nodes;
113+
114+
class _WidgetFactory extends WidgetFactory {
115+
final void Function(BuildTree)? onParse;
116+
final void Function(BuildTree)? onParseStyle;
117+
118+
_WidgetFactory({
119+
this.onParse,
120+
this.onParseStyle,
121+
});
122+
123+
@override
124+
void parse(BuildTree tree) {
125+
onParse?.call(tree);
126+
super.parse(tree);
127+
}
128+
129+
@override
130+
void parseStyle(BuildTree tree, css.Declaration style) {
131+
onParseStyle?.call(tree);
132+
super.parseStyle(tree, style);
133+
}
134+
}

packages/core/test/tag_table_test.dart

Lines changed: 80 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -549,8 +549,10 @@ Future<void> main() async {
549549
final explained = await explain(tester, html);
550550
expect(explained, equals('[widget0]'));
551551
});
552+
});
552553

553-
testWidgets('#171: background-color', (WidgetTester tester) async {
554+
group('background', () {
555+
testWidgets('#171: cell color', (WidgetTester tester) async {
554556
// https://github.com/daohoangson/flutter_widget_from_html/issues/171
555557
const html = '<table><tr>'
556558
'<td style="background-color: #f00">Foo</td>'
@@ -569,6 +571,59 @@ Future<void> main() async {
569571
),
570572
);
571573
});
574+
575+
testWidgets('#1028: row color', (WidgetTester tester) async {
576+
// https://github.com/daohoangson/flutter_widget_from_html/issues/1028
577+
const html = '<table><tr style="background-color: #f00">'
578+
'<td>Foo</td><td>Bar</td>'
579+
'</tr></table>';
580+
final explained = await explain(tester, html);
581+
expect(
582+
explained,
583+
equals(
584+
'[HtmlTable:children='
585+
'[HtmlTableCell:child='
586+
'[Container:bg=#FFFF0000,child='
587+
'[Padding:(1,1,1,1),child='
588+
'[Align:alignment=centerLeft,widthFactor=1.0,child='
589+
'[RichText:(:Foo)]'
590+
']]]],'
591+
'[HtmlTableCell:child='
592+
'[Container:bg=#FFFF0000,child='
593+
'[Padding:(1,1,1,1),child='
594+
'[Align:alignment=centerLeft,widthFactor=1.0,child='
595+
'[RichText:(:Bar)]'
596+
']]]]'
597+
']',
598+
),
599+
);
600+
});
601+
602+
testWidgets('overwrites row color', (WidgetTester tester) async {
603+
const html = '<table><tr style="background-color: #f00">'
604+
'<td>Foo</td><td style="background-color: #0f0">Bar</td>'
605+
'</tr></table>';
606+
final explained = await explain(tester, html);
607+
expect(
608+
explained,
609+
equals(
610+
'[HtmlTable:children='
611+
'[HtmlTableCell:child='
612+
'[Container:bg=#FFFF0000,child='
613+
'[Padding:(1,1,1,1),child='
614+
'[Align:alignment=centerLeft,widthFactor=1.0,child='
615+
'[RichText:(:Foo)]'
616+
']]]],'
617+
'[HtmlTableCell:child='
618+
'[Container:bg=#FF00FF00,child='
619+
'[Padding:(1,1,1,1),child='
620+
'[Align:alignment=centerLeft,widthFactor=1.0,child='
621+
'[RichText:(:Bar)]'
622+
']]]]'
623+
']',
624+
),
625+
);
626+
});
572627
});
573628

574629
testWidgets('renders display: table', (WidgetTester tester) async {
@@ -927,6 +982,30 @@ Future<void> main() async {
927982
<td valign="baseline"><div style="padding: 10px">$multiline</div></td>
928983
<td valign="baseline">Foo</td>
929984
</tr>
985+
</table>''',
986+
'row_color': '''
987+
<!-- https://github.com/daohoangson/flutter_widget_from_html/issues/1028 -->
988+
<table style="border-collapse: collapse;">
989+
<tr>
990+
<th>First Name</th>
991+
<th>Last Name</th>
992+
<th>Points</th>
993+
</tr>
994+
<tr style="background-color: #f2f2f2;">
995+
<td>Jill</td>
996+
<td>Smith</td>
997+
<td>50</td>
998+
</tr>
999+
<tr>
1000+
<td>Eve</td>
1001+
<td>Jackson</td>
1002+
<td>94</td>
1003+
</tr>
1004+
<tr style="background-color: #f2f2f2;">
1005+
<td>Adam</td>
1006+
<td>Johnson</td>
1007+
<td>67</td>
1008+
</tr>
9301009
</table>''',
9311010
'rtl': '''
9321011
<table dir="rtl">

0 commit comments

Comments
 (0)