diff --git a/packages/core/lib/src/core_widget_factory.dart b/packages/core/lib/src/core_widget_factory.dart index 06b71c42e..b39bd1c2f 100644 --- a/packages/core/lib/src/core_widget_factory.dart +++ b/packages/core/lib/src/core_widget_factory.dart @@ -16,6 +16,7 @@ import 'core_html_widget.dart'; import 'internal/core_ops.dart'; import 'internal/core_parser.dart'; import 'internal/margin_vertical.dart'; +import 'internal/ops/style_display_flex.dart'; import 'internal/platform_specific/fallback.dart' if (dart.library.io) 'internal/platform_specific/io.dart'; import 'internal/text_ops.dart' as text_ops; @@ -673,7 +674,6 @@ class WidgetFactory extends WidgetFactoryResetter with AnchorWidgetFactory { ), ); break; - case 'article': case 'aside': case 'dl': @@ -1134,6 +1134,9 @@ class WidgetFactory extends WidgetFactoryResetter with AnchorWidgetFactory { StyleSizing.maybeRegisterChildOp(this, tree); switch (value) { + case kCssDisplayFlex: + tree.register(StyleDisplayFlexOps.flexOp(tree)); + break; case kCssDisplayBlock: StyleSizing.registerBlockOp(this, tree); break; diff --git a/packages/core/lib/src/internal/core_ops.dart b/packages/core/lib/src/internal/core_ops.dart index 0aba1cae6..d24497bf3 100644 --- a/packages/core/lib/src/internal/core_ops.dart +++ b/packages/core/lib/src/internal/core_ops.dart @@ -80,6 +80,7 @@ const kCssDisplay = 'display'; const kCssDisplayBlock = 'block'; const kCssDisplayInline = 'inline'; const kCssDisplayInlineBlock = 'inline-block'; +const kCssDisplayFlex = 'flex'; const kCssDisplayNone = 'none'; const kCssLineHeight = 'line-height'; diff --git a/packages/core/lib/src/internal/ops/style_display_flex.dart b/packages/core/lib/src/internal/ops/style_display_flex.dart new file mode 100644 index 000000000..852b58203 --- /dev/null +++ b/packages/core/lib/src/internal/ops/style_display_flex.dart @@ -0,0 +1,115 @@ +import 'package:flutter/material.dart'; + +import '../../../flutter_widget_from_html_core.dart'; +import '../core_ops.dart'; + +const kCssFlexDirection = 'flex-direction'; +const kCssFlexDirectionRow = 'row'; +const kCssJustifyContent = 'justify-content'; +const kCssJustifyContentFlexStart = 'flex-start'; +const kCssJustifyContentFlexEnd = 'flex-end'; +const kCssJustifyContentCenter = 'center'; +const kCssJustifyContentSpaceBetween = 'space-between'; +const kCssJustifyContentSpaceAround = 'space-around'; +const kCssJustifyContentSpaceEvenly = 'space-evenly'; +const kCssAlignItems = 'align-items'; +const kCssAlignItemsFlexStart = 'flex-start'; +const kCssAlignItemsFlexEnd = 'flex-end'; +const kCssAlignItemsCenter = 'center'; +const kCssAlignItemsBaseline = 'baseline'; +const kCssAlignItemsStretch = 'stretch'; + +// ignore: avoid_classes_with_only_static_members +class StyleDisplayFlexOps { + /// Builds custom widget for div elements with display: flex from [meta] + static BuildOp flexOp(BuildTree tree) { + return BuildOp( + onVisitChild: (tree, subTree) { + subTree.register(_flexItemOp(subTree)); + }, + onRenderBlock: (tree, placeholder) { + final String id = tree.element.id; + String flexDirection = kCssFlexDirectionRow; + String justifyContent = kCssJustifyContentFlexStart; + String alignItems = kCssAlignItemsFlexStart; + + for (final element in tree.element.styles) { + final String? value = element.term; + + if (value != null) { + switch (element.property) { + case kCssFlexDirection: + flexDirection = value; + break; + case kCssJustifyContent: + justifyContent = value; + break; + case kCssAlignItems: + alignItems = value; + break; + } + } + } + + return Flex( + key: Key(id), + direction: kCssFlexDirectionRow == flexDirection + ? Axis.horizontal + : Axis.vertical, + mainAxisAlignment: _toMainAxisAlignment(justifyContent), + crossAxisAlignment: _toCrossAxisAlignment(alignItems), + children: [ + placeholder, + ], + ); + }, + ); + } + + /// Build op for child elements of flex containers + static BuildOp _flexItemOp(BuildTree tree) { + return BuildOp( + defaultStyles: (element) { + return {kCssWidth: kCssWidthAuto, kCssHeight: kCssHeightAuto}; + }, + ); + } + + /// Converts CSS [justifyContent] to Flutter Grid MainAxisAlignment + static MainAxisAlignment _toMainAxisAlignment(String justifyContent) { + switch (justifyContent) { + case kCssJustifyContentFlexStart: + return MainAxisAlignment.start; + case kCssJustifyContentFlexEnd: + return MainAxisAlignment.end; + case kCssJustifyContentCenter: + return MainAxisAlignment.center; + case kCssJustifyContentSpaceBetween: + return MainAxisAlignment.spaceBetween; + case kCssJustifyContentSpaceAround: + return MainAxisAlignment.spaceAround; + case kCssJustifyContentSpaceEvenly: + return MainAxisAlignment.spaceEvenly; + default: + return MainAxisAlignment.start; + } + } + + /// Converts CSS [alignItems] to Flutter Grid CrossAxisAlignment + static CrossAxisAlignment _toCrossAxisAlignment(String alignItems) { + switch (alignItems) { + case kCssAlignItemsFlexStart: + return CrossAxisAlignment.start; + case kCssAlignItemsFlexEnd: + return CrossAxisAlignment.end; + case kCssAlignItemsCenter: + return CrossAxisAlignment.center; + case kCssAlignItemsBaseline: + return CrossAxisAlignment.baseline; + case kCssAlignItemsStretch: + return CrossAxisAlignment.stretch; + default: + return CrossAxisAlignment.start; + } + } +} diff --git a/packages/core/lib/src/internal/ops/style_sizing.dart b/packages/core/lib/src/internal/ops/style_sizing.dart index 49ef44fe0..0695cc0f0 100644 --- a/packages/core/lib/src/internal/ops/style_sizing.dart +++ b/packages/core/lib/src/internal/ops/style_sizing.dart @@ -6,6 +6,8 @@ const kCssMaxWidth = 'max-width'; const kCssMinHeight = 'min-height'; const kCssMinWidth = 'min-width'; const kCssWidth = 'width'; +const kCssWidthAuto = 'auto'; +const kCssHeightAuto = 'auto'; class StyleSizing { static const k100percent = CssLength(100, CssLengthUnit.percentage); diff --git a/packages/core/test/_.dart b/packages/core/test/_.dart index 1427766c7..7ce416e79 100644 --- a/packages/core/test/_.dart +++ b/packages/core/test/_.dart @@ -556,6 +556,22 @@ class Explainer { return '+w${FontWeight.values.indexOf(fontWeight)}'; } + List _flex(Flex flex) { + final List result = []; + + result.add( + 'direction=${flex.direction.toString().replaceFirst('Axis.', '')}', + ); + result.add( + 'mainAxisAlignment=${flex.mainAxisAlignment.toString().replaceFirst('MainAxisAlignment.', '')}', + ); + result.add( + 'crossAxisAlignment=${flex.crossAxisAlignment.toString().replaceFirst('CrossAxisAlignment.', '')}', + ); + + return result; + } + String _widget(Widget widget) { final explained = explainer?.call(this, widget); if (explained != null) { @@ -756,6 +772,10 @@ class Explainer { attr.add('message=${widget.message}'); } + if (widget is! Column && (widget is Flex)) { + attr.addAll(_flex(widget)); + } + // Special cases // `RichText` is a `MultiChildRenderObjectWidget` but needs different logic attr.add( diff --git a/packages/core/test/style_display_flex_test.dart b/packages/core/test/style_display_flex_test.dart new file mode 100644 index 000000000..95b214edb --- /dev/null +++ b/packages/core/test/style_display_flex_test.dart @@ -0,0 +1,178 @@ +import 'package:flutter_test/flutter_test.dart'; + +import '_.dart'; + +void main() { + group('basic usage', () { + const html = '
'; + + testWidgets('renders', (WidgetTester tester) async { + final explained = await explain(tester, html); + expect( + explained, + equals( + '[Flex:direction=horizontal,mainAxisAlignment=start,crossAxisAlignment=start,children=[widget0]]', + ), + ); + }); + }); + + group('horizontal alignment - flex start', () { + const html = '
'; + + testWidgets('renders', (WidgetTester tester) async { + final explained = await explain(tester, html); + expect( + explained, + equals( + '[Flex:direction=horizontal,mainAxisAlignment=start,crossAxisAlignment=start,children=[widget0]]', + ), + ); + }); + }); + + group('horizontal alignment - center', () { + const html = '
'; + + testWidgets('renders', (WidgetTester tester) async { + final explained = await explain(tester, html); + expect( + explained, + equals( + '[Flex:direction=horizontal,mainAxisAlignment=start,crossAxisAlignment=start,children=[widget0]]', + ), + ); + }); + }); + + group('horizontal alignment - flex end', () { + const html = '
'; + + testWidgets('renders', (WidgetTester tester) async { + final explained = await explain(tester, html); + expect( + explained, + equals( + '[Flex:direction=horizontal,mainAxisAlignment=start,crossAxisAlignment=end,children=[widget0]]', + ), + ); + }); + }); + + group('horizontal alignment - stretch', () { + const html = '
'; + + testWidgets('renders', (WidgetTester tester) async { + final explained = await explain(tester, html); + expect( + explained, + equals( + '[Flex:direction=horizontal,mainAxisAlignment=start,crossAxisAlignment=stretch,children=[widget0]]', + ), + ); + }); + }); + + group('vertical alignment - flex start', () { + const html = + '
'; + + testWidgets('renders', (WidgetTester tester) async { + final explained = await explain(tester, html); + expect( + explained, + equals( + '[Flex:direction=horizontal,mainAxisAlignment=start,crossAxisAlignment=start,children=[widget0]]', + ), + ); + }); + }); + + group('vertical alignment - center', () { + const html = '
'; + + testWidgets('renders', (WidgetTester tester) async { + final explained = await explain(tester, html); + expect( + explained, + equals( + '[Flex:direction=horizontal,mainAxisAlignment=center,crossAxisAlignment=start,children=[widget0]]', + ), + ); + }); + }); + + group('vertical alignment - flex end', () { + const html = + '
'; + + testWidgets('renders', (WidgetTester tester) async { + final explained = await explain(tester, html); + expect( + explained, + equals( + '[Flex:direction=horizontal,mainAxisAlignment=end,crossAxisAlignment=start,children=[widget0]]', + ), + ); + }); + }); + + group('vertical alignment - space between', () { + const html = + '
'; + + testWidgets('renders', (WidgetTester tester) async { + final explained = await explain(tester, html); + expect( + explained, + equals( + '[Flex:direction=horizontal,mainAxisAlignment=spaceBetween,crossAxisAlignment=start,children=[widget0]]', + ), + ); + }); + }); + + group('vertical alignment - space around', () { + const html = + '
'; + + testWidgets('renders', (WidgetTester tester) async { + final explained = await explain(tester, html); + expect( + explained, + equals( + '[Flex:direction=horizontal,mainAxisAlignment=spaceAround,crossAxisAlignment=start,children=[widget0]]', + ), + ); + }); + }); + + group('vertical alignment - space evenly', () { + const html = + '
'; + + testWidgets('renders', (WidgetTester tester) async { + final explained = await explain(tester, html); + expect( + explained, + equals( + '[Flex:direction=horizontal,mainAxisAlignment=spaceEvenly,crossAxisAlignment=start,children=[widget0]]', + ), + ); + }); + }); + + group('flex direction - row', () { + const html = '
'; + + testWidgets('renders', (WidgetTester tester) async { + final explained = await explain(tester, html); + expect( + explained, + equals( + '[Flex:direction=horizontal,mainAxisAlignment=start,crossAxisAlignment=start,children=[widget0]]', + ), + ); + }); + }); +}