Skip to content

Commit d60226b

Browse files
committed
fix(css): defer inset-inline-* mapping until direction resolves
- Avoid eagerly mapping insetInlineStart/End to left/right during style flush; store logical insets and resolve in getters so RTL/LTR changes don’t require HMR. - Treat insetInlineStart/End like left/right for percentage resolution. - Add regression test for remapping on direction change.
1 parent a52d887 commit d60226b

File tree

4 files changed

+131
-13
lines changed

4 files changed

+131
-13
lines changed

webf/lib/src/css/position.dart

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
* Licensed under GNU GPL with Enterprise exception.
55
*/
66

7+
import 'dart:ui';
8+
79
import 'package:webf/css.dart';
810
import 'package:webf/dom.dart';
911

@@ -53,8 +55,6 @@ mixin CSSPositionMixin on RenderStyle {
5355
_markContainingBlockNeedsLayout();
5456
}
5557

56-
@override
57-
CSSLengthValue get left => _left ?? CSSLengthValue.auto;
5858
CSSLengthValue? _left;
5959
set left(CSSLengthValue? value) {
6060
if (_left == value) {
@@ -65,7 +65,25 @@ mixin CSSPositionMixin on RenderStyle {
6565
}
6666

6767
@override
68-
CSSLengthValue get right => _right ?? CSSLengthValue.auto;
68+
CSSLengthValue get left {
69+
final CSSLengthValue physical = _left ?? CSSLengthValue.auto;
70+
// Keep logical values so they can remap when `direction` changes. When a
71+
// physical side is explicitly specified, it should override the logical
72+
// fallback (matches paddingInlineStart/end behavior).
73+
final CSSLengthValue? logical =
74+
(direction == TextDirection.rtl) ? _insetInlineEnd : _insetInlineStart;
75+
if (_left != null) return physical;
76+
return logical ?? physical;
77+
}
78+
79+
@override
80+
CSSLengthValue get right {
81+
final CSSLengthValue physical = _right ?? CSSLengthValue.auto;
82+
final CSSLengthValue? logical =
83+
(direction == TextDirection.rtl) ? _insetInlineStart : _insetInlineEnd;
84+
if (_right != null) return physical;
85+
return logical ?? physical;
86+
}
6987
CSSLengthValue? _right;
7088
set right(CSSLengthValue? value) {
7189
if (_right == value) {
@@ -75,6 +93,20 @@ mixin CSSPositionMixin on RenderStyle {
7593
_markContainingBlockNeedsLayout();
7694
}
7795

96+
CSSLengthValue? _insetInlineStart;
97+
set insetInlineStart(CSSLengthValue? value) {
98+
if (_insetInlineStart == value) return;
99+
_insetInlineStart = value;
100+
_markContainingBlockNeedsLayout();
101+
}
102+
103+
CSSLengthValue? _insetInlineEnd;
104+
set insetInlineEnd(CSSLengthValue? value) {
105+
if (_insetInlineEnd == value) return;
106+
_insetInlineEnd = value;
107+
_markContainingBlockNeedsLayout();
108+
}
109+
78110
// The z-index property specifies the stack order of an element.
79111
// Only works on positioned elements(position: absolute/relative/fixed).
80112
int? _zIndex;

webf/lib/src/css/render_style.dart

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1689,7 +1689,7 @@ class CSSRenderStyle extends RenderStyle
16891689

16901690
// Map logical properties to physical properties based on current direction.
16911691
//
1692-
// Note: Do NOT eagerly map padding-inline-start/end to physical paddings here.
1692+
// Note: Do NOT eagerly map padding-inline-start/end or inset-inline-start/end to physical sides here.
16931693
// `direction` is inherited and may change after the property is applied (e.g., when
16941694
// an ancestor sets `direction` later in the same style flush). Eager mapping can
16951695
// leave stale padding on the wrong side (LTR->RTL), shrinking the content box.
@@ -1705,8 +1705,6 @@ class CSSRenderStyle extends RenderStyle
17051705
propertyName = isRTL ? BORDER_RIGHT_STYLE : BORDER_LEFT_STYLE;
17061706
} else if (name == BORDER_INLINE_START_COLOR) {
17071707
propertyName = isRTL ? BORDER_RIGHT_COLOR : BORDER_LEFT_COLOR;
1708-
} else if (name == INSET_INLINE_START) {
1709-
propertyName = isRTL ? RIGHT : LEFT;
17101708
}
17111709
// Handle inline-end properties (maps to right in LTR, left in RTL)
17121710
else if (name == BORDER_INLINE_END) {
@@ -1717,8 +1715,6 @@ class CSSRenderStyle extends RenderStyle
17171715
propertyName = isRTL ? BORDER_LEFT_STYLE : BORDER_RIGHT_STYLE;
17181716
} else if (name == BORDER_INLINE_END_COLOR) {
17191717
propertyName = isRTL ? BORDER_LEFT_COLOR : BORDER_RIGHT_COLOR;
1720-
} else if (name == INSET_INLINE_END) {
1721-
propertyName = isRTL ? LEFT : RIGHT;
17221718
}
17231719
// Handle block-start properties (maps to top)
17241720
else if (name == MARGIN_BLOCK_START) {
@@ -1784,12 +1780,18 @@ class CSSRenderStyle extends RenderStyle
17841780
case TOP:
17851781
top = value;
17861782
break;
1783+
case INSET_INLINE_START:
1784+
insetInlineStart = value;
1785+
break;
17871786
case LEFT:
17881787
left = value;
17891788
break;
17901789
case BOTTOM:
17911790
bottom = value;
17921791
break;
1792+
case INSET_INLINE_END:
1793+
insetInlineEnd = value;
1794+
break;
17931795
case RIGHT:
17941796
right = value;
17951797
break;
@@ -2202,7 +2204,7 @@ class CSSRenderStyle extends RenderStyle
22022204

22032205
// Map logical properties to physical properties based on current direction.
22042206
//
2205-
// Note: Do NOT eagerly map padding-inline-start/end to physical paddings here.
2207+
// Note: Do NOT eagerly map padding-inline-start/end or inset-inline-start/end to physical sides here.
22062208
// See [setProperty] above for rationale.
22072209
String mappedPropertyName = propertyName;
22082210
final bool isRTL = direction == TextDirection.rtl;
@@ -2216,8 +2218,6 @@ class CSSRenderStyle extends RenderStyle
22162218
mappedPropertyName = isRTL ? BORDER_RIGHT_STYLE : BORDER_LEFT_STYLE;
22172219
} else if (propertyName == BORDER_INLINE_START_COLOR) {
22182220
mappedPropertyName = isRTL ? BORDER_RIGHT_COLOR : BORDER_LEFT_COLOR;
2219-
} else if (propertyName == INSET_INLINE_START) {
2220-
mappedPropertyName = isRTL ? RIGHT : LEFT;
22212221
}
22222222
// Handle inline-end properties (maps to right in LTR, left in RTL)
22232223
else if (propertyName == BORDER_INLINE_END) {
@@ -2228,8 +2228,6 @@ class CSSRenderStyle extends RenderStyle
22282228
mappedPropertyName = isRTL ? BORDER_LEFT_STYLE : BORDER_RIGHT_STYLE;
22292229
} else if (propertyName == BORDER_INLINE_END_COLOR) {
22302230
mappedPropertyName = isRTL ? BORDER_LEFT_COLOR : BORDER_RIGHT_COLOR;
2231-
} else if (propertyName == INSET_INLINE_END) {
2232-
mappedPropertyName = isRTL ? LEFT : RIGHT;
22332231
}
22342232
// Handle block-start properties (maps to top)
22352233
else if (propertyName == MARGIN_BLOCK_START) {
@@ -2310,6 +2308,8 @@ class CSSRenderStyle extends RenderStyle
23102308
case LEFT:
23112309
case BOTTOM:
23122310
case RIGHT:
2311+
case INSET_INLINE_START:
2312+
case INSET_INLINE_END:
23132313
case FLEX_BASIS:
23142314
case WIDTH:
23152315
case MIN_WIDTH:

webf/lib/src/css/values/length.dart

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -761,6 +761,8 @@ class CSSLengthValue {
761761
break;
762762
case LEFT:
763763
case RIGHT:
764+
case INSET_INLINE_START:
765+
case INSET_INLINE_END:
764766
// Offset of positioned element starts from the edge of padding box of containing block.
765767
if (parentPaddingBoxWidth != null) {
766768
_computedValue = value! * parentPaddingBoxWidth;
@@ -1004,6 +1006,8 @@ class CSSLengthValue {
10041006
case BOTTOM:
10051007
case LEFT:
10061008
case RIGHT:
1009+
case INSET_INLINE_START:
1010+
case INSET_INLINE_END:
10071011
if (computedValue == double.infinity) {
10081012
return true;
10091013
}
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
/*
2+
* Copyright (C) 2022-present The WebF authors. All rights reserved.
3+
*/
4+
5+
import 'package:flutter_test/flutter_test.dart';
6+
import 'package:webf/dom.dart' as dom;
7+
import 'package:webf/webf.dart';
8+
9+
import '../../setup.dart';
10+
import '../widget/test_utils.dart';
11+
12+
void main() {
13+
setUpAll(() {
14+
setupTest();
15+
});
16+
17+
setUp(() {
18+
WebFControllerManager.instance.initialize(
19+
WebFControllerManagerConfig(
20+
maxAliveInstances: 5,
21+
maxAttachedInstances: 5,
22+
enableDevTools: false,
23+
),
24+
);
25+
});
26+
27+
tearDown(() async {
28+
WebFControllerManager.instance.disposeAll();
29+
await Future.delayed(const Duration(milliseconds: 100));
30+
});
31+
32+
group('logical inset with direction inheritance', () {
33+
testWidgets('insetInlineStart remaps when direction changes to RTL', (WidgetTester tester) async {
34+
final prepared = await WebFWidgetTestUtils.prepareWidgetTest(
35+
tester: tester,
36+
controllerName: 'logical-inset-rtl-${DateTime.now().millisecondsSinceEpoch}',
37+
html: '''
38+
<html>
39+
<head>
40+
<style>
41+
body { margin: 0; padding: 0; }
42+
#cb { position: relative; width: 200px; height: 100px; }
43+
#abs { background: blue; }
44+
</style>
45+
</head>
46+
<body>
47+
<div id="cb">
48+
<div id="abs"></div>
49+
</div>
50+
</body>
51+
</html>
52+
''',
53+
);
54+
55+
final dom.Element cb = prepared.getElementById('cb');
56+
final dom.Element abs = prepared.getElementById('abs');
57+
58+
abs.style.setProperty('position', 'absolute');
59+
abs.style.setProperty('width', '20px');
60+
abs.style.setProperty('height', '10px');
61+
abs.style.setProperty('top', '0px');
62+
abs.style.setProperty('insetInlineStart', '10%');
63+
abs.style.flushPendingProperties();
64+
65+
await tester.pump(const Duration(milliseconds: 50));
66+
67+
final cbRect = cb.getBoundingClientRect();
68+
final absRectLtr = abs.getBoundingClientRect();
69+
final expectedLtr = cbRect.left + cbRect.width * 0.1;
70+
expect(absRectLtr.left, closeTo(expectedLtr, 0.5));
71+
72+
cb.style.setProperty('direction', 'rtl');
73+
cb.style.flushPendingProperties();
74+
75+
await tester.pump(const Duration(milliseconds: 50));
76+
77+
final absRectRtl = abs.getBoundingClientRect();
78+
final expectedLeftRtl = cbRect.left + cbRect.width - cbRect.width * 0.1 - absRectRtl.width;
79+
expect(absRectRtl.left, closeTo(expectedLeftRtl, 0.5));
80+
});
81+
});
82+
}

0 commit comments

Comments
 (0)