Skip to content

breaking: Remove SelectableHtml #1153

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 4 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`).
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be updated now to reflect it's removal rather than deprecation.


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:

Expand Down
24 changes: 0 additions & 24 deletions lib/custom_render.dart
Original file line number Diff line number Diff line change
Expand Up @@ -102,32 +102,8 @@ class CustomRender {
}) : inlineSpan = null;
}

class SelectableCustomRender extends CustomRender {
final TextSpan Function(RenderContext, List<TextSpan> Function()) textSpan;

SelectableCustomRender.fromTextSpan({
required this.textSpan,
}) : super.inlineSpan(inlineSpan: null);
}

CustomRender blockElementRender({Style? style, List<InlineSpan>? children}) =>
CustomRender.inlineSpan(inlineSpan: (context, buildChildren) {
if (context.parser.selectable) {
return TextSpan(
style: context.style.generateTextStyle(),
children: (children as List<TextSpan>?) ??
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,
Expand Down
175 changes: 0 additions & 175 deletions lib/flutter_html.dart
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,6 @@ class _HtmlState extends State<Html> {
onCssParseError: widget.onCssParseError,
onImageError: widget.onImageError,
shrinkWrap: widget.shrinkWrap,
selectable: false,
style: widget.style,
customRenders: {}
..addAll(widget.customRenders)
Expand All @@ -197,177 +196,3 @@ class _HtmlState extends State<Html> {
);
}
}

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 (`<a href>`)
/// 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<String> tagsList;

/// An API that allows you to override the default style for any HTML element
final Map<String, Style> 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<CustomRenderMatcher, SelectableCustomRender> customRenders;

static List<String> get tags =>
List<String>.from(HtmlElements.selectableElements);

@override
State<StatefulWidget> createState() => _SelectableHtmlState();
}

class _SelectableHtmlState extends State<SelectableHtml> {
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,
),
);
}
}
23 changes: 0 additions & 23 deletions lib/html_parser.dart
Original file line number Diff line number Diff line change
Expand Up @@ -33,15 +33,12 @@ class HtmlParser extends StatelessWidget {
final OnCssParseError? onCssParseError;
final ImageErrorListener? onImageError;
final bool shrinkWrap;
final bool selectable;

final Map<String, Style> style;
final Map<CustomRenderMatcher, CustomRender> customRenders;
final List<String> tagsList;
final OnTap? internalOnAnchorTap;
final Html? root;
final TextSelectionControls? selectionControls;
final ScrollPhysics? scrollPhysics;

final Map<String, Size> cachedImageSizes = {};

Expand All @@ -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);

Expand Down Expand Up @@ -100,9 +94,6 @@ class HtmlParser extends StatelessWidget {
return CssBoxWidget.withInlineSpanChildren(
style: processedTree.style,
children: [parsedTree],
selectable: selectable,
scrollPhysics: scrollPhysics,
selectionControls: selectionControls,
shrinkWrap: shrinkWrap,
);
}
Expand Down Expand Up @@ -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!
Expand Down
36 changes: 1 addition & 35 deletions lib/src/css_box_widget.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -115,30 +105,6 @@ class CssBoxWidget extends StatelessWidget {
);
}

static Widget _generateSelectableWidgetChild(
List<InlineSpan> 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
Expand Down
Loading