Less.js: Saving selector to a variable

Created on 20 Apr 2017  ·  50Comments  ·  Source: less/less.js

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
  }
}
feature request medium priority needs decision research needed

All 50 comments

Curiously I was absolutely sure such request already exists. Apparently it's not.
(Though obviously the idea did appear in many tickets before: #1174, https://github.com/less/less.js/issues/1075#issuecomment-16891103 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).

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.

@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! :).

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

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

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?

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 https://github.com/less/less-meta/issues/16#issuecomment-292679320 (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
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.

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():
    less: 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).

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

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.

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

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

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.

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

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.

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?

Ah ok.

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

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.

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

Manifold joys of open source projects documentation...

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.

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

Removed the "stale" label. This is still a good issue to explore.

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.

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 : &();
}

🤔
Yeah, that should be fine. Any objections?

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.

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

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

Another way to think about this. This:

.component {
  @this: &()
}

Is the equivalent of writing:

.component {
  @this: .component;
}

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

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

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.

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

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

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

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

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

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

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 notifications@github.com 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.

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

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

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 &(...) thing...

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 & in-place with a function-like construct. - https://github.com/less/less.js/issues/1075#issuecomment-397697714

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.

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

Was this page helpful?
0 / 5 - 0 ratings