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

Spec for the @at-root directive #774

Closed
chriseppstein opened this Issue May 28, 2013 · 25 comments

Comments

Projects
None yet
8 participants
@chriseppstein
Member

chriseppstein commented May 28, 2013

I'd like to nail down the exact spec for how @at-root will work.

So here's some test cases:

inline selector mode

.foo { @at-root .bar { color: gray; } }
.bar { color: gray; }

& still works when not in script mode

.foo { @at-root .bar & { color: gray; } }
.bar .foo { color: gray; }

& works when in script mode

.foo { @at-root .bar #{&} { color: gray; } }
.bar .foo { color: gray; }

@at-root can take a block

.foo { @at-root { .bar #{&} { color: gray; } } }
.bar .foo { color: gray; }

@at-root blocks only affect the nearest child selectors.

.foo { @at-root { .bar { .baz { color: gray; } } } }
.bar .baz { color: gray; }

@at-root doesn't remove selectors from directives by default.

@media print {
  @supports ( transform-origin: 5% 5% ) {
    .foo {
      @at-root {
        .bar #{&} {
          color: gray; } } } } }
@media print { @supports ( transform-origin: 5% 5% ) { .bar .foo { color: gray; } } }

@at-root in block form can remove a selector from all directives.

@media print {
  @supports ( transform-origin: 5% 5% ) {
    .foo {
      @at-root without-directives {
        .bar #{&} {
          color: gray; } } } } }
.bar .foo { color: gray; }

@at-root in block form can remove a selector from specific directives.

Media:

@media print {
  @supports ( transform-origin: 5% 5% ) {
    .foo {
      @at-root without-media {
        .bar #{&} {
          color: gray; } } } } }
@supports ( transform-origin: 5% 5% ) { .bar .foo { color: gray; } }

Supports:

@media print {
  @supports ( transform-origin: 5% 5% ) {
    .foo {
      @at-root without-supports {
        .bar #{&} {
          color: gray; } } } } }
@media print { .bar .foo { color: gray; } }

Several directives removed:

@media print {
  @supports ( transform-origin: 5% 5% ) {
    .foo {
      @at-root without-supports, without-media {
        .bar #{&} {
          color: gray; } } } } }
.bar .foo { color: gray; }

Selector inheritance uses the resulting selector and directive context.

%outside-the-media-block { color: gray }
@media print {
  @supports ( transform-origin: 5% 5% ) {
    .foo {
      @at-root without-directives {
        .bar #{&} {
          // not an error because the directives were removed.
          @extend %outside-the-media-block;  } } } } }
.bar .foo { color: gray; }

In theory we can make without-xxx be agnostic of directive type, but this may have issues with prefixed directives. Or we can have a whitelist of directive names.

The cases around directive removal will solve a big problem with the limitations around using @extend within media blocks. Specifically, this allows you to extend a selector via a mixin for all media contexts instead of just within the current media block.

@nex3

This comment has been minimized.

Show comment
Hide comment
@nex3

nex3 May 28, 2013

Contributor

@at-root without-whatever is syntactically ambiguous with the @at-root <selector> syntax. If we're going to add flags like that, I think we'd need to find a better syntax for it. One possibility is using the !important flag syntax, as in @at-root !without-supports.

Contributor

nex3 commented May 28, 2013

@at-root without-whatever is syntactically ambiguous with the @at-root <selector> syntax. If we're going to add flags like that, I think we'd need to find a better syntax for it. One possibility is using the !important flag syntax, as in @at-root !without-supports.

@chriseppstein

This comment has been minimized.

Show comment
Hide comment
@chriseppstein

chriseppstein May 28, 2013

Member

@nex3 it is semantically ambiguous, but not syntactically.

Member

chriseppstein commented May 28, 2013

@nex3 it is semantically ambiguous, but not syntactically.

@chriseppstein

This comment has been minimized.

Show comment
Hide comment
@chriseppstein

chriseppstein May 28, 2013

Member

That is, it would be a semantic conflict if there was ever a html element added named without-xxx. But this is just a selector sequence at parse time.

Member

chriseppstein commented May 28, 2013

That is, it would be a semantic conflict if there was ever a html element added named without-xxx. But this is just a selector sequence at parse time.

@chriseppstein

This comment has been minimized.

Show comment
Hide comment
@chriseppstein

chriseppstein May 28, 2013

Member

Another test case: Make sure interpolation works in @at-root.

Member

chriseppstein commented May 28, 2013

Another test case: Make sure interpolation works in @at-root.

@Hannes-III

This comment has been minimized.

Show comment
Hide comment
@Hannes-III

Hannes-III May 29, 2013

Quite complex but cool! Why not use

@at-root (without:supports media){ ... }

Like the Syntax of the other at-rules?

Hannes-III commented May 29, 2013

Quite complex but cool! Why not use

@at-root (without:supports media){ ... }

Like the Syntax of the other at-rules?

@chriseppstein

This comment has been minimized.

Show comment
Hide comment
@chriseppstein

chriseppstein May 29, 2013

Member

@Hannes-III I like this.

Member

chriseppstein commented May 29, 2013

@Hannes-III I like this.

@chriseppstein

This comment has been minimized.

Show comment
Hide comment
@chriseppstein

chriseppstein May 29, 2013

Member

Another test case: ensure that properties are disallowed within a block-style @at-root.

Member

chriseppstein commented May 29, 2013

Another test case: ensure that properties are disallowed within a block-style @at-root.

@nex3

This comment has been minimized.

Show comment
Hide comment
@nex3

nex3 May 29, 2013

Contributor

@nex3 it is semantically ambiguous, but not syntactically.

That is, it would be a semantic conflict if there was ever a html element added named without-xxx. But this is just a selector sequence at parse time.

Whatever you want to call it, I don't want to hijack some specific valid selector syntax to have a special meaning in this context.

Contributor

nex3 commented May 29, 2013

@nex3 it is semantically ambiguous, but not syntactically.

That is, it would be a semantic conflict if there was ever a html element added named without-xxx. But this is just a selector sequence at parse time.

Whatever you want to call it, I don't want to hijack some specific valid selector syntax to have a special meaning in this context.

@robwierzbowski

This comment has been minimized.

Show comment
Hide comment
@robwierzbowski

robwierzbowski May 31, 2013

Contributor

I understand this idea is already pretty solid, so please take this semi-rhetorically.

With the block and flag additions to @at-root you're really adding a transform selector directive. Calling it something like @selector (at-root) (without: media) would keep the directive abstract and open for any additional selector transforming enhancements in future versions.

Contributor

robwierzbowski commented May 31, 2013

I understand this idea is already pretty solid, so please take this semi-rhetorically.

With the block and flag additions to @at-root you're really adding a transform selector directive. Calling it something like @selector (at-root) (without: media) would keep the directive abstract and open for any additional selector transforming enhancements in future versions.

@robwierzbowski

This comment has been minimized.

Show comment
Hide comment
@robwierzbowski

robwierzbowski May 31, 2013

Contributor

There are plenty of use cases for stripping @media and @supports without putting a selector at-root, but they're already possible with mixins. Being able to apply without on its own would simplify things though.

Contributor

robwierzbowski commented May 31, 2013

There are plenty of use cases for stripping @media and @supports without putting a selector at-root, but they're already possible with mixins. Being able to apply without on its own would simplify things though.

@chriseppstein

This comment has been minimized.

Show comment
Hide comment
@chriseppstein

chriseppstein May 31, 2013

Member

There are plenty of use cases for stripping @media and @supports without putting a selector at-root

That's a good point. But I'm not sure it's common enough to have completely different behaviors. might as well have several directives at that point.

Member

chriseppstein commented May 31, 2013

There are plenty of use cases for stripping @media and @supports without putting a selector at-root

That's a good point. But I'm not sure it's common enough to have completely different behaviors. might as well have several directives at that point.

@robwierzbowski

This comment has been minimized.

Show comment
Hide comment
@robwierzbowski

robwierzbowski May 31, 2013

Contributor

Either a single directive with multiple flags or multiple nestable directives works equally well for me, as long as the choice is consistant. I think it would be confusing if there's @at-root (without: media), @without-media, and @future-selector-transform (at-root).

I prefer single directives/functions/mixins with flags, but I know you're a big fan of breaking things out into descriptive single use pieces :).

Contributor

robwierzbowski commented May 31, 2013

Either a single directive with multiple flags or multiple nestable directives works equally well for me, as long as the choice is consistant. I think it would be confusing if there's @at-root (without: media), @without-media, and @future-selector-transform (at-root).

I prefer single directives/functions/mixins with flags, but I know you're a big fan of breaking things out into descriptive single use pieces :).

@nex3

This comment has been minimized.

Show comment
Hide comment
@nex3

nex3 May 31, 2013

Contributor

I like the consistent syntax of @robwierzbowski's suggestion, but I don't like how verbose it makes the simple cases. What used to be @at-root #{&}-foo { ... } becomes @selector (without: parent) { #{&}-foo { ... } }, which is a lot more burdensome to read and write.

Contributor

nex3 commented May 31, 2013

I like the consistent syntax of @robwierzbowski's suggestion, but I don't like how verbose it makes the simple cases. What used to be @at-root #{&}-foo { ... } becomes @selector (without: parent) { #{&}-foo { ... } }, which is a lot more burdensome to read and write.

@robwierzbowski

This comment has been minimized.

Show comment
Hide comment
@robwierzbowski

robwierzbowski Jun 1, 2013

Contributor

True. The all directive pattern would look like:

...selectors... {
  @at-root {
    @without-media {
      ...rules... }
    @future-selector-transformer {
      ...rules... } }

which I like too, now that I wrote it out.

Contributor

robwierzbowski commented Jun 1, 2013

True. The all directive pattern would look like:

...selectors... {
  @at-root {
    @without-media {
      ...rules... }
    @future-selector-transformer {
      ...rules... } }

which I like too, now that I wrote it out.

@nex3

This comment has been minimized.

Show comment
Hide comment
@nex3

nex3 Jun 10, 2013

Contributor

I'm also not a big fan of adding a bunch of new directives for this. I'd like to find a way to do this with a single directive that can be configured to address the needs of all the use cases, but is easiest to use in the most common case.

One possibility would be to have @at-root default to removing all parents, be they directives or selectors, then be configurable using (with: ...) or (without: ...). This is very consistent, but it has the notable downside that @at-root #{&} foo means something different than & foo.

This suggests a second possibility: removing the parent selector but not any parent directives, while still being configurable. This fits the common case better, but is less consistent for more complex cases.

Finally, a much more radical possibility is to start thinking about bubblable directives like @media and @support as part of the parent selector. Thus something like .foo { @media screen { .bar { ... } } } would notionally have the selector .foo (@media screen) .bar. Perhaps that could even be writable by the user. Then we could use the first possibility I listed as the semantics for @at-root, and have @at-root #{&} foo resolve to .foo (@media screen) .bar foo, this giving it the same semantics as & foo. This would probably make selectors substantially more difficult to manipulate via SassScript, though, and may have other complicating factors as well.

Contributor

nex3 commented Jun 10, 2013

I'm also not a big fan of adding a bunch of new directives for this. I'd like to find a way to do this with a single directive that can be configured to address the needs of all the use cases, but is easiest to use in the most common case.

One possibility would be to have @at-root default to removing all parents, be they directives or selectors, then be configurable using (with: ...) or (without: ...). This is very consistent, but it has the notable downside that @at-root #{&} foo means something different than & foo.

This suggests a second possibility: removing the parent selector but not any parent directives, while still being configurable. This fits the common case better, but is less consistent for more complex cases.

Finally, a much more radical possibility is to start thinking about bubblable directives like @media and @support as part of the parent selector. Thus something like .foo { @media screen { .bar { ... } } } would notionally have the selector .foo (@media screen) .bar. Perhaps that could even be writable by the user. Then we could use the first possibility I listed as the semantics for @at-root, and have @at-root #{&} foo resolve to .foo (@media screen) .bar foo, this giving it the same semantics as & foo. This would probably make selectors substantially more difficult to manipulate via SassScript, though, and may have other complicating factors as well.

@chriseppstein

This comment has been minimized.

Show comment
Hide comment
@chriseppstein

chriseppstein Jun 11, 2013

Member

I'm opposed to making the default behavior of @at-root be to remove directive context. Such a behavior would ruin the primary use case of using @at-root to use & in SassScript. If that means we need two directives with different names, then that's what we need.

Regarding making directives part of &. I'm glad you're thinking outside the box, but I do not like this idea. I'd much rather introduce functions like current-media-query(), current-support-condition() and maybe some helper functions for working with the results. If we're going to do expose so much context, we should also expose current-property() as was mentioned in #727.

Member

chriseppstein commented Jun 11, 2013

I'm opposed to making the default behavior of @at-root be to remove directive context. Such a behavior would ruin the primary use case of using @at-root to use & in SassScript. If that means we need two directives with different names, then that's what we need.

Regarding making directives part of &. I'm glad you're thinking outside the box, but I do not like this idea. I'd much rather introduce functions like current-media-query(), current-support-condition() and maybe some helper functions for working with the results. If we're going to do expose so much context, we should also expose current-property() as was mentioned in #727.

@nex3

This comment has been minimized.

Show comment
Hide comment
@nex3

nex3 Jun 14, 2013

Contributor

I'm opposed to making the default behavior of @at-root be to remove directive context. Such a behavior would ruin the primary use case of using @at-root to use & in SassScript. If that means we need two directives with different names, then that's what we need.

I agree that it's important that the default behavior of @at-root should support the primary use-case. However, I'd very much like to avoid additional directives. I'd much rather add options to at-root (the second possibility I listed above).

Regarding making directives part of &. I'm glad you're thinking outside the box, but I do not like this idea.

I'm by no means wedded to it, but I would like to understand more about why you dislike it.

I'd much rather introduce functions like current-media-query(), current-support-condition() and maybe some helper functions for working with the results.

I'm not a huge fan of adding a separate function for getting each different type of context. Regardless, this is something that will likely not happen for a while; without being necessary for @at-root, there's no pressing need to give access to parent directives.

If we're going to do expose so much context, we should also expose current-property() as was mentioned in #727.

This conclusion doesn't follow. Getting block-level parent context is very different than accessing a representation of a property that's actively being defined.

Contributor

nex3 commented Jun 14, 2013

I'm opposed to making the default behavior of @at-root be to remove directive context. Such a behavior would ruin the primary use case of using @at-root to use & in SassScript. If that means we need two directives with different names, then that's what we need.

I agree that it's important that the default behavior of @at-root should support the primary use-case. However, I'd very much like to avoid additional directives. I'd much rather add options to at-root (the second possibility I listed above).

Regarding making directives part of &. I'm glad you're thinking outside the box, but I do not like this idea.

I'm by no means wedded to it, but I would like to understand more about why you dislike it.

I'd much rather introduce functions like current-media-query(), current-support-condition() and maybe some helper functions for working with the results.

I'm not a huge fan of adding a separate function for getting each different type of context. Regardless, this is something that will likely not happen for a while; without being necessary for @at-root, there's no pressing need to give access to parent directives.

If we're going to do expose so much context, we should also expose current-property() as was mentioned in #727.

This conclusion doesn't follow. Getting block-level parent context is very different than accessing a representation of a property that's actively being defined.

@chriseppstein

This comment has been minimized.

Show comment
Hide comment
@chriseppstein

chriseppstein Jun 19, 2013

Member

However, I'd very much like to avoid additional directives.

I agree with you that the second option is the best so far, it was also what was already proposed 😄

I would like to understand more about why you dislike it.

Although it doesn't seem to have any backwards compatiblity issue at this time, it fundamentally changes (what I think is) the mental model of &. As we've agreed, & and @at-root #{&} should be essentially the same, but the change you're proposing would make .foo #{&} create illegal output unless, as you point out, we want to create a syntax for embedding directives within selectors which I'm very reticent to do but I asked on twitter about it to see what others think.

I'm not a huge fan of adding a separate function for getting each different type of context.

Obviously, alternative forms of the same concept (using a function to access non-selector context) are welcome and encouraged.

Getting block-level parent context is very different than accessing a representation of a property that's actively being defined.

I don't see a fundamental difference. Context is context. Please explain.

Member

chriseppstein commented Jun 19, 2013

However, I'd very much like to avoid additional directives.

I agree with you that the second option is the best so far, it was also what was already proposed 😄

I would like to understand more about why you dislike it.

Although it doesn't seem to have any backwards compatiblity issue at this time, it fundamentally changes (what I think is) the mental model of &. As we've agreed, & and @at-root #{&} should be essentially the same, but the change you're proposing would make .foo #{&} create illegal output unless, as you point out, we want to create a syntax for embedding directives within selectors which I'm very reticent to do but I asked on twitter about it to see what others think.

I'm not a huge fan of adding a separate function for getting each different type of context.

Obviously, alternative forms of the same concept (using a function to access non-selector context) are welcome and encouraged.

Getting block-level parent context is very different than accessing a representation of a property that's actively being defined.

I don't see a fundamental difference. Context is context. Please explain.

@nex3

This comment has been minimized.

Show comment
Hide comment
@nex3

nex3 Jun 19, 2013

Contributor

I don't see a fundamental difference. Context is context. Please explain.

The block-level context already exists when the SassScript is getting run; property values is being defined while the SassScript accessing it is run. Accessing block-level context preserves the invariant that $var: expression; property: $var works the same as property: expression; accessing property values does not. Block-level context is conceptually treated by Sass and CSS as a context, in that it provides meaning to the statements nested beneath it; property values are treated as values, not context.

Contributor

nex3 commented Jun 19, 2013

I don't see a fundamental difference. Context is context. Please explain.

The block-level context already exists when the SassScript is getting run; property values is being defined while the SassScript accessing it is run. Accessing block-level context preserves the invariant that $var: expression; property: $var works the same as property: expression; accessing property values does not. Block-level context is conceptually treated by Sass and CSS as a context, in that it provides meaning to the statements nested beneath it; property values are treated as values, not context.

@chriseppstein

This comment has been minimized.

Show comment
Hide comment
@chriseppstein

chriseppstein Jun 19, 2013

Member

Accessing block-level context preserves the invariant

Gotcha. Seems worth preserving without a very compelling use case -- which I've not see to-date.

Member

chriseppstein commented Jun 19, 2013

Accessing block-level context preserves the invariant

Gotcha. Seems worth preserving without a very compelling use case -- which I've not see to-date.

@JackCA

This comment has been minimized.

Show comment
Hide comment
@JackCA

JackCA Jun 28, 2013

👍 on this feature. I have been looking for a solution to moving out of the current scope for a long time. Would be super helpful

JackCA commented Jun 28, 2013

👍 on this feature. I have been looking for a solution to moving out of the current scope for a long time. Would be super helpful

@amacneil

This comment has been minimized.

Show comment
Hide comment
@amacneil

amacneil Aug 2, 2013

👍 will be very helpful for writing media query mixins

amacneil commented Aug 2, 2013

👍 will be very helpful for writing media query mixins

@PabloMarch

This comment has been minimized.

Show comment
Hide comment
@PabloMarch

PabloMarch May 15, 2014

Hi, Is there a way to access to the parent element to add a class? I used lot of times the parent to trigger css animations and would like to have something like this:

HTML:

<div class="base">
    <div class="item-1"></div>
    <div class="item-2"></div>
    <div class="item-3"></div>
</div>

SCSS:

.base {

    & > div {
        transition: all 1s;
        transform: scale(1);
    }

        .item-1 {
            properties...

            @go-out(1) #{&}.animated {
                transform: scale(0.70);
            }
        }

        .item-1 {
            properties...

            @go-out(1) #{&}.animated {
                transform: scale(0.70);
            }
        }

        .item-1 {
            properties...

            @go-out(1) #{&}.animated {
                transform: scale(0.70);
            }
        }

}

OUTPUT CSS:

.base > div {
    transition: all 1s;
    transform: scale(1);
}

.base .item-1 { properties... }

.base .item-2 { properties... }

.base .item-3 { properties... }

.base.animated .item-1 { transform: scale(0.70); }

.base.animated .item-2 { transform: scale(0.60); }

.base.animated .item-3 { transform: scale(0.50); }

and the parameter passed to @Go-Out(X) indicate the how many levels want to go out.

Thanks!

PabloMarch commented May 15, 2014

Hi, Is there a way to access to the parent element to add a class? I used lot of times the parent to trigger css animations and would like to have something like this:

HTML:

<div class="base">
    <div class="item-1"></div>
    <div class="item-2"></div>
    <div class="item-3"></div>
</div>

SCSS:

.base {

    & > div {
        transition: all 1s;
        transform: scale(1);
    }

        .item-1 {
            properties...

            @go-out(1) #{&}.animated {
                transform: scale(0.70);
            }
        }

        .item-1 {
            properties...

            @go-out(1) #{&}.animated {
                transform: scale(0.70);
            }
        }

        .item-1 {
            properties...

            @go-out(1) #{&}.animated {
                transform: scale(0.70);
            }
        }

}

OUTPUT CSS:

.base > div {
    transition: all 1s;
    transform: scale(1);
}

.base .item-1 { properties... }

.base .item-2 { properties... }

.base .item-3 { properties... }

.base.animated .item-1 { transform: scale(0.70); }

.base.animated .item-2 { transform: scale(0.60); }

.base.animated .item-3 { transform: scale(0.50); }

and the parameter passed to @Go-Out(X) indicate the how many levels want to go out.

Thanks!

@nex3

This comment has been minimized.

Show comment
Hide comment
@nex3

nex3 May 15, 2014

Contributor

Not currently. In version 3.4, you'll be able to access the parent selector in SassScript using & and perform whatever operations you want on it.

Contributor

nex3 commented May 15, 2014

Not currently. In version 3.4, you'll be able to access the parent selector in SassScript using & and perform whatever operations you want on it.

@davestewart

This comment has been minimized.

Show comment
Hide comment
@davestewart

davestewart Jun 3, 2014

Yeah, an @at-node(n) construct to traverse back up the selector tree would be great. I've needed that a few times.

davestewart commented Jun 3, 2014

Yeah, an @at-node(n) construct to traverse back up the selector tree would be great. I've needed that a few times.

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