Is there a Plotly library or plugin to suggest chart types based on the dataset? [closed]

I’m working on a project using Plotly and Angular, where I need to allow users to switch between different chart types dynamically (e.g., bar, pie, scatter, waterfall, etc.).

Is there any external library, plugin, or built-in feature in Plotly that can suggest appropriate chart types based on the structure or type of the dataset? For example, if the data is categorical vs. numerical, the tool should recommend bar or pie charts.

Alternatively, are there any tools or frameworks that integrate with Plotly to provide such functionality? Any guidance or examples would be greatly appreciated!

How to scroll the document before a list of divs is rendered on the screen?

<div key={"parent-div"}>
  <div key={"child-div-1"}></div>
  <div key={"child-div-2"}></div>
  <div key={"child-div-3"}></div>
  <div key={"child-div-4"}></div>
  <div key={"child-div-5"}></div>
</div>

The above React component will overflow the viewpoint.

Task: before the component is rendered on the screen, I want child-div-3 to be scrolled to the top.

If the parent div is rendered on the screen first and then the scrolling takes place, then there will be a slight visual glitch. This is what I want to prevent.

Node JS – Express JS API query variable with parameters for EXEC stored procedure

I am working with the API which will serve as my data access layer in my project architecture.

This is the original form of my API query setup:

const pool = await poolPromise;
const request = pool.request();
const result = await request.input('user_id', sql.NVarChar(50), user_id || null)
    .input('img_data', sql.VarBinary(sql.MAX), Buffer.from(img_data))
    .input('img_size', sql.Decimal(10, 3), img_size)
    .input('birthday', sql.Date, birthday)
    .input('country', sql.VarChar(200), country)
    .input('email_add', sql.NVarChar(150), email_add)
    .input('mobile_no', sql.VarChar(50), mobile_no)
    .input('key', sql.VarChar(100), key)
    .query('EXEC testStoredProcedure @user_id, @img_data, @img_size, @birthday, @country, @email_add, @mobile_no, @key');

It works well with that setup.

However, if there are more parameters than expected, I want to improve the API query’s readability with the same outcomes.

So I use these approaches using variable and backticks:

with req.body.[param]

const query = `
    'EXEC testStoredProcedure
    @user_id = ${req.body.user_id},
    @img_data = ${req.body.img_data},
    @img_size = ${req.body.img_size},
    @birthday = ${req.body.birthday},
    @country = ${req.body.country},
    @email_add = ${req.body.email_add},
    @mobile_no = ${req.body.mobile_no},
    @key = ${req.body.key}'
`;

const pool = await poolPromise;
const request = pool.request();
const result = await request.input('user_id', sql.NVarChar(50), user_id || null)
    .input('img_data', sql.VarBinary(sql.MAX), Buffer.from(img_data))
    .input('img_size', sql.Decimal(10, 3), img_size)
    .input('birthday', sql.Date, birthday)
    .input('country', sql.VarChar(200), country)
    .input('email_add', sql.NVarChar(150), email_add)
    .input('mobile_no', sql.VarChar(50), mobile_no)
    .input('key', sql.VarChar(100), key)
    .query(query);

without req.body

const query = `
    'EXEC testStoredProcedure
    @user_id = ${user_id},
    @img_data = ${img_data},
    @img_size = ${img_size},
    @birthday = ${birthday},
    @country = ${country},
    @email_add = ${email_add},
    @mobile_no = ${mobile_no},
    @key = ${key}'
`;

const pool = await poolPromise;
const request = pool.request();
const result = await request.input('user_id', sql.NVarChar(50), user_id || null)
    .input('img_data', sql.VarBinary(sql.MAX), Buffer.from(img_data))
    .input('img_size', sql.Decimal(10, 3), img_size)
    .input('birthday', sql.Date, birthday)
    .input('country', sql.VarChar(200), country)
    .input('email_add', sql.NVarChar(150), email_add)
    .input('mobile_no', sql.VarChar(50), mobile_no)
    .input('key', sql.VarChar(100), key)
    .query(query);

Both fails to process the request through this API call from my Flutter project:

await _api.testMethod(
    '/api/request', // route
    null, // user_id
    '', // img_data
    0.00, // img_size
    DateTime.now().toIso8601String(), // birthday
    'Philippines', // country
    null, // email_add
    _mobileNo, // mobile_no
    'SIGN_IN'); // key

This is what the logs say for with req.body.[param] and without req.body.[param]:

Uncaught exception: Exception: Failed to make POST request: Exception:
API Error: 500 {message: Incorrect syntax near ‘EXEC
testStoredProcedure
@user_id = null,
@img_data = ”,
‘.}

According to the logs, the syntax near specifying the parameter for EXEC testStoredProcedure is incorrect. Therefore, what am I missing here?

Why second function is not working in servicenow widget?

My Second function is not woroking in ServiceNow widget while first one is working properly, Both functions code is same still having problem in second function

Hi, Laiba here! My first function is working properly but second one is not even running I tried so many times (eg. console log, alerts) Its still not working, I dont know why, Can anyone please help me?

// YES Button Functionality
function yesButn() {
  alert("YES IS WORKING")
  document.querySelectorAll(".yes-btn").forEach(function (button) {
    button.addEventListener("click", function () {
      alert("YES IS WORKING")
      console.log("YES IS WORKING");
    })
  })
}
yesButn();

// NO Button Functionality
function noBtn() {
  alert(" NO IS WORKING")
  document.querySelectorAll(".no-btn").forEach(function (button) {
    button.addEventListener("click", function () {
      alert("NO IS WORKING")
      console.log("NO IS WORKING")
    })
  })
}
noBtn();

The expected result should be : alert dialog will popup everytime the buttons wil be clicked, but it didn’t happen. I already tested it in Google Chrome Its working there perfectly, but getting this issue in ServiceNow Widget.

Custom Button Not Showing in Odoo 18 POS Interface

I’m trying to add a custom button to the Odoo 18 POS interface using a custom module. I’ve followed the documentation and tried all possible ways, but the button is not showing up. The module is loading correctly, and there are no errors in the console. Here’s my code:

complaint.xml
xml

<?xml version="1.0" encoding="UTF-8"?>
<templates id="template" xml:space="preserve">
    <t t-name="pos_custom.ControlButtons" t-inherit="point_of_sale.ControlButtons" t-inherit-mode="extension">
        <xpath expr="//div[@class='control-buttons']" position="inside">
            <button class="btn btn-light btn-lg flex-shrink-0 ms-auto" t-on-click="onClickPopupSingleField">
                Custom Button
            </button>
        </xpath>
    </t>
</templates>

complaint.js
javascript

import { ControlButtons } from "@point_of_sale/app/screens/product_screen/control_buttons/control_buttons";
import { patch } from "@web/core/utils/patch";

patch(ControlButtons.prototype, {
    onClickPopupSingleField() {
        alert("Custom Button Clicked!");
    }
});

manifest.py
python

    'name': 'POS Complaint',
    'version': '1.0',
    'category': 'Point of Sale',
    'summary': 'Add a Create Complaint button in POS Partner List',
    'depends': ['point_of_sale'],
    'assets': {
        'point_of_sale.assets': [
            'pos_complaint/static/src/js/complaint.js',
            'pos_complaint/static/src/xml/complaint.xml',
        ],
    },
    'installable': True,
    'application': True,
}```

**Directory Structure**

`pos_complaint/
├── __init__.py
├── __manifest__.py
├── static/
│   └── src/
│       ├── js/
│       │   └── complaint.js
│       └── xml/
│           └── complaint.xml`

**What I've Tried:**

Rebuilt assets using **--dev=all**.

**Cleared browser cache.**

Verified that the module is installed correctly.

**Checked the browser console for errors (no errors found).**

The custom button should appear in the POS interface.

**Odoo Version: 18
Python Version: 3.10**

Can anyone help me figure out why the button is not showing up? Thanks in advance!

JavaScript Standard Returning Undefined When Using Fetch [duplicate]

I’m updating my AJAX to Fetch and reading through some basic Fetch examples to understand it. The examples given often use arrowed functions which my brain breaks when I’m trying to learn a new concept. So, I’m trying to translate it to a standard JavaScript function but it’s not working. Actually it works, but instead of returning the JSON, it returns “undefined”.

Arrowed function: This works.

fetch('https://reqres.in/api/users')
    .then(MyResults => MyResults.json()
    .then(MyData => console.log(MyData))
;

Standard JavaScript. This works, but returns “undefined” instead of the desired results.

fetch('https://reqres.in/api/users')
    .then(
        function MyFunction1(MyResults) {
            MyResults.json()
        }
    )
    .then(
        function MyFunction2(MyData) {
            console.log(MyData)
        }
    )
;

Searching for a travel blog with pretty animated SVG Maps + Explanation of Those [closed]

I’ve been banging my head against Google for a solid 3-4 hours at this point but it is pointless to try to search something there these days, so I turn to Stack Overflow and other forums in the hope that somebody might be able to help me.

I’m searching for a travel blog that wrote a tutorial on how he created custom SVG animations for the routes he took along his trips. I’m looking to reproduce something similar in functionality to what he had and wanted to use his work as a starting point.

I remember that the blogger in question was into photography and photographed the Te Whanganui-A-Hei (Cathedral Cove) in New Zealand during early morning light with no people there and wrote a post about the process. And of course that they had these little SVG animation of their trips with a map displaying the route they took, animating the path.

They wrote a pretty in-depth article about how they accomplished this with the help of some custom JS and SVG animation and I am after that article.

If you know the name of the blog that would absolutely make my day!

Please also feel free to answer if you know anything similar to the title of the blog where somebody has created stylized animated SVG maps of their trips and documented the process they used to create those.

Or some ideas/instructions/examples of how to achieve the same.

I’ve tried finding this information on Google but there is nothing there of value anymore. Just links and more links to paid tools that don’t serve my use case.

ThreeJS FPS Movement, Stop Velocity When Key Is Released

For my game I need a simple FPS player character and I have used the ThreeJS FPS example as template for my setup. This is the code used for player WASD movement:

if (this.keyStates['KeyW']) {
    this.playerVelocity.add( getForwardVector().multiplyScalar(speedDelta))
}

and in my update loop I have this:

this.playerVelocity.addScaledVector(this.playerVelocity, damping) // damping is for jumping
const deltaPosition = this.playerVelocity.clone().multiplyScalar(deltaTime)
this.bounds.translate(deltaPosition)
this.camera.position.copy(this.bounds.end)

This works fine but as you can also see in the ThreeJS FPS example, when the key is released the character still moves slightly along before stopping. I’m not entirely against a little bit of easing when movement stops but in this case it’s way to extreme, almost like your walking on ice.

How can I stop the movement of the player character once the key is released without sliding along?

How to get satisfactory response using AJAX

I am trying to make a request using ajax in my browser console, I get a satisfactory response about 6 or 7 times each time I do it, but after that my browser console throws the following error

VM301:25 Error: Error: jQuery21101333788921190291_1738537757753 was not called
at Function.error (jquery-2.1.1.min.js:2:1821)
at b.dataTypes.<computed>.b.converters.script json (jquery-2.1.1.min.js:4:16293)
at vc (jquery-2.1.1.min.js:4:7397)
at x (jquery-2.1.1.min.js:4:10802)
at HTMLScriptElement.c (jquery-2.1.1.min.js:4:15583)
at HTMLScriptElement.dispatch (jquery-2.1.1.min.js:3:6404)
at r.handle (jquery-2.1.1.min.js:3:3179)
error @ VM301:25
j @ jquery-2.1.1.min.js:2
fireWith @ jquery-2.1.1.min.js:2
x @ jquery-2.1.1.min.js:4
c @ jquery-2.1.1.min.js:4
dispatch @ jquery-2.1.1.min.js:3
r.handle @ jquery-2.1.1.min.js:3
load
add @ jquery-2.1.1.min.js:3
(anonymous) @ jquery-2.1.1.min.js:3
each @ jquery-2.1.1.min.js:2
each @ jquery-2.1.1.min.js:2
on @ jquery-2.1.1.min.js:3
send @ jquery-2.1.1.min.js:4
ajax @ jquery-2.1.1.min.js:4
fetchData @ VM301:3

I am using this code for the request with its corresponding data

$(document).ready(function() {
    function fetchData() {
        $.ajax({
            url: '',
            type: 'GET',
            dataType: 'jsonp',
            data: {
                callback: '',
                type: '',
                lang: 'es',
                services: [''],
                version: '5',
                src: '',
                srvsrc: '',
                start: '',
                end: '',
            },
            success: function(response) {
                console.log(response);
            },
            error: function(xhr, status, error) {
                console.error('Error:', error);
            }
        });
    }

    fetchData();
});

How to open a pdf file in desktop app from ms graph api

We use onedrive/sharepoint for our document storage and there is a ability to open files in the desktop app. You use an extension on the url which is built into most browsers for your javascript so I prebuild the url with the correct extension below. It seems that there might be one for adobe as onedrive has this functionality but I think I am doing this wrong as it errors out in my javascript when it goes to open the new window via the built url:

        [HttpPost]
    [ValidateAntiForgeryToken]
    public async Task<ActionResult> SharedLinkData([FromForm] string FilePath)
    {

        var fullPath = Path.Combine(fileDirectory, FilePath);
        var SharedLinkResponse = await oneDrive.DriveItemLinksAsync(SharedDriveID, fullPath, "path", "users", "edit");
        var driveItem = await oneDrive.DriveItemInfoAsync(SharedDriveID, fullPath, "path");
        var wordExtensions = new List<String>{".doc", ".docx", ".dotx", ".odt"};
        var excelExtensions =  new List<String>{".xlsx", ".xlsm", ".xls", ".xlsb", ".xltx", ".xlt"};
        var powerPointExtensions =  new List<String>{".ppt", ".pptx", ".pptm", ".potx", ".pot", ".potm", ".ppam", ".ppa", ".thmx"};
        var adobeExtensions =  new List<String>{".pdf"};
        var currentFileExtension = Path.GetExtension(driveItem.WebDavUrl).ToLower();

        if(wordExtensions.Contains(currentFileExtension)) 
        {
            return Ok("ms-word:ofe|u|"+driveItem.WebDavUrl);
        } else if (excelExtensions.Contains(currentFileExtension)) 
        {
            return Ok("ms-excel:ofe|u|"+driveItem.WebDavUrl);
        } else if (powerPointExtensions.Contains(currentFileExtension))
        {
            return Ok("ms-powerpoint:ofe|u|"+driveItem.WebDavUrl);
        } else if (adobeExtensions.Contains(currentFileExtension))
        {
            return Ok("odopen://"+driveItem.WebDavUrl);
        }
        return Ok(SharedLinkResponse.Link.WebUrl);
    }

It is unclear if odopen:// is the correct start. If you try it in onedrive and monitor the network requests I see

odopen://openFile/?fileId=blasdflasdflas&siteId=asdfsadfsdfsdf&listId=%7Bd02b7574%2D2dfasdfasdfa&userEmail=john%2EDoe%40test%2Esomething%2Ecome&userId=dfsdfsf5&webUrl=dfasdfasfdsadfsadfasdfsadf

So maybe I need to compile all of this for it to work? Or even better maybe I can get ms graph to generate this somehow for me to make my life easier?

bcryptjs.hash returns undefined in vitest

I am trying to learn vitest, in my createUser controller I generate a hashed password using genSalt and hash from bcryptjsin my test file I mocked the two functions using this way

vi.mock("bcryptjs", () => ({
  genSalt: vi.fn(),
  hash: vi.fn(),
}));

it works but i wanted to write a test that makes sure that the password in the user object is a hashed password, so I wanted to restore the original implementation of genSalt and hash, I found a tutorial online that says I should import the acutal module using vi.importActual and mock the two functions implementation with the original imports.

it("User is created with a hashed password", async () => {
    req.body = { name: "foo", email: "[email protected]", password: "1234" };

    const genSaltOriginal = (await vi.importActual<any>("bcryptjs")).genSalt;
    const hashOriginal = (await vi.importActual<any>("bcryptjs")).hash;

    (genSalt as Mock).mockImplementationOnce(genSaltOriginal);
    (hash as Mock).mockImplementationOnce(hashOriginal);

    await createUser(req as Request, res as Response, fakeNext as NextFunction);
    await new Promise(setImmediate);

    expect(fakeNext).toHaveBeenCalledWith(new CustomError(undefined, 500, `User save failedn`));
  });

This does not work, when I log the salt and hashed password values they return undefined, and for some reason when I comment out (hash as Mock).mockImplementationOnce(hashOriginal) the genSalt works and returns an actual salt when I log it to the console, but enabling this line breaks it, also here is the code for hash generation:

let salt;
  let hashedPassword;

  try {
    salt = await genSalt(10);
    hashedPassword = await hash(password, salt);
  } catch (err) {
    next(new CustomError(undefined, 500, `Password hashing failedn${err}`));
    return;
  }

Cropper error, import declarations may only appear at top level of a module

The Cropper example at https://codesandbox.io/p/sandbox/cropperjs-crop-auto-update-canvas-zi548 works fine there, but not in my localhost. I downloaded the code using the Download link, a cloud image in the left hand panel on the Nodebox line.

When I run it on my local, I see only the main image in the browser window. In the Console, the error is “Uncaught SyntaxError: import declarations may only appear at top level of a module”. That error is from line 1 of index.js

This is the second example code I’ve tried running in my local. The first had similar errors, which I tried to patch, without success.
With this one I added type=”module” to the <script src="src/index.js"></script> line in index.html but that just changes the error to “Uncaught TypeError: The specifier “cropperjs” was a bare specifier, but was not remapped to anything. Relative module specifiers must start with “./”, “../” or “/”.” I’ve tried resolving that one before, but just seemed to be going down rabbitholes.

I am running with http: protocol from my local IIS, as pure file access gives CORS errors.

Any help is greatly appreciated, I am familiar with old-school Javascript and have used jQuery a lot but not so much with the latest.

Remove invisible shapes from HTML5 canvas

Is it possible to find completely invisible shapes (fully overlapped by other shapes) on the HTML5 canvas to remove them?

Or maybe another way to export the canvas content to an SVG file with visible shapes only? Any way to ignore shapes that are hidden.

The root issue: there is a canvas where user can draw anything. This drawing tool has an erase brush that draws with the destination-out composite operation. It works fine, but when user leaves the page – the canvas is being stored as an svg file to the server (and loads when the page is being opened again). And when user draws and erases too much – it works very slowly. The fabric library with toSVG method is being used in the project.

Service Worker Offline Caching Issue: Loading cached HTML files into object element does not load embedded images

I have a web application where there’s a main page index.html that contains a button. The button loads a separate page.html file into an <object> element on the main page. The page.html file contains an <img> element that needs to load while offline. I’m able to load page.html into the <object> while offline, however for some reason the image is not loading.

Is this something that’s possible. If so, what do I need to do?

Here’s a simple example of what I have:

index.html:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Offline Web App</title>    
    
    <style>
        body {
            font-family: Arial, sans-serif;
            text-align: center;
        }
        button {
            padding: 10px 20px;
            margin: 20px;
            font-size: 16px;
        }
    </style>

</head>
<body>
    <h1>Offline Web App</h1>
    <button id="loadPage">Load Separate HTML Page</button>

    <object id="contentFrame" width="100%" height="400px"></object>


    <script>
        document.getElementById('loadPage').addEventListener('click', () => {
            document.getElementById('contentFrame').data = 'page.html';
        });
    </script>


    <script>
        if ('serviceWorker' in navigator) {
          window.addEventListener('load', () => {
            navigator.serviceWorker.register('service-worker.js')
              .then(registration => {
                console.log('Service Worker registered:', registration);
              })
              .catch(error => {
                console.error('Service Worker registration failed:', error);
              });
          });
        }
      </script>
</body>
</html>

service-worker.js:

const CACHE_NAME = 'offline-cache-v6';
const ASSETS_TO_CACHE = [
    '/app/index.html', // Main app page
    '/app/page.html', // Page loaded in <object>
    '/app/service-worker.js', // This file itself
    '/app/img.png' // Image on page.html
];

self.addEventListener('install', event => {
  event.waitUntil(
    caches.open(CACHE_NAME)
      .then(cache => cache.addAll(ASSETS_TO_CACHE))
  );
});

self.addEventListener('fetch', event => {
  event.respondWith(
    caches.match(event.request)
      .then(response => {
        if (response) {
          return response; // Return cached asset
        }

        return fetch(event.request)
          .then(response => {
            // Check if we received a valid response
            if(!response || response.status !== 200 || response.type !== 'basic') {
              return response;
            }

            // Clone the response.  A response is a Stream and already read.
            // We want to use it in the browser and in the cache.
            const responseToCache = response.clone();

            caches.open(CACHE_NAME)
              .then(cache => {
                return cache.put(event.request, responseToCache);
              });

            return response;
          });
      })
  );
});


self.addEventListener('activate', (event) => {
  const cacheWhitelist = [CACHE_NAME];

  event.waitUntil(
    caches.keys().then((cacheNames) => {
      return Promise.all(
        cacheNames.map((cacheName) => {
          if (cacheWhitelist.indexOf(cacheName) === -1) {
            return caches.delete(cacheName);
          }
        })
      );
    })
  );
});

page.html:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Page</title>
</head>
<body>
    <h2>This is a Separate HTML Page</h2>
    <img src="/app/img.png" alt="Cached Image" width="300">
</body>
</html>

How can I restrict text selection to the current page in a Flutter WebView EPUB reader (paginated mode)?

I’m developing an EPUB reader in Flutter as a learning exercise. I’m using the locally downloaded flutter_epub_viewer package (version 1.2.0) with Flutter SDK ^3.6.0. My goal is to use the default paginated mode for page transitions, but I’ve encountered an issue with text selection.
Problem:
When the EPUB content is rendered inside the WebView, if the user selects text and the mobile default text selection handles (the bubble-like controls) reach the edge of a page, the selection unexpectedly extends into the adjacent page. This causes layout issues and an undesirable selection behavior.
This is my epub_reader_screen.dart

    import 'package:flutter/material.dart';
import 'package:flutter_epub_viewer/flutter_epub_viewer.dart';
import 'package:path_provider/path_provider.dart';
import 'dart:io';
import 'dart:async';
import '../controllers/home_controller.dart';
import '../screens/epub_searching_screen.dart';
import '../database/highlight_db.dart';
import '../screens/highlight_viewer_screen.dart';
import 'package:flutter_svg/flutter_svg.dart';
import '../widgets/chapter_drawer.dart';

class EpubReaderScreen extends StatefulWidget {
  final String novelId;
  final String title;
  final String cover;

  const EpubReaderScreen({
    required this.novelId,
    required this.title,
    required this.cover,
    super.key,
  });

  @override
  _EpubReaderScreenState createState() => _EpubReaderScreenState();
}

class _EpubReaderScreenState extends State<EpubReaderScreen> {
  final EpubController _epubController = EpubController();
  bool _isLoading = true;
  String? _epubFilePath;
  // Using page-based variables instead of percentage-based progress
  int _totalPages = 0;
  int _currentPage = 1;
  String? _lastSavedCfi;
  bool _isAppBarVisible = false; // Manage app bar and bottom bar visibility
  final HomeController _homeController = HomeController();
  late Future<void> _loadEpubFuture;
  double? _lastSavedProgress;
  Timer? _sliderDebounce;
  final HighlightDB _highlightDB = HighlightDB();
  Color highlightColor = Color(0xFFFFFF00);
  var textSelectionCfi = '';
  String textSelection = '';
  double highlightopacity = 0.5;
  bool _isTextSelectionActive = false;
  double _progress = 0.0;
  double _currentFontSize = 16.0;
  double _currentLineHeight = 30.0;

  @override
  void initState() {
    super.initState();
    _loadEpubFuture = _loadEpub();
  }

  Future<void> _loadEpub() async {
    try {
      final directory = await getApplicationDocumentsDirectory();
      final filePath = '${directory.path}/epub/${widget.title}.epub';
      final file = File(filePath);

      if (!file.existsSync()) {
        _epubFilePath = null;
        return;
      }

      // Load saved CFI
      _lastSavedCfi = await _homeController.loadLastReadCfi(widget.novelId);
      _epubFilePath = filePath;
    } catch (e) {
      _epubFilePath = null;
    }
  }

  Future<void> _addHighlight(String cfi, Color color, double opacity, String selectedText) async {
    try {
      await _highlightDB.saveHighlight(widget.novelId, cfi, color.value, opacity, selectedText);
      _epubController.addHighlight(cfi: cfi, color: color, opacity: opacity);
    } catch (e) {
      // Handle error silently
    }
  }

  Future<void> _loadHighlights() async {
    try {
      List<Map<String, dynamic>> highlights = await _highlightDB.loadHighlights(widget.novelId);
      for (var highlight in highlights) {
        String cfi = highlight['cfi'];
        _epubController.addHighlight(cfi: cfi, color: highlightColor, opacity: 0.5);
      }
    } catch (e) {
      // Handle error silently
    }
  }

  Future<void> _saveCfiProgress() async {
    try {
      EpubLocation? location = await _epubController.getCurrentLocation();
      String? cfi = location?.startCfi;
      if (cfi != null && cfi != _lastSavedCfi) {
        _lastSavedCfi = cfi;
        await _homeController.saveLastReadCfi(widget.novelId, cfi);
      }
    } catch (e) {
      // Handle error silently
    }
  }

  Future<void> _jumpToSavedCfi() async {
    try {
      String? lastReadCfi = await _homeController.loadLastReadCfi(widget.novelId);
      if (lastReadCfi != null && lastReadCfi.isNotEmpty) {
        await _epubController.display(cfi: lastReadCfi);
      }
      EpubLocation? location = await _epubController.getCurrentLocation();
      setState(() {
        // Update page number based on progress
      });
    } catch (e) {
      // Handle error silently
    }
  }

  Future<void> _toggleAppBar() async {
    setState(() {
      _isAppBarVisible = !_isAppBarVisible;
    });
  }
  
  void _onSliderChanged(double value) {
    setState(() => _progress = value);
    _sliderDebounce?.cancel();
    _sliderDebounce = Timer(Duration(milliseconds: 300), () {
      _epubController.toProgressPercentage(value / 100);
    });
  }

  Future<void> _updatePageInfo(EpubLocation location) async {
    setState(() {
      _progress = location.progress * 100;
      _currentPage = (_progress / 100 * _totalPages).ceil();
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      drawer: ChapterDrawer(controller: _epubController),
      backgroundColor: Colors.grey[200],
      body: FutureBuilder<void>(
        future: _loadEpubFuture,
        builder: (context, snapshot) {
          if (snapshot.connectionState == ConnectionState.waiting) {
            return Center(child: CircularProgressIndicator());
          } else if (_epubFilePath == null) {
            return Center(
              child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  Text("EPUB file not found.", style: TextStyle(fontSize: 16)),
                  SizedBox(height: 10),
                  ElevatedButton(
                    onPressed: () {
                      setState(() {
                        _loadEpubFuture = _loadEpub();
                      });
                    },
                    child: Text("Retry"),
                  ),
                ],
              ),
            );
          } else {
            return Stack(
              children: [
                Positioned.fill(
                  child: EpubViewer(
                    epubSource: EpubSource.fromFile(File(_epubFilePath!)),
                    epubController: _epubController,
                    displaySettings: EpubDisplaySettings(
                      flow: EpubFlow.paginated,
                      snap: true,
                      theme: EpubTheme.light(),
                      spread: EpubSpread.auto,
                      allowScriptedContent: true,
                    ),
                    selectionContextMenu: ContextMenu(
                      settings: ContextMenuSettings(hideDefaultSystemContextMenuItems: false),
                      menuItems: [
                        ContextMenuItem(
                          title: "highlight",
                          id: 0,
                          action: () async {
                            _addHighlight(textSelectionCfi, highlightColor, highlightopacity, textSelection);
                          },
                        ),
                      ],
                    ),
                    initialCfi: _lastSavedCfi,
                    onTextSelected: (epubTextSelection) {
                      textSelectionCfi = epubTextSelection.selectionCfi;
                      textSelection = epubTextSelection.selectedText;
                    },
                    onEpubLoaded: () async {
                      _isTextSelectionActive = true;
                      await _loadHighlights();
                    },
                    onRelocated: (relocation) async {
                      double? newProgress = relocation.progress;
                      _lastSavedProgress = newProgress;
                      setState(() {
                        _progress = newProgress * 100;
                      });
                      await _saveCfiProgress();
                      await _updatePageInfo(relocation);
                    },
                  ),
                ),
                Positioned(
                  top: _isAppBarVisible ? kToolbarHeight : 0,
                  left: 0,
                  right: 0,
                  height: 10,
                  child: AbsorbPointer(
                    absorbing: true,
                    child: Container(color: Colors.transparent),
                  ),
                ),
                Positioned(
                  bottom: 10,
                  left: 0,
                  right: 0,
                  height: 10,
                  child: AbsorbPointer(
                    absorbing: true,
                    child: Container(color: Colors.transparent),
                  ),
                ),
                Positioned(
                  top: _isAppBarVisible ? kToolbarHeight : 0,
                  bottom: 10,
                  left: 0,
                  width: 10,
                  child: AbsorbPointer(
                    absorbing: true,
                    child: Container(color: Colors.transparent),
                  ),
                ),
                Positioned(
                  top: _isAppBarVisible ? kToolbarHeight : 0,
                  bottom: 10,
                  right: 0,
                  width: 10,
                  child: AbsorbPointer(
                    absorbing: true,
                    child: Container(color: Colors.transparent),
                  ),
                ),
                Align(
                  alignment: Alignment.center,
                  child: GestureDetector(
                    onTap: _toggleAppBar,
                    child: Container(width: 140, height: 175, color: Colors.transparent),
                  ),
                ),
                if (_isAppBarVisible) ...[
                  Positioned(
                    top: 0,
                    left: 0,
                    right: 0,
                    child: AppBar(
                      title: Text(widget.title),
                      leading: IconButton(
                        icon: Icon(Icons.arrow_back),
                        onPressed: () => Navigator.pop(context),
                      ),
                      actions: [
                        IconButton(
                          icon: SvgPicture.asset(
                            'assets/icons/search_icon.svg',
                            width: 24,
                            height: 24,
                            colorFilter: ColorFilter.mode(Colors.blue, BlendMode.srcIn),
                          ),
                          onPressed: () {
                            showModalBottomSheet(
                              context: context,
                              isScrollControlled: true,
                              builder: (context) => EpubSearchingScreen(epubController: _epubController),
                            );
                          },
                        ),
                        IconButton(
                          icon: Icon(Icons.highlight),
                          onPressed: () async {
                            await Navigator.push(
                              context,
                              MaterialPageRoute(
                                builder: (context) => HighlightViewerScreen(
                                  novelId: widget.novelId,
                                  onHighlightSelected: (String cfi) async {
                                    await _epubController.display(cfi: cfi);
                                  },
                                  onHighlightDeleted: (String cfi) async {
                                    _epubController.removeHighlight(cfi: cfi);
                                  },
                                ),
                              ),
                            );
                          },
                        ),
                        IconButton(
                          icon: Icon(Icons.text_fields),
                          onPressed: _showFontPicker,
                        ),
                      ],
                    ),
                  ),
                  Positioned(
                    bottom: 0,
                    left: 0,
                    right: 0,
                    child: _buildBottomBar(),
                  ),
                ],
              ],
            );
          }
        },
      ),
    );
  }

  Widget _buildBottomBar() {
    return Container(
      padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
      color: Colors.grey[200],
      child: Row(
        children: [
          Builder(
            builder: (context) {
              return IconButton(
                icon: const Icon(Icons.menu),
                onPressed: () {
                  Scaffold.of(context).openDrawer();
                },
              );
            },
          ),
          const SizedBox(width: 8),
          Expanded(
            child: Slider(
              value: _progress,
              min: 0,
              max: 100,
              onChanged: _onSliderChanged,
            ),
          ),
          Text(
            "${_progress.toStringAsFixed(1)}%",
            style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
          ),
        ],
      ),
    );
  }

  void _showFontPicker() {
    showModalBottomSheet(
      context: context,
      builder: (context) {
        return StatefulBuilder(
          builder: (BuildContext context, StateSetter modalSetState) {
            return Column(
              mainAxisSize: MainAxisSize.min,
              children: [
                ListTile(
                  title: const Text("Font Size"),
                  trailing: DropdownButton<double>(
                    value: _currentFontSize,
                    items: [12.0, 14.0, 16.0, 18.0, 20.0, 22.0]
                        .map((size) => DropdownMenuItem(
                              value: size,
                              child: Text(size.toString()),
                            ))
                        .toList(),
                    onChanged: (value) {
                      modalSetState(() {});
                      setState(() {
                        _currentFontSize = value!;
                        _epubController.setFontSize(fontSize: _currentFontSize);
                      });
                    },
                  ),
                ),
                ListTile(
                  title: const Text("Line Height"),
                  trailing: DropdownButton<double>(
                    value: _currentLineHeight,
                    items: [10.0, 20.0, 30.0, 40.0, 100.0]
                        .map((value) => DropdownMenuItem<double>(
                              value: value,
                              child: Text("$value"),
                            ))
                        .toList(),
                    onChanged: (value) {
                      modalSetState(() {});
                      setState(() {
                        _currentLineHeight = value!;
                        _epubController.setLineHeight(lineHeight: "${_currentLineHeight}px");
                      });
                    },
                  ),
                ),
              ],
            );
          },
        );
      },
    );
  }
}

1.CSS Multi-Column Layout:
I injected CSS into the swipe.html file’s <style> section

`body {
  display: flex;
  -webkit-align-items: center;
  -webkit-justify-content: center;
}

#viewer {
  width: 100%;
  height: 100%;
  /* width: 400px;
  height: 580px; */
  /* box-shadow: 0 0 4px #ccc; */
  /* padding: 10px 10px 0px 10px; */
  margin: 5px 5px;
  background: white;
  padding: 0px 0px;
  overflow: hidden;
  column-width: 400px;
  column-gap: 20px;
}


@media only screen
  and (min-device-width : 320px)
  and (max-device-width : 667px) {
    #viewer {
      height: 100vh
    }
    #viewer iframe {
      
    }
    .arrow {
      position: inherit;
      display: none;
    }
}

`

This approach visually divides the content into multiple “pages.” However, it only affects the visual layout; the browser still treats the content as a continuous flow, so the selection extends into adjacent columns pages.
2.DOM Splitting via epub.js Hook:
I attempted to split the content into separate DOM containers by using epub.js’s hook.
`rendition.hooks.content.register((contents) => {

    contents.document.body.style.webkitColumnWidth = 'auto';
    contents.document.body.style.columnWidth = 'auto';

   
    var style = contents.document.createElement('style');
    style.type = "text/css";
    style.innerHTML = `
    
      .page-container {
         width: 100vw;
         height: 100vh;
         overflow: hidden;
         display: inline-block;
         vertical-align: top;
         box-sizing: border-box;
         margin: 0;
         padding: 10px;
         white-space: normal; 
      }
      
      .page-container * {
         white-space: normal !important;
         word-wrap: break-word;
         overflow-wrap: break-word;
         word-break: break-all;
      }
     
      #pages-wrapper {
         width: 100vw;
         overflow-x: hidden;
         font-size: inherit;
         white-space: nowrap;
      }
    `;
    contents.document.head.appendChild(style);


    var viewportHeight = window.innerHeight;


    var pagesWrapper = contents.document.createElement('div');
    pagesWrapper.id = 'pages-wrapper';


    var bodyChildren = Array.from(contents.document.body.childNodes);


    contents.document.body.innerHTML = '';

 
    var currentPage = contents.document.createElement('div');
    currentPage.className = 'page-container';
    pagesWrapper.appendChild(currentPage);


    bodyChildren.forEach(function(child) {
      currentPage.appendChild(child);

    
      if (currentPage.scrollHeight > viewportHeight) {`your text`
        
          currentPage.removeChild(child);

          
          currentPage = contents.document.createElement('div');
          currentPage.className = 'page-container';
          currentPage.appendChild(child);
          pagesWrapper.appendChild(currentPage);
      }
    });


    contents.document.body.appendChild(pagesWrapper);

    if (useCustomSwipe) {
      const el = contents.document.documentElement;
      if (el) {
        detectSwipe(el, function(el, direction){
          if(direction === 'l'){
            rendition.next();
          }
          if(direction === 'r'){
            rendition.prev();
          }
        });
      }
    }
  });`

However, this method does not feel as natural as the default paginated mode and makes the reading experience uncomfortable. Even with this approach, when the user selects text on mobile, the selection handles still extend beyond the boundaries of the current page into the adjacent page.

My Question:

1.How can I confine text selection to the current page in a Flutter WebView-based EPUB reader while still using the default paginated mode?
2.Is there a way to override or control the default text selection handles’ behavior (or implement custom text selection handles) so that the selection does not span adjacent pages?
3.Are there any recommended approaches or open source libraries that demonstrate a similar solution for handling text selection boundaries in a paginated view?

Additional Information:

Development Environment:
Flutter SDK: ^3.6.0
Locally downloaded flutter_epub_viewer version: 1.2.0
Target platforms: Android and iOS
Reproduction:
The EPUB content is rendered in paginated mode within a WebView. When text selection handles (default mobile controls) reach the edge of a page, the selection extends into the adjacent page, causing layout and usability issues.
Goal:
I want the text selection to remain confined to the current page, preventing it from extending into adjacent pages, while keeping the natural feel of the default paginated mode.