Using :global() and custom data attributes to apply styles to dynamically added classes

Currently, I am working on creating a typing game that is reminiscent of monkeytype.com. In this game, every letter is linked to classes that change dynamically from an empty string to either 'correct' or 'incorrect', depending on whether the user's input matches the expected letter.

One issue I'm facing is related to how Svelte handles unused classes - the 'correct' and 'incorrect' classes may not exist in the DOM right away. This makes it challenging for me to apply specific styles to these letter classes when they become active.

Below is a snippet of my code:

<script lang="ts">
    type Game = 'waiting for game' | 'in progress' | 'game over';
    type Word = string;

    let game: Game = 'waiting for game';
    let typedLetter = '';

    let words: Word[] = 'the quick brown fox jumped over the lazy dog'.split(' ');

    let wordIndex = 0;
    let letterIndex = 0;
    let correctLetter = 0;

    let correctLetters = 0;

    let wordsEl: HTMLDivElement;
    let letterEl: HTMLSpanElement;
    let inputEl: HTMLInputElement;

    function startGame() {
        setGameState('in progress');
    }

    function setGameState(state: Game) {
        game = state;
    }

    function updateGameState() {
        setLetter();
        checkLetter();
        nextLetter();
        resetLetter();
    }

    function setLetter() {
        const isWordCompleted = letterIndex > words[wordIndex].length - 1;

        if (!isWordCompleted) {
            letterEl = wordsEl.children[wordIndex].children[letterIndex] as HTMLSpanElement;
        }
    }

    function checkLetter() {
        const currentLetter = words[wordIndex][letterIndex];

        if (typedLetter === currentLetter) {
            letterEl.dataset.letter = 'correct';
            increaseScore();
        }

        if (typedLetter !== currentLetter) {
            letterEl.dataset.letter = 'incorrect';
        }
    }

    function increaseScore() {
        correctLetters += 1;
    }

    function nextLetter() {
        letterIndex += 1;
    }

    function resetLetter() {
        typedLetter = '';
    }

    function handleKeydown(event: KeyboardEvent) {
        if (event.code === 'Space') {
            event.preventDefault();
        }

        if (game === 'waiting for game') {
            startGame();
        }
    }
</script>

<div class="game" data-game={game}>
    <input
        type="text"
        class="input"
        bind:this={inputEl}
        bind:value={typedLetter}
        on:input={updateGameState}
        on:keydown={handleKeydown}
    />
</div>

<div class="words" bind:this={wordsEl}>
    {#each words as word}
        <span class="word">
            {#each word as letter}
                <span class="letter">{letter}</span>
            {/each}
        </span>
    {/each}
</div>

<style lang="scss">
    .words {
        --line-height: 1em;
        --lines: 3;

        width: 100%;
        max-height: calc(var(--line-height) * var(--lines) * 1.42);
        display: flex;
        flex-wrap: wrap;
        gap: 0.6em;
        position: relative;
        font-size: 1.5rem;
        line-height: var(--line-height);
        overflow: hidden;
        user-select: none;

        .letter {
            opacity: 0.4;
            transition: all 0.3s ease;

            &:global([data-letter='correct']) {
                opacity: 0.8;
            }

            &:global([data-letter='incorrect']) {
                color: var(--primary);
                opacity: 1;
            }
        }
    }
</style>

I've made an attempt to use :global for the data attributes, but I encountered an error message stating ':global(...) not at the start of a selector sequence should not contain type or universal selectors svelte(css-invalid-global-selector-position)'. All packages and Svelte are fully up to date.

Answer №1

If the component's markup creates the element, there is no need for :global.

Typically, using :global should not be required as Svelte generates scoped classes for elements.

You can also specify attributes and classes directly in the markup without needing to manipulate them through code.

For instance:

<script>
    let correct = 'world';
    let words = ['loner'];
</script>

<div class="words">
    {#each words as word}
        <span class="word">
            {#each word as letter, i}
                <span class="letter"
                    class:exists={correct.indexOf(letter) != -1}
                    class:correct={correct[i] == letter}>
                    {letter}
                </span>
            {/each}
        </span>
    {/each}
</div>

<style>
    .letter.exists { color: orange; }
    .letter.correct { color: green; }
</style>

REPL

Using a data-attribute:

<span class="letter"
    data-letter={
        correct[i] == letter ? 'correct' :
        correct.indexOf(letter) != -1 ? 'exists' :
        undefined
    }>
...
<style>
    .letter[data-letter=exists] { color: orange; }
    .letter[data-letter=correct] { color: green; }
</style>

REPL

Answer №2

When you see the error message:

:global(...) not at the start of a selector sequence should not contain type or universal selectors

it indicates that the compiled style is invalid CSS. For more information, visit this Github issue and try the solutions in the Svelte playground

To properly target the <span/> elements with the data-letter attribute, make sure to nest the selector inside the :global selector like below:

.word > :global(.letter[data-letter="correct"]) {
  opacity: 0.8;
}

.word > :global(.letter[data-letter="incorrect"]) {
  color: var(--primary);
  opacity: 1;
}

Ensure that the .word > :global(...) selectors are placed outside of the .words selector.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Combining Multiple .ts Files into a Single File: A Simplified Application Structure with TypeScript 1.8

Currently, I am in the process of developing an Electron application and I have decided to implement TypeScript for this project. While TypeScript essentially boils down to JavaScript in the end, my familiarity with it makes the transition seamless. As of ...

Minimize the count of switch cases that are not empty

How can I optimize the number of cases in my switch statement to align with SonarQube recommendations? Currently, I have 37 cases in a switch statement, but SonarQube recommends only 30. I believe that my code is functioning correctly, and the issue lies ...

Conceal the PayPal Button

Currently, I'm facing a challenge where I need to dynamically show or hide a PayPal button based on the status of my switch. The issue is that once the PayPal button is displayed, it remains visible even if the switch is toggled back to credit card pa ...

Extending Angular 2 functionality from a parent component

As a continuation of the discussion on Angular2 and class inheritance support here on SO, I have a question: Check out my plunckr example: http://plnkr.co/edit/ihdAJuUcyOj5Ze93BwIQ?p=preview Here is what I am attempting to achieve: I want to implement s ...

What is the best method to condense an array or extract only the valid values?

Looking to find the total count of properties that are true from an array of objects? Here's an example: let data = [ { comment: true, attachment: true, actionPlan: true }, { whenValue: '', ...

Encountered issue with accessing the Error Object in the Error Handling Middleware

Below is the custom error validation code that I have developed : .custom(async (username) => { const user = await UserModel.findOne({ username }) if (user) throw new ConflictError('username already used& ...

How can the outcome of the useQuery be integrated with the defaultValues in the useForm function?

Hey there amazing developers! I need some help with a query. When using useQuery, the imported values can be undefined which makes it tricky to apply them as defaultValues. Does anyone have a good solution for this? Maybe something like this would work. ...

What could be causing my background image not to appear in the Next 13 application?

Having trouble with a background image not rendering in my global SCSS file. While all other styles are working fine, the bg image is causing some issues. I don't think it's the file paths, but the new app dir in Next 13 might be throwing me off. ...

The Angular 2 router is not compatible with using the same component but with different IDs

Currently utilizing the alpha8 router with 3 main routes: export const appRoutes: RouterConfig = [ { path: '', component: LandingComponent }, { path: 'blog', component: BlogComponent }, { path: 'posts/:id', compon ...

Creating a class in TypeScript involves declaring a method that takes a parameter of type string, which matches the property name of a specific derived class type

I am working on a scenario where I have a base class containing a method that can be called from derived classes. In this method, you provide the name of a property from the derived class which must be of a specific type. The method then performs an operat ...

Issue with Component in Angular not functioning properly with proxy construct trap

Currently working with Angular 17, I have a straightforward decorator that wraps the target with Proxy and a basic Angular component: function proxyDecorator(target: any) { return new Proxy(target, { construct(target: any, argArray: any[], newTarget: ...

The latest release of Angular2, rc1, eliminates all parameters that are not in

In the previous beta version, I was able to analyze using split Location.path(), but now it seems to have been removed. How can I prevent this removal? Interestingly, everything works well with matrix parameters (;id=123;token=asd). This was tested on a ...

I am looking to update the color based on the prop value in React

Currently using react along with typescript. My objective is to adjust the color based on the value passed through props. The props will contain either "primary," "secondary," or "brand" as a string type. When the value "primary" is provided, I aim ...

In a Typescript Next Js project, the useReducer Hook cannot be utilized

I'm completely new to Typescript and currently attempting to implement the useReducer hook with Typescript. Below is the code I've written: import { useReducer, useContext, createContext } from "react" import type { ReactNode } from &q ...

What is the best way to transform this component into a function rather than a class?

import React, { Component } from "react"; class ChatHistory extends Component { render() { const messages = this.props.chatHistory.map((msg, index) => ( <p key={index}>{msg.data}</p> )); return ( <div ...

Certain Material-UI components appear to lack proper styling

I found a tutorial on how to incorporate material UI into my app at this link: https://mui.com/material-ui/getting-started However, I noticed that some components are not styled as expected and customizing the theme seems to have no effect... This is how ...

Making retries with the RetryWhen filter in Angular 2 RxJS Observables when encountering errors in the status

I'm currently working with the Angular 2 HTTP library, which returns an observable. I'm trying to set up a retry mechanism for specific error statuses/codes. The problem I'm facing is that when the error status is not 429, Observable.of(err ...

A new concept within the realm of programming is the Export class statement that utilizes

Is it possible to define both a class and an interface in the same file and export the class directly without any issues? I have encountered problems when using export default Foo, as it exports as { default: Foo } instead of the actual class object. I am ...

Issue with dynamic HTML preventing Bootstrap tooltip functionality

There's an HTML page where a section is dynamically generated through HTML injection from a typescript angularjs controller using $sce and ng-bind-html. The issue is that the custom bootstrap tooltip style doesn't seem to be applied, and only t ...

How to easily upload zip files in Angular 8

Currently, I am working on integrating zip file upload feature into my Angular 8 application. There are 3 specific requirements that need to be met: 1. Only allow uploading of zip files; display an error message for other file types 2. Restrict the file s ...