-
Notifications
You must be signed in to change notification settings - Fork 3.4k
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
Saving selector to a variable #3053
Comments
Curiously I was absolutely sure such request already exists. Apparently it's not. Btw., just in case (and to collect some use-cases to think of possible impl./syntax conflicts): a {
@r: &;
b {
something: @r;
}
} will result in: a b {
something: a b; // not a!
} Because |
You'd need that special language construction to capture the selector context at the point it is defined, not at the point it is called by evaluation of the variable to which it is assigned. Evaluation should just emit the context which was captured at the definition site. Not much different from looking up variables by closure, but yes; it will require a special language construct and not a function. |
Yes, I guess we've been discussing some a {
@var: selector(x, #y & .z);
b {
@{var} { // ? is it regular & or "saved-context-&" ?
// ...
}
}
} So it possibly may require other than |
I know there's a general hesitation to add new things to the language, but a selector specifier could be handy. I like a {
@var: |x, #y & .z|; // starting w/ `|` means selector, in current context, ended w/ another `|`
b {
@{var} {
//...
}
}
} (In fact, it could be used to designate that any var should be processed right away and stored, or whatever? But that feels like it's way over-scoping.) |
I'd say the selector context should be captured at the site the When any variable holding a Reasoning behind this is that both are captured selectors: Then, if a user requires interpolation of a captured In closing a {
@var: selector(x, #y & .z);
b {
@{var} { ... }
}
} should generate x, #y a .z { ... } Whereas a {
@var: selector(x, #y & .z);
b {
& @{var} { ... }
}
} should generate a b x,
a b #y a .z { ... } |
Hmm, |
I'm not a fan of a function-like construct for selectors. I am supportive of being able to reference, modify, or transport (assign to variable and re-use) inherited selectors though. Just to make sure, though, how much of this is a variation on #1075 (targeting parent selectors) or in possible conflict with less/less-meta#16 (comment) (assigning a single selector to a variable)? Or is this different than aliasing mixins because this is a selector list and not a single evaluated selector (or mixin), and the output is the selector list and not the evaluated ruleset? I'm assuming this feature is different; I just want to make sure that all of these are moving in the same direction. |
@matthew-dean One could imagine a function like E.g. given a mixin .my-bem-component(@a, @b) {
// Component will only ever be constructed on the first selector in
// a list, for simplicity.
@selectors : selector(&);
@selector : extract(@selectors, 1);
// Generate a clean block name, cleared of modifiers.
// Grabs e.g. "bar" from ".foo > .bar--baz"
@block-name : replace(@selector, "\.([^\.\s]+)--\S+$", "$1" );
// Generate the modifier name and generate different CSS for
// BEM classes that have one.
// Grabs e.g. "baz" in ".foo > .bar--baz"
@mod-name : replace(@selector, "\.+--(\S+)$", "$1" );
.generate-block();
// When @mod-name matches @selector, no replacement has
// occured and we are infact in the situation where we have no
// BEM modifier and generate the 'base' component.
.generate-block() when (@mod-name = "@{selector}") {
@{selector} {
prop-a : @a;
}
@{selector}__element {
prop-b : @b;
}
}
.generate-block() when (default()) {
@{selector} {
prop-a : @a;
}
@{selector} > .@{block}__element {
prop-b : @b;
}
}
} The following Less .block {
.my-bem-component(foo, bar);
}
.block--caps {
.my-bem-component(FOO, BAR);
} generates CSS .block {
prop-a : foo;
}
.block__element {
prop-b : bar;
}
.block--caps {
prop-a : FOO;
}
.block--caps > .block__element {
prop-b : BAR;
} Sass has, afaik, had this for a long time and there are numerous examples of this technique being put to very clever use. In code factories like in my example, or for other purposes. |
As for:
Personally I assume the following behaviour: @var: .ns.mixin; // OK, its just Anonymous value (representing an arbitrary identifier)
function(.mixin); // error: TODO ^This has (essentially) nothing to do with selectors at all - these values are (attempted to be) converted to a selector format (to lookup a mixin) only when we try to call/eval them with Whereas a complex or "real" selectors require
In summary, (All above does not mean that a value returned by |
Sure we could adjust functions to work with strings assuming such string may contain some selector, but that means every such function (not just (Note that the selector interpolation feature itself still have to convert
To be honest, this looks like an reincarnation of "inline JavaScript and LessHat-like hackery", can't prohibit but will aggressively advertise against. |
And getting back to |
Absolutely. The regex-based extraction was just to provide an example that didn't involve custom functions. ;) |
Also, if you only mean how it looks... It could be some other construction of course, e.g. |
Now, the fun part. I created a quick and dirty plugin implementation of the div {
@x: current-selector();
span {
r: @x; // -> div
}
} results in div {
@x: current-selector(); // [1]
@y: current-selector() @v; // [2]
@z: current-selector(@v); // [3]
@v: whatever;
span {
1: @x; // div
2: @y; // div span
3: @z; // div span
4: current-selector(); // [4] div span
}
} There only the That is, a plugin based version of the div#zoo {
@x: current-selector();
span {
@y: replace(@x, div, body);
r: @y; // OK, body#zoo
@{y} {
// ^ not very useful this way (except maybe bem stuff) since you can't remove div
color: red;
}
}
} i.e. subsequent variable assignments / function calls do not affect the evaluation-point of the initial variable. |
@seven-phases-max Even if lazy-evaluation currently throws a spanner in the works when a parameter argument is present, that is presumably something which can be worked around for a 'real' implementation. |
Fair enough. I haven't really wrapped my brain about this as far as usage nor do I have any better ideas. I guess there seemed something special about this, but maybe not. I know there was discussion at some point about And it seems like |
These are different things. As for |
Ah ok. |
@matthew-dean Quite hilariously; the plural term 'selectors' is not even the official way to designate a comma-separated set. The strictly correct terminology for the plural form is, I believe, a selector list. So you can kind of see how deeply rooted this ambiguous reference really is. |
Yeah, I also did search w3c yesterday, it's "group of selectors", "a list of selectors" etc., nothing really fancy like weird "A selector is a chain of one or more sequences of simple selectors separated by combinators" they also have there :) Just "selectors" form is mostly used there to describe the "selector types" thing. |
And for anyone thinking that might be refering to comma-separated lists: it isn't. The full form of that quote should be: "a complex selector is a chain of one or more sequences of simple selectors separated by combinators." The CSS specs have another problem where the generalized form of "selector" is used mostly to refer to what the specs officially call complex selectors. Complex selectors are simple selectors, e.g. Something like WARNING the following will be a bit of a rant: The CSS specs are sadly a mire of inconsistent and ill-named terminology. The further back you go, the worse it progressively gets. Doesn't help that loads of CSS3 modules keep referring back to CSS 2.1 modules or that new CSS3 modules were specced by copying over their old CSS 2.1 documentation verbatim. The specs for selectors and the visual formatting model are the worst offenders though; so much ambiguous, similar-sounding or plain poorly named terminology. Take for instance something far less trivial than I had to try and explain parts of the latter visual formatting model spec to one of my more design-oriented colleagues at one point a few years back as well. I think a few fuses were actually blown in both our brains while we went over the passages that treat the concept of line boxes and that wasn't even the worst part of it. (Try treading into the magical la-la-land that is the table formatting model, if you have little value for your sanity.) |
Manifold joys of open source projects documentation... |
That actually makes sense to me, lol, just because it doesn't use a combinator. It follows the definition precisely. Just because it uses a lot of symbols doesn't make it more "complex".
Right, you are correct. "Selectors" are really just the defined bits that allow you to select elements, but |
Just ran across another minor issue with the quick-n-dirty plugin function: it doesn't handle access from a namespaced mixin correctly. Namespaces are a regular A real implementation should probably cover that case as well. [EDIT] (Basically; this skips over the 'closure' frames that are added into the stack when a |
Maybe |
Just name it .rule {
@selectors : &();
} |
🤔 |
I'm gonna try to summarize to see if I'm understanding the proposed feature: New function .component { // I only write "component" once! Much concise, such DRY!
@this: &();
/* base styles */
&_child {
/* styles for the child */
}
&-variant { // component-variant styles all together, and inside the `.component` block
/* nothing too schmancy so far */
@{this}_child {
/* IT'S MAGIC! */
}
}
} And that all would output this. .component {
/* base styles */
}
.component_child {
/* styles for the child */
}
.component-variant {
/* nothing too schmancy so far */
}
.component-variant .component_child {
/* IT'S MAGIC! */
} Because that's amazing, and I love it. |
Thinking about this more, I think the most compelling to me (after a first-pass; stop me if I'm getting too nuts) would be the possibility of having a standardized component "block" (ruleset) style. Basically, I'd almost hope that instead of a simple selector string value saved, the function would map to " .component{ @this:&();
/* default styles */
@{this}_child {
// ↑ The crucial difference: `@{this}` here behaves _like `&`_, **NOT** like `.component`
/* child default styles */
}
} Then I could say "use My only concern would be the flip case (which I'll call Behavior B), but I can't think of a compelling case where I'd want that behavior. That is I can't see why someone would want to do this. .foo { @and: &();
@{and} {
/*stuff meant to live under selector `.foo.foo` */
}
} Because the current way to accomplish that is much more concise (and readable, too, once .foo {
&& {
/*stuff meant to live under selector `.foo.foo` */
}
} Is there a compelling case (aside from difficulty implementing) for Behavior B over Behavior A? This is just one of those questions I think should be answered before work begins. TL;DR: My vote is for |
@calvinjuarez Your examples are somewhat confusing because you're not writing your expected output, so they seem somewhat in the realm of the theoretical, but basically: .component{
@this: &(); // @this is now assigned the value of `.component`
@{this}_child { a: b; } // this variable, when evaluated, forms the selector .component_child
}
// therefore this output is:
.component .component_child {
a: b;
}
I really don't understand what this means. |
Another way to think about this. This: .component {
@this: &()
} Is the equivalent of writing: .component {
@this: .component;
} |
Yes. But think about it through the lens of mixins, where It enables writing mixin-based components where authors themselves can freely decide on the root of the classname in a natural manner. E.g. given .my-button {
#buttons.base();
#buttons.size( ... );
#buttons.inset-icon-support( left right );
}
.my-button--wide {
#buttons.size( ... )
}
.my-button--condensed {
#buttons.size( ... )
} the mixins used there could read the class via That is; it could be used to generate a selector like Mixin-based component factories like this avoid many of the all-or-nothing our-way-or-the-highway problems you get with using styling frameworks. They allow you to register the framework, but granularly pick which components you want to actually incorporate and under which name. |
Yep, I get it. It's probably most useful in mixins. I definitely get the utility of |
To go further, I think it's a good syntactic solution, and I would personally give a 👍 to moving forward with implementation of |
Whoops, apologies. I'll restate it properly. I feel like .component { @this:&();
/* default styles */
@{this}_child {
// ↑ The crucial difference: `@{this}` here behaves _like `&`_, **NOT** like `.component`
// (since it's in the same rule block and scope level).
/* child default styles */
}
} .component {
/* default styles */
}
.component_child {
/* child default styles */
} If |
It means I think that It means, more practically, that you don't have to write a mixin to do easy BEM, but can define it inline. Going back one of my older examples: component.less .component { // I only write "component" once! Much concise, such DRY!
@this: &();
/* base styles */
@{this}_child {
/* styles for the child */
}
@{this}-variant { // component-variant styles all together, and inside the `.component` block
/* nothing too schmancy so far */
@{this}_child {
/* IT'S MAGIC! */
}
}
} ↓↓↓ component.css .component {
/* base styles */
}
.component_child {
/* styles for the child */
}
.component-variant {
/* nothing too schmancy so far */
}
.component-variant .component_child {
/* IT'S MAGIC! */
} The benefit: You don't have to ask your team whether they mean |
It'd actually make a component factory mixin definition more internally consistent as well. hypothetical-button-mixin.less #button () {
.size(large) { @button: &();
@{button} { // same scope, so it behaves _exactly_ like `&`.
font-size: 1.8rem;
}
@{button}-primary { // same scope, so it behaves _exactly_ like `&`.
border-width: 5px;
@{button}_icon { // nested scope, behaves like the parent selector at the mixin call (.btn).
height: 1.8rem;
width: 1.8rem;
}
}
}
}
// ... hypothetical-styles.less .btn {
#button.size(large);
} hypothetical-styles.css .btn {
font-size: 1.8rem;
}
.btn-primary {
border-width: 5px;
}
.btn-primary .btn_icon {
height: 1.8rem;
width: 1.8rem;
} |
Yes, I think we're saying the same things but let me confirm with this example. This is how I see this feature vs. in-place .mixin() {
@this: &();
.a {
.b @{this} { c: d; }
}
}
.component {
.mixin();
}
// outputs:
.component .a .b .component {
c: d;
} Whereas: .mixin() {
.a {
.b & { c: d; }
}
} Would produce: .b .component .a {
c: d;
} |
@calvinjuarez I guess I was confused because I think no one was suggesting something different than your example. |
@matthew-dean Have e.g.
output
just like the native |
Yes, that’s exactly what it would do. In 3.5, any variables evaluated in selectors cause the entire selector list to be re-parsed as a new selector list. So yes, that would work as expected. It’s actually quite easy because of some recent PRs I did.
… On Jul 7, 2018, at 10:34 AM, rjgotten ***@***.***> wrote:
@matthew-dean
It would actually be even more awesome if it would expose the selector list as an actual list of selectors, including the special behavior for expanding selectors based on all list members.
Have e.g.
.a, .b {
@this : &();
@{this} {
c : d;
}
}
output
.a .a,
.a .b,
.b. .a,
.a .b {
c : d
}
just like the native & would.
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub, or mute the thread.
|
The extra .component{
@this: &(); // @this is now assigned the value of `.component < &`
@{this}_child { a: b; } // this variable evaluates like `&_child`
}
// therefore this output is:
.component_child { // < Note: `.component_child` !== `.component .component_child`
a: b;
} It seems like the feature is going a different direction though. Just wanted to clarify my position. |
I.e. if there's a substitution token in a selector that is a selector list instead of a plain string, then the substitution token acts the same as if If Infact, if it were to output a dedicated node type, that might also assist the creation of plugin functions to manipulate the captured selector list, later on. |
To me, that sounds like making &() do too much work at once. If you want it to save selectors to a variable, that’s one thing, but to have that variable disable selector chaining because of its _content_ would be unclear in the syntax. That variable could come from anywhere (e.g. passed from a mixin) and the selector list could be generated by simple variable assignment. That is, it’s not clear from variable usage that a different chaining behaviour would happen based on the variable’s contents.
I think if you wanted to disable chaining, you’d have to specify that you want to replace the implicit & with another value, like (forgive the formatting, I’m on my phone) -
.component {
@var: &();
&(@var)_child {} // or some such “replacement of &” syntax
}
So I get why the result is desirable, but IMO we can’t just “magic-switch” variable merging behaviour based on where the selector list comes from. This requires two different features.
|
ooh... I actually like the |
Ha, really? You don’t think there would be semantic confusion of `&()` (capture selectors from &) and `&(@arg)` ( replace & with selectors)?
You might want to consider not mixing them, since someone might want to replace `&` with an empty selector in order to essentially discard it. (To place a child at the root.) Although I guess maybe it could be `&(“”)` .child?
I dunno, it deserves some thought / consideration.
|
Also, as noted in the “parent selectors should have targets” thread, there are use cases for replacing specific parts of the inherited selector (or entirely), so thinking of that, I think those should be tracked as two separate issues. This issue should just be about capturing &
|
Just to close this circle, here's where I mentioned altering So, I'd prefer if discussion about "how/whether to alter |
I'm trying to do this
but that won't work until this is implemented |
We have this solution:
I propose to add a feature: writing
@r: &;
instead of@r: ~".selector";
to get the current selector and save it in any variable.Examples:
The text was updated successfully, but these errors were encountered: