diff --git a/README.md b/README.md index 9210a56651..50524d7be1 100644 --- a/README.md +++ b/README.md @@ -148,19 +148,11 @@ If you would like to modify or sanitize the HTML before rendering it, then `Html #### Selectable Text -The package also has two constructors for selectable text support - `SelectableHtml()` and `SelectableHtml.fromDom()`. +Note: These constructors are deprecated. It is preferred that you use Flutter's new `SelectionArea` instead (just wrap the `Html` widget in a `SelectionArea`). -The difference between the two is the same as noted above. - -Please note: Due to Flutter [#38474](https://github.com/flutter/flutter/issues/38474), selectable text support is significantly watered down compared to the standard non-selectable version of the widget. The changes are as follows: - -1. The list of tags that can be rendered is significantly reduced. Key omissions include no support for images/video/audio, table, and ul/ol. - -2. No support for `customRender`, `customImageRender`, `onImageError`, `onImageTap`, `onMathError`, and `navigationDelegateForIframe`. (Support for `customRender` may be added in the future). - -3. Styling support is significantly reduced. Only text-related styling works (e.g. bold or italic), while container related styling (e.g. borders or padding/margin) do not work. - -Once the above issue is resolved, the aforementioned compromises will go away. Currently the `SelectableText.rich()` constructor does not support `WidgetSpan`s, resulting in the feature losses above. +> The package also has two constructors for selectable text support - `SelectableHtml()` and `SelectableHtml.fromDom()`. +> +> The difference between the two is the same as noted above. ### Parameters: diff --git a/lib/custom_render.dart b/lib/custom_render.dart index abb1931418..5ed72f9128 100644 --- a/lib/custom_render.dart +++ b/lib/custom_render.dart @@ -102,32 +102,8 @@ class CustomRender { }) : inlineSpan = null; } -class SelectableCustomRender extends CustomRender { - final TextSpan Function(RenderContext, List Function()) textSpan; - - SelectableCustomRender.fromTextSpan({ - required this.textSpan, - }) : super.inlineSpan(inlineSpan: null); -} - CustomRender blockElementRender({Style? style, List? children}) => CustomRender.inlineSpan(inlineSpan: (context, buildChildren) { - if (context.parser.selectable) { - return TextSpan( - style: context.style.generateTextStyle(), - children: (children as List?) ?? - context.tree.children - .expandIndexed((i, childTree) => [ - context.parser.parseTree(context, childTree), - if (i != context.tree.children.length - 1 && - childTree.style.display == Display.block && - childTree.element?.localName != "html" && - childTree.element?.localName != "body") - const TextSpan(text: "\n"), - ]) - .toList(), - ); - } return WidgetSpan( alignment: PlaceholderAlignment.baseline, baseline: TextBaseline.alphabetic, diff --git a/lib/flutter_html.dart b/lib/flutter_html.dart index 1b5d7fe55e..8d6296519e 100644 --- a/lib/flutter_html.dart +++ b/lib/flutter_html.dart @@ -188,7 +188,6 @@ class _HtmlState extends State { onCssParseError: widget.onCssParseError, onImageError: widget.onImageError, shrinkWrap: widget.shrinkWrap, - selectable: false, style: widget.style, customRenders: {} ..addAll(widget.customRenders) @@ -197,177 +196,3 @@ class _HtmlState extends State { ); } } - -class SelectableHtml extends StatefulWidget { - /// The `SelectableHtml` widget takes HTML as input and displays a RichText - /// tree of the parsed HTML content (which is selectable) - /// - /// **Attributes** - /// **data** *required* takes in a String of HTML data (required only for `Html` constructor). - /// **documentElement** *required* takes in a Element of HTML data (required only for `Html.fromDom` and `Html.fromElement` constructor). - /// - /// **onLinkTap** This function is called whenever a link (``) - /// is tapped. - /// - /// **onAnchorTap** This function is called whenever an anchor (#anchor-id) - /// is tapped. - /// - /// **tagsList** Tag names in this array will be the only tags rendered. By default, all tags that support selectable content are rendered. - /// - /// **style** Pass in the style information for the Html here. - /// See [its wiki page](https://github.com/Sub6Resources/flutter_html/wiki/Style) for more info. - /// - /// **PLEASE NOTE** - /// - /// There are a few caveats due to Flutter [#38474](https://github.com/flutter/flutter/issues/38474): - /// - /// 1. The list of tags that can be rendered is significantly reduced. - /// Key omissions include no support for images/video/audio, table, and ul/ol because they all require widgets and `WidgetSpan`s. - /// - /// 2. No support for `customRender`, `customImageRender`, `onImageError`, `onImageTap`, `onMathError`, and `navigationDelegateForIframe`. - /// - /// 3. Styling support is significantly reduced. Only text-related styling works - /// (e.g. bold or italic), while container related styling (e.g. borders or padding/margin) - /// do not work because we can't use the `ContainerSpan` class (it needs an enclosing `WidgetSpan`). - - SelectableHtml({ - Key? key, - GlobalKey? anchorKey, - required this.data, - this.onLinkTap, - this.onAnchorTap, - this.onCssParseError, - this.shrinkWrap = false, - this.style = const {}, - this.customRenders = const {}, - this.tagsList = const [], - this.selectionControls, - this.scrollPhysics, - }) : documentElement = null, - assert(data != null), - _anchorKey = anchorKey ?? GlobalKey(), - super(key: key); - - SelectableHtml.fromDom({ - Key? key, - GlobalKey? anchorKey, - @required dom.Document? document, - this.onLinkTap, - this.onAnchorTap, - this.onCssParseError, - this.shrinkWrap = false, - this.style = const {}, - this.customRenders = const {}, - this.tagsList = const [], - this.selectionControls, - this.scrollPhysics, - }) : data = null, - assert(document != null), - documentElement = document!.documentElement, - _anchorKey = anchorKey ?? GlobalKey(), - super(key: key); - - SelectableHtml.fromElement({ - Key? key, - GlobalKey? anchorKey, - @required this.documentElement, - this.onLinkTap, - this.onAnchorTap, - this.onCssParseError, - this.shrinkWrap = false, - this.style = const {}, - this.customRenders = const {}, - this.tagsList = const [], - this.selectionControls, - this.scrollPhysics, - }) : data = null, - assert(documentElement != null), - _anchorKey = anchorKey ?? GlobalKey(), - super(key: key); - - /// A unique key for this Html widget to ensure uniqueness of anchors - final GlobalKey _anchorKey; - - /// The HTML data passed to the widget as a String - final String? data; - - /// The HTML data passed to the widget as a pre-processed [dom.Element] - final dom.Element? documentElement; - - /// A function that defines what to do when a link is tapped - final OnTap? onLinkTap; - - /// A function that defines what to do when an anchor link is tapped. When this value is set, - /// the default anchor behaviour is overwritten. - final OnTap? onAnchorTap; - - /// A function that defines what to do when CSS fails to parse - final OnCssParseError? onCssParseError; - - /// A parameter that should be set when the HTML widget is expected to be - /// have a flexible width, that doesn't always fill its maximum width - /// constraints. For example, auto horizontal margins are ignored, and - /// block-level elements only take up the width they need. - final bool shrinkWrap; - - /// A list of HTML tags that are the only tags that are rendered. By default, this list is empty and all supported HTML tags are rendered. - final List tagsList; - - /// An API that allows you to override the default style for any HTML element - final Map style; - - /// Custom Selection controls allows you to override default toolbar and build custom toolbar - /// options - final TextSelectionControls? selectionControls; - - /// Allows you to override the default scrollPhysics for [SelectableText.rich] - final ScrollPhysics? scrollPhysics; - - /// Either return a custom widget for specific node types or return null to - /// fallback to the default rendering. - final Map customRenders; - - static List get tags => - List.from(HtmlElements.selectableElements); - - @override - State createState() => _SelectableHtmlState(); -} - -class _SelectableHtmlState extends State { - late final dom.Element documentElement; - - @override - void initState() { - super.initState(); - documentElement = widget.data != null - ? HtmlParser.parseHTML(widget.data!) - : widget.documentElement!; - } - - @override - Widget build(BuildContext context) { - return SizedBox( - width: widget.shrinkWrap ? null : MediaQuery.of(context).size.width, - child: HtmlParser( - key: widget._anchorKey, - htmlData: documentElement, - onLinkTap: widget.onLinkTap, - onAnchorTap: widget.onAnchorTap, - onImageTap: null, - onCssParseError: widget.onCssParseError, - onImageError: null, - shrinkWrap: widget.shrinkWrap, - selectable: true, - style: widget.style, - customRenders: {} - ..addAll(widget.customRenders) - ..addAll(generateDefaultRenders()), - tagsList: - widget.tagsList.isEmpty ? SelectableHtml.tags : widget.tagsList, - selectionControls: widget.selectionControls, - scrollPhysics: widget.scrollPhysics, - ), - ); - } -} diff --git a/lib/html_parser.dart b/lib/html_parser.dart index a739b2b987..df0155e250 100644 --- a/lib/html_parser.dart +++ b/lib/html_parser.dart @@ -33,15 +33,12 @@ class HtmlParser extends StatelessWidget { final OnCssParseError? onCssParseError; final ImageErrorListener? onImageError; final bool shrinkWrap; - final bool selectable; final Map style; final Map customRenders; final List tagsList; final OnTap? internalOnAnchorTap; final Html? root; - final TextSelectionControls? selectionControls; - final ScrollPhysics? scrollPhysics; final Map cachedImageSizes = {}; @@ -54,13 +51,10 @@ class HtmlParser extends StatelessWidget { required this.onCssParseError, required this.onImageError, required this.shrinkWrap, - required this.selectable, required this.style, required this.customRenders, required this.tagsList, this.root, - this.selectionControls, - this.scrollPhysics, }) : internalOnAnchorTap = onAnchorTap ?? (key != null ? _handleAnchorTap(key, onLinkTap) : onLinkTap); @@ -100,9 +94,6 @@ class HtmlParser extends StatelessWidget { return CssBoxWidget.withInlineSpanChildren( style: processedTree.style, children: [parsedTree], - selectable: selectable, - scrollPhysics: scrollPhysics, - selectionControls: selectionControls, shrinkWrap: shrinkWrap, ); } @@ -349,20 +340,6 @@ class HtmlParser extends StatelessWidget { if (entry.call(newContext)) { buildChildren() => tree.children.map((tree) => parseTree(newContext, tree)).toList(); - if (newContext.parser.selectable && - customRenders[entry] is SelectableCustomRender) { - selectableBuildChildren() => tree.children - .map((tree) => parseTree(newContext, tree) as TextSpan) - .toList(); - return (customRenders[entry] as SelectableCustomRender) - .textSpan - .call(newContext, selectableBuildChildren); - } - if (newContext.parser.selectable) { - return customRenders[entry]! - .inlineSpan! - .call(newContext, buildChildren) as TextSpan; - } if (customRenders[entry]?.inlineSpan != null) { return customRenders[entry]! .inlineSpan! diff --git a/lib/src/css_box_widget.dart b/lib/src/css_box_widget.dart index 20843fdd8d..0c775cf36e 100644 --- a/lib/src/css_box_widget.dart +++ b/lib/src/css_box_widget.dart @@ -22,17 +22,7 @@ class CssBoxWidget extends StatelessWidget { this.textDirection, this.childIsReplaced = false, this.shrinkWrap = false, - bool selectable = false, - TextSelectionControls? selectionControls, - ScrollPhysics? scrollPhysics, - }) : child = selectable - ? _generateSelectableWidgetChild( - children, - style, - selectionControls, - scrollPhysics, - ) - : _generateWidgetChild(children, style); + }) : child = _generateWidgetChild(children, style); /// The child to be rendered within the CSS Box. final Widget child; @@ -115,30 +105,6 @@ class CssBoxWidget extends StatelessWidget { ); } - static Widget _generateSelectableWidgetChild( - List children, - Style style, - TextSelectionControls? selectionControls, - ScrollPhysics? scrollPhysics, - ) { - if (children.isEmpty) { - return Container(); - } - - return SelectableText.rich( - TextSpan( - style: style.generateTextStyle(), - children: children, - ), - style: style.generateTextStyle(), - textAlign: style.textAlign, - textDirection: style.direction, - maxLines: style.maxLines, - selectionControls: selectionControls, - scrollPhysics: scrollPhysics, - ); - } - static InlineSpan? _generateMarkerBoxSpan(Style style) { if (style.display == Display.listItem) { // First handle listStyleImage diff --git a/lib/src/html_elements.dart b/lib/src/html_elements.dart index 304c9ff395..72f6fe0e9d 100644 --- a/lib/src/html_elements.dart +++ b/lib/src/html_elements.dart @@ -141,67 +141,4 @@ class HtmlElements { ]; static const replacedExternalElements = ["iframe", "img", "video", "audio"]; - - static const selectableElements = [ - "br", - "a", - "article", - "aside", - "blockquote", - "body", - "center", - "dd", - "div", - "dl", - "dt", - "figcaption", - "figure", - "footer", - "h1", - "h2", - "h3", - "h4", - "h5", - "h6", - "header", - "hr", - "html", - "main", - "nav", - "noscript", - "p", - "pre", - "section", - "summary", - "abbr", - "acronym", - "address", - "b", - "bdi", - "bdo", - "big", - "cite", - "code", - "data", - "del", - "dfn", - "em", - "font", - "i", - "ins", - "kbd", - "mark", - "q", - "s", - "samp", - "small", - "span", - "strike", - "strong", - "time", - "tt", - "u", - "var", - "wbr", - ]; } diff --git a/test/flutter_html_test.dart b/test/flutter_html_test.dart index 30acdbe449..6a43154333 100644 --- a/test/flutter_html_test.dart +++ b/test/flutter_html_test.dart @@ -17,20 +17,6 @@ void main() { }, ); - testWidgets( - "Check that selectable widget does not fail on empty data", - (tester) async { - await tester.pumpWidget( - MaterialApp( - home: SelectableHtml( - data: '', - ), - ), - ); - expect(find.text('', findRichText: true), findsOneWidget); - }, - ); - testWidgets( "Check that widget displays given text", (tester) async { diff --git a/test/html_parser_test.dart b/test/html_parser_test.dart index 056de9c148..18482c8344 100644 --- a/test/html_parser_test.dart +++ b/test/html_parser_test.dart @@ -48,12 +48,9 @@ void testNewParser(BuildContext context) { onCssParseError: null, onImageError: null, shrinkWrap: false, - selectable: true, style: const {}, customRenders: generateDefaultRenders(), tagsList: Html.tags, - selectionControls: null, - scrollPhysics: null, ), ); @@ -73,12 +70,9 @@ void testNewParser(BuildContext context) { onCssParseError: null, onImageError: null, shrinkWrap: false, - selectable: true, style: const {}, customRenders: generateDefaultRenders(), tagsList: Html.tags, - selectionControls: null, - scrollPhysics: null, ), ); @@ -97,12 +91,9 @@ void testNewParser(BuildContext context) { onCssParseError: null, onImageError: null, shrinkWrap: false, - selectable: true, style: const {}, customRenders: generateDefaultRenders(), tagsList: Html.tags, - selectionControls: null, - scrollPhysics: null, ), ); @@ -122,12 +113,9 @@ void testNewParser(BuildContext context) { onCssParseError: null, onImageError: null, shrinkWrap: false, - selectable: true, style: const {}, customRenders: generateDefaultRenders(), tagsList: Html.tags, - selectionControls: null, - scrollPhysics: null, ), );