UNPKG

35.9 kBJavaScriptView Raw
1import { unsetValue, _evaluateCssVariableExpression, _evaluateCssCalcExpression, isCssVariable, isCssVariableExpression, isCssCalcExpression } from '../core/properties';
2import { SelectorsMap, SelectorsMatch, fromAstNodes } from './css-selector';
3import { Trace } from '../../trace';
4import { File, knownFolders, path } from '../../file-system';
5import { Application } from '../../application';
6import { profile } from '../../profiling';
7let keyframeAnimationModule;
8function ensureKeyframeAnimationModule() {
9 if (!keyframeAnimationModule) {
10 keyframeAnimationModule = require('../animation/keyframe-animation');
11 }
12}
13import { sanitizeModuleName } from '../../utils/common';
14import { resolveModuleName } from '../../module-name-resolver';
15import { cleanupImportantFlags } from './css-utils';
16let cssAnimationParserModule;
17function ensureCssAnimationParserModule() {
18 if (!cssAnimationParserModule) {
19 cssAnimationParserModule = require('./css-animation-parser');
20 }
21}
22let parser = 'css-tree';
23try {
24 const appConfig = require('~/package.json');
25 if (appConfig) {
26 if (appConfig.cssParser === 'rework') {
27 parser = 'rework';
28 }
29 else if (appConfig.cssParser === 'nativescript') {
30 parser = 'nativescript';
31 }
32 }
33}
34catch (e) {
35 //
36}
37/**
38 * Evaluate css-variable and css-calc expressions
39 */
40function evaluateCssExpressions(view, property, value) {
41 const newValue = _evaluateCssVariableExpression(view, property, value);
42 if (newValue === 'unset') {
43 return unsetValue;
44 }
45 value = newValue;
46 try {
47 value = _evaluateCssCalcExpression(value);
48 }
49 catch (e) {
50 Trace.write(`Failed to evaluate css-calc for property [${property}] for expression [${value}] to ${view}. ${e.stack}`, Trace.categories.Error, Trace.messageType.error);
51 return unsetValue;
52 }
53 return value;
54}
55export function mergeCssSelectors() {
56 applicationCssSelectors = applicationSelectors.slice();
57 applicationCssSelectors.push(...applicationAdditionalSelectors);
58 applicationCssSelectorVersion++;
59}
60let applicationCssSelectors = [];
61let applicationCssSelectorVersion = 0;
62let applicationSelectors = [];
63const tagToScopeTag = new Map();
64let currentScopeTag = null;
65const applicationAdditionalSelectors = [];
66const applicationKeyframes = {};
67const animationsSymbol = Symbol('animations');
68const kebabCasePattern = /-([a-z])/g;
69const pattern = /('|")(.*?)\1/;
70class CSSSource {
71 constructor(_ast, _url, _file, _keyframes, _source) {
72 this._ast = _ast;
73 this._url = _url;
74 this._file = _file;
75 this._keyframes = _keyframes;
76 this._source = _source;
77 this._selectors = [];
78 this.parse();
79 }
80 static fromDetect(cssOrAst, keyframes, fileName) {
81 if (typeof cssOrAst === 'string') {
82 // raw-loader
83 return CSSSource.fromSource(cssOrAst, keyframes, fileName);
84 }
85 else if (typeof cssOrAst === 'object') {
86 if (cssOrAst.default) {
87 cssOrAst = cssOrAst.default;
88 }
89 if (cssOrAst.type === 'stylesheet' && cssOrAst.stylesheet && cssOrAst.stylesheet.rules) {
90 // css-loader
91 return CSSSource.fromAST(cssOrAst, keyframes, fileName);
92 }
93 }
94 // css2json-loader
95 return CSSSource.fromSource(cssOrAst.toString(), keyframes, fileName);
96 }
97 static fromURI(uri, keyframes) {
98 // webpack modules require all file paths to be relative to /app folder
99 const appRelativeUri = CSSSource.pathRelativeToApp(uri);
100 const sanitizedModuleName = sanitizeModuleName(appRelativeUri);
101 const resolvedModuleName = resolveModuleName(sanitizedModuleName, 'css');
102 try {
103 const cssOrAst = global.loadModule(resolvedModuleName, true);
104 if (cssOrAst) {
105 return CSSSource.fromDetect(cssOrAst, keyframes, resolvedModuleName);
106 }
107 }
108 catch (e) {
109 if (Trace.isEnabled()) {
110 Trace.write(`Could not load CSS from ${uri}: ${e}`, Trace.categories.Error, Trace.messageType.warn);
111 }
112 }
113 return CSSSource.fromFile(appRelativeUri, keyframes);
114 }
115 static pathRelativeToApp(uri) {
116 if (!uri.startsWith('/')) {
117 return uri;
118 }
119 const appPath = knownFolders.currentApp().path;
120 if (!uri.startsWith(appPath)) {
121 Trace.write(`${uri} does not start with ${appPath}`, Trace.categories.Error, Trace.messageType.error);
122 return uri;
123 }
124 const relativeUri = `.${uri.substr(appPath.length)}`;
125 return relativeUri;
126 }
127 static fromFile(url, keyframes) {
128 // .scss, .sass, etc. css files in vanilla app are usually compiled to .css so we will try to load a compiled file first.
129 const cssFileUrl = url.replace(/\..\w+$/, '.css');
130 if (cssFileUrl !== url) {
131 const cssFile = CSSSource.resolveCSSPathFromURL(cssFileUrl);
132 if (cssFile) {
133 return new CSSSource(undefined, url, cssFile, keyframes, undefined);
134 }
135 }
136 const file = CSSSource.resolveCSSPathFromURL(url);
137 return new CSSSource(undefined, url, file, keyframes, undefined);
138 }
139 static fromFileImport(url, keyframes, importSource) {
140 const file = CSSSource.resolveCSSPathFromURL(url, importSource);
141 return new CSSSource(undefined, url, file, keyframes, undefined);
142 }
143 static resolveCSSPathFromURL(url, importSource) {
144 const app = knownFolders.currentApp().path;
145 const file = resolveFileNameFromUrl(url, app, File.exists, importSource);
146 return file;
147 }
148 static fromSource(source, keyframes, url) {
149 return new CSSSource(undefined, url, undefined, keyframes, source);
150 }
151 static fromAST(ast, keyframes, url) {
152 return new CSSSource(ast, url, undefined, keyframes, undefined);
153 }
154 get selectors() {
155 return this._selectors;
156 }
157 get source() {
158 return this._source;
159 }
160 load() {
161 const file = File.fromPath(this._file);
162 this._source = file.readTextSync();
163 }
164 parse() {
165 try {
166 if (!this._ast) {
167 if (!this._source && this._file) {
168 this.load();
169 }
170 // [object Object] check guards against empty app.css file
171 if (this._source && this.source !== '[object Object]') {
172 this.parseCSSAst();
173 }
174 }
175 if (this._ast) {
176 this.createSelectors();
177 }
178 else {
179 this._selectors = [];
180 }
181 }
182 catch (e) {
183 if (Trace.isEnabled()) {
184 Trace.write('Css styling failed: ' + e, Trace.categories.Style, Trace.messageType.error);
185 }
186 this._selectors = [];
187 }
188 }
189 parseCSSAst() {
190 if (this._source) {
191 if (__CSS_PARSER__ === 'css-tree') {
192 const cssTreeParse = require('../../css/css-tree-parser').cssTreeParse;
193 this._ast = cssTreeParse(this._source, this._file);
194 }
195 else if (__CSS_PARSER__ === 'nativescript') {
196 const CSS3Parser = require('../../css/CSS3Parser').CSS3Parser;
197 const CSSNativeScript = require('../../css/CSSNativeScript').CSSNativeScript;
198 const cssparser = new CSS3Parser(this._source);
199 const stylesheet = cssparser.parseAStylesheet();
200 const cssNS = new CSSNativeScript();
201 this._ast = cssNS.parseStylesheet(stylesheet);
202 }
203 else if (__CSS_PARSER__ === 'rework') {
204 const parseCss = require('../../css').parse;
205 this._ast = parseCss(this._source, { source: this._file });
206 }
207 }
208 }
209 createSelectors() {
210 if (this._ast) {
211 this._selectors = [...this.createSelectorsFromImports(), ...this.createSelectorsFromSyntaxTree()];
212 }
213 }
214 createSelectorsFromImports() {
215 const imports = this._ast['stylesheet']['rules'].filter((r) => r.type === 'import');
216 const urlFromImportObject = (importObject) => {
217 const importItem = importObject['import'];
218 const urlMatch = importItem && importItem.match(pattern);
219 return urlMatch && urlMatch[2];
220 };
221 const sourceFromImportObject = (importObject) => importObject['position'] && importObject['position']['source'];
222 const toUrlSourcePair = (importObject) => ({
223 url: urlFromImportObject(importObject),
224 source: sourceFromImportObject(importObject),
225 });
226 const getCssFile = ({ url, source }) => (source ? CSSSource.fromFileImport(url, this._keyframes, source) : CSSSource.fromURI(url, this._keyframes));
227 const cssFiles = imports
228 .map(toUrlSourcePair)
229 .filter(({ url }) => !!url)
230 .map(getCssFile);
231 const selectors = cssFiles.map((file) => (file && file.selectors) || []);
232 return selectors.reduce((acc, val) => acc.concat(val), []);
233 }
234 createSelectorsFromSyntaxTree() {
235 const nodes = this._ast.stylesheet.rules;
236 nodes.filter(isKeyframe).forEach((node) => (this._keyframes[node.name] = node));
237 const rulesets = fromAstNodes(nodes);
238 if (rulesets && rulesets.length) {
239 ensureCssAnimationParserModule();
240 rulesets.forEach((rule) => {
241 rule[animationsSymbol] = cssAnimationParserModule.CssAnimationParser.keyframeAnimationsFromCSSDeclarations(rule.declarations);
242 });
243 }
244 return rulesets;
245 }
246 toString() {
247 return this._file || this._url || '(in-memory)';
248 }
249}
250__decorate([
251 profile,
252 __metadata("design:type", Function),
253 __metadata("design:paramtypes", []),
254 __metadata("design:returntype", void 0)
255], CSSSource.prototype, "load", null);
256__decorate([
257 profile,
258 __metadata("design:type", Function),
259 __metadata("design:paramtypes", []),
260 __metadata("design:returntype", void 0)
261], CSSSource.prototype, "parse", null);
262__decorate([
263 profile,
264 __metadata("design:type", Function),
265 __metadata("design:paramtypes", []),
266 __metadata("design:returntype", void 0)
267], CSSSource.prototype, "parseCSSAst", null);
268__decorate([
269 profile,
270 __metadata("design:type", Function),
271 __metadata("design:paramtypes", []),
272 __metadata("design:returntype", void 0)
273], CSSSource.prototype, "createSelectors", null);
274__decorate([
275 profile,
276 __metadata("design:type", Function),
277 __metadata("design:paramtypes", [String, String]),
278 __metadata("design:returntype", String)
279], CSSSource, "resolveCSSPathFromURL", null);
280export function removeTaggedAdditionalCSS(tag) {
281 let changed = false;
282 for (let i = 0; i < applicationAdditionalSelectors.length; i++) {
283 if (applicationAdditionalSelectors[i].tag === tag) {
284 applicationAdditionalSelectors.splice(i, 1);
285 i--;
286 changed = true;
287 }
288 }
289 if (changed) {
290 mergeCssSelectors();
291 }
292 return changed;
293}
294export function addTaggedAdditionalCSS(cssText, tag) {
295 const parsed = CSSSource.fromDetect(cssText, applicationKeyframes, undefined).selectors;
296 const tagScope = currentScopeTag || (tag && tagToScopeTag.has(tag) && tagToScopeTag.get(tag)) || null;
297 if (tagScope && tag) {
298 tagToScopeTag.set(tag, tagScope);
299 }
300 let changed = false;
301 if (parsed && parsed.length) {
302 changed = true;
303 if (tag != null || tagScope != null) {
304 for (let i = 0; i < parsed.length; i++) {
305 parsed[i].tag = tag;
306 parsed[i].scopedTag = tagScope;
307 }
308 }
309 applicationAdditionalSelectors.push(...parsed);
310 mergeCssSelectors();
311 }
312 return changed;
313}
314const onCssChanged = profile('"style-scope".onCssChanged', (args) => {
315 if (args.cssText) {
316 const parsed = CSSSource.fromSource(args.cssText, applicationKeyframes, args.cssFile).selectors;
317 if (parsed) {
318 applicationAdditionalSelectors.push(...parsed);
319 mergeCssSelectors();
320 }
321 }
322 else if (args.cssFile) {
323 loadCss(args.cssFile, null, null);
324 }
325});
326function onLiveSync(args) {
327 loadCss(Application.getCssFileName(), null, null);
328}
329const loadCss = profile(`"style-scope".loadCss`, (cssModule) => {
330 if (!cssModule) {
331 return undefined;
332 }
333 // safely remove "./" as global CSS should be resolved relative to app folder
334 if (cssModule.startsWith('./')) {
335 cssModule = cssModule.substr(2);
336 }
337 const result = CSSSource.fromURI(cssModule, applicationKeyframes).selectors;
338 if (result.length > 0) {
339 applicationSelectors = result;
340 mergeCssSelectors();
341 }
342});
343global.NativeScriptGlobals.events.on('cssChanged', onCssChanged);
344global.NativeScriptGlobals.events.on('livesync', onLiveSync);
345// Call to this method is injected in the application in:
346// - no-snapshot - code injected in app.ts by [bundle-config-loader](https://github.com/NativeScript/nativescript-dev-webpack/blob/9b1e34d8ef838006c9b575285c42d2304f5f02b5/bundle-config-loader.ts#L85-L92)
347// - with-snapshot - code injected in snapshot bundle by [NativeScriptSnapshotPlugin](https://github.com/NativeScript/nativescript-dev-webpack/blob/48b26f412fd70c19dc0b9c7763e08e9505a0ae11/plugins/NativeScriptSnapshotPlugin/index.js#L48-L56)
348// Having the app.css loaded in snapshot provides significant boost in startup (when using the ns-theme ~150 ms). However, because app.css is resolved at build-time,
349// when the snapshot is created - there is no way to use file qualifiers or change the name of on app.css
350export const loadAppCSS = profile('"style-scope".loadAppCSS', (args) => {
351 loadCss(args.cssFile, null, null);
352 global.NativeScriptGlobals.events.off('loadAppCss', loadAppCSS);
353});
354if (Application.hasLaunched()) {
355 loadAppCSS({
356 eventName: 'loadAppCss',
357 object: Application,
358 cssFile: Application.getCssFileName(),
359 }, null, null);
360}
361else {
362 global.NativeScriptGlobals.events.on('loadAppCss', loadAppCSS);
363}
364export class CssState {
365 constructor(viewRef) {
366 this.viewRef = viewRef;
367 this._appliedPropertyValues = CssState.emptyPropertyBag;
368 this._onDynamicStateChangeHandler = () => this.updateDynamicState();
369 }
370 /**
371 * Called when a change had occurred that may invalidate the statically matching selectors (class, id, ancestor selectors).
372 * As a result, at some point in time, the selectors matched have to be requerried from the style scope and applied to the view.
373 */
374 onChange() {
375 const view = this.viewRef.get();
376 if (view && view.isLoaded) {
377 this.unsubscribeFromDynamicUpdates();
378 this.updateMatch();
379 this.subscribeForDynamicUpdates();
380 this.updateDynamicState();
381 }
382 else {
383 this._matchInvalid = true;
384 }
385 }
386 isSelectorsLatestVersionApplied() {
387 const view = this.viewRef.get();
388 if (!view) {
389 Trace.write(`isSelectorsLatestVersionApplied returns default value "false" because "this.viewRef" cleared.`, Trace.categories.Style, Trace.messageType.warn);
390 return false;
391 }
392 return this.viewRef.get()._styleScope.getSelectorsVersion() === this._appliedSelectorsVersion;
393 }
394 onLoaded() {
395 if (this._matchInvalid) {
396 this.updateMatch();
397 }
398 this.subscribeForDynamicUpdates();
399 this.updateDynamicState();
400 }
401 onUnloaded() {
402 this.unsubscribeFromDynamicUpdates();
403 this.stopKeyframeAnimations();
404 }
405 updateMatch() {
406 const view = this.viewRef.get();
407 if (view && view._styleScope) {
408 this._match = view._styleScope.matchSelectors(view);
409 this._appliedSelectorsVersion = view._styleScope.getSelectorsVersion();
410 }
411 else {
412 this._match = CssState.emptyMatch;
413 }
414 this._matchInvalid = false;
415 }
416 updateDynamicState() {
417 const view = this.viewRef.get();
418 if (!view) {
419 Trace.write(`updateDynamicState not executed to view because ".viewRef" is cleared`, Trace.categories.Style, Trace.messageType.warn);
420 return;
421 }
422 const matchingSelectors = this._match.selectors.filter((sel) => (sel.dynamic ? sel.match(view) : true));
423 if (!matchingSelectors || matchingSelectors.length === 0) {
424 // Ideally we should return here if there are no matching selectors, however
425 // if there are property removals, returning here would not remove them
426 // this is seen in STYLE test in automated.
427 // return;
428 }
429 view._batchUpdate(() => {
430 this.stopKeyframeAnimations();
431 this.setPropertyValues(matchingSelectors);
432 this.playKeyframeAnimations(matchingSelectors);
433 });
434 }
435 playKeyframeAnimations(matchingSelectors) {
436 const animations = [];
437 matchingSelectors.forEach((selector) => {
438 const ruleAnimations = selector.ruleset[animationsSymbol];
439 if (ruleAnimations) {
440 ensureKeyframeAnimationModule();
441 for (const animationInfo of ruleAnimations) {
442 const animation = keyframeAnimationModule.KeyframeAnimation.keyframeAnimationFromInfo(animationInfo);
443 if (animation) {
444 animations.push(animation);
445 }
446 }
447 }
448 });
449 if ((this._playsKeyframeAnimations = animations.length > 0)) {
450 const view = this.viewRef.get();
451 if (!view) {
452 Trace.write(`KeyframeAnimations cannot play because ".viewRef" is cleared`, Trace.categories.Animation, Trace.messageType.warn);
453 return;
454 }
455 animations.map((animation) => animation.play(view));
456 Object.freeze(animations);
457 this._appliedAnimations = animations;
458 }
459 }
460 stopKeyframeAnimations() {
461 if (!this._playsKeyframeAnimations) {
462 return;
463 }
464 this._appliedAnimations.filter((animation) => animation.isPlaying).forEach((animation) => animation.cancel());
465 this._appliedAnimations = CssState.emptyAnimationArray;
466 const view = this.viewRef.get();
467 if (view) {
468 view.style['keyframe:rotate'] = unsetValue;
469 view.style['keyframe:rotateX'] = unsetValue;
470 view.style['keyframe:rotateY'] = unsetValue;
471 view.style['keyframe:scaleX'] = unsetValue;
472 view.style['keyframe:scaleY'] = unsetValue;
473 view.style['keyframe:translateX'] = unsetValue;
474 view.style['keyframe:translateY'] = unsetValue;
475 view.style['keyframe:backgroundColor'] = unsetValue;
476 view.style['keyframe:opacity'] = unsetValue;
477 }
478 else {
479 Trace.write(`KeyframeAnimations cannot be stopped because ".viewRef" is cleared`, Trace.categories.Animation, Trace.messageType.warn);
480 }
481 this._playsKeyframeAnimations = false;
482 }
483 /**
484 * Calculate the difference between the previously applied property values,
485 * and the new set of property values that have to be applied for the provided selectors.
486 * Apply the values and ensure each property setter is called at most once to avoid excessive change notifications.
487 * @param matchingSelectors
488 */
489 setPropertyValues(matchingSelectors) {
490 const view = this.viewRef.get();
491 if (!view) {
492 Trace.write(`${matchingSelectors} not set to view's property because ".viewRef" is cleared`, Trace.categories.Style, Trace.messageType.warn);
493 return;
494 }
495 const newPropertyValues = new view.style.PropertyBag();
496 matchingSelectors.forEach((selector) => selector.ruleset.declarations.forEach((declaration) => (newPropertyValues[declaration.property] = declaration.value)));
497 const oldProperties = this._appliedPropertyValues;
498 // Update values for the scope's css-variables
499 view.style.resetScopedCssVariables();
500 const valuesToApply = {};
501 const cssExpsProperties = {};
502 const replacementFunc = (g) => g[1].toUpperCase();
503 for (const property in newPropertyValues) {
504 const value = cleanupImportantFlags(newPropertyValues[property], property);
505 const isCssExp = isCssVariableExpression(value) || isCssCalcExpression(value);
506 if (isCssExp) {
507 // we handle css exp separately because css vars must be evaluated first
508 cssExpsProperties[property] = value;
509 continue;
510 }
511 delete oldProperties[property];
512 if (property in oldProperties && oldProperties[property] === value) {
513 // Skip unchanged values
514 continue;
515 }
516 if (isCssVariable(property)) {
517 view.style.setScopedCssVariable(property, value);
518 delete newPropertyValues[property];
519 continue;
520 }
521 valuesToApply[property] = value;
522 }
523 //we need to parse CSS vars first before evaluating css expressions
524 for (const property in cssExpsProperties) {
525 delete oldProperties[property];
526 const value = evaluateCssExpressions(view, property, cssExpsProperties[property]);
527 if (property in oldProperties && oldProperties[property] === value) {
528 // Skip unchanged values
529 continue;
530 }
531 if (value === unsetValue) {
532 delete newPropertyValues[property];
533 }
534 if (isCssVariable(property)) {
535 view.style.setScopedCssVariable(property, value);
536 delete newPropertyValues[property];
537 }
538 valuesToApply[property] = value;
539 }
540 // Unset removed values
541 for (const property in oldProperties) {
542 if (property in view.style) {
543 view.style[`css:${property}`] = unsetValue;
544 }
545 else {
546 const camelCasedProperty = property.replace(kebabCasePattern, replacementFunc);
547 view[camelCasedProperty] = unsetValue;
548 }
549 }
550 // Set new values to the style
551 for (const property in valuesToApply) {
552 const value = valuesToApply[property];
553 try {
554 if (property in view.style) {
555 view.style[`css:${property}`] = value;
556 }
557 else {
558 const camelCasedProperty = property.replace(kebabCasePattern, replacementFunc);
559 view[camelCasedProperty] = value;
560 }
561 }
562 catch (e) {
563 Trace.write(`Failed to apply property [${property}] with value [${value}] to ${view}. ${e.stack}`, Trace.categories.Error, Trace.messageType.error);
564 }
565 }
566 this._appliedPropertyValues = newPropertyValues;
567 }
568 subscribeForDynamicUpdates() {
569 const changeMap = this._match.changeMap;
570 changeMap.forEach((changes, view) => {
571 if (changes.attributes) {
572 changes.attributes.forEach((attribute) => {
573 view.addEventListener(attribute + 'Change', this._onDynamicStateChangeHandler);
574 });
575 }
576 if (changes.pseudoClasses) {
577 changes.pseudoClasses.forEach((pseudoClass) => {
578 const eventName = ':' + pseudoClass;
579 view.addEventListener(':' + pseudoClass, this._onDynamicStateChangeHandler);
580 if (view[eventName]) {
581 view[eventName](+1);
582 }
583 });
584 }
585 });
586 this._appliedChangeMap = changeMap;
587 }
588 unsubscribeFromDynamicUpdates() {
589 this._appliedChangeMap.forEach((changes, view) => {
590 if (changes.attributes) {
591 changes.attributes.forEach((attribute) => {
592 view.removeEventListener(attribute + 'Change', this._onDynamicStateChangeHandler);
593 });
594 }
595 if (changes.pseudoClasses) {
596 changes.pseudoClasses.forEach((pseudoClass) => {
597 const eventName = ':' + pseudoClass;
598 view.removeEventListener(eventName, this._onDynamicStateChangeHandler);
599 if (view[eventName]) {
600 view[eventName](-1);
601 }
602 });
603 }
604 });
605 this._appliedChangeMap = CssState.emptyChangeMap;
606 }
607 toString() {
608 const view = this.viewRef.get();
609 if (!view) {
610 Trace.write(`toString() of CssState cannot execute correctly because ".viewRef" is cleared`, Trace.categories.Animation, Trace.messageType.warn);
611 return '';
612 }
613 return `${view}._cssState`;
614 }
615}
616CssState.emptyChangeMap = Object.freeze(new Map());
617CssState.emptyPropertyBag = {};
618CssState.emptyAnimationArray = Object.freeze([]);
619CssState.emptyMatch = {
620 selectors: [],
621 changeMap: new Map(),
622 addAttribute: () => { },
623 addPseudoClass: () => { },
624 properties: null,
625};
626__decorate([
627 profile,
628 __metadata("design:type", Function),
629 __metadata("design:paramtypes", []),
630 __metadata("design:returntype", void 0)
631], CssState.prototype, "updateMatch", null);
632__decorate([
633 profile,
634 __metadata("design:type", Function),
635 __metadata("design:paramtypes", []),
636 __metadata("design:returntype", void 0)
637], CssState.prototype, "updateDynamicState", null);
638CssState.prototype._appliedChangeMap = CssState.emptyChangeMap;
639CssState.prototype._appliedAnimations = CssState.emptyAnimationArray;
640CssState.prototype._matchInvalid = true;
641export class StyleScope {
642 constructor() {
643 this._css = '';
644 this._localCssSelectors = [];
645 this._localCssSelectorVersion = 0;
646 this._localCssSelectorsAppliedVersion = 0;
647 this._applicationCssSelectorsAppliedVersion = 0;
648 this._keyframes = new Map();
649 this._cssFiles = [];
650 }
651 get css() {
652 return this._css;
653 }
654 set css(value) {
655 this.setCss(value);
656 }
657 addCss(cssString, cssFileName) {
658 this.appendCss(cssString, cssFileName);
659 }
660 addCssFile(cssFileName) {
661 this.appendCss(null, cssFileName);
662 }
663 changeCssFile(cssFileName) {
664 if (!cssFileName) {
665 return;
666 }
667 this._cssFiles.push(cssFileName);
668 currentScopeTag = cssFileName;
669 const cssSelectors = CSSSource.fromURI(cssFileName, this._keyframes);
670 currentScopeTag = null;
671 this._css = cssSelectors.source;
672 this._localCssSelectors = cssSelectors.selectors;
673 this._localCssSelectorVersion++;
674 this.ensureSelectors();
675 }
676 setCss(cssString, cssFileName) {
677 this._css = cssString;
678 const cssFile = CSSSource.fromSource(cssString, this._keyframes, cssFileName);
679 this._localCssSelectors = cssFile.selectors;
680 this._localCssSelectorVersion++;
681 this.ensureSelectors();
682 }
683 appendCss(cssString, cssFileName) {
684 if (!cssString && !cssFileName) {
685 return;
686 }
687 if (cssFileName) {
688 this._cssFiles.push(cssFileName);
689 currentScopeTag = cssFileName;
690 }
691 const parsedCssSelectors = cssString ? CSSSource.fromSource(cssString, this._keyframes, cssFileName) : CSSSource.fromURI(cssFileName, this._keyframes);
692 currentScopeTag = null;
693 this._css = this._css + parsedCssSelectors.source;
694 this._localCssSelectors.push(...parsedCssSelectors.selectors);
695 this._localCssSelectorVersion++;
696 this.ensureSelectors();
697 }
698 getKeyframeAnimationWithName(animationName) {
699 const cssKeyframes = this._keyframes[animationName];
700 if (!cssKeyframes) {
701 return;
702 }
703 ensureKeyframeAnimationModule();
704 const animation = new keyframeAnimationModule.KeyframeAnimationInfo();
705 ensureCssAnimationParserModule();
706 animation.keyframes = cssAnimationParserModule.CssAnimationParser.keyframesArrayFromCSS(cssKeyframes.keyframes);
707 return animation;
708 }
709 ensureSelectors() {
710 if (!this.isApplicationCssSelectorsLatestVersionApplied() || !this.isLocalCssSelectorsLatestVersionApplied() || !this._mergedCssSelectors) {
711 this._createSelectors();
712 }
713 return this.getSelectorsVersion();
714 }
715 _increaseApplicationCssSelectorVersion() {
716 applicationCssSelectorVersion++;
717 }
718 isApplicationCssSelectorsLatestVersionApplied() {
719 return this._applicationCssSelectorsAppliedVersion === applicationCssSelectorVersion;
720 }
721 isLocalCssSelectorsLatestVersionApplied() {
722 return this._localCssSelectorsAppliedVersion === this._localCssSelectorVersion;
723 }
724 _createSelectors() {
725 const toMerge = [];
726 toMerge.push(applicationCssSelectors.filter((v) => !v.scopedTag || this._cssFiles.indexOf(v.scopedTag) >= 0));
727 this._applicationCssSelectorsAppliedVersion = applicationCssSelectorVersion;
728 toMerge.push(this._localCssSelectors);
729 this._localCssSelectorsAppliedVersion = this._localCssSelectorVersion;
730 for (const keyframe in applicationKeyframes) {
731 this._keyframes[keyframe] = applicationKeyframes[keyframe];
732 }
733 if (toMerge.length > 0) {
734 this._mergedCssSelectors = toMerge.reduce((merged, next) => merged.concat(next || []), []);
735 this._applyKeyframesOnSelectors();
736 this._selectors = new SelectorsMap(this._mergedCssSelectors);
737 }
738 }
739 // HACK: This @profile decorator creates a circular dependency
740 // HACK: because the function parameter type is evaluated with 'typeof'
741 matchSelectors(view) {
742 // should be (view: ViewBase): SelectorsMatch<ViewBase>
743 this.ensureSelectors();
744 return this._selectors.query(view);
745 }
746 query(node) {
747 this.ensureSelectors();
748 return this._selectors.query(node).selectors;
749 }
750 getSelectorsVersion() {
751 // The counters can only go up. So we can return just appVersion + localVersion
752 // The 100000 * appVersion is just for easier debugging
753 return 100000 * this._applicationCssSelectorsAppliedVersion + this._localCssSelectorsAppliedVersion;
754 }
755 _applyKeyframesOnSelectors() {
756 for (let i = this._mergedCssSelectors.length - 1; i >= 0; i--) {
757 const ruleset = this._mergedCssSelectors[i];
758 const animations = ruleset[animationsSymbol];
759 if (animations !== undefined && animations.length) {
760 ensureCssAnimationParserModule();
761 for (const animation of animations) {
762 const cssKeyframe = this._keyframes[animation.name];
763 if (cssKeyframe !== undefined) {
764 animation.keyframes = cssAnimationParserModule.CssAnimationParser.keyframesArrayFromCSS(cssKeyframe.keyframes);
765 }
766 }
767 }
768 }
769 }
770 getAnimations(ruleset) {
771 return ruleset[animationsSymbol];
772 }
773}
774__decorate([
775 profile,
776 __metadata("design:type", Function),
777 __metadata("design:paramtypes", [String, Object]),
778 __metadata("design:returntype", void 0)
779], StyleScope.prototype, "setCss", null);
780__decorate([
781 profile,
782 __metadata("design:type", Function),
783 __metadata("design:paramtypes", [String, Object]),
784 __metadata("design:returntype", void 0)
785], StyleScope.prototype, "appendCss", null);
786__decorate([
787 profile,
788 __metadata("design:type", Function),
789 __metadata("design:paramtypes", []),
790 __metadata("design:returntype", void 0)
791], StyleScope.prototype, "_createSelectors", null);
792__decorate([
793 profile,
794 __metadata("design:type", Function),
795 __metadata("design:paramtypes", [Object]),
796 __metadata("design:returntype", SelectorsMatch)
797], StyleScope.prototype, "matchSelectors", null);
798export function resolveFileNameFromUrl(url, appDirectory, fileExists, importSource) {
799 let fileName = typeof url === 'string' ? url.trim() : '';
800 if (fileName.indexOf('~/') === 0) {
801 fileName = fileName.replace('~/', '');
802 }
803 const isAbsolutePath = fileName.indexOf('/') === 0;
804 const absolutePath = isAbsolutePath ? fileName : path.join(appDirectory, fileName);
805 if (fileExists(absolutePath)) {
806 return absolutePath;
807 }
808 if (!isAbsolutePath) {
809 if (fileName[0] === '~' && fileName[1] !== '/' && fileName[1] !== '"') {
810 fileName = fileName.substr(1);
811 }
812 if (importSource) {
813 const importFile = resolveFilePathFromImport(importSource, fileName);
814 if (fileExists(importFile)) {
815 return importFile;
816 }
817 }
818 const external = path.join(appDirectory, 'tns_modules', fileName);
819 if (fileExists(external)) {
820 return external;
821 }
822 }
823 return null;
824}
825function resolveFilePathFromImport(importSource, fileName) {
826 const importSourceParts = importSource.split(path.separator);
827 const fileNameParts = fileName
828 .split(path.separator)
829 // exclude the dot-segment for current directory
830 .filter((p) => !isCurrentDirectory(p));
831 // remove current file name
832 importSourceParts.pop();
833 // remove element in case of dot-segment for parent directory or add file name
834 fileNameParts.forEach((p) => (isParentDirectory(p) ? importSourceParts.pop() : importSourceParts.push(p)));
835 return importSourceParts.join(path.separator);
836}
837export const applyInlineStyle = profile(function applyInlineStyle(view, styleStr) {
838 const localStyle = `local { ${styleStr} }`;
839 const inlineRuleSet = CSSSource.fromSource(localStyle, new Map()).selectors;
840 // Reset unscoped css-variables
841 view.style.resetUnscopedCssVariables();
842 // Set all the css-variables first, so we can be sure they are up-to-date
843 inlineRuleSet[0].declarations.forEach((d) => {
844 // Use the actual property name so that a local value is set.
845 const property = d.property;
846 if (isCssVariable(property)) {
847 view.style.setUnscopedCssVariable(property, d.value);
848 }
849 });
850 inlineRuleSet[0].declarations.forEach((d) => {
851 // Use the actual property name so that a local value is set.
852 const property = d.property;
853 try {
854 if (isCssVariable(property)) {
855 // Skip css-variables, they have been handled
856 return;
857 }
858 const value = evaluateCssExpressions(view, property, d.value);
859 if (property in view.style) {
860 view.style[property] = value;
861 }
862 else {
863 view[property] = value;
864 }
865 }
866 catch (e) {
867 Trace.write(`Failed to apply property [${d.property}] with value [${d.value}] to ${view}. ${e}`, Trace.categories.Error, Trace.messageType.error);
868 }
869 });
870 // This is needed in case of changes to css-variable or css-calc expressions.
871 view._onCssStateChange();
872});
873function isCurrentDirectory(uriPart) {
874 return uriPart === '.';
875}
876function isParentDirectory(uriPart) {
877 return uriPart === '..';
878}
879function isKeyframe(node) {
880 return node.type === 'keyframes';
881}
882//# sourceMappingURL=style-scope.js.map
\No newline at end of file