Svelte 5: Form briefly appears then disappears on page load when using conditional rendering for an image

I’m encountering a strange issue with a Svelte 5 component where a form briefly flashes on the screen for about half a second when the page loads, and then disappears. This happens when I have conditional rendering (using {#if}) for an image AND have bind:value={name} if I remove BOTH the form appears.

<script>
    let name = $state('');
    let name2 = 'test';
</script>

<body>
    <div class="container">
        {#if name.toLowerCase() === name2.toLowerCase()}
            <img src="example" alt="fantasma" class="fantasma" />
        {/if}
        <div class="box">
            <form class="form-group" action="">
                <label for="name" class="pergunta">Seu nome completo</label>
                <input type="text" id="name" class="input" bind:value={name} />
                <label for="date" class="pergunta">data de nascimento</label>
                <input type="date" id="data" class="input" />
            </form>
        </div>
    </div>
</body>

<style>
    .fantasma {
        width: 24%;
        height: auto;
    }
    .container {
        display: flex;
        justify-content: center;
        align-items: center;
        height: 100vh;
        flex-direction: column;
        background-color: #f0f0f0;
        margin: 0;
    }
    .form-group {
        display: flex;
        flex-direction: column;
        align-items: center;
    }
    .pergunta {
        margin-bottom: 7px;
    }
    .input {
        width: 300px;
        margin-bottom: 20px;
    }

    .box {
        width: 370px;
        height: 180px;
        margin: 40px;
        align-items: center;
        display: flex;
        flex-direction: column;
        justify-content: center;
        border-radius: 10px;
        background-color: red;
        border: 2.13px solid #ccc;
        box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
    }
</style>

I understand that initially, name is empty (”), so the condition name.toLowerCase() === name2.toLowerCase() evaluates to false. This prevents the image from rendering, which is the intended behavior. However, I don’t understand why this causes the form to disappear after briefly appearing. It seems like the conditional rendering is somehow interfering with the initial rendering of the form itself.

What I’ve Tried:

  • Removing the {#if} block: Does not solve the problem.
  • Removing bind:value={name}: Does not solve the problem.
  • Removing both the {#if} and bind:value: Solves the form disappearing issue, but obviously removes the conditional image display.

Can’t Open An Object in Javascript

My code is below. This is for a Google Map where you click a spot on the map and it shows in a left pane info from the point of the click. I want to use the lattitude and longitude in my Javascript to do other processing. I see the code returns an object, and when I open it via clicking on it in the Console Log I can see the data that the object contains. I can’t seem to open the object with Javascript to read data from it. I am trying to do this near “alert(response);” in my code. I have read solutions on the internet but none have produced anything but blanks while I can see a lot of data by manually reviewing the Console after I click. Thanks for the help!

<!DOCTYPE html>
<!--
 @license
 Copyright 2019 Google LLC. All Rights Reserved.
 SPDX-License-Identifier: Apache-2.0
-->
<html>
  <head>
    <title>Geocoding Service</title>
    <script>
      /**
       * @license
       * Copyright 2019 Google LLC. All Rights Reserved.
       * SPDX-License-Identifier: Apache-2.0
       */
      let map;
      let marker;
      let geocoder;
      let responseDiv;
      let response;

      function initMap() {
        map = new google.maps.Map(document.getElementById("map"), {
          zoom: 7,
          center: { lat: 44.630978, lng: -86.072480 },
          mapTypeControl: false,
        });
        geocoder = new google.maps.Geocoder();

        const inputText = document.createElement("input");

        inputText.type = "text";
        inputText.placeholder = "Enter a location";

        const submitButton = document.createElement("input");

        submitButton.type = "button";
        submitButton.value = "Geocode";
        submitButton.classList.add("button", "button-primary");

        const clearButton = document.createElement("input");

        clearButton.type = "button";
        clearButton.value = "Clear";
        clearButton.classList.add("button", "button-secondary");
        response = document.createElement("pre");
        response.id = "response";
        response.innerText = "";
        responseDiv = document.createElement("div");
        responseDiv.id = "response-container";
        responseDiv.appendChild(response);

        const instructionsElement = document.createElement("p");

        instructionsElement.id = "instructions";
        instructionsElement.innerHTML =
          "<strong>Instructions</strong>: Enter an address in the textbox to geocode or click on the map to reverse geocode.";
        map.controls[google.maps.ControlPosition.TOP_LEFT].push(inputText);
        map.controls[google.maps.ControlPosition.TOP_LEFT].push(submitButton);
        map.controls[google.maps.ControlPosition.TOP_LEFT].push(clearButton);
        map.controls[google.maps.ControlPosition.LEFT_TOP].push(
          instructionsElement
        );
        map.controls[google.maps.ControlPosition.LEFT_TOP].push(responseDiv);
        marker = new google.maps.Marker({
          map,
        });
        map.addListener("click", (e) => {
          geocode({ location: e.latLng });
        });
        submitButton.addEventListener("click", () =>
          geocode({ address: inputText.value })
        );
        clearButton.addEventListener("click", () => {
          clear();
        });
        clear();
      }

      function clear() {
        marker.setMap(null);
      }

      function geocode(request) {
        clear();
        geocoder
          .geocode(request)
          .then((result) => {
            const { results } = result;

            map.setCenter(results[0].geometry.location);
            marker.setPosition(results[0].geometry.location);
            marker.setMap(map);
            response.innerText = JSON.stringify(result, null, 2);
            return results;
          })
          .catch((e) => {
            alert("Geocode was not successful for the following reason: " + e);
          });

          
          alert(response);
          console.log("response =");
          console.log(response);


          function readPreElement() {
            const preElement = document.getElementById("response"); // Replace "myPre" with the ID of your <pre> element
            const textContent = preElement.textContent;

            console.log("text content = " + textContent); // Output the content to the console
            // You can do whatever you want with the textContent here
          }

          readPreElement();



//          const preElement = document.getElementById("response");
//          const content = preElement.textContent;

//          console.log("content = " + content);

          var _html = document.getElementsByTagName('pre')[0].innerHTML;
          console.log("_html = " + _html);

            alert("_html is type: " + typeof(_html));
            alert("_html is length of: " + _html.length);

          var _query = document.querySelector('pre').innerHTML;
          console.log(_query);

          alert("_query is type: " + typeof(_query));
            alert("_query is length of: " + _query.length);



          //alert(typeof(map));
          //alert(response.innerText.length);
          //console.log(map);
          //report_it();

      }

      window.initMap = initMap;


    </script>
    <style>
      /**
       * @license
       * Copyright 2019 Google LLC. All Rights Reserved.
       * SPDX-License-Identifier: Apache-2.0
       */
      /**
       * Always set the map height explicitly to define the size of the div element
       * that contains the map. 
       */
      #map {
        height: 50%;
      }

      /* Optional: Makes the sample page fill the window. */
      html,
      body {
        height: 100%;
        margin: 0;
        padding: 0;
      }

      input[type="text"] {
        background-color: #fff;
        border: 0;
        border-radius: 2px;
        box-shadow: 0 1px 4px -1px rgba(0, 0, 0, 0.3);
        margin: 10px;
        padding: 0 0.5em;
        font: 400 18px Roboto, Arial, sans-serif;
        overflow: hidden;
        line-height: 40px;
        margin-right: 0;
        min-width: 25%;
      }

      input[type="button"] {
        background-color: #fff;
        border: 0;
        border-radius: 2px;
        box-shadow: 0 1px 4px -1px rgba(0, 0, 0, 0.3);
        margin: 10px;
        padding: 0 0.5em;
        font: 400 18px Roboto, Arial, sans-serif;
        overflow: hidden;
        height: 40px;
        cursor: pointer;
        margin-left: 5px;
      }
      input[type="button"]:hover {
        background: rgb(235, 235, 235);
      }
      input[type="button"].button-primary {
        background-color: #1a73e8;
        color: white;
      }
      input[type="button"].button-primary:hover {
        background-color: #1765cc;
      }
      input[type="button"].button-secondary {
        background-color: white;
        color: #1a73e8;
      }
      input[type="button"].button-secondary:hover {
        background-color: #d2e3fc;
      }

      #response-container {
        background-color: #fff;
        border: 0;
        border-radius: 2px;
        box-shadow: 0 1px 4px -1px rgba(0, 0, 0, 0.3);
        margin: 10px;
        padding: 0 0.5em;
        font: 400 18px Roboto, Arial, sans-serif;
        overflow: hidden;
        overflow: auto;
        max-height: 50%;
        max-width: 90%;
        background-color: rgba(255, 255, 255, 0.95);
        font-size: small;
      }

      #instructions {
        background-color: #fff;
        border: 0;
        border-radius: 2px;
        box-shadow: 0 1px 4px -1px rgba(0, 0, 0, 0.3);
        margin: 10px;
        padding: 0 0.5em;
        font: 400 18px Roboto, Arial, sans-serif;
        overflow: hidden;
        padding: 1rem;
        font-size: medium;
      }
    </style>
  </head>
  <body>
    <div id="map"></div>
    <script
      src="https://maps.googleapis.com/maps/api/js?key=AIzaSyBxeRjDvCOd2QP-12ayt-vg1YZ71WPcwSk&callback=initMap&v=weekly&solution_channel=GMP_CCS_geocodingservice_v2"
      defer
      ></script>
    

      <script src="final_NWS.js">
      </script>;

      <div id="profile">
        temperatureString;
      </div>
    
  



  </body>
</html>

I implemented Zibal payment gateway for NextJS, it gives an error zibal.init is not a function

import { NextResponse } from 'next/server';
const Zibal = require('zibal');

Zibal.init({
  merchant: 'zibal',
  callbackUrl: 'https://some-callback-url.tld',
  logLevel: 2
});

export async function POST(req) {
  try{
    const { amount, mobile, description } = req.json();

    try {
      const result = await Zibal.request(amount, { mobile, description });
      if (result.result === 100) {
        const paymentUrl = Zibal.startURL(result.trackId);
        return NextResponse.json({ paymentUrl },{status:200});
      } else {
        return NextResponse.json({ error: result.statusMessage },{status:400});
      }
    } catch (error) {
      console.log(error)
      return NextResponse.json({ error: 'Error creating payment request' },{status:500});
    }
  } catch {
    return NextResponse.json({ error: 'The method is illegal' },{status:405});
  }
}

TypeError: Zibal.init is not a function

How can I solve it? It is also written like this in the documentation of their site

Manual options to hide a Bootstrap 5 popover

My use-case is this: I have a list of files that users can click on to download. I’d like them to be able to hover over a file and see the file size and checksum. I also want them to be able to select and copy the checksum value to their clipboard if so desired. Initializing the popover on each item and manually getting it to show on mouseover works fine. However, I’m not sure how to hide the popover. I was hoping I could add a mouseleave eventListener to the popover itself, but that doesn’t seem to be an option. Obviously, I want to reserve clicking of the element itself for initiating the download.

<!doctype html>
<html>
<head>
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.7.0/css/all.min.css" integrity="sha512-9xKTRVabjVeZmc+GUW8GgSmcREDunMM+Dt/GrzchfN8tkwHizc5RP4Ok/MXFFy5rIjJjzhndFScTceq5e6GvVQ==" crossorigin="anonymous" referrerpolicy="no-referrer" />

    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js" integrity="sha512-v2CJ7UaYy4JwqLDIrZUI/4hqeoQieOmAZNXBeQyjo21dadnwR+8ZaIJVT8EE2iyI61OV8e6M8PP2/4hpQINQ/g==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
    <script src="https://cdn.jsdelivr.net/npm/@popperjs/[email protected]/dist/umd/popper.min.js" integrity="sha384-I7E8VVD/ismYTF4hNIPjVp/Zjvgyol6VFvRkX/vR+Vc4jQkC+hVqc2pM8ODewa9r" crossorigin="anonymous"></script>
    <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.min.js" integrity="sha384-0pUGZvbkm6XF6gxjEnlmuGrJXVbNuzT9qBBavbLwCsOGabYfZo0T0to5eqruptLy" crossorigin="anonymous"></script>
</head>
<body>
<br/>
<a href="#"
   data-bs-toggle="popover"
   data-bs-content="size: 5MB<br/>sha1: 123456789"
   >
   FILE_TO_DOWNLOAD_1
 </a>
<br/>
<br/>
<a href="#"
   data-bs-toggle="popover"
   data-bs-content="size: 5MB<br/>sha1: 123456789"
>
    FILE_TO_DOWNLOAD_2
</a>

<script>
    $(document).ready(function() {

        var popoverTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="popover"]'));
        var popoverList = popoverTriggerList.map(function (popoverTriggerEl) {
          var popover = new bootstrap.Popover(popoverTriggerEl, {
            container: 'body',
            html: true,
            trigger: 'manual'
          });
          popoverTriggerEl.addEventListener('mouseover', (event) => {
            popover.show();
          });
          return popover;
        });

    });
</script>

</body>

How can I ensure my SQLite database is initialized before interacting with it in a web app using sql.js and IndexedDB

I’m building a web app using sql.js with WebAssembly to manage a SQLite database in the browser. I’m storing the SQLite database in IndexedDB and need to ensure that the database is loaded and initialized properly before performing any SQL operations.

Here’s the current setup:

When the page loads, I try to load the database from IndexedDB or create a new one if it doesn’t exist.
I’m using async/await to load the database, but I’m still running into issues where I try to interact with the database before it has been fully initialized.

When I attempt to insert data into the SQLite database or run queries, I sometimes encounter the error: “Database is not initialized.”

let db;
const dbName = "db_transactions.db"; // Your specific database name

// Function to load the database from IndexedDB
async function loadDatabaseFromIndexedDB() {
  return new Promise((resolve, reject) => {
    const request = indexedDB.open('sqliteDBs', 1); // Open IndexedDB database
    request.onupgradeneeded = () => {
      const dbStore = request.result.createObjectStore('databases', {
        keyPath: 'name'
      });
    };

    request.onsuccess = () => {
      const dbStore = request.result.transaction('databases', 'readwrite').objectStore('databases');
      const getRequest = dbStore.get(dbName);

      getRequest.onsuccess = function() {
        const data = getRequest.result;
        if (data) {
          // Import the saved database binary data
          db = new SQL.Database(new Uint8Array(data.dbBinary));
          resolve(db);
        } else {
          // If no saved database, create a new one
          db = new SQL.Database();
          resolve(db);
        }
      };
      getRequest.onerror = reject;
    };

    request.onerror = reject;
  });
}

// Function to save the database to IndexedDB
async function saveDatabaseToIndexedDB(db) {
  const binaryData = db.export(); // Export the SQLite database to binary format

  return new Promise((resolve, reject) => {
    const request = indexedDB.open('sqliteDBs', 1); // Open IndexedDB database
    request.onupgradeneeded = () => {
      const dbStore = request.result.createObjectStore('databases', {
        keyPath: 'name'
      });
    };

    request.onsuccess = () => {
      const dbStore = request.result.transaction('databases', 'readwrite').objectStore('databases');
      const putRequest = dbStore.put({
        name: dbName,
        dbBinary: binaryData
      });

      putRequest.onsuccess = resolve;
      putRequest.onerror = reject;
    };

    request.onerror = reject;
  });
}

// Function to initialize the database
async function initializeDatabase() {
  try {
    // Wait for the database to be loaded
    await loadDatabaseFromIndexedDB();
    console.log("Database loaded from IndexedDB or created.");

    // Create table if not exists
    db.run("CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY, name TEXT, email TEXT);");
    console.log("Table 'users' created or already exists.");
  } catch (err) {
    console.error("Failed to initialize database:", err);
  }
}

// Wait for the database to initialize before allowing interactions
initializeDatabase().then(() => {
  console.log("Database is initialized.");
});

// Handle form submission
document.getElementById('dataForm').addEventListener('submit', async function(event) {
  event.preventDefault();

  // Ensure db is initialized before proceeding
  if (!db) {
    console.error("Database is not initialized.");
    return;
  }

  // Get form data
  const name = document.getElementById('name').value;
  const email = document.getElementById('email').value;

  // Insert data into the SQLite database
  try {
    db.run("INSERT INTO users (name, email) VALUES (?, ?);", [name, email]);

    // Save the updated database to IndexedDB
    await saveDatabaseToIndexedDB(db);
    console.log('Database saved to IndexedDB.');

    // Retrieve and display data from the database
    const result = db.exec("SELECT * FROM users;");
    document.getElementById('output').textContent = JSON.stringify(result, null, 2);
  } catch (err) {
    console.error("Error while inserting data or saving database:", err);
  }
});
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>SQLite with WebAssembly</title>
  <!-- Correct inclusion of sql.js -->
  <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/sql-wasm.js"></script>
</head>

<body>
  <h1>SQLite with WebAssembly (Persistent Database)</h1>

  <form id="dataForm">
    <label for="name">Name:</label>
    <input type="text" id="name" name="name" required><br><br>

    <label for="email">Email:</label>
    <input type="email" id="email" name="email" required><br><br>

    <button type="submit">Submit</button>
  </form>

  <h2>Database Output</h2>
  <pre id="output"></pre>


</body>

</html>

Why doesn’t my input clear when pressing “Enter” using a custom useKey hook in React?

I am trying to create a search input component in React where the search query should reset to an empty string when the “Enter” key is pressed, unless the input is currently focused.

I am using a custom useKey hook to listen for key events globally, but the input doesn’t reset as expected when I press “Enter”. Here’s my setup:

Custom Hook: useKey
This hook listens for specific key presses and triggers a callback:

import { useEffect } from "react";

export function useKey(key, action) {
  useEffect(() => {
    function callback(e) {
      if (e.code.toLowerCase() === key.toLowerCase()) {
        action();
      }
    }

    document.addEventListener("keydown", callback);

    return () => {
      document.removeEventListener("keydown", callback);
    };
  }, [key, action]);
}

Search Component

This is my Search component, which uses the useKey hook to reset the query state and focus on the input:

import { useRef } from "react";
import { useKey } from "./useKey";

export default function Search({ query, setQuery }) {
  const inputEl = useRef(null);

  useKey("Enter", function () {
    if (document.activeElement === inputEl.current) {
      // If the input is focused, do nothing
      return;
    }

    // Reset the query and focus on the input
    inputEl.current.focus();
    setQuery(""); 
  });

  return (
    <input
      type="text"
      placeholder="Search"
      value={query}
      onChange={(e) => setQuery(e.target.value)}
      ref={inputEl}
    />
  );
}

Parent Component
Here is my parent component, which manages the query state:

import { useState } from "react";
import Search from "./Search";

export default function App() {
  const [query, setQuery] = useState("");

  return (
    <div>
      <Search query={query} setQuery={setQuery} />
      <p>Current search: {query}</p>
    </div>
  );
}

Problem:

When I type something in the input (e.g., “hello”) and press “Enter”, I expect the query state to reset to an empty string (“”), and the displayed search query to update to “Current search: “. However, the query state doesn’t reset, and the input still shows the same text.

Here’s what I’ve tried so far:

  1. Verified that the setQuery function is being called by adding logs,
    and it is called with an empty string (“”).

  2. Checked that the useKey hook is firing the callback when the “Enter” key is pressed,and it is.

  3. Ensured no other listeners or components are interfering.

Despite this, the input value doesn’t reset as expected.

What I Expect:

  • The query state should reset to “” when “Enter” is pressed.
  • The input field should clear, and the “Current search” text should
    reflect the empty state.

Additional Notes:

  • I’m using React 18.3.1

  • The useKey hook is used in other components as well, but I don’t
    think they interfere with this one.

Question:

What could be causing the query state to not reset as expected, and how can I fix this issue?

How to hide Chrome’s side panel using a keyboard shortcut in Chrome Extension

I would like to write an extension that shows and hides a side panel when Ctrl+Space is pressed. I came up with the following solution:

background.js

var sidePanelOpen = false;

chrome.commands.onCommand.addListener((command) => {
  console.log(`Command: ${command}`);

  if (!sidePanelOpen) {
    chrome.tabs.query({ active: true, currentWindow: true }, ([tab]) => {
      chrome.sidePanel.open({ tabId: tab.id });
    });
    sidePanelOpen = true;
  } else {
    chrome.tabs.query({ active: true, currentWindow: true }, ([tab]) => {
      console.log(`Disabling sidepanel: ${tab.id}`);
      chrome.sidePanel.setOptions({ tabId: tab.id, enabled: false });
      console.log(`Disabled sidepanel: ${tab.id}`);
    });
    sidePanelOpen = false;
  }
});

manifest.json

{
  "name": "Test Side Panel",
  "version": "1.0",
  "manifest_version": 3,
  "permissions": [
    "sidePanel",
    "storage",
    "tabs",
    "scripting"
  ],
  "background": {
    "service_worker": "background.js"
  },
  "side_panel": {
    "default_path": "panel.html"
  },
  "host_permissions": [
    "<all_urls>"
  ],
  "commands": {
    "toggle-side-panel": {
      "suggested_key": {
        "default": "Ctrl+Space",
      },
      "description": "Toggle side panel"
    }
  }
}

The Chrome side panel opens as expected, but it does not hide when Ctrl+Space is pressed a second time. The logs for disabling are printed, but the operation appears to be a no-op.
What am I doing wrong?

DocuSign embedded signing, sudden formatting issue

We have a couple of instances of DocuSign embedded forms in our app. In both cases, the user signs the form using an embedded iframe (using the DS SDK), and both worked fine up until October ’24.

One of them is a DS template, that one worked and continues to work.

The other is a PDF that we generate, indicate where the signature goes, and send it to DS on the backend. This one stopped working in our dev environments sometime in the middle of October; every step of the process works as expected, including the formatting, except for the view where the user signs. At that point, the document that they were shown became a relatively unformatted view (like a basic word doc instead of the styling the PDF had).

We asked our rep about it and they told us nothing had changed on their end that should affect the formatting, but nothing changed on our end either, so I doubt that.

We noticed that there is a console error now (that we don’t think we saw before):

Error importing mockServiceWorker.js
NetworkError: Failed to execute 'importScripts' on 'WorkerGlobalScope': Failed to import 'https://docutest-a.akamaihd.net/integrations/1ds/libs/msw/2.2.11/lib/mockSeviceWorker.js'. importScripts() of new scripts after service worker installation is not allowed.`

along with

Permissions policy violation: Geolocation access has been blocked because of a permissions policy applied to the current document.

Our app is React (vite) + Node/Express and we’re pulling DocuSign in using their recommended CDK url as an index.html script include.

Any leads? DocuSign blew us off, but we haven’t changed anything in code (DocuSign is administered by a different team, but we don’t believe any changes have been made) and this error just popped up.

And again, the error first appeared in dev in October, and at that point we tested prod and the error was not occurring. When we checked yesterday we noticed it in prod, also.

We’re banging our heads against the wall and I’d love any insight!

Can I avoid cumulative layout shifts in Lit by pre-loading components?

I have a Lit project and Chrome’s Lighthouse shows very high cumulative layout shift (CLS). I assume the problem arises due to the fact that component’s CSS loads with JS files and cannot (?) be loaded with a in header. Because CSS is loaded late, the layout shifts.

For example, let’s say we have nested components, such as

<component-one>
    <component-two></component-two>
</component-one>

And that component-two is defined as

import {css, html, LitElement} from 'lit'
import {customElement} from 'lit/decorators.js'

@customElement('component-two')
export class ComponentTwo extends LitElement {
    static styles = css`
        :host {
            display: block;
            min-height: 500px;
        }
    `

    render() {
        return html`
            <div>Something</div>
        `
    }
}

declare global {
    interface HTMLElementTagNameMap {
        'component-two': ComponentTwo
    }
}

Is there a way to load component-two’s CSS earlier so it wouldn’t cause a layout shift?

If the problem were with component-one, it’d be easy, because its height could be defined in stylesheet in head. But that approach wouldn’t work with (possibly deeply) nested components.

Creating/moving a dropzone while dragging with interact.js

I’m writing an application where I use interact.js for DND(drag and drop). Often the start of a DND interaction causes a change in the page; dropzones changing position, being deleted, and new ones being created.

Creation (main issue)

In the snippet below, you get to initiate new dropzones by hovering on the orange box with the draggable. But they don’t activate until you drop the draggable and pick it up again.

// DRAGGABLE
const draggable = document.getElementById("draggable");
let x = 0;
let y = 0;
interact(draggable).draggable({
  autoScroll: false,
  onmove(e) {
    x += e.dx;
    y += e.dy;
    // translate the element
    draggable.style.transform = "translate(" + x + "px, " + y + "px)";
  }
});

// HOVERZONE
const hoverzone = document.getElementById("hoverzone");
setupDropzone(hoverzone, addNewZone);

// ADDED ZONES
function addNewZone() {
  // Make html element
  const node = document.createElement("div");
  node.classList.add("box", "zone");
  const textNode = document.createTextNode("Hi!");
  node.appendChild(textNode);
  document.body.appendChild(node);
  // Ininialize interactable
  setupDropzone(node);
}

// HELPER
function setupDropzone(element, ondragenter = () => {}) {
  interact(element).dropzone({
    accept: "#draggable",
    ondrop(e) {
      element.classList.remove("drag-hover");
    },
    ondragenter(e) {
      element.classList.add("drag-hover");
      ondragenter();
    },
    ondragleave(e) {
      element.classList.remove("drag-hover");
    }
  });
}
body {
  display: flex;
}

.box {
  width: 120px;
  border-radius: 8px;
  padding: 20px;
  margin: 1rem;
  background-color: peru;
  color: white;

  touch-action: none;
  user-select: none;

  box-sizing: border-box;
}

#draggable {
  background-color: #29e;
  z-index: 99;
}

.zone {
  background-color: pink;
}

.drag-hover {
  outline: 3px solid black;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/interact.js/1.10.27/interact.min.js"></script>

<div class="box" id="hoverzone">
  Hover draggable on me!
</div>

<div class="box" id="draggable">
  Drag me!
</div>

At some point, I tried using event.interaction.start(...) in new dropzones (like in here) by storing the dragstart event of the draggable, but that just broke things and lead nowhere.

I also found no way to delay the DND process (rect calculation, firing dragactivate on dropzones, etc.) after dragstart, until the new zones initialize.

Do you have either a solution to this with interact.js, or a similar DND library that solves this issue and the issue below elegantly?


Movement (solved but worth mentioning)

I solved the issue of dropzones moving using interact.dynamicDrop(true);. This is unideal as it recalculates rectangles on every dragmove, while the actual movement happens rarely in my case. Is there a way to just trigger recalculation once? Doing something like below complicated my code in way that’s not worth it and felt too hacky:

  1. Enable dynamicDrop
  2. Wait for dragmove
  3. Disable dynamicDrop

React state updates variable only when Chrome Inspect element is open

I’ve ran into a problem using React that there is a part of my code that only renders when the variable tipo assigned from useState gets a value. In my case the useState only updates the variable tipo if the inspect element from Chrome specifically is open.

import { Outlet, Link } from 'react-router-dom'
import { useForm } from "react-hook-form"
import React, { useState, useEffect } from 'react'
import { Separator } from '@/components/ui/separator'
import { DMA } from '@/hooks/getDatePTBR'
import { Button } from '@/components/ui/button'
import AlertDialogComponent from '@/components/alert-dialog-component.jsx'
import { Input } from "@/components/ui/input"
import {
    Select,
    SelectContent,
    SelectItem,
    SelectTrigger,
    SelectValue,
} from "@/components/ui/select"

export default function CadastrarCliente() {
    const {
        register,
        handleSubmit,
        setError,
        formState: { errors, isSubmitting },
    } = useForm()

    const onSubmit = async (data) => { //recebe as informações do form
        try {
            if (tipo === "FISICA") {
                data.tipo = "FISICA";
                data.cnpj = null;
                data.razaosocial = null;
                data.responsavel = null;
            } else {
                data.tipo = "JURIDICA";
                data.cpf = null;
                data.rg = null;
            }
            console.log(data);
        } catch (err) {
            setError("root", {
                message: "Erro do backend",
            });
        }

    }

    const [tipo, setTipo] = useState(null);

    return (
        <>
            <div className='w-full m-6 mt-10' id='clientes'>
                <div className='grid grid-cols-2' id='clientes-header'>
                    <h3>Cadastrar cliente</h3>
                    <div className='justify-self-end'><DMA /></div>
                </div>

                <div className='mb-4 mt-2'><Separator /></div>

                <form onSubmit={handleSubmit(onSubmit)} className='grid grid-cols-4 gap-4 items-start'>
                    <div>
                        <p>Tipo</p>
                        <Select>
                            <SelectTrigger>
                                <SelectValue placeholder="Selecionar" />
                            </SelectTrigger>
                            <SelectContent>
                                <SelectItem value="fisica" onClick={() => setTipo('FISICA')}>Física</SelectItem>
                                <SelectItem value="juridica" onClick={() => setTipo('JURIDICA')}>Jurídica</SelectItem>
                            </SelectContent>
                        </Select>
                    </div>
                    <div>
                        <p>{tipo === 'FISICA' ? "Nome" : "Nome fantasia"} </p>
                        <Input {...register("nome", {
                            required: "Nome é obrigatório",
                            minLength: { value: 2, message: "Nome precisa ter pelo menos 2 caracteres." }
                        }
                        )} type='text'></Input>
                        {errors.nome && <div className='text-red-500 mt-2'>{errors.nome.message}</div>}
                    </div>
                    <div>
                        <p>Email</p>
                        <Input {...register("email")} type='email' autoComplete='email'></Input>
                        {errors.email && <div className='text-red-500 mt-2'>{errors.email.message}</div>}
                    </div>
                    <div>
                        <p>Celular</p>
                        <Input {...register("cel")} type='text'></Input>
                        {errors.cel && <div className='text-red-500 mt-2'>{errors.cel.message}</div>}
                    </div>

                    {tipo === 'FISICA' && <>
                        <div>
                            <p>CPF</p>
                            <Input {...register("cpf", {
                                required: "CPF é obrigatório",
                                minLength: { value: 11, message: "CPF precisa ter 11 caracteres." }
                            }
                            )} type='text'></Input>
                            {errors.cpf && <div className='text-red-500 mt-2'>{errors.cpf.message}</div>}
                        </div>
                        <div>
                            <p>RG</p>
                            <Input {...register("rg")} type='text'></Input>
                            {errors.rg && <div className='text-red-500 mt-2'>{errors.rg.message}</div>}
                        </div>
                    </>}
                    {tipo === 'JURIDICA' && <>
                        <div>
                            <p>CNPJ</p>
                            <Input {...register("cnpj", {
                                required: "CNPJ é obrigatório",
                                minLength: { value: 14, message: "CNPJ precisa ter 14 caracteres." }
                            }
                            )} type='text'></Input>
                            {errors.cnpj && <div className='text-red-500 mt-2'>{errors.cnpj.message}</div>}
                        </div>
                        <div>
                            <p>Razão social</p>
                            <Input {...register("razaosocial")} type='text'></Input>
                            {errors.razaosocial && <div className='text-red-500 mt-2'>{errors.razaosocial.message}</div>}
                        </div>
                        <div>
                            <p>Responsável</p>
                            <Input {...register("responsavel")} type='text'></Input>
                            {errors.responsavel && <div className='text-red-500 mt-2'>{errors.responsavel.message}</div>}
                        </div>
                    </>}
                    
                    <div>
                        <p>CEP</p>
                        <Input {...register("cep", {
                            minLength: { value: 8, message: "CEP precisa ter 8 caracteres." }
                        }
                        )} type='text'></Input>
                        {errors.cep && <div className='text-red-500 mt-2'>{errors.cep.message}</div>}
                    </div>
                    <div>
                        <p>Número</p>
                        <Input {...register("numero")} type='text'></Input>
                        {errors.numero && <div className='text-red-500 mt-2'>{errors.numero.message}</div>}
                    </div>
                    <div>
                        <p>Complemento</p>
                        <Input {...register("complemento")} type='text'></Input>
                        {errors.complemento && <div className='text-red-500 mt-2'>{errors.complemento.message}</div>}
                    </div>
                    <div>
                        <p>Bairro</p>
                        <Input {...register("bairro")} type='text'></Input>
                        {errors.bairro && <div className='text-red-500 mt-2'>{errors.bairro.message}</div>}
                    </div>
                    <div>
                        <p>Cidade/UF</p>
                        <Input {...register("cidade")} type='text'></Input>
                        {errors.cidade && <div className='text-red-500 mt-2'>{errors.cidade.message}</div>}
                    </div>
                    <p>{tipo}</p>
                    <div className='grid grid-cols-2 gap-4 col-span-2 self-end '>
                        <Link to='/dashboard/clientes/'>
                            <Button variant='outline' className='w-full' href='/dashboard/clientes/'>Voltar</Button>
                        </Link>

                        <Button disabled={isSubmitting} type='submit'>
                            {isSubmitting ? "Cadastrando..." : "Cadastrar"}
                        </Button>
                    </div>
                    
                    {errors.root && (
                        <AlertDialogComponent
                            tipo='warning'
                            title='Erro!'
                            desc={errors.root.message}
                            onClose={() => setError('root', null)}
                        />)}

                </form>
            </div>
            <Outlet />
        </>
    )
}

At the end I am rendering the tipo variable to see if it changes without opening the console and as expected, it only renders/change its value if I am with inspect element open on Chrome.

I tried using this page on Firefox but didn’t work and I really don’t know what else to do.

supabase Import createClient produces: unable to resolve module specifier

Created a new VS Code project with a blank HTML file added a button to it (to activate a database request for this test)

Got a supabase account, created database/tables no problem, installed supabase through the terminal via:

npm install @supabase/supabase-js

No issues, a node_modules folder was created with a @supbase sub folder, so entered the code from the supabase website to connect to the database an this line:

import { createClient } from “@supabase/supabase-js”;

Ran in FireFox VS Code debug console showed this message:
Produces this error:

@supabase/supabase-js unable to resolve module specifier
“@supabase/supabase-js/”. Relative references must start with either “/”, “./”, or “../”.

Modified the import to the folder location:
import { createClient } from “./node_modules/@supabase/supabase-js”;

Noticed NOTHING was happening, the button was unresponsive, no errors in the debug console, OR in the FireFox console..

Added an alert:
import { createClient } from “./node_modules/@supabase/supabase-js”;
alert(“e”);

This is also ignored..

Ran a debug client in Chrome, and its console report an error on line 1 (the import line):

server responded with a MIME type of “text/html”

import { createClient } from “./node_modules/@supabase/supabase-js/”;
err_aborted 404 (not found)

Have tried this experiment now on two different computers one Windows one OSX and THE SAME ISSUE occurs the import line is not working

What step is missing from the supabase website to get the import line to work?
Have uninstalled/installed supabase and nothing changes the supplied import command does work in any variation of the path – HEEELLLPPP!

Is this the expected output of .replace() or is it bug? [duplicate]

This code:

const source = `'$' apple, orange`
const replaceFrom = `'$' apple`
const replaceTo = `'$' peach`
const result = source.replace(replaceFrom, replaceTo)

console.log('nSource:n', source)
console.log('nReplaceFrom:n', replaceFrom)
console.log('nReplaceTo:n', replaceTo)
console.log('nResult:n', result)

produces this output:

Source:
 '$' apple, orange

ReplaceFrom:
 '$' apple

ReplaceTo:
 '$' peach

Result:
 ', orange peach, orange

Is this the expected output of .replace() in JavaScript, or is it some sort of bug?

If this behavior is expected, how can we reliably replace arbitrary strings with other arbitrary strings in JavaScript?

Rules for normalising Scandinavian names to English alphabet, for email?

We have been asked to create an email address generator, that will create a normalised format (based on the alphabet used by English), given first name and last name.

So far we have a solution that works for any latin-type name, but we are stuck on how to deal with characters used in Scandinavian names. Namely: å, ø, æ.

Right now we are likely going with:

  • å -> a
  • ø -> o (some sources suggest oe)
  • æ -> ae

We’ve looked to see if there are any accepted rules on how to ‘transliterate’ (not even sure the right term for this?) from extended latin alphabets to the basic one used by English, but haven’t found one. Can anyone point us to a good reference?

The code we are using to ‘flatten’ / ‘normalise’ the text:

  function flattenText (text) {
    const extendedChar = ['ß', 'Œ', 'œ'];
    const englishChar = ['ss', 'oe', 'oe'];

    // Deal with accents
    text = text.normalize('NFD').replace(/[u0300-u036f]/g, '');
    
    // Deal with other letter types
    let newText = '';
    for (let i = 0; i < text.length; i++) {
      const charIdx = extendedChar.indexOf(text.charAt(i));
      if (charIdx > -1) {
        newText += englishChar[charIdx];
      } else {
        newText += text.charAt(i);
      }
    }

    return newText;
  }

Edit: Looks like it is just ‘Ø’ causing issues now. The normalize() method dealt with the other cases, though would still be interested is a good reference

How to Restrict Azure Speech SDK AudioConfig to Only System Audio and Exclude Microphone Input?

Question:
I am working on a Blazor project where I integrate Azure Speech Service to perform speech-to-text transcription on system audio during screen sharing. However, I am facing an issue where audio from the microphone is still being captured, even though I’ve explicitly disabled it in the JavaScript code that manages the screen sharing.

Here’s the flow of my implementation:

JavaScript Code (Screen Sharing)
This function enables screen sharing with system audio, while explicitly disabling the microphone tracks:

window.startScreenSharing = async function () {
    try {
        const container = document.getElementById('sharedScreen');
        if (!container) {
            console.error('Element with ID "sharedScreen" not found');
            return false;
        }

        // Start screen sharing with video and system audio
        const stream = await navigator.mediaDevices.getDisplayMedia({
            video: true,
            audio: true  // Request system audio along with video
        });

        // Disable microphone tracks
        stream.getAudioTracks().forEach(track => track.enabled = false);

        // Display the shared screen in a video element
        const videoElement = document.createElement('video');
        videoElement.srcObject = stream;
        videoElement.autoplay = true;
        videoElement.muted = true;  // Mute local audio to avoid feedback
        videoElement.controls = false;
        videoElement.style.width = '100%';
        videoElement.style.height = '100%';
        container.innerHTML = '';
        container.appendChild(videoElement);
        window.currentScreenStream = stream;

        return true;
    } catch (err) {
        console.error('Screen capture error:', err);
        alert('Screen capture failed. Check your browser settings.');
        return false;
    }
};

Blazor Page (C# Method to Start Screen Sharing)
The StartScreenSharing method in Blazor calls the above JavaScript function and initiates transcription:

  private async Task StartScreenSharing()
    {
        try
        {
            Console.WriteLine("Starting screen sharing...");
            bool started = await JSRuntime.InvokeAsync<bool>("startScreenSharing");
            if (started)
            {
                isScreenSharing = true;
                StateHasChanged();
                StartTimer();

                _speechService = new SpeechToTextService(); 
                await _speechService.StartRecognitionAsync(_cancellationTokenSource.Token);
            }
            else
            {
                Console.WriteLine("Failed to start screen sharing.");
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine($"Error starting screen sharing: {ex.Message}");
        }
    }

Azure Speech SDK (C# Code)
This is the code where I configure the Azure Speech SDK for transcription:

 public async Task StartRecognitionAsync(CancellationToken cancellationToken)
    {
        var config = SpeechConfig.FromSubscription(subscriptionKey, region);

        // Using default speaker output
        using var audioConfig = AudioConfig.FromDefaultSpeakerOutput();
        recognizer = new SpeechRecognizer(config, audioConfig);

        recognizer.Recognizing += async (s, e) =>
        {
            if (!string.IsNullOrEmpty(e.Result.Text))
            {
                Console.WriteLine($"Recognized: {e.Result.Text}");
            }
        };

        await recognizer.StartContinuousRecognitionAsync().ConfigureAwait(false);

        cancellationToken.Register(async () =>
        {
            await recognizer.StopContinuousRecognitionAsync().ConfigureAwait(false);
        });
    }

The Problem
Despite disabling the microphone in JavaScript (stream.getAudioTracks().forEach(track => track.enabled = false)), Azure Speech SDK still captures microphone audio. Even when I explicitly block microphone access in the browser settings, Azure Speech continues to detect microphone input.

I suspect the issue lies in:

using var audioConfig = AudioConfig.FromDefaultSpeakerOutput();

It seems like AudioConfig ignores the JavaScript constraints and pulls audio from all available sources, including the microphone. This behavior breaks the expected functionality, as my use case only requires system audio from the shared screen.

What I Have Tried
Disabled microphone tracks in the JavaScript function managing screen sharing.
Blocked microphone access in the browser settings.
Experimented with AudioConfig options, such as FromStreamInput and FromDefaultSpeakerOutput.
None of these approaches have successfully restricted AudioConfig to system audio only.

My Questions
How can I configure Azure Speech SDK’s AudioConfig to exclusively capture system audio and completely ignore microphone input?
Is there a better way to handle audio streams (e.g., passing audio streams from JavaScript to Blazor and then to Azure)?

Any guidance or suggestions would be highly appreciated.