Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(35)

Side by Side Diff: appengine/config_service/ui/bower_components/shadycss/src/style-transformer.js

Issue 2923973003: Added base template for config ui. (Closed)
Patch Set: Created 3 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 /**
2 @license
3 Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
4 This code may only be used under the BSD style license found at http://polymer.g ithub.io/LICENSE.txt
5 The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
6 The complete set of contributors may be found at http://polymer.github.io/CONTRI BUTORS.txt
7 Code distributed by Google as part of the polymer project is also
8 subject to an additional IP rights grant found at http://polymer.github.io/PATEN TS.txt
9 */
10
11 'use strict';
12
13 import {StyleNode} from './css-parse.js' // eslint-disable-line no-unused-vars
14 import * as StyleUtil from './style-util.js'
15 import {nativeShadow} from './style-settings.js'
16
17 /* Transforms ShadowDOM styling into ShadyDOM styling
18
19 * scoping:
20
21 * elements in scope get scoping selector class="x-foo-scope"
22 * selectors re-written as follows:
23
24 div button -> div.x-foo-scope button.x-foo-scope
25
26 * :host -> scopeName
27
28 * :host(...) -> scopeName...
29
30 * ::slotted(...) -> scopeName > ...
31
32 * ...:dir(ltr|rtl) -> [dir="ltr|rtl"] ..., ...[dir="ltr|rtl"]
33
34 * :host(:dir[rtl]) -> scopeName:dir(rtl) -> [dir="rtl"] scopeName, scopeName[dir ="rtl"]
35
36 */
37 const SCOPE_NAME = 'style-scope';
38
39 class StyleTransformer {
40 get SCOPE_NAME() {
41 return SCOPE_NAME;
42 }
43 // Given a node and scope name, add a scoping class to each node
44 // in the tree. This facilitates transforming css into scoped rules.
45 dom(node, scope, shouldRemoveScope) {
46 // one time optimization to skip scoping...
47 if (node['__styleScoped']) {
48 node['__styleScoped'] = null;
49 } else {
50 this._transformDom(node, scope || '', shouldRemoveScope);
51 }
52 }
53
54 _transformDom(node, selector, shouldRemoveScope) {
55 if (node.nodeType === Node.ELEMENT_NODE) {
56 this.element(node, selector, shouldRemoveScope);
57 }
58 let c$ = (node.localName === 'template') ?
59 (node.content || node._content).childNodes :
60 node.children || node.childNodes;
61 if (c$) {
62 for (let i=0; i<c$.length; i++) {
63 this._transformDom(c$[i], selector, shouldRemoveScope);
64 }
65 }
66 }
67
68 element(element, scope, shouldRemoveScope) {
69 // note: if using classes, we add both the general 'style-scope' class
70 // as well as the specific scope. This enables easy filtering of all
71 // `style-scope` elements
72 if (scope) {
73 // note: svg on IE does not have classList so fallback to class
74 if (element.classList) {
75 if (shouldRemoveScope) {
76 element.classList.remove(SCOPE_NAME);
77 element.classList.remove(scope);
78 } else {
79 element.classList.add(SCOPE_NAME);
80 element.classList.add(scope);
81 }
82 } else if (element.getAttribute) {
83 let c = element.getAttribute(CLASS);
84 if (shouldRemoveScope) {
85 if (c) {
86 let newValue = c.replace(SCOPE_NAME, '').replace(scope, '');
87 StyleUtil.setElementClassRaw(element, newValue);
88 }
89 } else {
90 let newValue = (c ? c + ' ' : '') + SCOPE_NAME + ' ' + scope;
91 StyleUtil.setElementClassRaw(element, newValue);
92 }
93 }
94 }
95 }
96
97 elementStyles(element, styleRules, callback) {
98 let cssBuildType = element['__cssBuild'];
99 // no need to shim selectors if settings.useNativeShadow, also
100 // a shady css build will already have transformed selectors
101 // NOTE: This method may be called as part of static or property shimming.
102 // When there is a targeted build it will not be called for static shimming,
103 // but when the property shim is used it is called and should opt out of
104 // static shimming work when a proper build exists.
105 let cssText = '';
106 if (nativeShadow || cssBuildType === 'shady') {
107 cssText = StyleUtil.toCssText(styleRules, callback);
108 } else {
109 let {is, typeExtension} = StyleUtil.getIsExtends(element);
110 cssText = this.css(styleRules, is, typeExtension, callback) + '\n\n';
111 }
112 return cssText.trim();
113 }
114
115 // Given a string of cssText and a scoping string (scope), returns
116 // a string of scoped css where each selector is transformed to include
117 // a class created from the scope. ShadowDOM selectors are also transformed
118 // (e.g. :host) to use the scoping selector.
119 css(rules, scope, ext, callback) {
120 let hostScope = this._calcHostScope(scope, ext);
121 scope = this._calcElementScope(scope);
122 let self = this;
123 return StyleUtil.toCssText(rules, function(/** StyleNode */rule) {
124 if (!rule.isScoped) {
125 self.rule(rule, scope, hostScope);
126 rule.isScoped = true;
127 }
128 if (callback) {
129 callback(rule, scope, hostScope);
130 }
131 });
132 }
133
134 _calcElementScope(scope) {
135 if (scope) {
136 return CSS_CLASS_PREFIX + scope;
137 } else {
138 return '';
139 }
140 }
141
142 _calcHostScope(scope, ext) {
143 return ext ? `[is=${scope}]` : scope;
144 }
145
146 rule(rule, scope, hostScope) {
147 this._transformRule(rule, this._transformComplexSelector,
148 scope, hostScope);
149 }
150
151 /**
152 * transforms a css rule to a scoped rule.
153 *
154 * @param {StyleNode} rule
155 * @param {Function} transformer
156 * @param {string=} scope
157 * @param {string=} hostScope
158 */
159 _transformRule(rule, transformer, scope, hostScope) {
160 // NOTE: save transformedSelector for subsequent matching of elements
161 // against selectors (e.g. when calculating style properties)
162 rule['selector'] = rule.transformedSelector =
163 this._transformRuleCss(rule, transformer, scope, hostScope);
164 }
165
166 /**
167 * @param {StyleNode} rule
168 * @param {Function} transformer
169 * @param {string=} scope
170 * @param {string=} hostScope
171 */
172 _transformRuleCss(rule, transformer, scope, hostScope) {
173 let p$ = rule['selector'].split(COMPLEX_SELECTOR_SEP);
174 // we want to skip transformation of rules that appear in keyframes,
175 // because they are keyframe selectors, not element selectors.
176 if (!StyleUtil.isKeyframesSelector(rule)) {
177 for (let i=0, l=p$.length, p; (i<l) && (p=p$[i]); i++) {
178 p$[i] = transformer.call(this, p, scope, hostScope);
179 }
180 }
181 return p$.join(COMPLEX_SELECTOR_SEP);
182 }
183
184 /**
185 * @param {string} selector
186 * @param {string} scope
187 * @param {string=} hostScope
188 */
189 _transformComplexSelector(selector, scope, hostScope) {
190 let stop = false;
191 selector = selector.trim();
192 // Remove spaces inside of selectors like `:nth-of-type` because it confuses SIMPLE_SELECTOR_SEP
193 selector = selector.replace(NTH, (m, type, inner) => `:${type}(${inner.repla ce(/\s/g, '')})`);
194 selector = selector.replace(SLOTTED_START, `${HOST} $1`);
195 selector = selector.replace(SIMPLE_SELECTOR_SEP, (m, c, s) => {
196 if (!stop) {
197 let info = this._transformCompoundSelector(s, c, scope, hostScope);
198 stop = stop || info.stop;
199 c = info.combinator;
200 s = info.value;
201 }
202 return c + s;
203 });
204 return selector;
205 }
206
207 _transformCompoundSelector(selector, combinator, scope, hostScope) {
208 // replace :host with host scoping class
209 let slottedIndex = selector.indexOf(SLOTTED);
210 if (selector.indexOf(HOST) >= 0) {
211 selector = this._transformHostSelector(selector, hostScope);
212 // replace other selectors with scoping class
213 } else if (slottedIndex !== 0) {
214 selector = scope ? this._transformSimpleSelector(selector, scope) :
215 selector;
216 }
217 // mark ::slotted() scope jump to replace with descendant selector + arg
218 // also ignore left-side combinator
219 let slotted = false;
220 if (slottedIndex >= 0) {
221 combinator = '';
222 slotted = true;
223 }
224 // process scope jumping selectors up to the scope jump and then stop
225 let stop;
226 if (slotted) {
227 stop = true;
228 if (slotted) {
229 // .zonk ::slotted(.foo) -> .zonk.scope > .foo
230 selector = selector.replace(SLOTTED_PAREN, (m, paren) => ` > ${paren}`);
231 }
232 }
233 selector = selector.replace(DIR_PAREN, (m, before, dir) =>
234 `[dir="${dir}"] ${before}, ${before}[dir="${dir}"]`);
235 return {value: selector, combinator, stop};
236 }
237
238 _transformSimpleSelector(selector, scope) {
239 let p$ = selector.split(PSEUDO_PREFIX);
240 p$[0] += scope;
241 return p$.join(PSEUDO_PREFIX);
242 }
243
244 // :host(...) -> scopeName...
245 _transformHostSelector(selector, hostScope) {
246 let m = selector.match(HOST_PAREN);
247 let paren = m && m[2].trim() || '';
248 if (paren) {
249 if (!paren[0].match(SIMPLE_SELECTOR_PREFIX)) {
250 // paren starts with a type selector
251 let typeSelector = paren.split(SIMPLE_SELECTOR_PREFIX)[0];
252 // if the type selector is our hostScope then avoid pre-pending it
253 if (typeSelector === hostScope) {
254 return paren;
255 // otherwise, this selector should not match in this scope so
256 // output a bogus selector.
257 } else {
258 return SELECTOR_NO_MATCH;
259 }
260 } else {
261 // make sure to do a replace here to catch selectors like:
262 // `:host(.foo)::before`
263 return selector.replace(HOST_PAREN, function(m, host, paren) {
264 return hostScope + paren;
265 });
266 }
267 // if no paren, do a straight :host replacement.
268 // TODO(sorvell): this should not strictly be necessary but
269 // it's needed to maintain support for `:host[foo]` type selectors
270 // which have been improperly used under Shady DOM. This should be
271 // deprecated.
272 } else {
273 return selector.replace(HOST, hostScope);
274 }
275 }
276
277 /**
278 * @param {StyleNode} rule
279 */
280 documentRule(rule) {
281 // reset selector in case this is redone.
282 rule['selector'] = rule['parsedSelector'];
283 this.normalizeRootSelector(rule);
284 this._transformRule(rule, this._transformDocumentSelector);
285 }
286
287 /**
288 * @param {StyleNode} rule
289 */
290 normalizeRootSelector(rule) {
291 if (rule['selector'] === ROOT) {
292 rule['selector'] = 'html';
293 }
294 }
295
296 /**
297 * @param {string} selector
298 */
299 _transformDocumentSelector(selector) {
300 return selector.match(SLOTTED) ?
301 this._transformComplexSelector(selector, SCOPE_DOC_SELECTOR) :
302 this._transformSimpleSelector(selector.trim(), SCOPE_DOC_SELECTOR);
303 }
304 }
305
306 let NTH = /:(nth[-\w]+)\(([^)]+)\)/;
307 let SCOPE_DOC_SELECTOR = `:not(.${SCOPE_NAME})`;
308 let COMPLEX_SELECTOR_SEP = ',';
309 let SIMPLE_SELECTOR_SEP = /(^|[\s>+~]+)((?:\[.+?\]|[^\s>+~=\[])+)/g;
310 let SIMPLE_SELECTOR_PREFIX = /[[.:#*]/;
311 let HOST = ':host';
312 let ROOT = ':root';
313 let SLOTTED = '::slotted';
314 let SLOTTED_START = new RegExp(`^(${SLOTTED})`);
315 // NOTE: this supports 1 nested () pair for things like
316 // :host(:not([selected]), more general support requires
317 // parsing which seems like overkill
318 let HOST_PAREN = /(:host)(?:\(((?:\([^)(]*\)|[^)(]*)+?)\))/;
319 // similar to HOST_PAREN
320 let SLOTTED_PAREN = /(?:::slotted)(?:\(((?:\([^)(]*\)|[^)(]*)+?)\))/;
321 let DIR_PAREN = /(.*):dir\((?:(ltr|rtl))\)/;
322 let CSS_CLASS_PREFIX = '.';
323 let PSEUDO_PREFIX = ':';
324 let CLASS = 'class';
325 let SELECTOR_NO_MATCH = 'should_not_match';
326
327 export default new StyleTransformer()
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698