Skip to content
This repository has been archived by the owner on Feb 22, 2018. It is now read-only.

Commit

Permalink
feat(css_shim): implement polyfill-unscoped-next-selector and polyfil…
Browse files Browse the repository at this point in the history
…l-non-strict
  • Loading branch information
vsavkin authored and rkirov committed Oct 17, 2014
1 parent 7a745d5 commit a3ff5cf
Show file tree
Hide file tree
Showing 2 changed files with 99 additions and 29 deletions.
106 changes: 78 additions & 28 deletions lib/core_dom/css_shim.dart
Original file line number Diff line number Diff line change
Expand Up @@ -37,15 +37,12 @@ String shimCssText(String css, String tag) =>
* * `:host`
* * `:host(.x)`
*
* When the shim is not powerful enough, you can fall back on the polyfill-next-selector
* directive.
* When the shim is not powerful enough, you can fall back on the polyfill-next-selector,
* polyfill-unscoped-next-selector, and polyfill-non-strict directives.
*
* polyfill-next-selector {content: 'x > y'}
* z {}
*
* Becomes:
*
* x[tag] > y[tag]
* * `polyfill-next-selector {content: 'x > y'}` z {} becomes `x[tag] > y[tag] {}`
* * `polyfill-unscoped-next-selector {content: 'x > y'} z {}` becomes `x > y {}`
* * `polyfill-non-strict {} z {}` becomes `tag z {}`
*
* See http://www.polymer-project.org/docs/polymer/styling.html#at-polyfill
*
Expand All @@ -54,17 +51,15 @@ String shimCssText(String css, String tag) =>
*/
class _CssShim {
static final List SELECTOR_SPLITS = const [' ', '>', '+', '~'];
static final RegExp POLYFILL_NEXT_SELECTOR_DIRECTIVE = new RegExp(
r"polyfill-next-selector"

static final RegExp CONTENT = new RegExp(
r"[^}]*"
r"content\:[\s]*"
r"'([^']*)'"
r"[^}]*}"
r"([^{]*)",
r"[^}]*}",
caseSensitive: false,
multiLine: true
);
static final int NEXT_SELECTOR_CONTENT = 1;

static final String HOST_TOKEN = '-host-element';
static final RegExp COLON_SELECTORS = new RegExp(r'(' + HOST_TOKEN + r')(\(.*\)){0,1}(.*)',
Expand All @@ -79,21 +74,29 @@ class _CssShim {
static final RegExp COLON_HOST = new RegExp('($HOST_TOKEN$PAREN_SUFFIX',
caseSensitive: false, multiLine: true);

static final String POLYFILL_NON_STRICT = "polyfill-non-strict";
static final String POLYFILL_UNSCOPED_NEXT_SELECTOR = "polyfill-unscoped-next-selector";
static final String POLYFILL_NEXT_SELECTOR = "polyfill-next-selector";

static final List<RegExp> COMBINATORS = [
new RegExp(r'/shadow/', caseSensitive: false),
new RegExp(r'/shadow-deep/', caseSensitive: false),
new RegExp(r'::shadow', caseSensitive: false),
new RegExp(r'/deep/', caseSensitive: false)
];

final String tag;
final String attr;

_CssShim(String tag)
: tag = tag, attr = "[$tag]";

String shimCssText(String css) {
final preprocessed = convertColonHost(applyPolyfillNextSelectorDirective(css));
final preprocessed = convertColonHost(css);
final rules = cssToRules(preprocessed);
return scopeRules(rules);
}

String applyPolyfillNextSelectorDirective(String css) =>
css.replaceAllMapped(POLYFILL_NEXT_SELECTOR_DIRECTIVE, (m) => m[NEXT_SELECTOR_CONTENT]);

String convertColonHost(String css) {
css = css.replaceAll(":host", HOST_TOKEN);

Expand All @@ -120,35 +123,82 @@ class _CssShim {
List<_Rule> cssToRules(String css) =>
new _Parser(css).parse();

String scopeRules(List<_Rule> rules) =>
rules.map(scopeRule).join("\n");
String scopeRules(List<_Rule> rules) {
final scopedRules = [];

var prevRule;
rules.forEach((rule) {
if (prevRule != null && prevRule.selectorText == POLYFILL_NON_STRICT) {
scopedRules.add(scopeNonStrictMode(rule));

} else if (prevRule != null && prevRule.selectorText == POLYFILL_UNSCOPED_NEXT_SELECTOR) {
final content = extractContent(prevRule);
scopedRules.add(ruleToString(new _Rule(content, body: rule.body)));

} else if (prevRule != null && prevRule.selectorText == POLYFILL_NEXT_SELECTOR) {
final content = extractContent(prevRule);
scopedRules.add(scopeStrictMode(new _Rule(content, body: rule.body)));

} else if (rule.selectorText != POLYFILL_NON_STRICT &&
rule.selectorText != POLYFILL_UNSCOPED_NEXT_SELECTOR &&
rule.selectorText != POLYFILL_NEXT_SELECTOR) {
scopedRules.add(scopeStrictMode(rule));
}

prevRule = rule;
});

return scopedRules.join("\n");
}

String extractContent(_Rule rule) {
return CONTENT.firstMatch(rule.body)[1];
}

String ruleToString(_Rule rule) {
return "${rule.selectorText} ${rule.body}";
}

String scopeRule(_Rule rule) {
String scopeStrictMode(_Rule rule) {
if (rule.hasNestedRules) {
final selector = rule.selectorText;
final rules = scopeRules(rule.rules);
return '$selector {\n$rules\n}';
} else {
final scopedSelector = scopeSelector(rule.selectorText);
final scopedSelector = scopeSelector(rule.selectorText, strict: true);
final scopedBody = cssText(rule);
return "$scopedSelector $scopedBody";
}
}

String scopeSelector(String selector) {
final parts = selector.split(",");
String scopeNonStrictMode(_Rule rule) {
final scopedBody = cssText(rule);
final scopedSelector = scopeSelector(rule.selectorText, strict: false);
return "${scopedSelector} $scopedBody";
}

String scopeSelector(String selector, {bool strict}) {
final parts = replaceCombinators(selector).split(",");
final scopedParts = parts.fold([], (res, p) {
res.add(scopeSimpleSelector(p.trim()));
res.add(scopeSimpleSelector(p.trim(), strict: strict));
return res;
});
return scopedParts.join(", ");
}

String scopeSimpleSelector(String selector) {
String replaceCombinators(String selector) {
return COMBINATORS.fold(selector, (sel, combinator) {
return sel.replaceAll(combinator, ' ');
});
}

String scopeSimpleSelector(String selector, {bool strict}) {
if (selector.contains(HOST_TOKEN)) {
return replaceColonSelectors(selector);
} else if (strict) {
return insertTagToEverySelectorPart(selector);
} else {
return insertTag(selector);
return "$tag $selector";
}
}

Expand All @@ -162,7 +212,7 @@ class _CssShim {
});
}

String insertTag(String selector) {
String insertTagToEverySelectorPart(String selector) {
selector = handleIsSelector(selector);

SELECTOR_SPLITS.forEach((split) {
Expand Down Expand Up @@ -258,7 +308,7 @@ class _Lexer {
int start = index;
advance();
while (isSelector(peek)) advance();
String string = input.substring(start, index);
String string = input.substring(start, index).trim();
return new _Token(string, "selector");
}

Expand Down
22 changes: 21 additions & 1 deletion test/core_dom/css_shim_spec.dart
Original file line number Diff line number Diff line change
Expand Up @@ -75,9 +75,29 @@ main() {
expect(s(':host(.x,.y) {}', "a")).toEqual('a.x, a.y {}');
});

it("should insert directives", () {
it("should support polyfill-next-selector", () {
var css = s("polyfill-next-selector {content: 'x > y'} z {}", "a");
expect(css).toEqual('x[a]>y[a] {}');
});

it("should support polyfill-unscoped-next-selector", () {
var css = s("polyfill-unscoped-next-selector {content: 'x > y'} z {}", "a");
expect(css).toEqual('x > y {}');
});

it("should support polyfill-non-strict-next-selector", () {
var css = s("polyfill-non-strict {} one, two {}", "a");
expect(css).toEqual('a one, a two {}');
});

it("should handle ::shadow", () {
var css = s("polyfill-non-strict {} x::shadow > y {}", "a");
expect(css).toEqual('a x > y {}');
});

it("should handle /deep/", () {
var css = s("polyfill-non-strict {} x /deep/ y {}", "a");
expect(css).toEqual('a x y {}');
});
});
}

0 comments on commit a3ff5cf

Please sign in to comment.