less / less.js

Less. The dynamic stylesheet language.
http://lesscss.org
Apache License 2.0
17.02k stars 3.41k forks source link

Public variables on namespaces #1848

Closed Soviut closed 8 years ago

Soviut commented 10 years ago

I realize this isn't a new topic but it I think the time has come to consider implementing public variables on namespaces. Many prominent libraries for LESS (bootstrap, hat, etc.), have emerged, each with dozens of configuration variables which could very well overlap and conflict with each other.

Currently, namespaces support private variables, the only way to get at them is via mixins within the namespace and those mixins can then be used externally; Sort of like a closure:

#ns {
    @size: 10px;
    .box() {
        width: @size;
        height: @size;
    }
}

.icon {
    #ns > .box();
}

Which yields:

.icon {
    width: 10px;
    height: 10px;
}

However, I think it would be very handy to do the following:

#ns {
    @size: 10px;
}

.icon {
    width: #ns > @size;
    height: #ns > @size;
}

As well as update those variables within the namespace:

#ns > @size: 20px;
goto-bus-stop commented 10 years ago

:+1:

lukeapage commented 10 years ago

yes. I am not against them.

We could implement them as they used to be implemented when they were part of less. see #6 !!

lukeapage commented 10 years ago

though I'd like to see some thought about the syntax. possibly only allowing on #id not .id.

just throwing out some other syntaxes

@#ns>size // could work interpolated e.g. 
@{#ns>size}
#ns > @size
#ns>@size
#ns['size']
#ns[size]
@#ns[size]
#ns[@size]
goto-bus-stop commented 10 years ago

@{#ns>size} seems to make the most sense to me. Since it kinda has to work with interpolation, using the existing @{} would be the logical way to go… Perhaps @{#ns[size]} or similar could work too but it's not very pretty. (e; actually it's not even that bad.) #ns[@size] is pretty but it's hard to interpolate (.grid-#ns[@grid-size])

lukeapage commented 10 years ago

yes to b clear I'd suggest the first 2 depending on whether it was an interpolated format or not (e.g.

.grid-@{#ns>size} {
  @width: @#ns>size;
}

the grammer being @# recognised as a special case and extracting from # until > as a 'namespace' followed by the variable name.

matthew-dean commented 10 years ago

+1 to @{#ns>size}. Makes sense.

@width: @#ns>size looks potentially more problematic, since you're assuming no white space. I think @{ #ns > size } would be perfectly legal, and more readable, but suddenly gets weird here:

@width: @#ns  >  size;

Especially during interpolation:

.myselector .parent@#ns > childVar > .childEl

Soviut commented 10 years ago

The idea of prefixing the namespace with an @ symbol when dealing with a variable seems very strange to me. I personally think the following makes the most sense.

@width: #ns > @size;

You're referencing the namespace with # then using the > accessor to access the @size variable. Without the @ next to the variable I worry that the syntax gets confusing since it starts looking like an element selector.

lukeapage commented 10 years ago

The idea of prefixing the namespace with an @ symbol when dealing with a variable seems very strange to me. I personally think the following makes the most sense.

I agree but this means it doesn't work in interpolated cases unless you have 2 differing syntaxes.

I don't like @{#ns>size} everywhere because it puts curly brackets in even more places :(

matthew-dean commented 10 years ago

I don't think it necessarily puts @{} where they wouldn't be already. I actually think both of these could co-exist:

@width: #ns > @size;
@width: @{#ns>size};

The first, like @Soviut says, reads as "within the namespace #ns, reference the @size variable" (and would be semantically in tune with namespaced mixins). The second reads as "within a variable context, reference size from the #ns namespace" (and would be semantically in tune with interpolation).

However, I still feel like @width: #ns > @size may only work because it's written without any surrounding functions or other value data. Compare:

.myElement {
   #gradient > .horizontal(darken(#ns > @color, 10%), #ns > @color);
}
.myElement {
   #gradient > .horizontal(darken(@{#ns > color}, 10%), @{#ns > color});
}

Hmm... it's actually hard to say which feels better. But... much as I've said on other issues, it might still suggest both could be correct? (To summarize, I believe @{} and @ references for vars should be more interchangeable. We already have two syntaxes to reference vars depending on placement, so why not two ways to reference a namespaced var?)

Soviut commented 10 years ago

Keep in mind that we should be able to set/override variables within namespaces so we need the syntax to work with that as well.

#ns > @size: 20px;
matthew-dean commented 10 years ago

Keep in mind that we should be able to set/override variables within namespaces so we need the syntax to work with that as well.

Agreed. Otherwise libraries would wrap vars in namespaces, but you wouldn't be able to "configure" the library.

seven-phases-max commented 10 years ago

(crosslinking the topic where this feature and the syntax emerged before: #1357, just for reference).

seven-phases-max commented 10 years ago

Just a thought on #ns > @var vs. @#ns > var

... but this means it doesn't work in interpolated cases unless you have 2 differing syntaxes.

We may to not allow to use that in a selector interpolation at all. Just the same way as we don't allow any functions or arithmetic (or anything else except a simple variable) to be there. After all one can always write:

@var: #ns > @size;
.grid-@{var} {
    // ...
}

Yep, a bit restrictive and makes the selector interpolation case verbose, but if it makes the syntax less confusing in other situations... why not?

matthew-dean commented 10 years ago

I just had this come up on a project, where I really really needed to namespace my variables, but also wanted to expose them. My solution:

// core library
#namespace {
  .vars() {
    @base-color: red;
  }
}

// custom project
#namespace {
  .vars() {
    @base-color: blue;
  }
}

// core library implementation
.box {
  #namespace > .vars();
  color: @base-color;
}

I'm assuming that one can, by design, do something like the above? The downside, of course, is needing to "import" colors into every scope that needs them available. But, basically, by "setting" variables via a mixin, then calling the mixin name in a scope, it has the desired effect.

Which leads me to the idea that for setting variables, because of inheritance, it doesn't need a special "setting" format. It can be:

#ns {
  @var: this;
}

// later, override
#ns {
  @var: that;
}

//referencing:
.foo {
  bar: #ns > @var;  // only referencing via >, not setting, just like regular namespacing
}
matthew-dean commented 10 years ago

@seven-phases-max I think your idea is valid, or we could just opt to not switchup the syntax within the interpolation syntax.

#ns {
  @var: red;
}
.@{#ns > @var} {
  foo: bar;
}
.foo {
  bar: #ns > @var;
}

On the one hand, it's possibly one extra @, but at least everyone would know what the heck you were referring to.

seven-phases-max commented 10 years ago
.box {
  #namespace > .vars();
  color: @base-color;
}

I'm assuming that one can, by design, do something like the above?

I guess yes. I believe it was considered as something dangerous and unintended during -> 1.4.x move mostly because of various variable leaking issues emerged first and its general strangeness at those times. But now it's pretty much "standard" functionality and it's too late to prohibit this even if we'd want to.

seven-phases-max commented 10 years ago

@matthew-dean

It can be:

#ns {
  @var: this;
}

// later, override
#ns {
  @var: that;
}

Don't forget that first of all the #ns is a a plain CSS ruleset and a mixin too. Making their scope "open" is too breaking change... I think I won't be mistaken to assume that out there they (and we) have a lot of code like:

#something {
    @tmp: 2 + 2px;
    width: @tmp;
}

#something {
    @tmp: 2 + 3px;
    height: @tmp;
}

In this context the second part of the propose, i.e. #ns > @size: 20px; makes me worry too. It's pretty much new concept of scope intrusion (by now we have nothing that can change the scope internals from outside unless it's explicitly intended/written by the "scope author" via mixin parameters or inheriting parent scope variables). I guess we need some extra care and further analysis of possible consequences for that part.

matthew-dean commented 10 years ago

@seven-phases-max The scope wouldn't be open. It would still only live in the context of the local scope (sort of). I get what you're saying, but I would see namespaced overrides like this...

SCRAP THAT, AN IDEA JUST HAPPENED.

I had examples with >, but then it DID seem confusing, just as you pointed out. So then I started thinking about detached rulesets... So, detached rulesets, which just was merged in (I think) look like this:

@rules: {
  prop1: value1;
  prop2: value2;
}

I love that we now have detached rulesets. I was looking at those and marvelling at what we could do with them. At some point some time ago, looking at the new detached rulesets, I thought, "You know, it would be great if I could extract property values from the ruleset, or even use mixin guards against a specific property value. Maybe I'll suggest that as a feature request".

I just realized that the goals of name-spaced variables, and the idea of referencing properties of a ruleset can actually be the same goal.

@rules: {
  prop1: value1;
  prop2: value2;
}
#something {
  width: @rules[prop1];
}

(When I was thinking about a specific property of a set, it seemed similar to me as a specific attribute of an element, hence I went straight to [], the established CSS syntax.)

So, what I mean is: since detached rulesets already exist, we already have a kind of syntax to assign a set of property / values to a single variable. What's missing is property accessors. With them, we could also do:

.mymixin(@rules) when (@rules[prop1] = value1) { 
  // 
}

What's nice is that it feels a little cleaner in interpolation without dots or >.

.@{var[prop]} {
  this: "feels so much better";
}

For library overrides:

// Theoretical variables.less
@bootstrap: {
  base-color: red;
}
// My project:
@bootstrap[base-color]: blue;
// or, for multiple overrides, just redefine the entire variable, like you would with vars now:
@bootstrap: {
  base-color: blue;
}

What do you guys think of this?

EDIT: Note below that the "ruleset syntax for variables" idea should be attributed to @jonschlinkert, proposed in #1357.

seven-phases-max commented 10 years ago

That's just "properies are variables!" feature. A "detached ruleset" is still a normal ruleset (just not explicitly named), i.e. if we can write @rules[prop1] then why we can't just #ns[prop1]? (Note that I'm not against it in general, honestly I actually always wondered why properties were not made "readable" from the very beginning - But it's another big story... And stacking one advanced feature ("readable" properties) on top of another advanced feature ("detached rulesets") looks a bit confusing and overengineered. Though I see how "detached ruleset" can help to solve "open scope" problem - neat spot!).

matthew-dean commented 10 years ago

Yep, I realize it's double-purposing, but that's exactly the point. The detached ruleset syntax already closely matches a name-spaced variable set suggested by others. Look at this syntax from @jonschlinkert for variables (the proposal he made in #1357).

@palette: {
    primary: #0000E0;
    info: #02d7e1;
    success: #02e10c;
}

This is the exact syntax for detached rulesets. In other words, by solving a problem elsewhere, we've already implemented part 1 of his proposal. Now, it's just a matter of implementing part 2: retrieval. The only difference is that I'm suggesting [] makes a bit more sense to me, since semantically it's a little like attributes, (more so than dots which are classes and Less mixins). But otherwise the approach is the same. I think Jon had the right idea, and I'm noting that we're already very close to that idea.

Accessing properties of other rulesets (non-detached, i.e. not assigned to a variable) is interesting, but I don't think it necessarily follows that we have to do that. Detached rulesets could be a special case.

Soviut commented 10 years ago

Attribute accessors do seem to make a lot of sense. The syntax is potentially cleaner than the > notation as well.

seven-phases-max commented 10 years ago

Another problem is that inside @ruleset they are not variables:

@palette: {
    primary: #0000E0;
    success: lighten(?primary?, 50%); 
}

In that sense more classic stuff seems to be a bit more easy:

#palette { // can be detached as well
    @primary: #0000E0;
    @success: lighten(@primary, 50%); 
}

.usage {
    color: #pallete[success];
}

I don't know how much it is contradictory with selector attributes though...

matthew-dean commented 10 years ago

Another problem is that inside @ruleset they are not variables:

I feel like you may be creating problems that may not exist. For one, are variables supported in detached rulesets? On the off chance it is or we ever get there, what's wrong with this?

@palette: {
    @primary: #0000E0;
    success: lighten(@primary, 50%); 
}

Then, you could support derived properties:

@palette[@primary]: #330000; // updates both @primary and the derived "success" property

To me, you've made an argument FOR this syntax, since you could create calculated properties, which sounds awesome.

In your second example, the syntax is less graceful or intuitive for interpolated vars. (@{#palette[success]}?) The bigger problem to me, though, is we'd have two syntaxes that look very nearly identical, which sounds like a potential for lots of confusion.

matthew-dean commented 10 years ago

So, yes, what I'm proposing is that you could use properties AS namespaced variables, but, because they are rulesets, you could ALSO use variables as variables in the ruleset, if we wanted to (or already) permit variables in the ruleset.

@palette: { 
    @primary: #0000E0;
    @success: lighten(@primary, 50%); 
}
.usage {
    color: @pallete[@success];
}

This, however, wouldn't be completely necessary if we don't currently evaluate variables in detached rulesets.

lukeapage commented 10 years ago

So this was is very similar to the format that was in the original ruby less - read #6

"Setting" "constants" or overriding constants in a "namespace" is another matter. For me the biggest problem is that constants are constants.. changing the constants from one scope in another seems like a big ask for a compiler written around declarative statements.

The technical problem is that less evaluates in one pass and that constants can be introduced by a mixin call - if the contents of that mixin could be overridden at some point, is the value still a constant or does it start becoming a variable - e.g.

# pallette {
  color: red;
}
@normal-var: red;

.class {
  color: @normal-var;
  color2: #pallette[color];
  #palette[color]: green;
  @normal-var: blue;
}
.class2 {
  color: @normal-var;
  color2: #pallette[color];
}

So.. you would think (or I would think) that constants would work the same way.. but its hard to define

#pallette {
  @color: green; // or does this stay the original color?
}
.class {
  color: blue;
  color2: green; // assuming the later variable overrides 
}
.class2 {
  color: red;
  color2: green; // or was this overridden for that scope only so this should be red?
}

and I guess the use-case for setting constants is that without that it is just a grouping mechanism for variables. with setting it allows libraries to group and keep seperate their own constants.

an alternative mechanism for overriding a variable might be..

# pallette {
  @color: red;
}
.class {
  color: #pallette[color];
}
# palette { // must be at the root level
  @color: green;
}

and I guess I prefer that.

One reason I bring this up is that it effects what @matthew-dean is saying about detached rulesets - it would be pretty weird to only half override a detached ruleset assigned to a variable, like I did in the second example, where as it isn't weird to just look up the variable from the last matching namespace with that property in it.

seven-phases-max commented 10 years ago

@matthew-dean

On the off chance it is or we ever get there, what's wrong with this?

@palette: {
    @primary: #0000E0;
    success: lighten(@primary, 50%); 
}

Nothing. My point is that "detached rulesets" should not be treated as something special. Notice that "detached rulesets" feature introduced no special syntax except @var(). The @var: {...} part of this feature just plain combo of old variable definition + unused-selector {...} statements (the new thing with the DT is only an ability to assign one to another and not the syntax itself, the fact that it has the same syntax as suggested by @jonschlinkert in #1357 is no more than just a coincidence). So I just can't see why @ruleset[property] is better then #ruleset[property] (they are equal for me, in both the property is a plain CSS property having no special meaning and the @ symbol of the first is just standard part of the ruleset variable name just like of any other @variable).

Now here is the problem: if we treat a detached ruleset as some kind of new special namespace (so that only @dtns[property] is valid but no #ns[property] or #ns[@variable] and so on) we almost don't solve the original problem at all.

  1. @ruleset[property] syntax is limited to one level only (so we can't access one dt-namespace nested in another (e.g. this is what supposed to be like #ns > #ns2 > @var etc.).
  2. Normal namespaces can contain both variables and mixins (and so can the detached ruleset) but currently we don't have syntax to directly call a mixin defined within detached ruleset. I.e. #ns > .mixin but no @dtns() > .mixin. So by allowing only @dtns[property] we force ourselves to put variables and mixins into different namespaces. E.g.:

#ns {
    @var: 1;
    .mixin() {
        property: @var;
    }
}

// can call mixin directly:
#ns.mixin();
// but can't access its @var (without prior #ns();)

@dtns: {
    property: 2; // ok, it's a variable for the the outer space but has no use here.
    @var: 3;
    .mixin() {
        property: @var;
    }
}

// can access its property (like variable)
... @dtns[property]
// but can't its @var
// and even if we allow @dtns[@var]
// we still can't access mixin directly:
@ns ? .mixin();

See now? So that's my point: with @dtns[property] syntax we only introduce a new kind of namespace that has its own limitations and solve no problems of the existing namespaces. The fact that the @dtns[property] syntax looks more graceful than #ns[property] or #ns[@variable] does not help too much. (And if we allow thing like @dtns[@variable] this automatically cancels the argument that #ns[@variable] is less graceful to interpolation).

For one, are variables supported in detached rulesets?

Yes, everything that can be used in a standard ruleset can be used in a detached ruleset (because the dt-ruleset is a standard ruleset too except its "name"). So this code is currently valid (will be in 1.7.0):

@ruleset: {

    @variable: 1;
    property:  2;

    #selector {
        @variable: 3;
        property:  4;
    }

    .mixin(@v) {
        // ...
    }

    .some-mixin-call();

    @another-detached-ruleset: {
        @variable: 5;
        property:  6;
        // and everything of above is valid here too
        // yet again because this is just plain old Less ruleset
    };

    // etc.
};

// .....

.some-mixin-call() {}
.usage {@ruleset();}

So no, I'm not against syntax, I'm against treating the DR feature as a yet another new special kind of namespace (so that instead of one namespace abstraction with problems we'll have two namespace abstractions with problems :)

SomMeri commented 10 years ago

My favourite syntax for reading variables from namespaces is:

It is simple and seems to me to be the most intuitive, it is almost the same as calling mixins. I do not like @{#ns > variable} much, because we are not interpolating anything here and I do not see much advantage in additional {} in it.

The #ns > @size would also make it easy to extend the syntax to detached rulesets, if we decide so later on:

@detached: {
  @variable: value;
}

@space {
  property: @detached > @variable;
}

or even chain them for much longer:

@detached: {
  #namespace {
    @variable: value;
  }
}

@space {
  property: @detached > #namespace > @variable;
}
SomMeri commented 10 years ago

The biggest use case for namespace variable modification seems to be library configuration.

Going wild with detached rulesets, it would be possible to keep less as declarative language, variables as constants and have library configuration too:

// library
#library(@base-color: green; @base-size: 3) { //default values
  .mixin() {
    color: @base-color;
    size: @base-size;
  }
}

//custom project
@library: { #library(red); } //initialize library, use default size 

div {
  @library > .mixin(); // using mixins and everything from the library
}

The advantage is that then it would be possible to have two "copies" of the same library initialized with two different arguments:

// library as before
#library(@base-color: green; @base-size: 3) { //default values
  .mixin() {
    color: @base-color;
    size: @base-size;
  }
}

//custom project
@library-glowing: { #library(red); } //initialize library, use default size 
@library-gentle: { #library(blue); } //initialize library, use default size 

.section-with-errors div {
  @library-glowing > .mixin();
}

.section-with-stuff div {
  @library-gentle > .mixin();
}

Other then that, i like the the trick shown in this comment, it feels to be "lessy". If it would be combined with variables reading, then it would be possible to do following:

// library
.namespaceConfiguration() {
  @base-color: 1;
}

#namespace {
  .namespaceConfiguration();
  color: @base-color;
}

// core library implementation
.box {
  #namespace >  @base-color;
}

// custom project
.namespaceConfiguration() {
  @base-color: 2;
}
seven-phases-max commented 10 years ago

library configuration

btw. we already have it:

// library
#library(@base-color: green; @base-size: 3) { // default values
    .mixin() {
        color: @base-color;
        size: @base-size;
    }
}

// custom project
#red-library { #library(red); } // initialize library, use default size

div {
    #red-library > .mixin(); // using mixins and everything from the library
}

(Not counting it has #1291 issue which the detached version will inherit too, i.e. the second example won't work until we fix this issue).

Update:

Oh, wait... the #1291 seems to be fixed in the current master... wonderful (I was really missing this "multiple configs" feature!)

matthew-dean commented 10 years ago

Hmmm....

I've been reading and re-reading these latest posts. I feel like we've got momentum towards the right idea, but a few questions:

First this example:

#ns {
    @var: 1;
    .mixin() {
        property: @var;
    }
}

// can call mixin directly:
#ns.mixin();
// but can't access its @var (without prior #ns();)

First, why is that? Why doesn't a mixin that executes in a scope not have access to the variables in its parent? That seems to be counter to some examples of scoped variables, and to me, if that weren't the case, it would eliminate most of my use cases of needing namespaced variables, since I could just place scoped variables that the mixins in my namespace had access to. (However, I realize that doesn't necessarily serve all use cases, I'm just curious what the philosophy is about why Less works this way.)

So, I'm hearing the feedback that detached rulesets have more complexity than can make them really work in the way I proposed. I think we're all trying to solve the issues of:

Now, to address some of what @lukeapage said:

changing the constants from one scope in another seems like a big ask for a compiler written around declarative statements.

The technical problem is that less evaluates in one pass and that constants can be introduced by a mixin call - if the contents of that mixin could be overridden at some point, is the value still a constant or does it start becoming a variable - e.g.

They're not really being overridden, at least no more so than variables now are being overridden if you declare the same one twice in the same scope. In other words, it would be intuitive to some if Less worked like this:

#namespace {
  @mycolor: red;
  color: @mycolor; //blue -- #namespace == #namespace in scope
}
#namespace {
  @mycolor: blue;
}
.box {
  color: #namespace > @mycolor;  // blue
}

That may seem counter-intuitive to some at first glance. However, it would be treating values the same way a browser would see CSS values.

#namespace {
  color: red; //nope, it's blue, no matter what you see here, sorry
}
#namespace {
  color: blue;
}

I'm not saying that's easy to implement. But it does work like CSS. And, to me, this also is intuitive.

#namespace {
  @mycolor: red;
  .mixin() {
    color: @mycolor;  // red, because .mixin() and @mycolor are in the same namespace
  }
}

The fact that Less "skips" the scope of a mixin is strange (if called from outside the namespace).

If we can't solve variable namespacing issues, and reworking detached rulesets is too problematic, then we're probably left with a unique syntax, perhaps something like CSS @-rule definitions.

@palette {
    primary: #0000E0;
    info: #02d7e1;
    success: #02e10c;
}

In other words, we could define a syntax for variables where we don't have to address these other problems. As @seven-phases-max said, we'll then be left with a scenario of two methods of variable name-spacing (sort of), although this variable block could exist in a namespaced block (with the same local scope as other vars).

Or we address some of the issues of variable visibility within namespaces, and name-spaced variable visibility to the global scope (and the ability to "override" [really, cascade] by declaring an "equal" scope). It seems we either define a new variable declaration syntax, or we fix name-spacing, each with it's own advantages and disadvantages.

seven-phases-max commented 10 years ago

Why doesn't a mixin that executes in a scope not have access to the variables in its parent?

Maybe I was a bit unclear, the mixins themselves see the variables of its parent namespace, who can't see the variable is the caller itself e.g.:

#ns {
    @var: 1;
    .mixin() {
        property: @var;
    }
}

.x {
    #ns.mixin(); // OK
}

// but
.y {
    width: #ns ? @var; // i.e. it's just what the core of this proposal is
}

// the current workaround is of course:
.z {
    #ns(); 
    width: @var;
    // but that way we pollute this scope with everything defined in #ns
    // (for simple cases it's ok but becomes a pain for complex cases, especially if #ns contains 
    // some mixins interfering with another mixins already brought to this scope from elsewhere)
} 
matthew-dean commented 10 years ago

the mixins themselves see the variables of its parent namespace

Huh. For some reason I thought I encountered an issue that echoed what you said. That I called a mixin within a namespace, and it didn't seem to access the parent var.

OR perhaps I'm confusing this with the fact that I had no way to redefine those vars, meaning that I was left with something like @SomMeri's pattern of defining multiple mixins with the same name / namespace, and then "importing" (calling) that mixin within every mixin where I wanted access to those variables.

So, maybe we're closer than I think?

seven-phases-max commented 10 years ago

Huh. For some reason I thought I encountered an issue that echoed what you said.

Most likely it was very similar example for "parametric namespaces" (or title those as "parent mixins") - e.g.:

#ns() {
    @var: 1;
    .mixin() {
        property: @var;
    }
}

.x {
    #ns.mixin(); // Error: no @var defined
}

But that's a bit another story.

SomMeri commented 10 years ago

@matthew-dean There is a bug #1316 (in 1.6.3 still is). Mixin called from namespace should see variables in the namespace, but is not.

matthew-dean commented 10 years ago

@SomMeri Thanks. It's hard to find these bug references sometimes.

So, then, it's possible that some of the variable issues and feature requests are also tack-on issues to some variable scoping bugs? (Of course, probably some new features needed too.)

matthew-dean commented 10 years ago

Okay, so I didn't mean to derail from namespacing, if we can make namespacing work. It would be good to keep up momentum, because I think this is a much-needed feature for the health of Less. It's not so much for the average developer as the larger groups of developers who write the things that help the average developer use Less (and integrate with things like Google Chrome).

I think for clarity, these are fine:

.@{#ns > @selectorVar} {
  property: #ns > @colorVar;
}

Why the extra @ for interpolation? I still would like to explore (in another issue) later the idea of accessing properties, or guarding against them. We set ourselves up for a potential naming conflict by dropping an @ just because it's not necessarily needed at this very moment.

But beyond that, specificity just maintains more clarity. It follows the Less convention everywhere else of referencing the thing exactly as its name appears when defining.

matthew-dean commented 10 years ago

Oh, and of course, the string form:

.box:after {
  content: "@{#ns > @content}";
}
matthew-dean commented 9 years ago

@seven-phases-max Rereading, it makes sense where you say we don't need a special interpolated form for this, and a person can just assign it to a variable if they want to use it that way.

One thing never discussed which lends itself to the > vs brackets:

#ns {
  .inner-ns {
    @foo: bar;
  }
}
.block {
  property: #ns > .inner-ns > @foo;
}
//vs
.block {
  property: #ns[.inner-ns[@foo]];
}

The first one seems more readable.

It's unfortunate we didn't also reserve the syntax that became detached rulesets, because we also could have just defined a "namespaced" variable form, which would probably be a lot easier to implement.

calvinjuarez commented 9 years ago

So if you're borrowing the syntax from mixin namespacing, does that mean carrying over all of the flexibility of that syntax?

#ns {
  @foo: bar;
  .mixin() {
    whatever: stuff;
  }
}

.rule {
  #ns > .mixin(); // allowed
  #ns>.mixin();   // allowed
  #ns .mixin();   // allowed
  #ns.mixin();    // allowed (this is my preference)
  // ... so ...
  property: #ns > @foo; // allowed
  property: #ns>@foo;   // allowed
  property: #ns @foo;   // allowed?
  property: #ns@foo;    // allowed? (this is probably what I'd use, if given the option)
}

I don't think you should borrow a syntax half-heartedly. My 2¢.


Edit/Also: (re: @matthew-dean)

In the backet syntax, I would think traversing namespaces should work like mixins, so that accessing variables would be the brackets' only job.

#ns {
  .inner-ns {
    @foo: bar;
  }
}
.block {
  property: #ns > .inner-ns[@foo];
  // or any these
  property: #ns>.inner-ns[@foo];
  property: #ns .inner-ns[@foo];
  property: #ns.inner-ns[@foo];
}
seven-phases-max commented 9 years ago

Well, as a user I'm not a big fan of > there since it pretends to be a HTML tree pointer while pointing to some Less tree entity (ah, I guess once I wrote of that in brief here). So ideally I would prefer the most primitive #ns@var form.

But I also know that in such cases whitespaces are not actually counted so #abc@var is already valid statement and evaluated as #aabbcc @var. Taking this into account, I'm afraid it should have a required operator (either > or []) there.

Though as always I can't stop complaining more. > @var syntax has problems in guards, e.g.

.mixin when (#abc > @var) { // `color > var` or `abc-namespaced var`?
    // ...
}

[] is a bit more safe probably (not counting that the same when story applies to a second level ns nesting, e.g. mixin when (#abc > .inner[@foo]) // eh?) since it directly conflicts only with attribute selectors (where we can't use variables w/o interpolation anyway)... Though to be honest, recalling all these "Nooooooo!" in earlier issues when someone proposed to use [] for other features, #ns > .inner-ns[@foo] will look like inglorious capitulation to JLesScript :(


But aside from above, yes, for either variant I believe it should be

#ns > .inner-ns > @foo

or

#ns > .inner-ns[@foo]

(maybe retaining optional combinators for the namespace part even if it was an unintentional defect, so that for the above "when" problem .mixin when (#ns.inner-ns[@foo] > 2) {} form becomes even the preferred one).

seven-phases-max commented 9 years ago

+ one more important note. In features like #2481 (and initially raised in https://github.com/less/less.js/pull/1648#issuecomment-33969612) it was considered that using selector syntax in value statements directly is not always a good idea (or at least quite burdening thing for the parser).. so there it was purposed to use

something: selector(#ns > .inner);

(or for syntactic sugar)

something: $(#ns > .inner);

like things... This obviously directly conflicts with above syntax. The features are almost not related but for the parser part it has to be decided if selector syntax (at least its simplest forms incl. only #.> chars) is or isn't allowed in value statements...

matthew-dean commented 9 years ago

Okay, so it sounds like there are some competing proposals, and that's partially why I was revisiting this one, because I'm not sure how the syntax would fit into the broader picture.

For instance, this:

something: $(#ns > .inner);

...somewhat competes with #2433, where we had fairly good consensus on property accessors. However, we did talk about the possibility of ${} being a kind of "lookup syntax".

In fact, what if.... similar to #2433, we did something like:

something: ${#ns > .inner};

That way it doesn't look like a function, but more like an accessor. So for this issue, rather than just wrapping the var in brackets, do it more like:

something: ${name};  // property (or for syntactic sugar, can be written $name in this simple case)
something: ${#ns > name }; // namespaced property
something: ${#ns > @var }; // namespaced var or
something: ${#ns>@var }; // namespaced var or
something: ${#ns @var }; // namespaced var or
something: ${#ns@var }; // namespaced var

That way, as @calvinjuarez notes, we can carry over all the mixin syntax, without having ambiguous whitespace. We could even address namespaced property interpolation, brought up in this issue:

${#ns @property-prefix}-prop: value;

It also more elegantly solves the problem of ambiguity and too much nesting (like #ns[#ns2[#ns3[val]]] with deeper namespacing)

something: ${#ns > #ns2 > #ns3 > @val};

Finally, it would more closely align this issue and the other issues listed. (Although, frankly, issue #2481 seems like kind of a non-issue - but feasibly, if ${} returns neither a property nor variable, it could return the selector. Not sure if that's needed though.)

I think this is more elegant because the usage can be more aligned with variable interpolation (and with namespaced mixins), instead of having to have a very different syntax for that usage. What do you think?

seven-phases-max commented 9 years ago

something: $(#ns > .inner > @var);

Maybe, though I'd prefer to avoid such heavy syntax. I have some prototype plugin that is capable of doing something like v(ns inner var) (or optionally v("#ns.inner@var")), thus more heavy syntax -> more favour for a shorter non-core Less dialect syntax to propagate...

matthew-dean commented 9 years ago

Wait, how do you find ${} heavy and not v()? They're the same number of characters! And only the first one could do interpolation.

seven-phases-max commented 9 years ago

They're the same number of characters!

Yes, but one of them I can already use right now ;)

matthew-dean commented 9 years ago

Yes, but one of them I can already use right now ;)

lol, ok, buuuut.... even if this wasn't core, I wouldn't mind this being an "official" plugin with a syntax closer to returning variables, rather than a selector() function (which wouldn't work for this issue), or a less intutive v() function.

seven-phases-max commented 9 years ago

@matthew-dean

I guess what I really mean somewhere in between lines that while ${#ns > #ns2 > #ns3 > @val} of course is definitely well-formed and less conflicting syntax... We probably still can leave some room for just #ns2 > #ns3 > @val for one who implements it to have more space to decide...

matthew-dean commented 9 years ago

Okay, fair enough. We could make it optional (and use for interpolation), similar to vars? That way, if someone wanted, they could write ultra-compact syntax like:

something: #ns2#ns3@val
matthew-dean commented 9 years ago

Funnily enough, just yesterday I ran into a situation where I wanted to use both namespaced variables and accessing the name of the "current" selector. And whilst trying different things, I realized I could have solved my problem with possibly accessing the property or name of a detached ruleset. Or even running an "each" on a list. So, essentially, everything I thought would solve my problem has been discussed but not implemented.

calvinjuarez commented 9 years ago

I really like ${}. I'm very into one syntax for both property & variable accessors. Could it theoretically work with a detached ruleset (${@ruleset @var}) or is that too much?

Also a side note, for accessing a property, I think ${#ns>property} & ${#ns property} should be allowed along with ${#ns > property} (again, for consistency). (${#nsproperty} is obviously impractical, so that can be thrown out.)

Do you think people will try/want #ns$property (because of #ns@var & $property)? Too much?