| // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file |
| // for details. All rights reserved. Use of this source code is governed by a |
| // BSD-style license that can be found in the LICENSE file. |
| |
| library polymer.src.css_emitters; |
| |
| import 'package:csslib/visitor.dart' show Visitor, CssPrinter, ElementSelector, |
| UriTerm, Selector, HostDirective, SimpleSelectorSequence, StyleSheet; |
| |
| import 'info.dart'; |
| |
| |
| /** Emit the contents of the style tag outside of a component. */ |
| String emitStyleSheet(StyleSheet ss, FileInfo file) => |
| (new _CssEmitter(file.components.keys.toSet()) |
| ..visitTree(ss, pretty: true)).toString(); |
| |
| /** Emit a component's style tag content emulating scoped css. */ |
| String emitComponentStyleSheet(StyleSheet ss, String tagName) => |
| (new _ComponentCssEmitter(tagName)..visitTree(ss, pretty: true)).toString(); |
| |
| String emitOriginalCss(StyleSheet css) => |
| (new CssPrinter()..visitTree(css)).toString(); |
| |
| /** Only x-tag name element selectors are emitted as [is="x-"]. */ |
| class _CssEmitter extends CssPrinter { |
| final Set _componentsTag; |
| _CssEmitter(this._componentsTag); |
| |
| void visitElementSelector(ElementSelector node) { |
| // If element selector is a component's tag name, then change selector to |
| // find element who's is attribute's the component's name. |
| if (_componentsTag.contains(node.name)) { |
| emit('[is="${node.name}"]'); |
| return; |
| } |
| super.visitElementSelector(node); |
| } |
| } |
| |
| /** |
| * Emits a css stylesheet applying rules to emulate scoped css. The rules adjust |
| * element selectors to include the component's tag name. |
| */ |
| class _ComponentCssEmitter extends CssPrinter { |
| final String _componentTagName; |
| bool _inHostDirective = false; |
| bool _selectorStartInHostDirective = false; |
| |
| _ComponentCssEmitter(this._componentTagName); |
| |
| /** Is the element selector an x-tag name. */ |
| bool _isSelectorElementXTag(Selector node) { |
| if (node.simpleSelectorSequences.length > 0) { |
| var selector = node.simpleSelectorSequences[0].simpleSelector; |
| return selector is ElementSelector && selector.name == _componentTagName; |
| } |
| return false; |
| } |
| |
| void visitSelector(Selector node) { |
| // If the selector starts with an x-tag name don't emit it twice. |
| if (!_isSelectorElementXTag(node)) { |
| if (_inHostDirective) { |
| // Style the element that's hosting the component, therefore don't emit |
| // the descendent combinator (first space after the [is="x-..."]). |
| emit('[is="$_componentTagName"]'); |
| // Signal that first simpleSelector must be checked. |
| _selectorStartInHostDirective = true; |
| } else { |
| // Emit its scoped as a descendent (space at end). |
| emit('[is="$_componentTagName"] '); |
| } |
| } |
| super.visitSelector(node); |
| } |
| |
| /** |
| * If first simple selector of a ruleset in a @host directive is a wildcard |
| * then don't emit the wildcard. |
| */ |
| void visitSimpleSelectorSequence(SimpleSelectorSequence node) { |
| if (_selectorStartInHostDirective) { |
| _selectorStartInHostDirective = false; |
| if (node.simpleSelector.isWildcard) { |
| // Skip the wildcard if first item in the sequence. |
| return; |
| } |
| assert(node.isCombinatorNone); |
| } |
| |
| super.visitSimpleSelectorSequence(node); |
| } |
| |
| void visitElementSelector(ElementSelector node) { |
| // If element selector is the component's tag name, then change selector to |
| // find element who's is attribute is the component's name. |
| if (_componentTagName == node.name) { |
| emit('[is="$_componentTagName"]'); |
| return; |
| } |
| super.visitElementSelector(node); |
| } |
| |
| /** |
| * If we're polyfilling scoped styles the @host directive is stripped. Any |
| * ruleset(s) processed in an @host will fixup the first selector. See |
| * visitSelector and visitSimpleSelectorSequence in this class, they adjust |
| * the selectors so it styles the element hosting the compopnent. |
| */ |
| void visitHostDirective(HostDirective node) { |
| _inHostDirective = true; |
| emit('/* @host */'); |
| for (var ruleset in node.rulesets) { |
| ruleset.visit(this); |
| } |
| _inHostDirective = false; |
| emit('/* end of @host */\n'); |
| } |
| } |