Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tailwind 3.3+ dark mode does not work with encapsulated angular components using @apply #12352

Closed
MarKayS opened this issue Nov 3, 2023 · 5 comments
Assignees

Comments

@MarKayS
Copy link

MarKayS commented Nov 3, 2023

What version of Tailwind CSS are you using?

v3.3.5

What build tool (or framework if it abstracts the build tool) are you using?

tried angular 13 and angular 16

What version of Node.js are you using?

v18.10.0

What browser are you using?

Chrome, Firefox

What operating system are you using?

Windows, macOS

Reproduction URL

https://github.com/MarKayS/tailwind-angular-dark-bug

Describe your issue

When using @apply with dark mode set to class and angular viewEncapsulation is not disabled, the dark: rules are also scoped.

Using this simple css:

:host{
@apply text-amber-900 dark:text-amber-500;
}

This is how the dark style is generated with tailwind 3.3+:

:is(.dark   )[_nghost-mxl-c11] {
    --tw-text-opacity: 1;
    color: rgb(245 158 11 / var(--tw-text-opacity))
}

Same style with tailwind 3.2.0:

.dark   [_nghost-kar-c11] {
    --tw-text-opacity: 1;
    color: rgb(245 158 11 / var(--tw-text-opacity))
}

This means that I would have to add .dark class to every component, turn off viewEncapsulation or use hostBinding insted of apply.

In previous versions there were spaces after the .dark . The same is true in 3.3, but the spaces are inside the :is function :is(.dark ).

I think the error comes from

addVariant('dark', `:is(${className} &)`)

I believe changing it to the following line should resolve the issue and hopefully not break anything:

addVariant('dark', `:is({className}) &`)

Should I submit a PR?

@faileon
Copy link

faileon commented Nov 3, 2023

I am having the same issue in my angular app.

@thecrypticace
Copy link
Contributor

Hey! So, this an issue with Angular. We generate the following CSS from the above:

:host {
  --tw-text-opacity: 1;
  color: rgb(120 53 15 / var(--tw-text-opacity))
}

:is(.dark :host) {
  --tw-text-opacity: 1;
  color: rgb(245 158 11 / var(--tw-text-opacity))
}

Then Angular processes it, removes :host, but does not place the data attribute in the right place. It generates :is(.dark )[_nghost-jja-c5] when it should generate :is(.dark [_nghost-jja-c5]) instead.

You can see an example of this (without Tailwind involved) in this example. You'll need to peek into the compiled source in the iframe using inspect element for this it seems though.

I'll look into filing an issue with them that includes a reproduction sometime today or early next week if I can't find an existing one.


As an aside, we don't use addVariant('dark', ':is({className}) &') because that is functionally different from current version when it comes to putting multiple classes/attributes on the same element or on elements nested in a different order. I've prepped an example on Tailwind Play that showcases this.

@thecrypticace thecrypticace self-assigned this Nov 3, 2023
@faileon
Copy link

faileon commented Nov 5, 2023

I've done some digging and found out it's a known issue in angular:

angular/angular#45686

There is also an open PR that might resolve the issue:

angular/angular#45807

However it seems to be forgotten in time.

Unfortunately it seems that even with this change the generated css from angular now looks like this:

[_ngcontent-gst-c11]:is(.dark :host) {
    --tw-text-opacity: 1;
    color: rgb(255 255 255 / var(--tw-text-opacity))
}

@thecrypticace
Copy link
Contributor

Given that this is a known Angular issue and that they've already got an issue (and PR) open about it I think it'd be preferable to track this there. Going to close this.

@faileon
Copy link

faileon commented Nov 10, 2023

Since that angular issue is forgotten in time and since it also doesn't solve the issue at hand, I am gonna share my solution for whoever needs it.

It's quite dirty, so please forgive me.

The fix requires to change one regex in the angular compiler.

// 722#node_modules/@angular/compiler/esm2022/src/shadow_css.mjs
// 8071#node_modules/@angular/compiler/fesm2022/compiler.mjs
const _polyfillHostNoCombinatorRe = /-shadowcsshost-no-combinator([^\s]*)/;

in such way that it respect ending brackets

const _polyfillHostNoCombinatorRe = /-shadowcsshost-no-combinator([^\s\)]*)/;

I have created the following script that will replace it in the node_modules:

// ./tools/angular-compiler-fix.ts

import { readdir, readFile, writeFile } from 'fs/promises';
import { resolve } from 'path';

async function* getFiles(dir: string): AsyncGenerator<string> {
    const dirents = await readdir(dir, { withFileTypes: true });
    for (const dirent of dirents) {
        const res = resolve(dir, dirent.name);
        if (dirent.isDirectory()) {
            yield* getFiles(res);
        } else {
            yield res;
        }
    }
}

const main = async () => {
    const ngPckgPath = `${__dirname}/../node_modules/@angular/compiler`;
    const originalExpression = '-shadowcsshost-no-combinator([^\\s]*)';
    const newExpression = '-shadowcsshost-no-combinator([^\\s\\)]*)';
    for await (const file of getFiles(ngPckgPath)) {
        if (file.endsWith('.mjs')) {
            const content = (await readFile(file)).toString();
            const index = content.indexOf(originalExpression);
            if (index > -1) {
                console.log('replacing expression...', file);
                const newContent = content.replace(originalExpression, newExpression);
                await writeFile(file, newContent);
            }
        }
    }
};

main();

And run it in the npm postinstall hook:

{
    "name": "my-org-repo",
    "version": "0.0.1",
    "license": "MIT",
    "scripts": {
        "postinstall": "node ./decorate-angular-cli.js && npx ts-node ./tools/angular-compiler-fix.ts",

I'm sure there are better ways to fix it and then submit a PR to Angular team. This solution is temporarily good enough for my usecase.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants