Azure function not recognizing handlers on deployment when importing @azure/cosmos

Summary

When deploying a Node.js Azure Functions v4 application, the function handlers are not detected by the Azure runtime if the @azure/cosmos package is imported and used. The deployment appears to succeed, but the functions list in the Azure portal is empty, and any attempt to call the endpoints results in a 404 Not Found error.

If the import { CosmosClient } from "@azure/cosmos"; line and any usage of CosmosClient are commented out, the code builds, deploys, and runs correctly, with the function handlers being detected as expected.

Environment Details:

  • Azure Functions Runtime: v4
  • Node.js Version: v22.x
  • TypeScript Version: latest
  • Bundler: tsup v8.5.0
  • @azure/functions: v4.7.3
  • @azure/cosmos: v4.4.1

deployment command im using func azure functionapp publish <app-name>

Expected Behavior

The deployed function app should recognize the get-user-details HTTP trigger, and the endpoint …/api/user/{userId} should be active and callable.

Actual Behavior

The deployment completes without error, but the Azure Functions runtime does not detect any functions. The “Functions” tab in the Azure Portal is empty, and all API calls return a 404 Not Found error.

Deployed Code (dist/index.js)

import { app } from "@azure/functions";
import { CosmosClient } from "@azure/cosmos";

var COSMOS_DB_ENDPOINT = process.env.COSMOS_DB_ENDPOINT;
var COSMOS_DB_KEY = process.env.COSMOS_DB_KEY;
var COSMOS_DB_NAME = process.env.COSMOS_DB_DATABASE_ID;
var cosmosConfig = {
  endpoint: COSMOS_DB_ENDPOINT,
  key: COSMOS_DB_KEY,
  databaseId: COSMOS_DB_NAME,
  containers: {
    users: "Users",
  }
};

// src/functions/user/userDetails.get.ts
var container = new CosmosClient({
  endpoint: cosmosConfig.endpoint,
  key: cosmosConfig.key,
  connectionPolicy: {
    enableEndpointDiscovery: true
  }
}).database(cosmosConfig.databaseId).container("Items");
async function getUserDetails(request) {
  try {
    console.log(container);
    return {
      status: 200,
      jsonBody: {
        data: "validatedUserItem"
      }
    };
  } catch (error) {
    console.log("error: ", error);
    return {
      status: 500,
      jsonBody: {
        data: "error"
      }
    };
  }
}
app.http("get-user-details", {
  methods: ["GET"],
  authLevel: "anonymous",
  route: "user/{userId}",
  handler: getUserDetails
});
export {
  getUserDetails,
};

this is my package.json

{
  "name": "my-app",
  "version": "1.0.0",
  "type": "module",
  "main": "dist/index.js",
  "scripts": {
    "build": "tsup",
    "dev": "npm run build -- --watch",
    "clean": "rm -rf package-lock.json node_modules",
    "start": "npm run build && func start --typescript",
    "func:deploy": "npm run build && func azure functionapp publish waqfwakalah-dev-func"
  },
  "imports": {
    "#common/*": "../common/src/*"
  },
  "dependencies": {
    "@azure/cosmos": "^4.4.1",
    "@azure/functions": "^4.7.3",
    "zod": "^4.0.5"
  },
  "devDependencies": {
    "@types/node": "^24.0.13",
    "esbuild-plugin-alias": "^0.2.1",
    "tsup": "^8.5.0"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "description": ""
}

this is my tsup config:

import { defineConfig } from 'tsup'
import alias from 'esbuild-plugin-alias'
import path from 'path'

export default defineConfig({
  entry: ['src/index.ts'],
  outDir: 'dist',
  format: 'esm',
  target: 'node22',
  splitting: false,
  sourcemap: false,
  dts: false,
  external: ['@azure/functions', '@azure/cosmos'],
  clean: true,
  esbuildPlugins: [
    alias({
      '#common': path.resolve(__dirname, '../common/src'),
    }),
  ],
})
``

The issue seems to be directly related to how the @azure/cosmos package is handled during the bundling process.

there is no errors when deploying so im not sure how to go about solving this, the only thing i can see is 0 functions found

Getting read access for specific document in case a field matches

I have the following rules in Cloud Firestore:

match /users/{userId}/{documents=**} {  
  allow read: if request.auth.token.email in resource.data.shareWith;
  allow read, write: if request.auth.uid == userId;
}

However, I still get a permission error when trying to access a document from a different user, even though that document does have the email address from the requesting user in the shareWith field array.


My code for accessing the document looks like this:

  const
    docRef = doc(db, 'users', uid)
  ;

  return getDoc(docRef).then(d => d.exists() ? d.data() : this.create(docRef));

And this is the db:
enter image description here


Additional info:


What am I missing?

Why isnt the file download is working? Error: ‘file is unable to be downloaded’ [closed]

When I download the file it says the file wasn’t available on the site. When I download the file based on my Javascript code it doesn’t download the file properly, it says file is unable to be downloaded

function addDownload() {
  const filename = document.getElementById("myfile").files[0].name;
  console.log(filename)

  let newelecment = document.createElement('a');
  newelecment.setAttribute('href', 'filename')
  newelecment.setAttribute('download', filename);
  alert(newelecment.value)
  newelecment.textContent = `${filename}`
  let par = document.createElement('p');
  par.appendChild(newelecment);
  console.log(par.value)

  document.getElementById('link').appendChild(par);
}

document.getElementById("myfile").addEventListener("input", addDownload);
<label for="myfile">Select a file:</label>
<input type="file" id="myfile" name="myfile">
<div id="link"></div>

Why isnt the file download is not working?

When I download file says file wasn’t available on site, when I download the file based on my javascript code it doesn’t download the file properly, it says file is unable to be downloaded

<html>
<head>
    <title>How to Download File in HTML</title>
</head>
<body>

        <label for="myfile">Select a file:</label>
        <input type="file" id="myfile" name="myfile">



    <div id="link"></div>
    <script>

        function addDownload(){
            
            const filename = document.getElementById("myfile").files[0].name;
            alert(filename)

            let newelecment = document.createElement('a');
            newelecment.setAttribute('href','filename')
            newelecment.setAttribute('download', filename);
            alert(newelecment.value)
            newelecment.textContent = `${filename}`
            let par = document.createElement('p');
            par.appendChild(newelecment);
            alert(par.value)

            document.getElementById('link').appendChild(par);
        }


        document.getElementById("myfile").addEventListener("input", addDownload);

        
    </script>
</body>
</html>```

Real-Time Discord Messaging via Chrome Extension using RGOK Node Integration(watch out! let’s build server) [closed]

I recently built a system that lets you send and receive Discord messages directly inside a Chrome Extension — without needing to switch to the Discord app. It uses Node.js + Discord.js for backend handling, and Ngrok for exposing local servers. The frontend handles real-time updates, read/unread message logic, and scroll-triggered UI behavior.

Currently, I run a local Node server to handle Discord bot events and webhooks.

My next goal:

  • Move the backend logic fully into the Chrome Extension,
    or
  • Connect to a cloud-hosted server (e.g., Firebase Functions, AWS Lambda, etc.)
    to eliminate the need to run node server.js every time.

My goal is to make this feel fully integrated and lightweight — no terminal needed.


Project Overview

  • UI built with popup.html, popup.js, and styles.css
  • Server logic isolated in /server/server.js and exposed via Ngrok
  • Uses webhook + bot combo for 2-way messaging

My Question

Is there a best-practice way to embed backend-like logic into a Chrome Extension (e.g., using Service Workers or background scripts)?
Or, what are efficient cloud deployment strategies (serverless, free-tier friendly) you recommend for low-latency Discord integrations?


Project Screenshots

  1. Introduction UI
  2. File Logic Architecture
  3. System Overview UI
  4. Conclusion: Idea for Global Cloud Server

Decreasing load times on blog post site

So, I’ve been working on an ExpressJS blog site which allows for image uploading for covers to a post. But, after using Artillery to see how an initial build fares under multiple concurrent users, I’ve found that the site’s performance degrades pretty quickly… I was wondering how I could fix/improve this? This is my first time doing any development of this sort and I could really use some pointers :).

Some details

  • Everytime a post is to be viewed in some way, the server fetches the post document from a MongoDB database, this includes a coverImageID.

  • This coverImageID is then used to find the image source at …/posts/image/<%= item.coverImageID %> using EJS

  • The server in turn handles this GET request using the following code:

     router.get('/image/:id', async (req, res) => {
     try {
     const bucket = getGridFSBucket();
     const fileId = new mongoose.Types.ObjectId(req.params.id);
    
     const files = await bucket.find({ _id: fileId }).toArray();
     if (!files || files.length === 0) {
         return res.status(404).send('Image not found');
     }
    
     res.set('Content-Type', files[0].contentType);
     const downloadStream = bucket.openDownloadStream(fileId);
     downloadStream.pipe(res);
     } catch (err) {
         res.status(500).send('Error retrieving image');
     }
     });
    

Retrieving the image from the database.

During load testing, it didn’t take long at all to overload the server and responses took up to ten seconds to respons (not ideal)
So, any tips/really dumb mistakes I’ve made, would be appreciated 🙂
Thanks.

Create a Canvas inside a Worker [closed]

I know about OffscreenCanvas, this works well.

But i need several Canvases for compositing operations.

Is it possible to create canvas inside a worker without pointing document ?

something like this :

const myOtherCanvas = new Canvas();

Blazor Server collocated JavaScript causing crash on Blazor connection timeout due to auth CORS policy

We have a Blazor Server app that requires authorization on all its components (internal business app):

// ...

app.UseHttpsRedirection();
app.MapStaticAssets();  // <-- Static files middleware before auth.
app.UseAuthentication();
app.UseAuthorization();

// ...

app.MapRazorComponents<App>()
    .AddInteractiveServerRenderMode()
    .RequireAuthorization(); // <-- All pages require auth.

We also have a component in a Razor Class Library (RCL) that requires JavaScript to work. We’ve put it inside a .razor.js collocated file.

We are using a mix of Azure AD and Azure B2C in our auth flow (handled via OIDC), and our Routes.razor uses the <AuthorizeRouteView> component to render our pages/layouts.

Everything seemed to work fine, but we’ve hit an issue when the user’s Blazor Server connection times out (i.e. they leave the app running in the background for 15-30 minutes).

We get this in the browser’s logs (sensitive information redacted):

Access to script at ‘https://login.microsoftonline.com/{OIDC URL parameters redacted}’ (redirected from ‘https://{site URL}/_content/{RCL}/Components/MyComponent.razor.js‘) from origin ‘https://{site URL}’ has been blocked by CORS policy: No ‘Access-Control-Allow-Origin’ header is present on the requested resource.

Failed to load resource: net::ERR_FAILED

Error: Microsoft.JSInterop.Exception: Failed to fetch dynamically imported module: ‘https://{site URL}/_content/{RCL}/Components/MyComponent.razor.js

{Stack trace that shows it tried and failed to import module}

Information: Connection disconnected.

My understanding is that Blazor tries to reconnect, but it tries to access the collocated JavaScript file (which is apparently a protected resources despite our static assets middleware being configured before the authentication/authorization middleware) before actually renewing the auth.

This crashes (unhandled exception, Blazor’s error ui pops-up) because it causes a redirect to our Azure AD/B2C setup, but that doesn’t know how to handle our static assets, because they’re not supposed to be protected resources (and we don’t want to have to configure every single static asset in Azure).

This leads me to believe that maybe collocated JavaScript files are considered “a part of” their component as far as .RequireAuthorization() is concerned?

Am I missing something? I can’t find anything in the documentation about collocated JavaScript files behaving differently with the authentication. In fact, the docs state this (Emphasis: mine):

[…] Blazor takes care of placing the JS file in published static assets for you.

Once the user refreshes the page, everything works as expected. So maybe it’s something to do with how renewing a Blazor connection works? I’m honestly not sure why this is happening. Any help would be appreciated.

How do I make my dialog-modal open a different modal for every link with the “data-open-modal” code?

I taught myself HTML, CSS, PHP, and MySQL many years ago, but I never got around to learning JavaScript. I’ve just picked up where I left off, but there are so many new things to learn since I last did any web coding. Can anyone help me with my JavaScript code as it currently is? I’ve only learned the basics of JS so far, and I can’t proceed with my project until I’ve learned a lot more.

Basically, my problem is, I want every edit button to open a unique dialog-modal, so far only the first one works.

const openButton = document.querySelector("[data-open-modal]")
const closeButton = document.querySelector("[data-close-modal]")
const modal = document.querySelector("[data-modal]")

openButton.addEventListener("click", () => {
  modal.showModal()
})
closeButton.addEventListener("click", () => {
  modal.close()
})

modal.addEventListener("click", e => {
  const dialogDimensions = modal.getBoundingClientRect()
  if (
    e.clientX < dialogDimensions.left ||
    e.clientX > dialogDimensions.right ||
    e.clientY < dialogDimensions.top ||
    e.clientY > dialogDimensions.bottom
  ) {
    modal.close()
  }
})
.rota-container {
  display: flex;
  flex-wrap: wrap;
  margin: 10px 0px 50px 0px;
}

.rota-item {
  flex: 1 0 13%;
  background-color: #dcdcdc;
  border: 1px solid #c0c0c0;
  height: auto;
  padding: 5px 0px 7px 0px;
}

.rota-item-day {
  height: auto;
  padding: 5px 0px 5px 0px;
  text-align: center;
  font-size: clamp(7px, 1.5vw, 50px);
  font-weight: bold;
  color: #000000;
}

.rota-date {
  text-align: right;
  margin: 0px 5px 0px 0px;
  font-size: clamp(7px, 1.5vw, 15px);
  color: #000000;
  font-family: Verdana, sans-serif;
}

.rota-edit {
  float: left;
  padding-left: 5px;
}

.rota-user {
  width: 90%;
  margin: 2px auto 0px auto;
  padding: 3px 0px 3px 3px;
  border-radius: clamp(0px, 0.4vw, 15px);
  color: white;
  font-size: clamp(7px, 1.5vw, 15px);
  font-family: Verdana, sans-serif;
}

.rota-user-1 {
  background-color: #008000;
  /*green*/
  border: 1px solid #008000;
}

.rota-user-2 {
  background-color: #cc006d;
  /*pink*/
  border: 1px solid #cc006d;
}

.rota-user-3 {
  background-color: #4b0082;
  /*blue*/
  border: 1px solid #4b0082;
}

.rota-user-4 {
  background-color: #707070;
  /*dark grey*/
  border: 1px solid #707070;
}

.rota-user-none {
  background-color: transparent;
  border: 1px solid transparent;
  color: transparent;
}

.modal {
  background-color: #f2f2f2;
  border: 1px solid #000000;
  border-radius: 10px;
  z-index: 10;
  padding: 20px;
}

.modal-container {
  display: grid;
  grid-template-columns: 1fr repeat(2, 1.5fr) 1fr;
  grid-template-rows: repeat(7, 1fr);
  grid-column-gap: 0px;
  grid-row-gap: 10px;
}

.modal-area-1 {
  grid-area: 2 / 2 / 3 / 3;
}

.modal-area-2 {
  grid-area: 3 / 2 / 4 / 3;
}

.modal-area-3 {
  grid-area: 4 / 2 / 5 / 3;
}

.modal-area-4 {
  grid-area: 5 / 2 / 6 / 3;
}

.modal-area-5 {
  grid-area: 6 / 2 / 7 / 3;
}

.modal-area-6 {
  grid-area: 2 / 3 / 3 / 4;
}

.modal-area-7 {
  grid-area: 3 / 3 / 4 / 4;
}

.modal-area-8 {
  grid-area: 4 / 3 / 5 / 4;
}

.modal-area-9 {
  grid-area: 5 / 3 / 6 / 4;
}

.modal-area-10 {
  grid-area: 6 / 3 / 7 / 4;
}

.modal-area-11 {
  grid-area: 2 / 4 / 3 / 5;
}

.modal-area-12 {
  grid-area: 3 / 4 / 4 / 5;
}

.modal-area-13 {
  grid-area: 4 / 4 / 5 / 5;
}

.modal-area-14 {
  grid-area: 5 / 4 / 6 / 5;
}

.modal-area-15 {
  grid-area: 6 / 4 / 7 / 5;
}

.modal-area-16 {
  grid-area: 1 / 2 / 2 / 3;
}

.modal-area-17 {
  grid-area: 1 / 3 / 2 / 4;
}

.modal-area-18 {
  grid-area: 2 / 1 / 3 / 2;
}

.modal-area-19 {
  grid-area: 3 / 1 / 4 / 2;
}

.modal-area-20 {
  grid-area: 4 / 1 / 5 / 2;
}

.modal-area-21 {
  grid-area: 5 / 1 / 6 / 2;
}

.modal-area-22 {
  grid-area: 6 / 1 / 7 / 2;
}

.modal-area-23 {
  grid-area: 7 / 4 / 8 / 5;
}
<div class="rota-container">
  <div class="rota-item rota-item-day">Monday</div>
  <div class="rota-item rota-item-day">Tuesday</div>
  <div class="rota-item rota-item-day">Wednesday</div>
  <div class="rota-item rota-item-day">Thursday</div>
  <div class="rota-item rota-item-day">Friday</div>
  <div class="rota-item rota-item-day">Saturday</div>
  <div class="rota-item rota-item-day">Sunday</div>
  <div class="rota-item">
    <div class="rota-date"><a class="rota-edit" data-open-modal>Edit</a>21</div>
    <div class="rota-user rota-user-1">8am-1pm</div>
    <div class="rota-user rota-user-none">1pm-4pm</div>
    <div class="rota-user rota-user-4">4pm-5pm</div>
    <div class="rota-user rota-user-none">5pm-9pm</div>
    <div class="rota-user rota-user-1">9pm-8am</div>
  </div>
  <div class="rota-item">
    <div class="rota-date"><a class="rota-edit" data-open-modal>Edit</a>22</div>
    <div class="rota-user rota-user-1">8am-1pm</div>
    <div class="rota-user rota-user-none">1pm-4pm</div>
    <div class="rota-user rota-user-2">4pm-5pm</div>
    <div class="rota-user rota-user-2">5pm-9pm</div>
    <div class="rota-user rota-user-2">9pm-8am</div>
  </div>
  <div class="rota-item">
    <div class="rota-date"><a class="rota-edit" data-open-modal>Edit</a>23</div>
    <div class="rota-user rota-user-2">8am-1pm</div>
    <div class="rota-user rota-user-none">1pm-4pm</div>
    <div class="rota-user rota-user-4">4pm-5pm</div>
    <div class="rota-user rota-user-none">5pm-9pm</div>
    <div class="rota-user rota-user-2">9pm-8am</div>
  </div>
  <div class="rota-item">
    <div class="rota-date"><a class="rota-edit" data-open-modal>Edit</a>24</div>
    <div class="rota-user rota-user-2">8am-1pm</div>
    <div class="rota-user rota-user-none">1pm-4pm</div>
    <div class="rota-user rota-user-3">4pm-5pm</div>
    <div class="rota-user rota-user-3">5pm-9pm</div>
    <div class="rota-user rota-user-3">9pm-8am</div>
  </div>
  <div class="rota-item">
    <div class="rota-date"><a class="rota-edit" data-open-modal>Edit</a>25</div>
    <div class="rota-user rota-user-3">8am-1pm</div>
    <div class="rota-user rota-user-none">1pm-4pm</div>
    <div class="rota-user rota-user-4">4pm-5pm</div>
    <div class="rota-user rota-user-none">5pm-9pm</div>
    <div class="rota-user rota-user-3">9pm-8am</div>
  </div>
  <div class="rota-item">
    <div class="rota-date"><a class="rota-edit" data-open-modal>Edit</a>26</div>
    <div class="rota-user rota-user-3">8am-1pm</div>
    <div class="rota-user rota-user-none">1pm-4pm</div>
    <div class="rota-user rota-user-4">4pm-5pm</div>
    <div class="rota-user rota-user-none">5pm-9pm</div>
    <div class="rota-user rota-user-3">9pm-8am</div>
  </div>
  <div class="rota-item">
    <div class="rota-date"><a class="rota-edit" data-open-modal>Edit</a>27</div>
    <div class="rota-user rota-user-3">8am-1pm</div>
    <div class="rota-user rota-user-none">1pm-4pm</div>
    <div class="rota-user rota-user-4">4pm-5pm</div>
    <div class="rota-user rota-user-none">5pm-9pm</div>
    <div class="rota-user rota-user-1">9pm-8am</div>
  </div>
</div>

<dialog class="modal" data-modal>
  <form method="post" action="" class="modal-container">

    <div class="modal-area-1"><input type="text" name="time-start-1" value="" /></div>
    <div class="modal-area-2"><input type="text" name="time-start-1" value="" /></div>
    <div class="modal-area-3"><input type="text" name="time-start-1" value="" /></div>
    <div class="modal-area-4"><input type="text" name="time-start-1" value="" /></div>
    <div class="modal-area-5"><input type="text" name="time-start-1" value="" /></div>
    <div class="modal-area-6"><input type="text" name="time-start-1" value="" /></div>
    <div class="modal-area-7"><input type="text" name="time-start-1" value="" /></div>
    <div class="modal-area-8"><input type="text" name="time-start-1" value="" /></div>
    <div class="modal-area-9"><input type="text" name="time-start-1" value="" /></div>
    <div class="modal-area-10"><input type="text" name="time-start-1" value="" /></div>
    <div class="modal-area-11">
      <select id="time-employee-1" name="time-employee-1">
        <option value="Helping Hands">None</option>
        <option value="Helping Hands">Helping Hands</option>
        <option value="Emma Cooper">Emma Cooper</option>
        <option value="Savannah Gregoire">Savannah Gregoire</option>
        <option value="Tracy Walker">Tracy Walker</option>
      </select>
    </div>
    <div class="modal-area-12">
      <select id="time-employee-1" name="time-employee-1">
        <option value="Helping Hands">None</option>
        <option value="Helping Hands">Helping Hands</option>
        <option value="Emma Cooper">Emma Cooper</option>
        <option value="Savannah Gregoire">Savannah Gregoire</option>
        <option value="Tracy Walker">Tracy Walker</option>
      </select>
    </div>
    <div class="modal-area-13">
      <select id="time-employee-1" name="time-employee-1">
        <option value="Helping Hands">None</option>
        <option value="Helping Hands">Helping Hands</option>
        <option value="Emma Cooper">Emma Cooper</option>
        <option value="Savannah Gregoire">Savannah Gregoire</option>
        <option value="Tracy Walker">Tracy Walker</option>
      </select>
    </div>
    <div class="modal-area-14">
      <select id="time-employee-1" name="time-employee-1">
        <option value="Helping Hands">None</option>
        <option value="Helping Hands">Helping Hands</option>
        <option value="Emma Cooper">Emma Cooper</option>
        <option value="Savannah Gregoire">Savannah Gregoire</option>
        <option value="Tracy Walker">Tracy Walker</option>
      </select>
    </div>
    <div class="modal-area-15">
      <select id="time-employee-1" name="time-employee-1">
        <option value="Helping Hands">None</option>
        <option value="Helping Hands">Helping Hands</option>
        <option value="Emma Cooper">Emma Cooper</option>
        <option value="Savannah Gregoire">Savannah Gregoire</option>
        <option value="Tracy Walker">Tracy Walker</option>
      </select>
    </div>
    <div class="modal-area-16">Start</div>
    <div class="modal-area-17">Finish</div>
    <div class="modal-area-18">Morning</div>
    <div class="modal-area-19">Afternoon</div>
    <div class="modal-area-20">Teatime</div>
    <div class="modal-area-21">Evening</div>
    <div class="modal-area-22">Night time</div>
    <div class="modal-area-23"><button data-close-modal>Close</button></div>

  </form>
</dialog>

I face a problem of email validaion in vue js [closed]

So How to solve this problem and how to set this email validtation by using vue js and html . Please solve My problem by using this method of validation or suggest some else

<input id="email"  type="email" class="form-control"
          :class="{ 'error-input': !$v.user.email.required || !$v.user.email }" placeholder="Email"  v-model.trim="$v.user.email.$model">
        <p class="error" v-if="!$v.user.email.required">Email required</p>
        <p class="error" v-if="!$v.user.email">Enter valid Email</p>

  email: {
        required,
        email
      },

Beforeunload Preventdefault workarounds?

So I have a website that is creating a file in the backend to store data. Once the user leaves or refreshes the page, that data is deleted.

window.addEventListener('beforeunload', function () {
    delete_file();
});

This works locally, but in some browsers / builds, if you refresh the page, beforeunload doesn’t trigger. So this is the workaround:

window.addEventListener('beforeunload', function (event) {
    event.preventDefault();
    delete_file();
});

Now, however, it gives a popup to the browser on whether or not they want to leave, even with unsaved changes. That’s fine, but if I press cancel and stay on the page, this still runs and deletes the file.

Here’s what I’ve tried:

  • Somehow get the canceled event from preventdefault
  • Create a fake “you may lose changes!” popup that calls preventdefault if user wants to leave

Compare dates of format dd/mm/yyyy hh:mm:ss and return the latest one in Zulu format [closed]

If I’ve an array like the following:

var "estd": [
        "30/05/2025 14:06:37",
        "30/05/2025 15:14:28",
        "01/06/2025 08:00:01",
        "26/05/2025 12:43:42"
    ]

What would be the way of returning the latest timestamp in yyyy-mm-ddTHH:MM:ssZ format?

Expected result:

2025-06-01T08:00:01Z

The way (longer or procedural way that I can think of is the following)

  • Loop through the array
  • On each iteration use split(" ") to get the date part and then split("/") on the date part to get the day, month and year, then join them via [2]-[1]-[0] index and finally concat it with the “time” part to get each date in
    yyyy-mm-ddThh:mm:ssZ format
  • Push this value into a new array
  • Once all dates in the original array are looped, as the last step use the .sort() method on the new array

But I’m sure there would be a more optimum/better way of doing the same.

Google Maps Custom Style Not Applying in WordPress (Elementor)

I’m working on a WordPress website using the Elementor Pro page builder, and I’m trying to integrate a custom-styled Google Map on the “Contact Us” page.

What I’ve done so far:

  • Created a project in the Google Cloud Console
  • Enabled Maps JavaScript API
  • Set up billing
  • Generated a valid API key

Integration steps:

  1. I used Elementor’s “Google Maps” widget and added a container with id="map".
  2. I added the following script via Elementor’s Integration > Custom Code section:
<script>
  function initMap() {
    const mapStyle = [{
        "elementType": "geometry", "stylers": [{ "color": "#f5f5f5" }]
    }, {
        "elementType": "labels.icon", "stylers": [{ "visibility": "off" }]
    }, {
        "elementType": "labels.text.fill", "stylers": [{ "color": "#616161" }]
    }, {
        "elementType": "labels.text.stroke", "stylers": [{ "color": "#f5f5f5" }]
    }, {
        "featureType": "administrative.land_parcel", "elementType": "labels", "stylers": [{ "visibility": "off" }]
    }, {
        "featureType": "administrative.land_parcel", "elementType": "labels.text.fill", "stylers": [{ "color": "#bdbdbd" }]
    }, {
        "featureType": "poi", "elementType": "geometry", "stylers": [{ "color": "#eeeeee" }]
    }, {
        "featureType": "poi", "elementType": "labels.text", "stylers": [{ "visibility": "off" }]
    }, {
        "featureType": "poi", "elementType": "labels.text.fill", "stylers": [{ "color": "#757575" }]
    }, {
        "featureType": "poi.park", "elementType": "geometry", "stylers": [{ "color": "#e5e5e5" }]
    }, {
        "featureType": "poi.park", "elementType": "labels.text.fill", "stylers": [{ "color": "#9e9e9e" }]
    }, {
        "featureType": "road", "elementType": "geometry", "stylers": [{ "color": "#ffffff" }]
    }, {
        "featureType": "road.arterial", "elementType": "labels", "stylers": [{ "visibility": "off" }]
    }, {
        "featureType": "road.arterial", "elementType": "labels.text.fill", "stylers": [{ "color": "#757575" }]
    }, {
        "featureType": "road.highway", "elementType": "geometry", "stylers": [{ "color": "#dadada" }]
    }, {
        "featureType": "road.highway", "elementType": "labels", "stylers": [{ "visibility": "off" }]
    }, {
        "featureType": "road.highway", "elementType": "labels.text.fill", "stylers": [{ "color": "#616161" }]
    }, {
        "featureType": "road.local", "stylers": [{ "visibility": "off" }]
    }, {
        "featureType": "road.local", "elementType": "labels", "stylers": [{ "visibility": "off" }]
    }, {
        "featureType": "road.local", "elementType": "labels.text.fill", "stylers": [{ "color": "#9e9e9e" }]
    }, {
        "featureType": "transit.line", "elementType": "geometry", "stylers": [{ "color": "#e5e5e5" }]
    }, {
        "featureType": "transit.station", "elementType": "geometry", "stylers": [{ "color": "#eeeeee" }]
    }, {
        "featureType": "water", "elementType": "geometry", "stylers": [{ "color": "#c9c9c9" }]
    }, {
        "featureType": "water", "elementType": "labels.text.fill", "stylers": [{ "color": "#9e9e9e" }]
    }];

    const styledMapType = new google.maps.StyledMapType(mapStyle, { name: 'Styled Map' });

    const map = new google.maps.Map(document.getElementById('map'), {
            
      center: { lat: 26.117184862578004, lng: 50.59311162700443 },
      zoom: 13,
      mapTypeControlOptions: {
        mapTypeIds: ['roadmap', 'satellite', 'styled_map']
      }
    });

    map.mapTypes.set('styled_map', styledMapType);
    map.setMapTypeId('styled_map');
        console.log(map);
  }
</script>

<script async defer
  src="https://maps.googleapis.com/maps/api/js?key=MYAPIKEY&callback=initMap">
</script>

I removed my API key

The issue:

The map loads, but the custom styling is not applied. It shows the default Google Map instead of the grayscale version I’m trying to achieve.

What I’m expecting:

  • A fully styled map in gray scale
  • Removal of unnecessary points of interest and labels

Console behavior:

No JavaScript errors except occasionally:

Uncaught (in promise) InvalidValueError: initMap is not a function

My question:

What am I doing wrong?
Is there a conflict with how Elementor loads scripts or with how I’ve structured the initMap function?

Any help or guidance on how to correctly apply custom styles in a Google Map embedded on an Elementor-powered WordPress site would be greatly appreciated.

Thank you in advance!

How can I dynamically draw a single thick border along a spiral path in an NxN grid (Javascript) [closed]

I’m working on a web project where I need to visually highlight a spiral path in a square grid (for example, 4×4, 6×6, or 8×8). The spiral should start at the bottom-left cell, move right along the bottom row, then up the rightmost column, then left along the top row, and so on, spiraling inward.

The key requirements are:

  • The spiral path should be highlighted with a thick border.
  • Each cell in the grid should have at most one thick border (on the side that connects it to the next cell in the spiral path).
  • The result should look like a single, continuous thick line tracing the spiral, with no cell having more than one thick border.
  • The solution should work for any NxN grid (N ≥ 2).

For example, in a 4×4 grid, the spiral order (starting from the bottom-left) would be:

10  9  8  7
11 16 15  6
12 13 14  5
 1  2  3  4

And the thick border should trace this path, with each cell having only one thick border, forming a continuous spiral.

What I’ve tried:
I’ve tried generating the spiral order and assigning borders, but I either end up with multiple borders per cell or the spiral is not visually continuous. I want a function that, given the grid size and the spiral path, tells me which side of each cell should have the thick border, so I can render it in HTML/CSS.

How can I implement this spiral border logic in JavaScript, so that each cell gets at most one thick border, and the spiral is visually continuous?

Any code snippets or algorithm explanations would be greatly appreciated!

Click event on buttons not working in dropdown Shadow DOM

I’m making a component and ran into an issue that I can’t think and find any working solutions. When anything is in the dropdown their events aren’t being listented too, but when they go outside of the dropdown and into the header then they work. I’m using the Calcite Design System component library for Calcite Actions but I tried using divs/buttons to make sure that wasn’t the issue. I tried playing with pointer-events too but it didn’t work too.

When you open the codepen and click the buttons, you’ll notice that text is being added in the console, however when you click the test button that sends the buttons to the dropdown by changing their slot attribute, the buttons will no longer send stuff to the console anymore.

https://codepen.io/Keeron1/pen/GgpJbEO

class Floater extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({
      mode: "open"
    });

    // Wait for Calcite's <calcite-action> to be defined before injecting HTML
    customElements.whenDefined("calcite-action").then(() => {

      // Inject HTML template
      const tpl = document.getElementById("floater-template");
      const clone = tpl.content.cloneNode(true);
      this.shadowRoot.appendChild(clone);

      this._init();
    });
  }

  // Public API
  get position() {
    return this.getAttribute("position") || "top-left";
  }
  set position(v) {
    this.setAttribute("position", v);
  }
  get isDraggable() {
    if (!this.hasAttribute("draggable")) return true
    return this.getAttribute("draggable") === "true" ? true : false
  }
  set isDraggable(v) {
    this.setAttribute("draggable", v);
  }
  get isResizable() {
    return this.getAttribute("resizable") || "none"
  }
  set isResizable(v) {
    this.setAttribute("resizable", v);
  }
  get isHandleEnabled() {
    if (!this.hasAttribute("handle-enabled")) return false
    return this.getAttribute("handle-enabled") === "true" ? true : false
  }
  set isHandleEnabled(v) {
    this.setAttribute("handle-enabled", v);
  }

  _init() {
    // Elements
    this.floaterCont = this.shadowRoot.querySelector(".floater-container")
    this.floater = this.shadowRoot.querySelector(".floater");
    // Heading
    this.heading = this.shadowRoot.querySelector(".floater-heading")
    this.hTitle = this.shadowRoot.querySelector(".floater-heading-title");
    this.originalhTitle = this.hTitle?.outerHTML || null;
    this.headingEnd = this.shadowRoot.querySelector(".floater-heading-end")
    this.headingEndSlot = this.shadowRoot.querySelector('slot[name="heading-end"]');
    this.closeButton = this.shadowRoot.querySelector(".floater-close");
    this.originalCloseButton = this.closeButton?.outerHTML || null
    // Dropdown
    this.headingDropdown = this.shadowRoot.querySelector(".floater-heading-dropdown")
    this.originalHeadingDropdown = this.headingDropdown?.outerHTML || null
    this.headingDropdownAction = this.shadowRoot.querySelector(".floater-heading-dropdown-action")
    this.headingDropdownItems = this.shadowRoot.querySelector(".floater-heading-dropdown-items")
    this.headingEndDropdownSlot = this.shadowRoot.querySelector('slot[name="dropdown-heading-end"]')
    // Content
    this.floaterContent = this.shadowRoot.querySelector(".floater-content");
    this.contentSlot = this.shadowRoot.querySelector('slot:not([name])');

    this.test = this.shadowRoot.querySelector(".floater-test")

    // Attributes
    this.isDragging = false;
    this.dragOffsetX = 0; // Distance between cursor and left of the component
    this.dragOffsetY = 0; // Distance between cursor and top of the component
    this.lastWindowWH = {
      width: null,
      height: null
    }; // Window width and height

    requestAnimationFrame(() => {
      this._updateTitle();
      this._updateClose();
      this._setScale();
      this._updateDraggable();
      this._updateResizable();
      window.addEventListener("resize", this._onResize);
      this.closeButton?.addEventListener("click", this._close);

      this.test?.addEventListener("click", this._testClick)


      this._setStartingPosition();

      this.lastWindowWH.width = window.innerWidth;
      this.lastWindowWH.height = window.innerHeight;
    });
  }

  // Trigger on component created (not used since we need to wait for calcite action)
  connectedCallback() {}

  // Trigger on component delete
  disconnectedCallback() {
    window.removeEventListener("resize", this._onResize)
    if (this.isDraggable) this.heading.removeEventListener("pointerdown", this._onDown)
    if (this.closeButton) this.closeButton.removeEventListener("click", this._close)

    if (this.test) this.test.removeEventListener("click", this._testClick)
  }

  static get observedAttributes() {
    return ["title", "close-disabled", "scale", "draggable", "resizable", "handle-enabled"];
  }

  // Trigger when attribute changes
  attributeChangedCallback(name, oldValue, newValue) {
    if (name === "title") this._updateTitle();
    if (name === "close-disabled") this._updateClose();
    if (name === "scale") this._setScale();
    if (name === "draggable") this._updateDraggable();
    if (name === "resizable") this._updateResizable();
    if (name === "handle-enabled") this._updateHandle();
  }

  _testClick = () => {
    if (!this.isHandleEnabled) this.isHandleEnabled = true
    else this.isHandleEnabled = false
  }

  _updateHandle = () => {
    if (this.isHandleEnabled) this._sendHeadingItemsToDropdown();
    else this._sendDropdownItemsToHeading();
  }

  _sendHeadingItemsToDropdown = () => {
    if (!this.hasAttribute("close-disabled"))
      this.headingDropdownItems.insertBefore(this.closeButton, this.headingEndDropdownSlot)

    const endSlot = this.headingEndSlot.assignedElements()
    if (endSlot.length === 0) return;
    endSlot.forEach(element => {
      element.setAttribute("slot", "dropdown-heading-end");
    });
  }

  _sendDropdownItemsToHeading = () => {
    if (!this.hasAttribute("close-disabled")) this.headingEnd.append(this.closeButton)

    const endSlot = this.headingEndDropdownSlot.assignedElements()
    if (endSlot.length === 0) return;
    endSlot.forEach(element => {
      element.setAttribute("slot", "heading-end");
    });
  }

  _close = () => {
    console.log("closed btn clicked")
    this.dispatchEvent(new CustomEvent("floaterClose", {
      bubbles: true,
      composed: true
    }));

    if (!this.hasAttribute("manual-close"))
      this.remove();
  }

  _onResize = () => {
    // Could be improved by saving the floater's left value before it gets pushed, so
    //  that when the window could grow it could stick to that value

    const winWidth = window.innerWidth
    const winHeight = window.innerHeight

    // Calculate window delta
    const deltaX = winWidth - this.lastWindowWH.width;
    const deltaY = winHeight - this.lastWindowWH.height;

    // Get floater's current properties
    const floaterRect = this.floater.getBoundingClientRect();
    const currentTop = floaterRect.top
    const currentLeft = floaterRect.left
    const fw = this.floater.offsetWidth;
    const fh = this.floater.offsetHeight;

    // Remove inital position class
    this.floaterCont.classList.remove("top-left", "top-right", "bottom-left", "bottom-right", "center");

    let newTop = currentTop + deltaY;
    let newLeft = currentLeft + deltaX;

    // Horizontal nudge only on shrink AND if closer to the right edge
    if (deltaX < 0) {
      const distRight = winWidth - fw - currentLeft;
      if (distRight <= currentLeft) // if right is smaller than left or equal
        newLeft = currentLeft + deltaX;
      else newLeft = currentLeft
    }

    // Vertical nudge only on shrink AND if closer to the bottom edge
    if (deltaY < 0) {
      const distBottom = winHeight - fh - currentTop;
      if (distBottom <= currentTop)
        newTop = currentTop + deltaY;
      else newTop = currentTop
    }

    // Clamp absolute position to viewport
    newLeft = Math.max(0, Math.min(winWidth - fw, newLeft));
    newTop = Math.max(0, Math.min(winHeight - fh, newTop));

    // Convert back to container-relative
    const contRect = this.floaterCont.getBoundingClientRect();
    newLeft -= contRect.left;
    newTop -= contRect.top;

    // Apply
    this.floater.style.top = `${newTop}px`;
    this.floater.style.left = `${newLeft}px`;

    // Save updated values
    this.lastWindowWH.width = winWidth
    this.lastWindowWH.height = winHeight
  }

  // This function will either create or delete a component, depending on the attribute
  _handleElementLife = (atr, currentEl, originalEl, parent) => {
    // If empty then remove the element
    if (!atr.trim()) {
      if (currentEl) currentEl.remove()
      return null;
    }

    // Add the element
    if (!currentEl && originalEl) {
      const temp = document.createElement("div");
      temp.innerHTML = originalEl;
      currentEl = temp.firstElementChild;
      parent.insertBefore(currentEl, parent.firstChild);
    }

    if (currentEl) {
      currentEl.textContent = atr;
      return currentEl;
    }
  }

  _updateDraggable = () => {
    if (this.isDraggable) {
      this.heading.classList.add("draggable")
      this.heading.addEventListener("pointerdown", this._onDown)
    } else {
      this.heading.classList.remove("draggable")
      this.heading.removeEventListener("pointerdown", this._onDown)
    }
  }

  _updateResizable = () => {
    this.floaterContent.classList.remove("resize-horizontal", "resize-vertical", "resize-both");
    switch (this.isResizable) {
      case "horizontal":
        this.floaterContent.classList.add("resize-horizontal")
        break
      case "vertical":
        this.floaterContent.classList.add("resize-vertical")
        break
      case "both":
        this.floaterContent.classList.add("resize-both")
        break
    }
  }

  _updateTitle = () => {
    const titleAtr = this.getAttribute("title") || "";
    this.hTitle = this._handleElementLife(titleAtr, this.hTitle, this.originalhTitle, this.heading)
  }

  _updateClose = () => {
    const disabled = this.hasAttribute("close-disabled");
    if (disabled) {
      this.closeButton.removeEventListener("click", this._close);
      this.closeButton.remove();
      this.closeButton = null;
      return;
    }

    // Add the element
    if (!this.closeButton && this.originalCloseButton) {
      const temp = document.createElement("div");
      temp.innerHTML = this.originalCloseButton;
      this.closeButton = temp.firstElementChild;
      this.closeButton.addEventListener("click", this._close);
      this.closeButton.scale = this.getAttribute("scale") || "s"
      if (this.isHandleEnabled) this.headingDropdownItems.insertBefore(this.closeButton, this.headingEndDropdownSlot)
      else this.headingEnd.append(this.closeButton);
    }
  }

  _setStartingPosition = () => {
    switch (this.position) {
      case "center":
        this.floaterCont.classList.add("center")
        break;
      case "top-left":
        this.floaterCont.classList.add("top-left")
        break;
      case "top-right":
        this.floaterCont.classList.add("top-right")
        break;
      case "bottom-left":
        this.floaterCont.classList.add("bottom-left")
        break;
      case "bottom-right":
        this.floaterCont.classList.add("bottom-right")
        break;
    }
  }

  _setScale = () => {
    let scaleAtr = this.getAttribute("scale") || "s";
    if (this.closeButton) this.closeButton.scale = scaleAtr
    if (this.headingDropdownAction) this.headingDropdownAction.scale = scaleAtr
  }

  // Handle floater movement
  _onDown = (e) => {
    if (e.target.closest(".floater-heading-end")) return;
    if (this.headingEndSlot.assignedElements().some(el => el === e.target)) return

    e.preventDefault();
    this.isDragging = true;

    // capture the pointer so we don't lose events
    this.setPointerCapture(e.pointerId);

    // Compute position based on visual location
    const rect = this.floater.getBoundingClientRect();
    this.dragOffsetX = e.clientX - rect.left;
    this.dragOffsetY = e.clientY - rect.top;

    this.addEventListener("pointermove", this._onMove);
    this.addEventListener("pointerup", this._onUp);
  };

  _onMove = (e) => {
    if (!this.isDragging) return;

    // Remove previous
    this.floaterCont.classList.remove("top-left", "top-right", "bottom-left", "bottom-right", "center");

    // New positions
    let newTop = e.clientY - this.dragOffsetY;
    let newLeft = e.clientX - this.dragOffsetX;

    const vw = window.innerWidth;
    const vh = window.innerHeight;
    const fw = this.floater.offsetWidth;
    const fh = this.floater.offsetHeight;

    // Clamp to viewport
    newTop = Math.max(0, Math.min(vh - fh, newTop));
    newLeft = Math.max(0, Math.min(vw - fw, newLeft));

    // Remove container offset
    const contRect = this.floaterCont.getBoundingClientRect();
    newTop -= contRect.top;
    newLeft -= contRect.left;

    this.floater.style.top = `${newTop}px`;
    this.floater.style.left = `${newLeft}px`;
  }

  _onUp = (e) => {
    this.isDragging = false;
    this.releasePointerCapture(e.pointerId);
    this.removeEventListener("mousemove", this._onMove);
    this.removeEventListener("mouseup", this._onUp);
  }
}

if (!customElements.get("custom-floater"))
  customElements.define("custom-floater", Floater);
<script type="module" src="https://cdn.jsdelivr.net/npm/@esri/calcite-components@latest/dist/calcite/calcite.esm.js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@esri/calcite-components@latest/dist/calcite/calcite.css" />

<template id="floater-template">
  <style>
    :host {
      --floater-offset-x: 0px;
      --floater-offset-y: 0px;
      --floater-min-size-x: auto;
      --floater-max-size-x: none;
      --floater-min-size-y: auto;
      --floater-max-size-y: none;
      --floater-content-padding: var(--calcite-spacing-md);
      --floater-opacity: 1;
      --floater-content-opacity: 1;
    }

    .floater-container {
      position: fixed;
      z-index: var(--calcite-z-index-modal);
      top: var(--floater-offset-y, 0);
      left: var(--floater-offset-x, 0);
      pointer-events: none;
    }

    /* Starting positions */
    .floater-container.center {
      inset: 0;

      >.floater {
        top: calc(50% + var(--floater-offset-y, 0px));
        left: calc(50% + var(--floater-offset-x, 0px));
        transform: translate(-50%, -50%);
      }
    }

    .floater-container.top-right {
      top: var(--floater-offset-y, 0);
      right: var(--floater-offset-x, 0);
      left: auto;

      >.floater {
        top: var(--calcite-spacing-md);
        right: var(--calcite-spacing-md);
      }
    }

    .floater-container.top-left>.floater {
      top: var(--calcite-spacing-md);
      left: var(--calcite-spacing-md);
    }

    .floater-container.bottom-left {
      bottom: var(--floater-offset-y, 0);
      left: var(--floater-offset-x, 0);
      top: auto;

      >.floater {
        bottom: var(--calcite-spacing-md);
        left: var(--calcite-spacing-md);
      }
    }

    .floater-container.bottom-right {
      bottom: var(--floater-offset-y, 0);
      right: var(--floater-offset-x, 0);
      top: auto;
      left: auto;

      >.floater {
        bottom: var(--calcite-spacing-md);
        right: var(--calcite-spacing-md);
      }
    }

    /* End of starting positions */
    .floater {
      display: flex;
      flex-direction: column;
      max-height: 100vh;
      position: absolute;
      pointer-events: all;
      box-sizing: border-box;
      border-radius: 0.25rem;
      background-color: var(--floater-background-color);
      font-family: var(--calcite-sans-family);
      box-shadow: var(--calcite-shadow-sm);
      opacity: var(--floater-opacity);
    }

    .floater-heading {
      display: flex;
      flex-direction: row;
      flex-wrap: nowrap;
      flex: 0 0 auto;
      border-bottom: 1px solid var(--calcite-color-border-3);
      user-select: none;
    }

    .floater-heading.draggable {
      cursor: move;
    }

    .floater-heading-title {
      padding: var(--calcite-spacing-xs) var(--calcite-spacing-md-plus);
      font-weight: var(--calcite-font-weight-medium);
      font-size: var(--calcite-font-size-0);
      color: var(--calcite-color-text-1);
    }

    .floater-heading-end {
      margin-left: auto;
      display: flex;
      flex-direction: row;
      flex-wrap: nowrap;
    }

    /* Dropdown menu */
    .floater-heading-dropdown-action {
      height: 100%;
    }

    .floater-heading-dropdown-items {
      pointer-events: all;
      visibility: hidden;
      position: absolute;
      top: auto;
      right: 0;
      background: var(--calcite-color-background);
      padding: 0.5rem;
      box-shadow: 0 1px 6px rgba(0, 0, 0, 0.2);
      z-index: 100;
      border-radius: 4px;
      cursor: default;
      overflow-y: auto;
      height: 100%;
      max-height: 300px;
      /* Firefox */
      scrollbar-width: thin;
      scrollbar-color: var(--scrollbar-color);
    }

    slot[name="dropdown-heading-end"] {
      display: flex;
      flex-direction: column-reverse;
    }

    /* Chrome, Edge, Safari */
    @supports selector(::-webkit-scrollbar) {
      .floater-heading-dropdown-items {
        /* Override rules */
        scrollbar-width: auto;
        scrollbar-color: auto;
      }

      .floater-heading-dropdown-items::-webkit-scrollbar {
        width: var(--scrollbar-width);
      }

      .floater-heading-dropdown-items::-webkit-scrollbar-thumb {
        background: var(--scrollbar-background-color);
        border-radius: var(--scrollbar-border-radius);
      }
    }

    .floater-heading-dropdown:hover .floater-heading-dropdown-items {
      visibility: visible;
    }

    /* End of Dropdown menu */
    /* .floater-close{ */
    /* height: 100%; */
    /* width: auto; */
    /* background-color: transparent; */
    /* border: 0; */
    /* cursor: pointer; */
    /* } */
    .floater-content {
      box-sizing: border-box;
      padding: var(--floater-content-padding);
      overflow: auto;
      flex: 1 1 auto;
      max-width: var(--floater-max-size-x);
      min-width: var(--floater-min-size-x);
      max-height: var(--floater-max-size-y);
      min-height: var(--floater-min-size-y);
      opacity: var(--floater-content-opacity);
    }

    .floater-content.resize-horizontal {
      resize: horizontal;
    }

    .floater-content.resize-vertical {
      resize: vertical;
    }

    .floater-content.resize-both {
      resize: both;
    }

    /* Hide the resize handle  */
    .floater-content.resize-horizontal::-webkit-resizer,
    .floater-content.resize-vertical::-webkit-resizer,
    .floater-content.resize-both::-webkit-resizer {
      display: none;
    }

    .floater-content.resize-horizontal::-moz-resizer,
    .floater-content.resize-vertical::-moz-resizer,
    .floater-content.resize-both::-moz-resizer {
      display: none;
    }
  </style>
  <div class="floater-container">
    <div class="floater">
      <div class="floater-heading">
        <div class="floater-heading-title"></div>
        <div class="floater-heading-end">
          <slot name="heading-end"></slot>
          <calcite-action class="floater-close" aria-label="Close" title="Close" icon="x"></calcite-action>

          <button class="floater-test">test</button>
        </div>

        <div class="floater-heading-dropdown">
          <calcite-action class="floater-heading-dropdown-action" aria-label="Actions Dropdown" title="Actions Dropdown" icon="handle-vertical"></calcite-action>
          <div class="floater-heading-dropdown-items">
            <slot name="dropdown-heading-end"></slot>
          </div>
        </div>

      </div>
      <div class="floater-content">
        <slot></slot>
      </div>
    </div>
  </div>
</template>

<custom-floater title="Draw" position="top-right" scale="s" manual-close style={{
                    "--floater-offset-x": offsetX + "rem",
                    "--floater-offset-y": offsetY + "rem",
                    "--floater-min-size-x": "379px"}}>

  <calcite-action scale="s" slot="heading-end" title="Change Layout" text="Change Layout" icon="layout-horizontal" onclick="console.log('clicked change layout')"></calcite-action>
  <calcite-action scale="s" slot="heading-end" title="Dock widget" text="Dock" icon="right-edge" onclick="console.log('clicked dock')"></calcite-action>
  <div className="custom-sketch" id="draw-container">
    <div>This is some placeholder content</div>
  </div>
</custom-floater>