Why does Angular 18 (ESBuild) generate so many initial chunks after migrating from Angular 14?

I recently migrated my project from Angular 14 to Angular 18, and I’m noticing a huge increase in the number of initial chunks generated during the production build using ESBuild.

In Angular 14 (with Webpack), the build typically produced 3–5 initial JS bundles. But now, Angular 18 generates dozens of small initial chunks, even though I haven’t significantly changed the app structure or added lazy-loaded modules.

This raises concerns about:

  • Browser performance, since too many HTTP requests can delay loading.
  • The lack of control over how these chunks are split.
  • No apparent way to reduce or merge these chunks using configuration.

Questions:

  • Is this expected behavior in Angular 18 with ESBuild?
  • Is there any way to reduce or control the number of initial chunks?
  • Can I customize the chunking strategy like we used to with Webpack?

Additional Info:

Angular version: 18.x

Builder: ESBuild (default in Angular 18)

Total JS size is similar, but split across many files

enter image description here

Eslint Resolve error: Cannot find native binding

When trying to run eslint on my HP EliteBook 640 G11 (Intel Core Ultra 5 125U) I got this error:

C:UserstestWorkeslint-native-binding-testeslint.config.mjs
   1:1   error  Resolve error: Cannot find native binding. npm has a bug related to optional dependencies (https://github.com/npm/cli/issues/4828). Please try `npm i` again after removing both package-lock.json and node_modules directory.
    at Object.<anonymous> (C:UserstestWorkeslint-native-binding-testnode_modulesunrs-resolverindex.js:376:11)
    at Module._compile (node:internal/modules/cjs/loader:1692:14)
    at Object..js (node:internal/modules/cjs/loader:1824:10)
    at Module.load (node:internal/modules/cjs/loader:1427:32)
    at Module._load (node:internal/modules/cjs/loader:1250:12)
    at TracingChannel.traceSync (node:diagnostics_channel:322:14)
    at wrapModuleLoad (node:internal/modules/cjs/loader:235:24)
    at Module.require (node:internal/modules/cjs/loader:1449:12)
    at require (node:internal/modules/helpers:135:16)
    at Object.<anonymous> (C:UserstestWorkeslint-native-binding-testnode_moduleseslint-import-resolver-typescriptlibindex.cjs:30:31)  import/no-unresolved

But on my Desktop PC (AMD CPU) it works well. I have spend a lot of time to solve it but no luck.

Please take a look at this (just for test) repo https://github.com/HakkaMc/eslint-native-binding-test and read the description and steps in the README. Probably the problem is with Intel architecture?

Thanks to everybody who is going to try it.

Slider switch state modified by Javascript: animation is not triggered

When clicking on a slider switch (made with CSS + an input checkbox under the hood), the animation shows.

However, when setting the checked value from JS, the animation is not triggered.

How to solve this?

Reproducible example:

document.querySelector("div").onclick = () => { 
    document.querySelector("input").checked = false;
}
.switch { 
    appearance: none; 
    margin: 0 1rem;
    padding: 1rem 2rem; 
    border-radius: 1rem; 
    background: radial-gradient(circle 0.75rem, white 100%, transparent 100%) transparent -1rem; 
    background-color: #ddd;
}
.switch:hover, .switch:active, .switch:focus, .switch:focus-visible, .switch:focus-within { 
    transition: all 0.3s; 
}
.switch:checked { 
    background-position: 1rem; 
}
<input type="checkbox" class="switch"></input><br> <br>
<div>[click me to disable via JS]</div>

Conditional format a field’s fill colour based on a drop-down list selection

I have a PDF form that I want to build conditional formatting into it to meet a client’s request. The formatting will be applied to 2 fields using cutom RGB colours. Below are my desired outcomes.

  1. Drop-down field “Item Action.1” – change colour based on the selection of this drop-down list. Custom RGB colours have been chosen for readability reasons.
  2. Text field “Comments_1” – change colour based on the selection of the “Item Action.1” drop-down list. Custom RGB colours have been chosen for readability reasons.

However, none of these codes listed below are achieving the desired outcomes listed above. Below is a link to the PDF document.

Sample report.pdf

This is the expectation for the drop-down field “Item Action.1”.

  • Select item ” ” – no fill, transparent
  • Select item “Non-compliant” – fill with colour RGB 0, 105, 97
  • Select item “Recommendation” – fill with colour RGB 0, 179, 71

Below is the JavaScript code used for the drop-down field “Item Action.1”.

if (event.value==" ") event.target.fillColor = color.transparent;
else if (event.value=="Non-compliant") event.target.fillColor = [“RGB”, 0/255, 105/255, 97/255];
else if (event.value=="Recommendation") event.target.fillColor = [“RGB”, 0/255, 179/255, 71/255];

Result: no colour changes when selection is changed.


This is the expectation for the text field “Comments_1”.

  • Select item ” ” from drop-down list “Item Action.1” – no fill, transparent
  • Select item “Non-compliant” from drop-down list “Item Action.1” – fill with colour RGB 0, 105, 97
  • Select item “Recommendation” from drop-down list “Item Action.1” – fill with colour RGB 0, 179, 71

Below is the JavaScript code used for the text field “Comments_1”.

var selectedValue = event.target.value; // Get the selected value from the dropdown
var targetField = this.getField("Item Action.1"); // Replace with your field name

// Change the background color based on the dropdown value
if (selectedValue === " ") {
targetField.fillColor = color.transparent;
} else if (selectedValue === "Non-compliant") {
targetField.fillColor = [“RGB”, 0/255, 105/255, 97/255];
} else if (selectedValue === "Recommendation") {
targetField.fillColor = [“RGB”, 0/255, 179/255, 71/255];
} else {
targetField.fillColor = color.white; // Default color
}

Result: no colour changes when the selection of the drop-down list is changed

How to set div margin or padding based on its own height (when the parent is a scrollable)?

I’m building a chat interface where the last message grows dynamically (streaming text). I need this message to stay aligned with the top of the scroll container as it grows.
I came up with this bad solution where I calculate (container height - message height) + 8px and set it as margin-bottom to allow some scroll padding (avoiding the message to be pushed at the bottom when the text is very short).

Is there a pure CSS solution to achieve dynamic padding/margin based on its own size without JavaScript querySelector and manual height calculations? Or any newer CSS features (container queries, anchor positioning, etc.) that could solve this?

I reality, I use tailwind and react so that using querySelector feels anti-pattern.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Minimal Chat Interface</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            margin: 20px;
            background-color: #f5f5f5;
        }

        .container {
            max-width: 500px;
            margin: 0 auto;
        }

        .controls {
            margin-bottom: 20px;
            text-align: center;
        }

        .controls button {
            padding: 10px 20px;
            margin: 5px;
            border: none;
            border-radius: 8px;
            background-color: #007bff;
            color: white;
            cursor: pointer;
        }

        .chat-container {
            height: 400px;
            border: 2px solid #333;
            border-radius: 8px;
            overflow-y: auto;
            background-color: white;
            position: relative;
        }

        /* Red reference line */
        .chat-container::before {
            content: '';
            position: absolute;
            top: 0;
            left: 0;
            right: 0;
            height: 2px;
            background-color: red;
            z-index: 10;
        }

        .chat-messages {
            padding: 20px;
            display: flex;
            flex-direction: column;
            min-height: 100%;
        }

        .message {
            margin-bottom: 12px;
            padding: 12px 16px;
            border-radius: 18px;
            max-width: 70%;
            line-height: 1.4;
        }

        .message.user {
            background-color: #007bff;
            color: white;
            align-self: flex-end;
        }

        .message.assistant {
            background-color: #f1f3f5;
            color: #333;
            align-self: flex-start;
        }

        .streaming-container {
            flex-grow: 1;
            display: flex;
            flex-direction: column;
        }

        .streaming-message {
            background-color: #f8f9fa;
            color: #333;
            padding: 16px;
            border-radius: 18px;
            border: 2px solid #007bff;
            max-width: 85%;
            align-self: flex-start;
            line-height: 1.5;
        }

    </style>
</head>
<body>
    <div class="container">
      
        
        <div class="controls">
            <button onclick="addText()">Add More Text</button>
            <button onclick="removeText()">Remove Text</button>
            <button onclick="resetText()">Reset</button>
        </div>

        <div class="chat-container" id="container">
            <div class="chat-messages">
                <!-- Previous static messages -->
                <div class="message user">Hey there! How are you doing?</div>
                <div class="message assistant">Hello! I'm doing great, thank you for asking.</div>
                <div class="message user">Can you explain how this works?</div>
                
                <!-- Streaming message container -->
                <div class="streaming-container">
                    <div class="streaming-message" id="streaming">
                        I'd be happy to explain! This is a streaming message...
                    </div>
                </div>
            </div>
        </div>
    </div>

    <script>
        let textLevel = 1;
        const baseText = "Lorem ipsum dolor sit amet, consectetur adipiscing elit";
        const loremTexts = [
            ", sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
            " Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris.",
            " Nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit.",
            " In voluptate velit esse cillum dolore eu fugiat nulla pariatur.",
            " Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia.",
            " Deserunt mollit anim id est laborum. Sed ut perspiciatis unde omnis iste natus.",
            " Error sit voluptatem accusantium doloremque laudantium, totam rem aperiam.",
            " Eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae.",
            " Dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur.",
            " Aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione."
        ];

        function getLoremText() {
            let fullText = baseText;
            for (let i = 0; i < textLevel; i++) {
                fullText += loremTexts[i % loremTexts.length];
            }
            return fullText;
        }

        function adjustPadding() {
            const container = document.getElementById('container');
            const lastAssistantMessage = document.getElementById('streaming');
            
            const containerHeight = container.clientHeight;
            const messageHeight = lastAssistantMessage.offsetHeight;
            
            const marginBottom = (containerHeight - messageHeight) + (8);
            lastAssistantMessage.style.marginBottom = marginBottom + 'px';
        }

        function updateStreamingMessage() {
            document.getElementById('streaming').textContent = getLoremText();
            adjustPadding();
        }

        function addText() {
            textLevel++;
            updateStreamingMessage();
        }

        function removeText() {
            if (textLevel > 1) {
                textLevel--;
                updateStreamingMessage();
            }
        }

        function resetText() {
            textLevel = 1;
            updateStreamingMessage();
        }

        function scrollToAlignment() {
            const container = document.getElementById('container');
            const streamingMessage = document.getElementById('streaming');
            
            // Scroll so streaming message top aligns with the red line
            const messageTopInContainer = streamingMessage.offsetTop - container.offsetTop;
            const targetScrollTop = messageTopInContainer - 22; // Account for padding + red line
            
            container.scrollTop = targetScrollTop;
        }

        // Initialize - only scroll on initial load
        updateStreamingMessage();
        
        // Initial scroll to alignment after page loads (only once)
        window.addEventListener('load', () => {
            setTimeout(scrollToAlignment, 100);
        });
    </script>
</body>
</html>

Trying to show hidden elements after selecting an option and selecting submit [duplicate]

I’m trying to show an HTML element that I have as display: none in my CSS file. Part of my code works but the result doesn’t seemed to be attached to my show items code. JavaScript Code below for context:

const Electronics = document.getElementById("Electronics");
const Hardware = document.getElementById("Hardware");
const Clothing = document.getElementById("Clothing");
const Parts = document.getElementById("Parts");
const mySubmit = document.getElementById("mySubmit");

mySubmit.onclick = function(){
    let result = Electronics.checked ? "Electronics" : Hardware.checked ? "Hardware" : Clothing.checked ? "Clothing" : Parts.checked ? "Parts" : "No category selected";
    activeFunction.textContent = `You selected: ${result}`;
}

if (result = Electronics.checked) {
    var availableElectronics = document.getElementById("availableElectronics");
    availableElectronics.style.display = "inline-block";
}

Choose random options from multiple select menus at the same time

Using Javascript select random option, I can see it’s possible to have a single select element on a page, and to set up a link on the page that will select a random option from that select element using jQuery.

This is the basic HTML, using the solution from the link – which chooses a random option from the first select.

$(document).ready(function () {
    $("#randomSelect").click(function () {
        var select = document.getElementById('cat01');
        var items = select.getElementsByTagName('option');
        var index = Math.floor(Math.random() * items.length);
        select.selectedIndex = index;
    });
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script>

<div class="selects">

    <select name="cat01" id="cat01" class="form-select">
        <option value="cats">Cats</option>
        <option value="dogs">Dogs</option>
        <option value="eagles">Eagles</option>
        <option value="rabbits">Rabbits</option>
    </select>
    
    <hr>

    <select name="cat02" id="cat02" class="form-select">
        <option value="cats">Cats</option>
        <option value="dogs">Dogs</option>
        <option value="eagles">Eagles</option>
        <option value="rabbits">Rabbits</option>
    </select>
    
    <hr>

    <select name="cat03" id="cat03" class="form-select">
        <option value="cats">Cats</option>
        <option value="dogs">Dogs</option>
        <option value="eagles">Eagles</option>
        <option value="rabbits">Rabbits</option>
    </select>
    
    <hr>

    <select name="cat04" id="cat04">
        <option value="cats">Cats</option>
        <option value="dogs">Dogs</option>
        <option value="eagles">Eagles</option>
        <option value="rabbits">Rabbits</option>
    </select>
    
    <hr>

    <a href="#" id="randomSelect">Select random</a>

</div>

How could I achieve the same result, but instead of using the link to select a random option from a single select element, use it to select a random option from all 4 select elements at the same time?

I tried using document.getElementsByClassName('form-select'); instead, but that didn’t work.

How can I ensure public/ appears on output once I deploy my php project on Vercel?

I have a php project, the structure has api/ and public/ on the root directory as required by vercel for php project. public/ carries static files; css/, js/ and images/ while api/ has index.php, init.php and pages/ that contains all .php web page files. After deploying my project on vercel, my structure appears correctly under source but under output only api/ shows, and visiting the url and appending /css/style.css I find 404 error.

{
  "version": 2,
  "builds": [
    {  
      "src": "api/**/*.php", 
      "use": "[email protected]" 
    },
    { 
      "src": "api/index.php", 
      "use": "[email protected]" 
    },
    { 
      "src": "api/init.php", 
      "use": "[email protected]" 
    }
  ],
  "outputDirectory": "public",
  "routes": [
    { 
      "src": "^/projects/([^/]+)/?$", 
      "dest": "/api/pages/project-details.php?slug=$1" 
    },
    { 
      "src": "^/skills/([^/]+)/?$", 
      "dest": "/api/pages/skill-details.php?slug=$1" 
    },
    { 
      "src": "^/blogs/([^/]+)/?$", 
      "dest": "/api/pages/blog-details.php?slug=$1" 
    },
    { 
      "src": "^/(about|contact|skills|blogs|projects|home)/?$", 
      "dest": "/api/pages/$1.php" 
    },
    {
      "src": "/(css|js|images)/(.*)",
      "headers": {
        "Cache-Control": "public, max-age=31536000, immutable"
      }
    },
    { 
      "src": "/", 
      "dest": "api/index.php" 
    }
  ]
}

I have tried explicitly using “outputDirectory”: “public”, in vercel.json, I’ve also tried moving index.php and init.php outside api/ but that only resluted in more issues so I left them and the html appears but no css or js.
I was expecting the css and js to be applied on the website.

Angular 15 ‘ng serve’ builds app very slowly, every change is very slow

Im using angular v15 for my project. It’s not small but not really big. I’d say medium to big. Running ng serve takes A LOT of time.

Build at: 2025-07-23T12:23:56.259Z – Hash: 5ba2b19880727f6c – Time: 150153ms

Making a really small change like adding console.log:

Build at: 2025-07-23T12:28:27.654Z – Hash: be9deeddc53e7be5 – Time: 60598ms

I mean this is just ridiculous and I have no idea what’s wrong..

I’m still using NgModules and have around 150 of them.

this is my angular.json setting for build and serve

 "build": {
      "builder": "@angular-builders/custom-webpack:browser",
      "options": {
        "customWebpackConfig": {
          "path": "webpack.config.js"
        },
        "sourceMap": true,
        "optimization": true,
        "statsJson": true,
        "outputPath": "dist/fuse",
        "index": "src/index.html",
        "main": "src/main.ts",
        "polyfills": [
          "zone.js"
        ],
        "tsConfig": "tsconfig.app.json",
        "inlineStyleLanguage": "scss",
        "allowedCommonJsDependencies": [
          "apexcharts",
          "highlight.js",
          "crypto-js/enc-utf8",
          "crypto-js/hmac-sha256",
          "crypto-js/enc-base64",
          "flat",
          "quill",
          "lodash",
          "moment",
          "pdfmake/build/pdfmake",
          "pdfmake/build/vfs_fonts",
          "html-to-pdfmake",
          "jquery",
          "quill-image-resize-module-ts"
        ],
        "assets": [
          "src/favicon-16x16.png",
          "src/favicon-32x32.png",
          "src/web.config",
          "src/assets",
          {
            "glob": "_redirects",
            "input": "src",
            "output": "/"
          }
        ],
        "stylePreprocessorOptions": {
          "includePaths": [
            "src/@fuse/styles"
          ]
        },
        "styles": [
          "node_modules/@fortawesome/fontawesome-free/css/all.min.css",
          "node_modules/angular-calendar/css/angular-calendar.css",
          "node_modules/flatpickr/dist/flatpickr.css",
          "node_modules/slick-carousel/slick/slick.css",
          "node_modules/slick-carousel/slick/slick-theme.css",
          "src/@fuse/styles/tailwind.scss",
          "src/@fuse/styles/themes.scss",
          "src/styles/vendors.scss",
          "src/@fuse/styles/main.scss",
          "src/styles/styles.scss",
          "src/styles/tailwind.scss"
        ],
        "scripts": [
          "node_modules/jquery/dist/jquery.min.js",
          "node_modules/slick-carousel/slick/slick.min.js"
        ]
      },
      "configurations": {
        "production": {
          "customWebpackConfig": {
            "path": "webpack.config.js"
          },
          "outputPath": "dist/production",
          "fileReplacements": [
            {
              "replace": "src/environments/environment.ts",
              "with": "src/environments/environment.prod.ts"
            }
          ],
          "budgets": [
            {
              "type": "initial",
              "maximumWarning": "3mb",
              "maximumError": "10mb"
            },
            {
              "type": "anyComponentStyle",
              "maximumWarning": "75kb",
              "maximumError": "90kb"
            }
          ],
          "outputHashing": "all",
          "sourceMap": false
        },
        "staging": {
          "outputPath": "dist/staging",
          "fileReplacements": [
            {
              "replace": "src/environments/environment.ts",
              "with": "src/environments/environment.staging.ts"
            }
          ],
          "budgets": [
            {
              "type": "initial",
              "maximumWarning": "3mb",
              "maximumError": "10mb"
            },
            {
              "type": "anyComponentStyle",
              "maximumWarning": "75kb",
              "maximumError": "90kb"
            }
          ],
          "outputHashing": "all",
          "sourceMap": false
        },
        "development": {
          "buildOptimizer": true,
          "optimization": true,
          "vendorChunk": true,
          "extractLicenses": false,
          "sourceMap": true,
          "namedChunks": true
        }
      },
      "defaultConfiguration": "production"
    },
    "serve": {
      "builder": "@angular-builders/custom-webpack:dev-server",
      "options": {
        "browserTarget": "fuse:build"
      },
      "configurations": {
        "production": {
          "browserTarget": "fuse:build:production"
        },
        "development": {
          "browserTarget": "fuse:build:development",
          "hmr": true
        }
      },
      "defaultConfiguration": "development"
    },
    "extract-i18n": {
      "builder": "@angular-devkit/build-angular:extract-i18n",
      "options": {
        "browserTarget": "fuse:build"
      }
    },

tailwind.config.js:

const path = require("path");
const colors = require("tailwindcss/colors");
const defaultTheme = require("tailwindcss/defaultTheme");
const generatePalette = require(path.resolve(
    __dirname,
    "src/@fuse/tailwind/utils/generate-palette"
));

/**
 * Custom palettes
 *
 * Uses the generatePalette helper method to generate
 * Tailwind-like color palettes automatically
 */
const customPalettes = {
    brand: generatePalette("#2196F3"),
};

/**
 * Themes
 */
const themes = {
    // Default theme is required for theming system to work correctly!
    default: {
        primary: {
            ...colors.indigo,
            DEFAULT: colors.indigo[600],
        },
        accent: {
            ...colors.slate,
            DEFAULT: colors.slate[800],
        },
        warn: {
            ...colors.red,
            DEFAULT: colors.red[600],
        },
        "on-warn": {
            500: colors.red["50"],
        },
    },
    // Rest of the themes will use the 'default' as the base
    // theme and will extend it with their given configuration.
    brand: {
        primary: customPalettes.brand,
    },
    teal: {
        primary: {
            ...colors.teal,
            DEFAULT: colors.teal[600],
        },
    },
    rose: {
        primary: colors.rose,
    },
    purple: {
        primary: {
            ...colors.purple,
            DEFAULT: colors.purple[600],
        },
    },
    amber: {
        primary: colors.amber,
    },
};

/**
 * Tailwind configuration
 */
const config = {
    darkMode: "class",
    content: ["./src/**/*.{html,scss,ts}", "./node_modules/flowbite/**/*.js"],
    important: true,
    theme: {
        fontSize: {
            xs: "0.625rem",
            sm: "0.75rem",
            md: "0.8125rem",
            base: "0.875rem",
            lg: "1rem",
            xl: "1.125rem",
            "2xl": "1.25rem",
            "3xl": "1.5rem",
            "4xl": "2rem",
            "5xl": "2.25rem",
            "6xl": "2.5rem",
            "7xl": "3rem",
            "8xl": "4rem",
            "9xl": "6rem",
            "10xl": "8rem",
        },
        screens: {
            sm: "600px",
            md: "960px",
            lg: "1280px",
            xl: "1440px",
        },
        extend: {
            gridTemplateColumns: {
                '5': 'repeat(5, minmax(0, 1fr))',
            },
            animation: {
                "spin-slow": "spin 3s linear infinite",
            },
            colors: {
                gray: colors.slate,
                'chat-message-blue': '#57E6FF',
                'primary-color': '#3C9ECF',
                'secondary-color': '#e3f4fb',
                'orange-color': '#ff8600',
                'title-color': '#90D5F7'
            },
            flex: {
                0: "0 0 auto",
            },
            fontFamily: {
                sans: `"Inter var", ${defaultTheme.fontFamily.sans.join(",")}`,
                mono: `"IBM Plex Mono", ${defaultTheme.fontFamily.mono.join(
                    ","
                )}`,
            },
            opacity: {
                12: "0.12",
                38: "0.38",
                87: "0.87",
            },
            rotate: {
                "-270": "270deg",
                15: "15deg",
                30: "30deg",
                60: "60deg",
                270: "270deg",
            },
            scale: {
                "-1": "-1",
            },
            zIndex: {
                "-1": -1,
                49: 49,
                60: 60,
                70: 70,
                80: 80,
                90: 90,
                99: 99,
                999: 999,
                9999: 9999,
                99999: 99999,
            },
            spacing: {
                13: "3.25rem",
                15: "3.75rem",
                18: "4.5rem",
                22: "5.5rem",
                26: "6.5rem",
                30: "7.5rem",
                50: "12.5rem",
                90: "22.5rem",

                // Bigger values
                100: "25rem",
                120: "30rem",
                128: "32rem",
                140: "35rem",
                160: "40rem",
                180: "45rem",
                192: "48rem",
                200: "50rem",
                240: "60rem",
                256: "64rem",
                280: "70rem",
                320: "80rem",
                360: "90rem",
                400: "100rem",
                480: "120rem",

                // Fractional values
                "1/2": "50%",
                "1/3": "33.333333%",
                "2/3": "66.666667%",
                "1/4": "25%",
                "2/4": "50%",
                "3/4": "75%",
            },
            minHeight: ({ theme }) => ({
                ...theme("spacing"),
            }),
            maxHeight: {
                none: "none",
            },
            minWidth: ({ theme }) => ({
                ...theme("spacing"),
                screen: "100vw",
            }),
            maxWidth: ({ theme }) => ({
                ...theme("spacing"),
                screen: "100vw",
            }),
            transitionDuration: {
                400: "400ms",
            },
            transitionTimingFunction: {
                drawer: "cubic-bezier(0.25, 0.8, 0.25, 1)",
            },

            // @tailwindcss/typography
            typography: ({ theme }) => ({
                DEFAULT: {
                    css: {
                        color: "var(--fuse-text-default)",
                        '[class~="lead"]': {
                            color: "var(--fuse-text-secondary)",
                        },
                        a: {
                            color: "var(--fuse-primary-500)",
                        },
                        strong: {
                            color: "var(--fuse-text-default)",
                        },
                        "ol > li::before": {
                            color: "var(--fuse-text-secondary)",
                        },
                        "ul > li::before": {
                            backgroundColor: "var(--fuse-text-hint)",
                        },
                        hr: {
                            borderColor: "var(--fuse-border)",
                        },
                        blockquote: {
                            color: "var(--fuse-text-default)",
                            borderLeftColor: "var(--fuse-border)",
                        },
                        h1: {
                            color: "var(--fuse-text-default)",
                        },
                        h2: {
                            color: "var(--fuse-text-default)",
                        },
                        h3: {
                            color: "var(--fuse-text-default)",
                        },
                        h4: {
                            color: "var(--fuse-text-default)",
                        },
                        "figure figcaption": {
                            color: "var(--fuse-text-secondary)",
                        },
                        code: {
                            color: "var(--fuse-text-default)",
                            fontWeight: "500",
                        },
                        "a code": {
                            color: "var(--fuse-primary)",
                        },
                        pre: {
                            color: theme("colors.white"),
                            backgroundColor: theme("colors.gray.800"),
                        },
                        thead: {
                            color: "var(--fuse-text-default)",
                            borderBottomColor: "var(--fuse-border)",
                        },
                        "tbody tr": {
                            borderBottomColor: "var(--fuse-border)",
                        },
                        'ol[type="A" s]': false,
                        'ol[type="a" s]': false,
                        'ol[type="I" s]': false,
                        'ol[type="i" s]': false,
                    },
                },
                sm: {
                    css: {
                        code: {
                            fontSize: "1em",
                        },
                        pre: {
                            fontSize: "1em",
                        },
                        table: {
                            fontSize: "1em",
                        },
                    },
                },
            }),
            
        },
    },
    corePlugins: {
        appearance: false,
        container: false,
        float: false,
        clear: false,
        placeholderColor: false,
        placeholderOpacity: false,
        verticalAlign: false,
    },
    plugins: [
        // Fuse - Tailwind plugins
        require(path.resolve(
            __dirname,
            "src/@fuse/tailwind/plugins/utilities"
        )),
        require(path.resolve(
            __dirname,
            "src/@fuse/tailwind/plugins/icon-size"
        )),
        require(path.resolve(__dirname, "src/@fuse/tailwind/plugins/theming"))({
            themes,
        }),
        require("flowbite/plugin"),

        // Other third party and/or custom plugins
        require("@tailwindcss/typography")({ modifiers: ["sm", "lg"] }),
        require("@tailwindcss/line-clamp"),
    ],
};

module.exports = config;

package.json:

{
  "name": "fuse-angular",
  "version": "17.0.1",
  "description": "Fuse - Angular Admin Template and Starter Project",
  "author": "https://themeforest.net/user/srcn",
  "license": "https://themeforest.net/licenses/standard",
  "private": true,
  "scripts": {
    "ng": "ng",
    "start": "ng serve",
    "start:prod": "ng serve --configuration production",
    "build": "ng build",
    "build:prod": "ng build --configuration production",
    "build:staging": "ng build --configuration staging",
    "build:stats": "ng build --stats-json",
    "analyze": "webpack-bundle-analyzer dist/fuse/stats.json",
    "watch": "ng build --watch --configuration development",
    "test": "ng test",
    "lint": "ng lint",
    "explore": "source-map-explorer dist/**/*.js"
  },
  "dependencies": {
    "@angular-eslint/schematics": "^18.3.0",
    "@angular-material-components/datetime-picker": "^16.0.1",
    "@angular/animations": "15.0.0",
    "@angular/cdk": "15.0.0",
    "@angular/common": "15.0.0",
    "@angular/compiler": "15.0.0",
    "@angular/core": "15.0.0",
    "@angular/forms": "15.0.0",
    "@angular/material": "15.0.0",
    "@angular/material-luxon-adapter": "15.0.0",
    "@angular/material-moment-adapter": "^18.0.0",
    "@angular/platform-browser": "15.0.0",
    "@angular/platform-browser-dynamic": "15.0.0",
    "@angular/router": "15.0.0",
    "@fortawesome/fontawesome-free": "^6.6.0",
    "@microsoft/signalr": "^7.0.2",
    "@ng-bootstrap/ng-bootstrap": "^14.2.0",
    "@ngneat/transloco": "4.1.1",
    "@ngx-translate/core": "^14.0.0",
    "@ngx-translate/http-loader": "^8.0.0",
    "@ngxs/devtools-plugin": "^3.8.2",
    "@ngxs/storage-plugin": "^3.8.2",
    "@ngxs/store": "^3.8.2",
    "@popperjs/core": "^2.11.8",
    "angular-calendar": "^0.31.0",
    "apexcharts": "^3.53.0",
    "bootstrap": "^5.3.2",
    "crypto-js": "^4.2.0",
    "date-fns": "^2.30.0",
    "flatpickr": "^4.6.13",
    "flowbite": "^2.2.1",
    "html-to-pdfmake": "^2.5.13",
    "jquery": "^3.7.1",
    "jwt-decode": "^3.1.2",
    "lodash-es": "4.17.21",
    "luxon": "3.1.0",
    "moment": "^2.30.1",
    "ng-apexcharts": "1.7.4",
    "ng-recaptcha": "^13.2.1",
    "ngx-cookie-service": "^18.0.0",
    "ngx-image-cropper": "^6.3.2",
    "ngx-mat-select-search": "^7.0.7",
    "ngx-mat-timepicker": "^18.0.0",
    "ngx-material-file-input": "^4.0.0",
    "ngx-quill": "^20.0.1",
    "ngx-slick-carousel": "^17.0.0",
    "ngx-spinner": "^15.0.1",
    "ngx-toastr": "^16.0.1",
    "ngxs-reset-plugin": "^3.0.0",
    "pdfmake": "^0.2.20",
    "perfect-scrollbar": "1.5.5",
    "quill": "1.3.7",
    "quill-image-resize-module-ts": "^3.0.3",
    "rxjs": "7.5.7",
    "slick-carousel": "^1.8.1",
    "zone.js": "0.12.0"
  },
  "devDependencies": {
    "@angular-builders/custom-webpack": "^15.0.0",
    "@angular-devkit/build-angular": "15.0.0",
    "@angular-devkit/core": "^18.2.5",
    "@angular-eslint/eslint-plugin": "15.2.1",
    "@angular-eslint/eslint-plugin-template": "15.2.1",
    "@angular/cli": "15.0.0",
    "@angular/compiler-cli": "15.0.0",
    "@angular/localize": "15.0.0",
    "@tailwindcss/line-clamp": "0.4.2",
    "@tailwindcss/typography": "0.5.8",
    "@types/crypto-js": "3.1.47",
    "@types/highlight.js": "10.1.0",
    "@types/jasmine": "4.3.0",
    "@types/jquery": "^3.5.32",
    "@types/lodash": "4.14.189",
    "@types/lodash-es": "4.17.6",
    "@types/luxon": "3.1.0",
    "@types/pdfmake": "^0.2.11",
    "@types/slick-carousel": "^1.6.40",
    "@typescript-eslint/eslint-plugin": "^5.48.2",
    "@typescript-eslint/parser": "^5.48.2",
    "chroma-js": "2.4.2",
    "eslint": "^8.57.0",
    "jasmine-core": "4.5.0",
    "karma": "6.4.1",
    "karma-chrome-launcher": "3.1.1",
    "karma-coverage": "2.2.0",
    "karma-jasmine": "5.1.0",
    "karma-jasmine-html-reporter": "2.0.0",
    "lodash": "^4.17.21",
    "postcss": "8.4.19",
    "raw-loader": "^2.0.0",
    "tailwindcss": "3.2.4",
    "typescript": "4.8.4",
    "webpack-bundle-analyzer": "^4.10.2"
  }
}

I also ran npx webpack-bundle-analyzer dist/fuse/stats.json (i know about report-generator.service.ts but i need that one as well as pdf and vfs stuff): enter image description here

Local javascript app blocked by CORS policy – adapting code to allow functionality

I am adapting an offline browser-based app to show the locations of projects within my organisation. The files will reside on a Windows server, accessed via a VPN connection.

I can get the code to work if I place one file (containing the GeoJSON) on a web server. However, this is my own server, not a company one, and I’ve only got it working for testing purposes. The data cannot rely on an internet server, and must be able to live in the same local directory as the rest of the code.

The browser page ‘index.html’ calls the following script:

<script src="assets/js/app.js"></script>

Within this app.js file, there is a config variable, which calls on the GeoJSON file I have put on the internet server:

var config = {
  geojson: "https://www.<<MYURL>>.co.uk/geojson/Projects List 20250623.geojson",
  title: "Projects List",
  layerName: "Projects"
};

With the above setup (i.e. the GeoJSON file being on the internet), it works. However, if I change the geojson: to point at a local version, i.e.

geojson: "assets/js/Projects List 20250623.geojson",

It doesn’t work, and I get the following error:

Access to XMLHttpRequest at ‘file:///C:/Users/<>/OneDrive/geojson-dashboard-master/assets/js/Projects List 20250623.geojson’ from origin ‘null’ has been blocked by CORS policy: Cross origin requests are only supported for protocol schemes: chrome, chrome-extension, chrome-untrusted, data, http, https, isolated-app.

I am confused, because I have other leaflet-based files which are capable of locally loading in the GeoJSON data from a file.

Does the above JavaScript need to be reworked to pull in the GeoJSON data in a different way?

It’s a mini-project which I want to keep on an internal server for others to be able to view, without needing to publish online any files.

Handling Clipboard API Permission Issues: Promise Not Resolving After User Grants Access

I encountered a problem when using the API to read the clipboard. When a user is using this feature for the first time, the browser displays a popup asking if the user allows the website to read the clipboard (for auto-filling forms; this is a tool I customized for specific users, so I’m not considering the case of denial), like this:

const text = await navigator.clipboard.readText();

But there’s an issue—when the user clicks “Allow,” readText doesn’t get resolved or rejected; it’s as if the promise disappears into thin air.

This prevents subsequent tasks from proceeding. To handle this situation, I either have to ask the user to go back and click the button again after granting permission, or set up a specific element on the page to deal with this scenario.

I’d like to know if there’s a way to avoid these extra steps?

AbortError: The play() request was interrupted because the media was removed from the document in React video conferencing app

I’m building a video conferencing app using React and Azure Communication Services.
I use the VideoStreamRenderer to render video streams for participants.
When a participant’s video stream changes, I dispose the previous renderer and create a new one:

const RenderDynamicView = ({ remoteVideoStream, item, participantCount, pageIndex }) => {
  const videoContainerRef = useRef(null);
  const rendererRef = useRef(null);
  const viewRef = useRef(null);

  const getInitials = (str) => {
    try {
      if (!str.includes(' ')) return str.slice(0, 2).toUpperCase();
      const words = str.split(' ');
      return words.map(w => w[0].toUpperCase()).join('').slice(0, 2);
    } catch {
      return str;
    }
  };

  const getClassName = () => {
    if (participantCount < 3) return "remote-one-custom";
    if (participantCount === 3) return "remote-three-custom";
    if (participantCount === 4) return "remote-four-custom";
    if (participantCount < 7) return "remote-morethan-four-custom";
    return "remote-greaterthan-six-custom";
  };

  const disposeRenderer = () => {
    try {
      if (viewRef.current) {
        viewRef.current.dispose();
        viewRef.current = null;
      }
      if (rendererRef.current) {
        rendererRef.current.dispose();
        rendererRef.current = null;
      }
    } catch (err) {
      console.error('Dispose error:', err);
    }
  };

  const renderStream = async () => {
    if (!remoteVideoStream || !remoteVideoStream.isAvailable || !videoContainerRef.current) return;

    try {
      disposeRenderer();

      const renderer = new VideoStreamRenderer(remoteVideoStream);
      const view = await renderer.createView();

      videoContainerRef.current.innerHTML = '';
      videoContainerRef.current.appendChild(view.target);

      const videoElement = view.target.querySelector('video');
      if (videoElement) {
        videoElement.style.objectFit = 'contain';
        videoElement.style.width = '100%';
        videoElement.style.height = '100%';
      }

      rendererRef.current = renderer;
      viewRef.current = view;
    } catch (err) {
      if (err.name === 'AbortError') {
        return;
      }
      console.error('Failed to render video stream:', err);
    }
  };

    if (remoteVideoStream) {
      renderStream();
    } else {
      disposeRenderer();
    }

    let isRemoteStream = remoteVideoStream && remoteVideoStream.kind !== 'LocalVideoStream';

    const handleAvailabilityChange = () => {
      if (remoteVideoStream) {
        renderStream();
      } else {
        disposeRenderer();
      }
    };

    if (isRemoteStream && remoteVideoStream?.on) {
      remoteVideoStream.on('isAvailableChanged', handleAvailabilityChange);
    }

    return () => {
      if (isRemoteStream && remoteVideoStream?.off) {
        remoteVideoStream.off('isAvailableChanged', handleAvailabilityChange);
      }
      disposeRenderer();
    };
  }, [remoteVideoStream]);

  return (
    <div className={`remoteUser ${getClassName()}`} ref={videoContainerRef}>
      {!remoteVideoStream?.isAvailable && (
        <>
          <div className="short-name">{item?.displayName}</div>
          <div className="name_div">{getInitials(item?.displayName)}</div>
        </>
      )}
      <img
        className={participantCount < 3 ? 'mute-status-right-top' : 'mute-status'}
        src={item?.isMute ? mute_incall : unmute_incall}
        alt="Mute Status"
        id={`mute-status-${item?.userId}`}
      />
      {item?.isRiseHand && (
        <img
          className="risehand-status"
          src={raise_hand_active}
          alt="Raise Hand"
          id={`risehand-status-${item?.userId}`}
        />
      )}
    </div>
  );
};

Despite this, I still get the following error once in the browser console:

AbortError: The play() request was interrupted because the media was removed from the document.
  • Is there a way to completely avoid this error?
  • Is this error harmful,
    or can it be safely ignored?
  • Is there a better pattern for disposing
    and re-attaching video elements in React?

How to create an exact circular video note UI like WhatsApp in React Native? [closed]

Description:

I’m trying to replicate the circular video note UI exactly like it’s implemented in WhatsApp. Here’s what I’m aiming for:

  • A perfectly circular video player.
  • The video auto-plays when visible and loops continuously.
  • The UI should include border animation or progress indicator like WhatsApp while recording or playing.

I am using React Native, and I’m open to using libraries like react-native-vision-camera, react-native-reanimated. while recording the video

Questions:

  1. What is the best way to create a circular video view in React Native?
  2. Any tips or recommended libraries for handling circular masking and border animations?

If someone has already tried replicating this WhatsApp-style video note or can share a code snippet or example, that would be super helpful!

Accessing backend through Cloudflare Tunnels

I have set up a tunnel to my Linux pc to host a simple website. Here is what I have set up so far:

I have 2 public hostnames associated with my tunnel:

  • Domain: example.com, Service: HTTP://localhost:5173
  • Domain: example.com, Path: api/*, Service: HTTP://localhost:6969

I configured the DNS with ‘cloudflared tunnel route dns’ in the command line.

Here is a snippet of an axios post request I have set up on my frontend:

export const getMatchedReportName = async () => {
  return await axios.post(`https://example.com/api/get-matched-report-name`);
}

Here is a snippet of my express backend:

const app = express();
const PORT = 6969;

const corsOptions = {
  origin: [
    `http://localhost:5173`,
    `http://localhost:6969`,
    "https://example.com",
  ],
  optionsSuccessStatus: 200,
};

app.post("/api/get-matched-report-name", Controller.getMatchedReportName);

app.listen(PORT, () => {
  console.log(`Server is running on port ${PORT}`);
});

I am able to access my website through the public internet no problem but I am not able to hit a backend route. Here is an example of the error I get when trying to access my backend from the website:

POST https://example.com/api/get-matched-report-name 404 (Not Found)

I have tried creating a config.yml file in my .cloudflared folder, but that has not worked. When I try curl -X POST http://localhost:6969/api/get-matched-report-name on my host PC terminal, I receive the correct information from the backend so the routes should be configured correctly and my backend is running. When I try curl -X POST https://example.com/api/get-matched-report-name, I do not get anything.