library flutter_html; import 'package:flutter/material.dart'; import 'package:html/dom.dart' as dom; import 'package:sonnat/core/html/custom_render.dart'; import 'package:sonnat/core/html/html_parser.dart'; import 'package:sonnat/core/html/src/html_elements.dart'; import 'package:sonnat/core/html/style.dart'; class Html extends StatefulWidget { Html({ super.key, GlobalKey? anchorKey, required this.data, this.onLinkTap, this.onAnchorTap, this.customRenders = const {}, this.onCssParseError, this.onImageError, this.shrinkWrap = false, this.onImageTap, this.tagsList = const [], this.style = const {}, }) : documentElement = null, assert(data != null), _anchorKey = anchorKey ?? GlobalKey(); Html.fromDom({ super.key, GlobalKey? anchorKey, @required dom.Document? document, this.onLinkTap, this.onAnchorTap, this.customRenders = const {}, this.onCssParseError, this.onImageError, this.shrinkWrap = false, this.onImageTap, this.tagsList = const [], this.style = const {}, }) : data = null, assert(document != null), documentElement = document!.documentElement, _anchorKey = anchorKey ?? GlobalKey(); Html.fromElement({ super.key, GlobalKey? anchorKey, @required this.documentElement, this.onLinkTap, this.onAnchorTap, this.customRenders = const {}, this.onCssParseError, this.onImageError, this.shrinkWrap = false, this.onImageTap, this.tagsList = const [], this.style = const {}, }) : data = null, assert(documentElement != null), _anchorKey = anchorKey ?? GlobalKey(); /// 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 function that defines what to do when an image errors final ImageErrorListener? onImageError; /// A parameter that should be set when the HTML widget is expected to be /// flexible final bool shrinkWrap; /// A function that defines what to do when an image is tapped final OnTap? onImageTap; /// 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; /// Either return a custom widget for specific node types or return null to /// fallback to the default rendering. final Map customRenders; /// An API that allows you to override the default style for any HTML element final Map style; static List get tags => List.from(HtmlElements.styledElements) ..addAll(HtmlElements.interactableElements) ..addAll(HtmlElements.replacedElements) ..addAll(HtmlElements.layoutElements) ..addAll(HtmlElements.tableCellElements) ..addAll(HtmlElements.tableDefinitionElements) ..addAll(HtmlElements.externalElements); @override State createState() => _HtmlState(); } class _HtmlState extends State { late dom.Element documentElement; @override void initState() { super.initState(); documentElement = widget.data != null ? HtmlParser.parseHTML(widget.data!) : widget.documentElement!; } @override void didUpdateWidget(Html oldWidget) { super.didUpdateWidget(oldWidget); if ((widget.data != null && oldWidget.data != widget.data) || oldWidget.documentElement != widget.documentElement) { documentElement = widget.data != null ? HtmlParser.parseHTML(widget.data!) : widget.documentElement!; } } @override Widget build(BuildContext context) { return HtmlParser( key: widget._anchorKey, htmlData: documentElement, onLinkTap: widget.onLinkTap, onAnchorTap: widget.onAnchorTap, onImageTap: widget.onImageTap, onCssParseError: widget.onCssParseError, onImageError: widget.onImageError, shrinkWrap: widget.shrinkWrap, selectable: false, style: widget.style, customRenders: {} ..addAll(widget.customRenders) ..addAll(generateDefaultRenders()), tagsList: widget.tagsList.isEmpty ? Html.tags : widget.tagsList, ); } } 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({ super.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(); SelectableHtml.fromDom({ super.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(); SelectableHtml.fromElement({ super.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(); /// 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, ), ); } }