mint icon indicating copy to clipboard operation
mint copied to clipboard

Details about targeting elements and what are the actual limitations when using selectors

Open frnco opened this issue 2 years ago • 3 comments

Mint makes it pretty clear that selectors are not reliable by showing the following message in the console, in all Caps:

!!!DO NOT TARGET ELEMENTS WITH SELECTORS BECAUSE THE SELECTORS WILL BE MINIMIZED IN THE PRODUCTION BUILD!!!

I actually think it's really nice that Mint deals with minifying stuff for me, and usually the information in the learn section is sufficient to clear any doubts (https://www.mint-lang.com/guide/getting-started/styling-with-css and https://www.mint-lang.com/guide/reference/components/styling-elements for CSS, and also https://www.mint-lang.com/guide/reference/components/referencing-entities). But in some rare situations I find information that implies there's more to this issue than would appear. A good example is https://www.mint-lang.com/guide/recipes/using-third-party-css where classes are used.

One specific scenario that worries me is when I need to modify something that is inside a component, let's call this "Child component", from another component, let's call this one "Parent component", and to clarify what I mean, let's consider a parent component "Main" which creates a header and a footer, and it includes a Child Component "Searchbox" both in the header and footer, only for the footer version it would like to set the opacity for the search box to 0.7, reverting to 1 only when the search box is hovered. Or, to use something I actually wanted to do as an example, making Mint-Ui's Ui.Native.Select full-width (100%, 100vw, whatever you fancy). Here's the documentation for Ui.Native.Select, just to reinforce the point that it doesn't mention any way to set its width: (https://ui.mint-lang.com/components/ui-native-select).

When dealing with mint components there's no simple way to target a child element, especially in more complex elements that may have multiple divs at each nesting level, and having to use some unreasonably long combination of >s and nth-childs doesn't make a lot of sense to me, but then there's no mention anywhere in the docs of any easy way to target stuff inside a child component, which brings me to the point of this issue:

What would be the preferred way to deal with this kind of situation (Or is this something that has no established solution)? What are the actual limitations when using selectors? Some examples of those limitations would be:

  • Are ids changed/minified/replaced/substituted? (I'll try to only use "substitution" from now on)
  • And what about classes?
  • When the substitution is made, it appears external stylesheets and classes in the html are synced, what else is and isn't synced?
  • Are sub-rules in style blocks checked to sync the class names?
  • What about values passed to Dom.getElementById and Dom.getElementBySelector?
    • Is there some other factor involved here (And/or in any other case), such as whether the value comes from a variable or is a literal that appears on the code together with this function call...?
  • External stylesheets?
  • Inline JavaScript code...?

This issue is actually a minor one, but it hurts to have no centralized, indexable source of truth about this, which is actually considered so fundamental that it deserves to be shown at the DevTools every time the browser loads the project in dev mode. The documentation about selectors is spread throughout many different sections and in very small chunks, plus it has some conflicting information (i.e., The use of classes when working with external CSS), and even worse than that, the docs mention the use of classes only superficially, without any further clarification about what actually works and doesn't work.

All things considered, at the very least I wanted to compile the references and whatever information I could think of here, as an issue, so anyone can contribute to make those things clear, and so that this may lead to, at some point in the future, improving the documentation.

frnco avatar Nov 13 '21 03:11 frnco

After reading this I think the DevTools warning is misleading. The warning only refers to generated selectors and to those only.

Let's take this as an example:

component Ui.Something {
  style base {
    // styles here
  }

  fun render {
    <div::base><{ "Some text" }></div>
  } 
}

In development, it would generate a selector like this: class="Ui·Something_base" on the div but in production it would be class="a". So if you would target that element using a selector like .Ui·Something_base in the parent it would break in production.

Everything else is kept as is: specified classes passed as attribute, ids, etc...

I would like to know that in hindsight what warning message would relay this information :pray:


Styling children in an idiomatic way has not been solved with Mint yet. What I usually do is wrap the element I want to style differently and use child selectors > * instead of classes.

Ideas on how to solve it are welcome.

gdotdesign avatar Nov 13 '21 06:11 gdotdesign

I would like to see some kind of support for first-class style values that can be used like mixins or themes. Then I can style my children by passing them a style.

See also #444

A hypothetical example:

store Global {
  state fontSizePts = 14
  style theme {
    font-family: serif;
    font-size: #{fontSizePts}pt;
  }
  fun increaseSize(n : Number) {
    next { fontSizePts = fontSizePts + n }
  }
}

component Parent {
  style childTheme {
    background: black;
    color: white;
  }
  fun bigger {
    Global.increaseSize(4)
  }
  fun render : Html {
    <div>
      <Child theme={childTheme} />
      <button onClick={bigger}>Bigger</button>
    </div>
  }
}

component Child {
  property theme : Style
  style content {
    @include #{Global.theme};
    @include #{theme};
    border-radius: 8px;
  }
  fun render : Html {
    <p::content>Hello world!</p>
  }
}

ryanprior avatar Nov 13 '21 18:11 ryanprior

I would like to see some kind of support for first-class style values that can be used like mixins or themes. Then I can style my children by passing them a style.

See also #444

A hypothetical example:

store Global {
  state fontSizePts = 14
  style theme {
    font-family: serif;
    font-size: #{fontSizePts}pt;
  }
  fun increaseSize(n : Number) {
    next { fontSizePts = fontSizePts + n }
  }
}

component Parent {
  style childTheme {
    background: black;
    color: white;
  }
  fun bigger {
    Global.increaseSize(4)
  }
  fun render : Html {
    <div>
      <Child theme={childTheme} />
      <button onClick={bigger}>Bigger</button>
    </div>
  }
}

component Child {
  property theme : Style
  style content {
    @include #{Global.theme};
    @include #{theme};
    border-radius: 8px;
  }
  fun render : Html {
    <p::content>Hello world!</p>
  }
}

After taking some time to check this well enough to be sure I didn't miss anything, I can definitely say this actually looks really amazing. The use of @include is something I completely failed to consider, which solves most of the difficulties I had envisioned. It seems preferable to use the standard syntax, though that depends on how much work would be required to the core. To me, something like this would be ideal (Using the example from @ryanprior as a base, changing only the syntax to make it work as I think would be preferrable):

store Global {
  state fontSizePts = 14
  style theme {
    font-family: serif;
    font-size: #{fontSizePts}pt;
  }
  fun increaseSize(n : Number) {
    next { fontSizePts = fontSizePts + n }
  }
}

component Parent {
  style childTheme {
    /* I can manually include anything in my styles, no changes here, so: */
    @include #{Global.theme};
    background: black;
    color: white;
  }
  fun bigger {
    Global.increaseSize(4)
  }
  fun render : Html {
    <div>
      <Child::childTheme />
      <button onClick={bigger}>Bigger</button>
    </div>
  }
}

component Child {
  style content {
    border-radius: 8px;
    /* My proposal: Core could use a conditional to automatically include styles passed to any component, i.e.: */
    @include #{scopedStyle};
  }
  fun render : Html {
    <p::content>Hello world!</p>
  }
}

Notice the use of <Child::childTheme /> as opposed to <Child theme={childTheme} />. This would require changing the Core. However, changing the core means the @include #{scopedStyle}; line would actually be dealt with by the core, making it possible to use this syntax for styling any component from any library. Of course, there's a chance that it isn't feasible to do that, in which case something like <Child scopedStyle={childTheme} /> could be done instead, but I believe the best-est solution would be to have the ::style syntax apply to components, and using @include would, as far as I can understand, solve most problems that might arise from trying to include the raw CSS from the style block to a component's "master" style block (If the component doesn't have a style for its root the compiler could simply apply the passed style to the component's root, if the component does have a style block just add the passed style at the end of said block, preferably after all the sub-styles have been included, so it takes precedence over the defaults.)

Seems pretty reasonable to me.

frnco avatar Nov 29 '21 23:11 frnco