Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Request for can-extend() #1083

Closed
ghost opened this issue Jan 10, 2014 · 5 comments
Closed

Request for can-extend() #1083

ghost opened this issue Jan 10, 2014 · 5 comments
Labels
enhancement New feature or request

Comments

@ghost
Copy link

ghost commented Jan 10, 2014

I'd like to propose a new function: can-extend($name)

In the same spirit as the *_exists functions in master (at the time of this writing), a function to determine whether an @extend-only selector exists or not would be quite useful.

The specific use-case that this arose from was: extend if exists, otherwise create then extend.

@mixin column($inner, $outer)
{
    @at-root %column-#{$inner}-of-#{$outer}
    {
        width: percentage($inner / $outer);
    }
    @extend %column-#{$inner}-of-#{$outer};
}

.menu { @include column(1, 4); }
.main { @include column(1, 2); }
.side { @include column(1, 4); }

This produces the (expected) output of:

.menu, .side {
  width: 25%; }
.main {
  width: 50%; }
.menu, .side {
  width: 25%; }

The issue being that the .menu, .side declaration is now duplicated. This can get explosive:

.a { @include column(1, 4); }
.b { @include column(1, 4); }
.c { @include column(1, 4); }
.d { @include column(1, 4); }
.e { @include column(1, 4); }
.f { @include column(1, 4); }

Producing:

.a, .b, .c, .d, .e, .f {
  width: 25%; }
.a, .b, .c, .d, .e, .f {
  width: 25%; }
.a, .b, .c, .d, .e, .f {
  width: 25%; }
.a, .b, .c, .d, .e, .f {
  width: 25%; }
.a, .b, .c, .d, .e, .f {
  width: 25%; }
.a, .b, .c, .d, .e, .f {
  width: 25%; }

Which negates the whole point here. Rewriting the @mixin with a hypothetical function:

@mixin column($inner, $outer)
{
    @if not can-extend(column-#{$inner}-of-#{$outer})
    {
        @at-root %column-#{$inner}-of-#{$outer}
        {
            width: percentage($inner / $outer);
        }
    }
    @extend %column-#{$inner}-of-#{$outer};
}

The above, of course, would not redefine the %column-* selector if it exists. The .a - .f example would then produce only:

.a, .b, .c, .d, .e, .f {
  width: 25%; }

I don't fully understand the internals of how @extend-only selectors are handled by Sass, so perhaps this isn't feasible. I'd imagine that it would have otherwise been done already, provided that the implementation were similar enough to the other *_exists functions.

@nex3
Copy link
Contributor

nex3 commented Jan 10, 2014

This isn't really possible, since @extend works with selectors either before or after the actual @extend, and functions are evaluated only with the knowledge of what came before. For example, there would be no correct way to compile this:

@if not can-extend("%selector") {
  %selector {a: b}
}

@nex3 nex3 closed this as completed Jan 10, 2014
@ghost
Copy link
Author

ghost commented Jan 10, 2014

Damn, I sort of feared that. I was initially going to propose that multiple instances of @extend-only selectors be merged (or something along that line) but quickly realized that would break all the things.

Is there no way to achieve the desired behavior as I outlined? Merging selectors would break everything, can-extend can't happen; any other possibilities?

Perhaps,

%selector !once { foo: bar; }

Subsequent attempts to redefine %selector would be ignored.

My @mixin would then be further simplified:

@mixin column($inner, $outer)
{
    @at-root %column-#{$inner}-of-#{$outer} !once { width: percentage($inner / $outer); }
    @extend  %column-#{$inner}-of-#{$outer};
}

Update -- I just realized this could conflict with the "selector target" proposal of parent > !target > child I've seen floating around. Either way, a means of indicating that redefining an @extend-only would be ignored.

@nex3
Copy link
Contributor

nex3 commented Jan 10, 2014

I don't think there's a great way to support this use case. In general, I feel like parameterized inclusions are a better fit for mixins anyway. I don't think you get much mileage out of using @extend here at all.

@ghost
Copy link
Author

ghost commented Jan 10, 2014

I see your point; the width property in this case wouldn't eat many bytes. I was considering cases where the @mixin would produce more output, and thus duplication may be problematic (browser-prefixed gradients, shadows, etc.), however I suppose that could be wrapped into an @extend-only selector manually.

It seemed like a good abstraction pattern.

@jslegers
Copy link

If you generate your placeholders dynamicly and you "register" them in a list, you can check whether a placeholder exists and create it if it doesn't already exist.

Consider the following code as an example. Note that you need Sass 3.3 for this to work.

I also created a Gist for this code at https://gist.github.com/jslegers/9805919

The 'magic' :

$dynamic-placeholders: (
);

@function str-replace($string, $old, $new : '') {
    @if type-of($string) != string {
        $string : '' + $string;
    } 
    $index: str-index($string, $old);
    @while $index and $index > 0 {
        $temp : $string;
        $string : $new;
        @if $index > 1 {
            $string : str-slice($temp, 1, $index - 1) + $string;
        }
        @if $index < str-length($string) {
            $string : $string + str-slice($temp, $index + 1);
        }
        $index: str-index($string, $old);
    }
    @return $string;
}

@function str-clean($string, $dreplacements: (
        '%' : '-perc-',
        '(' : '-l-b-',
        ')' : '-r-r-',
        '/' : '-ps-',
        '"' : '-dq-',
        "'" : '-sq-',
        '.' : '-dot-',
        ' ' : '-nbsp-'
    )) {
    @each $old, $new in $dreplacements {
        $string : str-replace($string, $old, $new);
    }
    @return $string;
}

@mixin generate-root-placeholder($placeholder, $styles) {
    @at-root %#{$placeholder} {
        @each $rule, $style in $styles {
            #{$rule}: $style;
        }
    }
    $dynamic-placeholders: append($dynamic-placeholders, $placeholder) !global;
}

@mixin extend-root-placeholder($placeholder) {
    @extend %#{$placeholder} !optional;
}

@mixin rules($styles) {
    @each $rule, $style in $styles {
        $value : str-clean($style);
        $placeholder : cascade-#{$rule}-#{$value};
        @if not index($dynamic-placeholders, $placeholder) {
            @include generate-root-placeholder($placeholder, ($rule : $style));
        }
        @include extend-root-placeholder($placeholder);
    }
}

@mixin ruleset($styles) {
    $placeholder : cascade;
    @each $rule, $style in $styles {
        $value : str-clean($style);
        $placeholder : #{$placeholder}-#{$rule}-#{$value};
    }
    @if not index($dynamic-placeholders, $placeholder) {
        @include generate-root-placeholder($placeholder, $styles);
    }
    @include extend-root-placeholder($placeholder);
}

Examples :

.s1 {
  @include rules((
    float : right,
    width : 30%,
    background : #fff,
  ));
}

.s2 {
  @include rules((
    float : right,
    width : 100%,
    background : #000,
  ));
}

.s3 {
  @include rules((
    float : left,
    width : 30%,
    background : #fff,
  ));
}

.s4 {
  @include rules((
    float : left,
    width : 30%
  ));
  @include rules((
    background : 'url(image/cool-background.jpg)'
  ));
}

.s5 {
  @include rules((
    float : none,
    width : 2em,
    background : 'url(image/cool-background2.jpg)',
  ));
}

.s6 {
  @include ruleset((
    float : right,
    width : 40%
  ));
  @include rules((
    background : 'url(image/cool-background.jpg)'
  ));
}

.s7 {
  @include rules((
    float : right,
    width : 40%,
    background : 'url(image/cool-background.jpg)',
  ));
}

.s8 {
  @include ruleset((
    float : right,
    width : 40%
  ));
  @include rules((
    background : 'url(image/cool-background2.jpg)'
  ));
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

2 participants