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

Saving selector to a variable #3053

Open
influsion opened this issue Apr 20, 2017 · 50 comments
Open

Saving selector to a variable #3053

influsion opened this issue Apr 20, 2017 · 50 comments

Comments

@influsion
Copy link

influsion commented Apr 20, 2017

We have this solution:

// LESS
.selector {
    @r: ~'.selector';

    &--mode {
        @{r}__block {
            prop: value;
         }
    }
}

// CSS
.selector--mode .selector__block {
  prop: value;
}

I propose to add a feature: writing @r: &; instead of @r: ~".selector"; to get the current selector and save it in any variable.

Examples:

// LESS
.selector {
  @r: &; // .selector
}

.selector {
  &__inner {
    @r: &; // .selector__inner
  }
}

.selector {
  &--modification &__inner {
    @r: &; // .selector--modification .selector__inner
  }
}
@seven-phases-max
Copy link
Member

seven-phases-max commented Apr 20, 2017

Curiously I was absolutely sure such request already exists. Apparently it's not.
(Though obviously the idea did appear in many tickets before: #1174, #1075 (comment) etc.)
So let it be I guess.


Btw., just in case (and to collect some use-cases to think of possible impl./syntax conflicts):
How are you going to use it? I suspect in many cases it won't work the way you expect because of lazy-evaluation. E.g.:

a {
    @r: &;
    b {
        something: @r;
    }
}

will result in:

a b {
    something: a b; // not a!
}

Because @r value is really evaluated inside a b (i.e where it's used - not at the point you define it).
(So I suspect certain use-cases may actually require some other language construction for this - not just a variable. And many other related use-cases were considered before as a subject of #1075).

@rjgotten
Copy link
Contributor

rjgotten commented Apr 21, 2017

in many cases it won't work the way you expect because of lazy-evaluation
I suspect certain use-cases may actually require some other language construction for this - not just a variable

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.

@seven-phases-max
Copy link
Member

seven-phases-max commented Apr 21, 2017

@rjgotten

Yes, I guess we've been discussing some selector pseudo-function earlier (pseudo because a regular function parsing can't handle all that selector specific combinators anyway) and then because it's pseudo (i.e. a dedicated type like Url) it would not be a problem to make it to pull its definition context within (if I recall correctly DRs do exactly like that).
So something like @foo: selector(&); could do a trick I guess. Though then a next minor problem rises:

a {
    @var: selector(x, #y & .z);

    b {
        @{var} { // ? is it regular & or "saved-context-&" ?
            // ...
        }
    }
}

So it possibly may require other than & operator/keyword. (Or maybe just a dedicated pseudo-function, e.g. current-selector()... Hmm, now that brings me to an idea of coding a generic PseudoFunction type to not pollute the API of every tiny goody, ooch! :).

@calvinjuarez
Copy link
Member

calvinjuarez commented Apr 22, 2017

I know there's a general hesitation to add new things to the language, but a selector specifier could be handy. I like |, though it is part of the CSS @namespace selector syntax. However, it doesn't seem like | is allowed to start a selector, so it shouldn't conflict. I'm using it 'cause it reminds me of Math's "absolute value" which seems vaguely appropriate, and 'cause it's simple. Just a thought to inspire conversation.

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.)

@seven-phases-max seven-phases-max changed the title Feature: Getting a selector and saving it to a variable Saving selector to a variable Apr 22, 2017
@rjgotten
Copy link
Contributor

rjgotten commented May 5, 2017

is it regular & or saved-context-& ?

I'd say the selector context should be captured at the site the selector() pseudo-function is called and & should be evaluated within that context and the end-result should be assigned to this new type of Selector tree node.

When any variable holding a Selector type of tree node is interpolated into a selector, such as is the case with the use of @{var} in the example, the resulting selector should be built in the same way as when a & interpolator is present in the selector, i.e. ; don't join and prefix with the selectors from one nesting level up.

Reasoning behind this is that both are captured selectors: selector() is a user-captured one, wheras & is the always-present and captured 'parent' selector.

Then, if a user requires interpolation of a captured Selector node together with the current selector context, they can be explicit about it. E.g. & @{var} { ... }

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 { ... }

@seven-phases-max
Copy link
Member

seven-phases-max commented May 5, 2017

Hmm, @{var} vs. & @{var} looks quite artificial, this is just not how these things work in Less. ... { & div ... and ... { div ... were always equal statements. Also, not even counting this, what shall I do if I need a b x, #y a b .z with x, #y * .z defined elsewhere?

@matthew-dean
Copy link
Member

matthew-dean commented May 5, 2017

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.

@rjgotten
Copy link
Contributor

rjgotten commented May 5, 2017

@matthew-dean
It is indeed about capturing the actual selector list, not about capturing the evaluated ruleset, and outputting said list for further use.

One could imagine a function like extract(list,index) to be updated to be able to also extract selector components from a selector list and similar enhancements to ease working with selectors, such that users can easily manipulate them in interesting ways. For instance, for components that follow certain naming schemes such as BEM.

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.

@seven-phases-max
Copy link
Member

seven-phases-max commented May 6, 2017

As for:

possible conflict with less/less-meta#16 (comment) (assigning a single selector to a variable)

Personally I assume the following behaviour:
"Trivial" selector values (e.g. .mixin, .ns.mixin, #foo .bar, baz etc, luckily this covers anything that can be used/defined as mixin/function) are assigned to a variable (or passed as parameters to function) directly. I.e. we actually already have this:

@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 @var(...) or @var[...] statements
(In general the logical convention would be to forget that mixin identifiers are (represented internally as) selectors but always to think of them as identifiers with just dot or # prefix, and things like .ns > .mixin to fade away eventually as redundant and useless :)

Whereas a complex or "real" selectors require selector pseudo function because of the syntax/parser ambiguity. I.e. things like:

  • @var:foo>bar <- selector and (potentially) a logical expression
  • @var:.1+.2; <- arithm expression and valid Less selector
    (etc. well just recall all the specific selector symbols - almost every conflicts with something in a value parsing context, and this goes even more dramatic when above values are potentially passed to a function/mixin as parameters inplace, e.g. the following is just impossible to support w/o selector():
some-function(abc, selector(#foo .is :not(> bar)[baz="qux"], abc), selector(bla), 42); 
// ^ remove `selector()` and try to parse

I'm not a fan of a function-like construct for selectors.

In summary, selector pseudo function is just necessary to cut out any current and potential syntax and semantics conflicts once and forever. So I doubt we really have too many options here (value and selector parsing contexts just have to be separated somehow).


(All above does not mean that a value returned by selector() cannot be used as a callable entity e.g. @var(), it might probably - but that would be just unnecessary/useless, so it's barely worth to bother).

@seven-phases-max
Copy link
Member

seven-phases-max commented May 6, 2017

@rjgotten

One could imagine a function like extract(list,index) to be updated to be able to also extract selector

Sure we could adjust functions to work with strings assuming such string may contain some selector, but that means every such function (not just extract) have to be updated/adjusted/modified. The opposite approach would be more efficient / less burdening. I.e. it's either a dedicated selector-string->values conversion function, OR even returning the proper structure of nodes directly by selector (in either case the most tricky part is to how to pack/unpack selector combinators as every use-case may prefer different representation).

(Note that the selector interpolation feature itself still have to convert @{var} to a proper format in the end anyway so it does not really matter what format the value of that var comes in - either if it's string, anonymous or whatever structure of nodes - the most of conversion trickery remains the same).

@block-name : replace(@selector, "\.([^\.\s]+)--\S+$", "$1" );

To be honest, this looks like an reincarnation of "inline JavaScript and LessHat-like hackery", can't prohibit but will aggressively advertise against.
(Not counting that the example is pretty unfortunate <- count the lines) I'd rather suggest some less-plugin-bem-selectors thing (where you can simply have a get-block-name function, btw. even w/o the & feature) instead of such ugly regexes (the "Using a CSS preprocessor as an arbitrary text processor" approach will eventually badly loose in the end to PostCSS-like stuff).

@seven-phases-max
Copy link
Member

And getting back to & vs. context-saving-&, so far I'm afraid I have no ideas better than either a dedicated flag for the function, e.g. selector(..., lazy or not), or even two separate functions. Or using other-than-&-keyword (e.g. ). Just can't see any safe method to automatically resolve the point-of-evaluation ambiguity.

@rjgotten
Copy link
Contributor

rjgotten commented May 6, 2017

I'd rather suggest some less-plugin-bem-selectors thing

Absolutely. The regex-based extraction was just to provide an example that didn't involve custom functions. ;)

@seven-phases-max
Copy link
Member

seven-phases-max commented May 6, 2017

I'm not a fan of a function-like construct for selectors.

Also, if you only mean how it looks... It could be some other construction of course, e.g. ⁞#foo:not(.bar)⁞, but you know we've already run out of free symbols. So the pseudo function syntax is only suggested because we already have such concept with url anyway (thus no need to think of things a new concept could or will break).

@seven-phases-max
Copy link
Member

seven-phases-max commented May 7, 2017

Now, the fun part.

I created a quick and dirty plugin implementation of the current-selector function (just to see how bloating it could be, expecting that of course it cannot be very useful because of var lazy-evaluation), and know what? This basic example:

div {
    @x: current-selector();
    span {
        r: @x; // -> div
    }
}

results in r: div :)
No idea exactly what piece of the compiler code handles this particular behavior, but here is more advanced example to illustrate the magic:

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 [2] and [3] statements are called twice (i.e. actually lazy-evaluated), while the [1] is not (apparently because the value does not contain any variables, though yet again I don't know if this intentional or just a side-effect of some caching, or it could be a fortunate bug in my code - for instance this line may trigger such side-effect for this caching - but then it's not clear why it's affected by extra variables - i.e. more research needed).


That is, a plugin based version of the context-saving-& seems to be possible (except that of course instead of @var: & you'll use something like @var: current-selector()) Though the function should not have any parameters otherwise it gets lazy-evaluated (if a variable is passed in) - this is sad since I initially planned it to have four :).
Quite abusive but could serve as a workaround/polyfill. A more real example also works as desired:

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.

@rjgotten
Copy link
Contributor

rjgotten commented May 7, 2017

@seven-phases-max
Love it.

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.

@matthew-dean
Copy link
Member

matthew-dean commented May 8, 2017

Also, if you only mean how it looks... It could be some other construction of course, e.g. ⁞#foo:not(.bar)⁞, but you know we've already run out of free symbols.

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 $(), but we ended up appropriating $. Btw, wouldn't it be selectors() and not selector()? Can't it (like the &) contain any number of selectors?

And it seems like selector(&) makes more sense than current-selector(). That is: "make a selector list from X object, be it & or a string". Whatever the final syntax is seems like it would take & as an argument.

@seven-phases-max
Copy link
Member

seven-phases-max commented May 8, 2017

And it seems like selector(&) makes more sense than current-selector()

These are different things. current-selector is just a function variant of & (since the latter is not supported by the parser). While selector(...) is that patch for the parser to support an arbitrary selector (incl. &).


As for selectors - well, it is. But since it's 99% of single selector use-cases, I guess a plural form would sound less evident for most of users (in most, they usually title h1, h2, h3 {} as a selector and keep talking of Less parent selector (even if it's selectors) :) So why bother?

@matthew-dean
Copy link
Member

Ah ok.

@rjgotten
Copy link
Contributor

rjgotten commented May 8, 2017

@matthew-dean
Plural and singular form is pretty much interchangeable for anyone but the CSS spec authors. Infact, make that: for anyone including the spec authors, as even the CSS specs themselves fall prey to interchanged use of the singular and plural form at times.

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.

@seven-phases-max
Copy link
Member

seven-phases-max commented May 8, 2017

The strictly correct terminology for the plural form is, I believe, a selector list.

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.

@rjgotten
Copy link
Contributor

rjgotten commented May 8, 2017

"A selector is a chain of one or more sequences of simple selectors separated by combinators"

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. tag; #id; .class; [attr]; etc. , chained via combinators, e.g. >; +; ~, etc.

Something like ul > li is called a complex selector.


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 ul > li, such as [*|attr^="value" i]. The latter is technically classified as a simple selector. (Yes, really.)

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.)

@seven-phases-max
Copy link
Member

Manifold joys of open source projects documentation...

@matthew-dean
Copy link
Member

Take for instance something far less trivial than ul > li, such as [*|attr^="value" i]. The latter is technically classified as a simple selector

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". ul > li is complex because it involves two sets of queries i.e. querying for all elements matching li and then traversing up the tree from each to determine which ones are contained with a ul. The latter only tests individual elements once. It's one query so it's a simple selector.

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.

Right, you are correct. "Selectors" are really just the defined bits that allow you to select elements, but ul > li > .title is a "selector" singular. So I guess selector() is, in fact, maybe closer semantically.

@rjgotten
Copy link
Contributor

rjgotten commented Aug 24, 2017

@seven-phases-max

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 Ruleset type frame and thus their name is added into the selectors.

A real implementation should probably cover that case as well.


[EDIT]
The trick to making it work is to check whether one of the frames on the function context's stack is a MixinDefinition and if it is, skip over the next x frames on that stack, where x is equal to the amount of frames on the stack of the MixinDefinition.

(Basically; this skips over the 'closure' frames that are added into the stack when a MixinCall executes the MixinDefinition.)

@stale stale bot added the stale label Dec 22, 2017
@stale stale bot closed this as completed Jan 5, 2018
@matthew-dean
Copy link
Member

Maybe current-selector() isn't so bad. Although, to be clear, it would actually be current-selectors(). But that's still a bit verbose. I think I would be more in favor if we could think of a function name for "capturing &" that's more concise.

@rjgotten
Copy link
Contributor

rjgotten commented Jun 28, 2018

function name for "capturing &" that's more concise.

Just name it &(). It's conceptually nothing more than a getter for what's in & after all.
E.g.

.rule {
  @selectors : &();
}

@matthew-dean
Copy link
Member

🤔
Yeah, that should be fine. Any objections?

@calvinjuarez
Copy link
Member

calvinjuarez commented Jul 1, 2018

I'm gonna try to summarize to see if I'm understanding the proposed feature:

New function &() returns what & would output in the current context, allowing for this.

.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.

@calvinjuarez
Copy link
Member

calvinjuarez commented Jul 1, 2018

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 "&, but in the scope the variable was defined in", which would allow for this authoring style for a component (I'll call this Behavior A):

.component{ @this:&();
  /* default styles */
  @{this}_child {
  // ↑ The crucial difference: `@{this}` here behaves _like `&`_, **NOT** like `.component`
    /* child default styles */
  }
}

Then I could say "use @this everywhere instead of &".

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 & is clear in your vocabulary).

.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 &() to be dynamic, meaning essentially "&, but as if nested here instead of deeper", rather than returning a static "the value of & right now."

@matthew-dean
Copy link
Member

@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;
}

meaning essentially "&, but as if nested here instead of deeper", rather than returning a static "the value of & right now."

I really don't understand what this means.

@matthew-dean
Copy link
Member

Another way to think about this. This:

.component {
  @this: &()
}

Is the equivalent of writing:

.component {
  @this: .component;
}

@rjgotten
Copy link
Contributor

rjgotten commented Jul 1, 2018

@matthew-dean

Yes. But think about it through the lens of mixins, where &() would grab the mixin caller's selector context.

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 &() and work it into their output appropriately. E.g. the selector captured for the second and third rules could have the BEM syntax decomposed to obtain the base block class, which could be used to generate overrides for nested element selectors.

That is; it could be used to generate a selector like .my-button--wide > .my-button__text, without needing to pass in any selector names as parameters. Just from the callee selector context alone.


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.

@matthew-dean
Copy link
Member

@rjgotten

the mixins used there could read the class via &() and work it into their output appropriately. E.g. the selector captured for the second and third rules could have the BEM syntax decomposed to obtain the base block class, which could be used to generate overrides for nested element selectors.

Yep, I get it. It's probably most useful in mixins. I definitely get the utility of &() over using the direct selector name. My point was just to try to clarify the value of &() in the given example.

@matthew-dean
Copy link
Member

To go further, I think it's a good syntactic solution, and I would personally give a 👍 to moving forward with implementation of &(), if someone wanted to take it on.

@calvinjuarez
Copy link
Member

calvinjuarez commented Jul 7, 2018

@matthew-dean

Your examples are somewhat confusing because you're not writing your expected output

Whoops, apologies. I'll restate it properly. I feel like &() would be a stronger feature if the Less here compiled to the CSS below.

.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 @this:&(); behaves just like @this:.component; in this case, we're relegating this feature to utility only inside mixins, but I think it has more to offer.

@calvinjuarez
Copy link
Member

meaning essentially "&, but as if nested here instead of deeper", rather than returning a static "the value of & right now."

I really don't understand what this means.

It means I think that .thing{ & {} } and .thing{ @amp:&(); @{amp} {} } should produce the same output.

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 & or @{this}. You just say "Just use @{this} everywhere."

@calvinjuarez
Copy link
Member

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;
}

@matthew-dean
Copy link
Member

matthew-dean commented Jul 7, 2018

It means I think that .thing{ & {} } and .thing{ @amp:&(); @{amp} {} } should produce the same output.

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;
}

@matthew-dean
Copy link
Member

matthew-dean commented Jul 7, 2018

@calvinjuarez I guess I was confused because I think no one was suggesting something different than your example. &() would essentially be like this.selectors.toCSS() eval'd at that location (not really, but just for illustration.... actually that might be the quickest way to do it). And then inserting that string in other places to be re-eval'd as selectors.

@rjgotten
Copy link
Contributor

rjgotten commented Jul 7, 2018

@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.

@matthew-dean
Copy link
Member

matthew-dean commented Jul 7, 2018 via email

@calvinjuarez
Copy link
Member

calvinjuarez commented Jul 14, 2018

.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;
}

The extra .component is what I'm arguing against. I'm suggesting it should work like this:

.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.

@rjgotten
Copy link
Contributor

rjgotten commented Jul 14, 2018

I'm suggesting it should work like this:

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 & were specified and it disables the normal selector chaining that results from nesting.

If &() were to output a node type that makes it identifiable as an actual selector list, that behavior would be comparatively easy to achieve, I think.

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.

@matthew-dean
Copy link
Member

matthew-dean commented Jul 14, 2018 via email

@rjgotten
Copy link
Contributor

ooh... I actually like the &(...) thing...

@matthew-dean
Copy link
Member

matthew-dean commented Jul 14, 2018 via email

@matthew-dean
Copy link
Member

matthew-dean commented Jul 14, 2018 via email

@matthew-dean
Copy link
Member

matthew-dean commented Jul 15, 2018

Just to close this circle, here's where I mentioned altering & in-place with a function-like construct. - #1075 (comment)

So, I'd prefer if discussion about "how/whether to alter & inheritance" remains in the parent selectors thread, and this thread is about whether or not @var: &() is appropriate to capture the in-place & selector to a variable. Which, in my mind, still seems okay, despite the other thread. I'm not sure if there's an opportunity to do both or not.

@matthew-dean matthew-dean added this to the 4.0 milestone Oct 17, 2019
@matthew-dean matthew-dean modified the milestones: 4.0, 5.0 Dec 18, 2020
@cobaltt7
Copy link

cobaltt7 commented Jan 6, 2021

I'm trying to do this

.html, .css, .js, .php, .mysql, .jquery, .txt, .java {
	@html: '\f2d0';
	@css: '\f034';
	@js: '\f121';
	@php: '\f120';
	@mysql: '\f1c0';
	@jquery: '\f78c';
	@java: '\f11b';
	@txt: '\f15c';
	&:before {
		content+_: @&;
	}
}

but that won't work until this is implemented

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

No branches or pull requests

6 participants