daohoangson / flutter_widget_from_html

Flutter package to render html as widgets that supports hyperlink, image, audio, video, iframe and many other tags.
https://pub.dev/packages/flutter_widget_from_html
MIT License
645 stars 242 forks source link

scrollToAnchor not work for headers with custom widgets #1357

Open MiniSuperDev opened 1 week ago

MiniSuperDev commented 1 week ago

Hi, how to preserve the scrollToAnchor behavior for the headers when use a custom widget?

The use case can be to make the title clickable and show a link button like a lot of webs. image

In the following example when you register a WidgetFactory with a BuildOp for the header the scrolToAnchor it does not work, if you comment the factoryBuilder you can see the expected behavior.

So, how can I do it?

Thank you

import 'package:flutter/material.dart';
import 'package:flutter_widget_from_html_core/flutter_widget_from_html_core.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      debugShowCheckedModeBanner: false,
      home: HtmlDemo(),
    );
  }
}

class HtmlDemo extends StatefulWidget {
  const HtmlDemo({super.key});

  @override
  State<HtmlDemo> createState() => _HtmlDemoState();
}

class _HtmlDemoState extends State<HtmlDemo> {
  final htmlKey = GlobalKey<HtmlWidgetState>();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Row(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          SingleChildScrollView(
            child: HtmlWidget(
              html,
              key: htmlKey,
              // Comment to see the scroll to anchor working
              factoryBuilder: _MyWidgetFactory.new,
            ),
          ),
          Center(
            child: Column(
              mainAxisSize: MainAxisSize.min,
              children: [
                ElevatedButton(
                  onPressed: () {
                    htmlKey.currentState?.scrollToAnchor('h1');
                  },
                  child: const Text('Go to H1'),
                ),
                ElevatedButton(
                  onPressed: () {
                    htmlKey.currentState?.scrollToAnchor('h2');
                  },
                  child: const Text('Go to H2'),
                ),
              ],
            ),
          ),
        ],
      ),
    );
  }
}

class _MyWidgetFactory extends WidgetFactory {
  final headerOp = BuildOp(
    onRenderBlock: (tree, placeholder) {
      return Header(text: tree.element.text);
    },
  );

  @override
  void parse(BuildTree tree) {
    final e = tree.element;
    if (e.localName == 'h1' || e.localName == 'h2') {
      tree.register(headerOp);
      return;
    }

    return super.parse(tree);
  }
}

class Header extends StatelessWidget {
  const Header({
    super.key,
    required this.text,
  });
  final String text;

  @override
  Widget build(BuildContext context) {
    return ElevatedButton(
      onPressed: () {
        // Some stuff like update url with the id
      },
      child: Text(text),
    );
  }
}

final html = '''
  <h1 id="h1">Heading 1</h1>
  <p>X</p><p>X</p><p>X</p><p>X</p><p>X</p><p>X</p><p>X</p><p>X</p><p>X</p><p>X</p>
  <p>X</p><p>X</p><p>X</p><p>X</p><p>X</p><p>X</p><p>X</p><p>X</p><p>X</p><p>X</p>
  <p>X</p><p>X</p><p>X</p><p>X</p><p>X</p><p>X</p><p>X</p><p>X</p><p>X</p><p>X</p>
  <p>X</p><p>X</p><p>X</p><p>X</p><p>X</p><p>X</p><p>X</p><p>X</p><p>X</p><p>X</p>
  <p>X</p><p>X</p><p>X</p><p>X</p><p>X</p><p>X</p><p>X</p><p>X</p><p>X</p><p>X</p>
  <p>X</p><p>X</p><p>X</p><p>X</p><p>X</p><p>X</p><p>X</p><p>X</p><p>X</p><p>X</p>
  <p>X</p><p>X</p><p>X</p><p>X</p><p>X</p><p>X</p><p>X</p><p>X</p><p>X</p><p>X</p>
  <p>X</p><p>X</p><p>X</p><p>X</p><p>X</p><p>X</p><p>X</p><p>X</p><p>X</p><p>X</p>
  <p>X</p><p>X</p><p>X</p><p>X</p><p>X</p><p>X</p><p>X</p><p>X</p><p>X</p><p>X</p>
  <p>X</p><p>X</p><p>X</p><p>X</p><p>X</p><p>X</p><p>X</p><p>X</p><p>X</p><p>X</p>
  <p>X</p><p>X</p><p>X</p><p>X</p><p>X</p><p>X</p><p>X</p><p>X</p><p>X</p><p>X</p>
  <p>X</p><p>X</p><p>X</p><p>X</p><p>X</p><p>X</p><p>X</p><p>X</p><p>X</p><p>X</p>
  <h2 id="h2">Heading 2</h2>
  ''';
daohoangson commented 1 week ago

Can you remove the early return here?


  @override
  void parse(BuildTree tree) {
    final e = tree.element;
    if (e.localName == 'h1' || e.localName == 'h2') {
      tree.register(headerOp);
      return; // <- this one
    }

    return super.parse(tree);
  }

You didn't allow the super WidgetFactory to parse the tree so the id attribute didn't register -> scrollToAnchor cannot see the #h1 to scroll.