mirror of
https://github.com/NativeScript/NativeScript.git
synced 2025-11-05 13:26:48 +08:00
Add parsers for the background css shorthand property, make ViewBase unit testable in node environment
Add background parser and linear-gradient parser
Use sticky regexes
Simplify some types, introduce generic Parsed<T> instead of & TokenRange
Apply each parser to return a { start, end, value } object
Move the css selector parser to the css/parser and unify types
Add the first steps toward building homegrown css parser
Add somewhat standards compliant tokenizer, add baseline, rework and shady css parsers
Enable all tests again, skip flaky perf test
Improve css parser tokenizer by converting some char token types to simple string
Implement 'parse a stylesheet'
Add gonzales css-parser
Add parseLib and css-tree perf
Add a thin parser layer that will convert CSS3 tokens to values, for now output is compatible with rework
Make root tsc green
Return the requires of tns-core-modules to use relative paths for webpack to work
Implement support for '@import 'url-string';
Fix function parser, function-token is no-longer neglected
Make the style-scope be able to load from "css" and "css-ast" modules
Add a loadAppCss event so theme can be added to snapshot separately from loaded
666 lines
23 KiB
TypeScript
666 lines
23 KiB
TypeScript
import { Keyframes } from "../animation/keyframe-animation";
|
|
import { ViewBase } from "../core/view-base";
|
|
import { View } from "../core/view";
|
|
import { unsetValue } from "../core/properties";
|
|
import {
|
|
SyntaxTree,
|
|
Keyframes as KeyframesDefinition,
|
|
parse as parseCss,
|
|
Node as CssNode,
|
|
} from "../../css";
|
|
import {
|
|
CSS3Parser,
|
|
CSSNativeScript
|
|
} from "../../css/parser";
|
|
|
|
import {
|
|
RuleSet,
|
|
SelectorsMap,
|
|
SelectorCore,
|
|
SelectorsMatch,
|
|
ChangeMap,
|
|
fromAstNodes,
|
|
Node,
|
|
} from "./css-selector";
|
|
import {
|
|
write as traceWrite,
|
|
categories as traceCategories,
|
|
messageType as traceMessageType,
|
|
} from "../../trace";
|
|
import { File, knownFolders, path } from "../../file-system";
|
|
import * as application from "../../application";
|
|
import { profile } from "../../profiling";
|
|
|
|
import * as kam from "../animation/keyframe-animation";
|
|
let keyframeAnimationModule: typeof kam;
|
|
function ensureKeyframeAnimationModule() {
|
|
if (!keyframeAnimationModule) {
|
|
keyframeAnimationModule = require("ui/animation/keyframe-animation");
|
|
}
|
|
}
|
|
|
|
import * as capm from "./css-animation-parser";
|
|
let cssAnimationParserModule: typeof capm;
|
|
function ensureCssAnimationParserModule() {
|
|
if (!cssAnimationParserModule) {
|
|
cssAnimationParserModule = require("./css-animation-parser");
|
|
}
|
|
}
|
|
|
|
let parser: "rework" | "nativescript" = "rework";
|
|
try {
|
|
const appConfig = require("~/package.json");
|
|
if (appConfig && appConfig.cssParser === "nativescript") {
|
|
parser = "nativescript";
|
|
}
|
|
} catch(e) {
|
|
//
|
|
}
|
|
|
|
export function mergeCssSelectors(): void {
|
|
applicationCssSelectors = applicationSelectors.slice();
|
|
applicationCssSelectors.push.apply(applicationCssSelectors, applicationAdditionalSelectors);
|
|
applicationCssSelectorVersion++;
|
|
}
|
|
|
|
let applicationCssSelectors: RuleSet[] = [];
|
|
let applicationCssSelectorVersion: number = 0;
|
|
let applicationSelectors: RuleSet[] = [];
|
|
const applicationAdditionalSelectors: RuleSet[] = [];
|
|
const applicationKeyframes: any = {};
|
|
const animationsSymbol: symbol = Symbol("animations");
|
|
const pattern: RegExp = /('|")(.*?)\1/;
|
|
|
|
class CSSSource {
|
|
private _selectors: RuleSet[] = [];
|
|
private static cssFilesCache: { [path: string]: CSSSource } = {};
|
|
|
|
private constructor(private _ast: SyntaxTree, private _url: string, private _file: string, private _keyframes: KeyframesMap, private _source: string) {
|
|
this.parse();
|
|
}
|
|
|
|
public static fromURI(uri: string, keyframes: KeyframesMap): CSSSource {
|
|
try {
|
|
const cssOrAst = global.loadModule(uri);
|
|
if (cssOrAst) {
|
|
if (typeof cssOrAst === "string") {
|
|
return CSSSource.fromSource(cssOrAst, keyframes, uri);
|
|
} else if (typeof cssOrAst === "object" && cssOrAst.type === "stylesheet" && cssOrAst.stylesheet && cssOrAst.stylesheet.rules) {
|
|
return CSSSource.fromAST(cssOrAst, keyframes, uri);
|
|
} else {
|
|
// Probably a webpack css-loader exported object.
|
|
return CSSSource.fromSource(cssOrAst.toString(), keyframes, uri);
|
|
}
|
|
}
|
|
} catch(e) {
|
|
//
|
|
}
|
|
return CSSSource.fromFile(uri, keyframes);
|
|
}
|
|
|
|
public static fromFile(url: string, keyframes: KeyframesMap): CSSSource {
|
|
const file = CSSSource.resolveCSSPathFromURL(url);
|
|
return new CSSSource(undefined, url, file, keyframes, undefined);
|
|
}
|
|
|
|
@profile
|
|
public static resolveCSSPathFromURL(url: string): string {
|
|
const app = knownFolders.currentApp().path;
|
|
const file = resolveFileNameFromUrl(url, app, File.exists);
|
|
return file;
|
|
}
|
|
|
|
public static fromSource(source: string, keyframes: KeyframesMap, url?: string): CSSSource {
|
|
return new CSSSource(undefined, url, undefined, keyframes, source);
|
|
}
|
|
|
|
public static fromAST(ast: SyntaxTree, keyframes: KeyframesMap, url?: string): CSSSource {
|
|
return new CSSSource(ast, url, undefined, keyframes, undefined);
|
|
}
|
|
|
|
get selectors(): RuleSet[] { return this._selectors; }
|
|
get source(): string { return this._source; }
|
|
|
|
@profile
|
|
private load(): void {
|
|
const file = File.fromPath(this._file);
|
|
this._source = file.readTextSync();
|
|
}
|
|
|
|
@profile
|
|
private parse(): void {
|
|
try {
|
|
if (!this._ast) {
|
|
if (!this._source && this._file) {
|
|
this.load();
|
|
}
|
|
if (this._source) {
|
|
this.parseCSSAst();
|
|
}
|
|
}
|
|
if (this._ast) {
|
|
this.createSelectors();
|
|
} else {
|
|
this._selectors = [];
|
|
}
|
|
} catch (e) {
|
|
traceWrite("Css styling failed: " + e, traceCategories.Error, traceMessageType.error);
|
|
this._selectors = [];
|
|
}
|
|
}
|
|
|
|
@profile
|
|
private parseCSSAst() {
|
|
if (this._source) {
|
|
switch(parser) {
|
|
case "nativescript":
|
|
const cssparser = new CSS3Parser(this._source);
|
|
const stylesheet = cssparser.parseAStylesheet();
|
|
const cssNS = new CSSNativeScript();
|
|
this._ast = cssNS.parseStylesheet(stylesheet);
|
|
return;
|
|
case "rework":
|
|
this._ast = parseCss(this._source, { source: this._file });
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
@profile
|
|
private createSelectors() {
|
|
if (this._ast) {
|
|
this._selectors = [
|
|
...this.createSelectorsFromImports(),
|
|
...this.createSelectorsFromSyntaxTree()
|
|
];
|
|
}
|
|
}
|
|
|
|
private createSelectorsFromImports(): RuleSet[] {
|
|
let selectors: RuleSet[] = [];
|
|
const imports = this._ast["stylesheet"]["rules"].filter(r => r.type === "import");
|
|
for (let i = 0; i < imports.length; i++) {
|
|
const importItem = imports[i]["import"];
|
|
|
|
const match = importItem && (<string>importItem).match(pattern);
|
|
const url = match && match[2];
|
|
|
|
if (url !== null && url !== undefined) {
|
|
const cssFile = CSSSource.fromURI(url, this._keyframes);
|
|
selectors = selectors.concat(cssFile.selectors);
|
|
}
|
|
}
|
|
|
|
return selectors;
|
|
}
|
|
|
|
private createSelectorsFromSyntaxTree(): RuleSet[] {
|
|
const nodes = this._ast.stylesheet.rules;
|
|
(<KeyframesDefinition[]>nodes.filter(isKeyframe)).forEach(node => this._keyframes[node.name] = node);
|
|
|
|
const rulesets = fromAstNodes(nodes);
|
|
if (rulesets && rulesets.length) {
|
|
ensureCssAnimationParserModule();
|
|
|
|
rulesets.forEach(rule => {
|
|
rule[animationsSymbol] = cssAnimationParserModule.CssAnimationParser
|
|
.keyframeAnimationsFromCSSDeclarations(rule.declarations);
|
|
});
|
|
}
|
|
|
|
return rulesets;
|
|
}
|
|
|
|
toString(): string {
|
|
return this._file || this._url || "(in-memory)";
|
|
}
|
|
}
|
|
|
|
const onCssChanged = profile('"style-scope".onCssChanged', (args: application.CssChangedEventData) => {
|
|
if (args.cssText) {
|
|
const parsed = CSSSource.fromSource(args.cssText, applicationKeyframes, args.cssFile).selectors;
|
|
if (parsed) {
|
|
applicationAdditionalSelectors.push.apply(applicationAdditionalSelectors, parsed);
|
|
mergeCssSelectors();
|
|
}
|
|
} else if (args.cssFile) {
|
|
loadCss(args.cssFile);
|
|
}
|
|
});
|
|
|
|
function onLiveSync(args: application.CssChangedEventData): void {
|
|
loadCss(application.getCssFileName());
|
|
}
|
|
|
|
const loadCss = profile(`"style-scope".loadCss`, (cssFile: string) => {
|
|
if (!cssFile) {
|
|
return undefined;
|
|
}
|
|
|
|
const result = CSSSource.fromURI(cssFile, applicationKeyframes).selectors;
|
|
if (result.length > 0) {
|
|
applicationSelectors = result;
|
|
mergeCssSelectors();
|
|
}
|
|
});
|
|
|
|
application.on("cssChanged", onCssChanged);
|
|
application.on("livesync", onLiveSync);
|
|
|
|
export const loadAppCSS = profile('"style-scope".loadAppCSS', (args: application.LoadAppCSSEventData) => {
|
|
loadCss(args.cssFile);
|
|
application.off("loadAppCss", loadAppCSS);
|
|
});
|
|
|
|
if (application.hasLaunched()) {
|
|
loadAppCSS({ eventName: "loadAppCss", object: <any>application, cssFile: application.getCssFileName() });
|
|
} else {
|
|
application.on("loadAppCss", loadAppCSS);
|
|
}
|
|
|
|
export class CssState {
|
|
static emptyChangeMap: Readonly<ChangeMap<ViewBase>> = Object.freeze(new Map());
|
|
static emptyPropertyBag: Readonly<{}> = Object.freeze({});
|
|
static emptyAnimationArray: ReadonlyArray<kam.KeyframeAnimation> = Object.freeze([]);
|
|
static emptyMatch: Readonly<SelectorsMatch<ViewBase>> = { selectors: [], changeMap: new Map() };
|
|
|
|
_onDynamicStateChangeHandler: () => void;
|
|
_appliedChangeMap: Readonly<ChangeMap<ViewBase>>;
|
|
_appliedPropertyValues: Readonly<{}>;
|
|
_appliedAnimations: ReadonlyArray<kam.KeyframeAnimation>;
|
|
|
|
_match: SelectorsMatch<ViewBase>;
|
|
_matchInvalid: boolean;
|
|
_playsKeyframeAnimations: boolean;
|
|
|
|
constructor(private view: ViewBase) {
|
|
this._onDynamicStateChangeHandler = () => this.updateDynamicState();
|
|
}
|
|
|
|
/**
|
|
* Called when a change had occurred that may invalidate the statically matching selectors (class, id, ancestor selectors).
|
|
* As a result, at some point in time, the selectors matched have to be requerried from the style scope and applied to the view.
|
|
*/
|
|
public onChange(): void {
|
|
if (this.view.isLoaded) {
|
|
this.unsubscribeFromDynamicUpdates();
|
|
this.updateMatch();
|
|
this.subscribeForDynamicUpdates();
|
|
this.updateDynamicState();
|
|
} else {
|
|
this._matchInvalid = true;
|
|
}
|
|
}
|
|
|
|
public onLoaded(): void {
|
|
if (this._matchInvalid) {
|
|
this.updateMatch();
|
|
}
|
|
this.subscribeForDynamicUpdates();
|
|
this.updateDynamicState();
|
|
}
|
|
|
|
public onUnloaded(): void {
|
|
this.unsubscribeFromDynamicUpdates();
|
|
}
|
|
|
|
@profile
|
|
private updateMatch() {
|
|
this._match = this.view._styleScope ? this.view._styleScope.matchSelectors(this.view) : CssState.emptyMatch;
|
|
this._matchInvalid = false;
|
|
}
|
|
|
|
@profile
|
|
private updateDynamicState(): void {
|
|
const matchingSelectors = this._match.selectors.filter(sel => sel.dynamic ? sel.match(this.view) : true);
|
|
|
|
this.view._batchUpdate(() => {
|
|
this.stopKeyframeAnimations();
|
|
this.setPropertyValues(matchingSelectors);
|
|
this.playKeyframeAnimations(matchingSelectors);
|
|
});
|
|
}
|
|
|
|
private playKeyframeAnimations(matchingSelectors: SelectorCore[]): void {
|
|
const animations: kam.KeyframeAnimation[] = [];
|
|
|
|
matchingSelectors.forEach(selector => {
|
|
let ruleAnimations: kam.KeyframeAnimationInfo[] = selector.ruleset[animationsSymbol];
|
|
if (ruleAnimations) {
|
|
ensureKeyframeAnimationModule();
|
|
for (let animationInfo of ruleAnimations) {
|
|
let animation = keyframeAnimationModule.KeyframeAnimation.keyframeAnimationFromInfo(animationInfo);
|
|
if (animation) {
|
|
animations.push(animation);
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
if (this._playsKeyframeAnimations = animations.length > 0) {
|
|
animations.map(animation => animation.play(<View>this.view));
|
|
Object.freeze(animations);
|
|
this._appliedAnimations = animations;
|
|
}
|
|
}
|
|
|
|
private stopKeyframeAnimations(): void {
|
|
if (!this._playsKeyframeAnimations) {
|
|
return;
|
|
}
|
|
|
|
this._appliedAnimations
|
|
.filter(animation => animation.isPlaying)
|
|
.forEach(animation => animation.cancel());
|
|
this._appliedAnimations = CssState.emptyAnimationArray;
|
|
|
|
this.view.style["keyframe:rotate"] = unsetValue;
|
|
this.view.style["keyframe:scaleX"] = unsetValue;
|
|
this.view.style["keyframe:scaleY"] = unsetValue;
|
|
this.view.style["keyframe:translateX"] = unsetValue;
|
|
this.view.style["keyframe:translateY"] = unsetValue;
|
|
this.view.style["keyframe:backgroundColor"] = unsetValue;
|
|
this.view.style["keyframe:opacity"] = unsetValue;
|
|
this._playsKeyframeAnimations = false;
|
|
}
|
|
|
|
/**
|
|
* Calculate the difference between the previously applied property values,
|
|
* and the new set of property values that have to be applied for the provided selectors.
|
|
* Apply the values and ensure each property setter is called at most once to avoid excessive change notifications.
|
|
* @param matchingSelectors
|
|
*/
|
|
private setPropertyValues(matchingSelectors: SelectorCore[]): void {
|
|
const newPropertyValues = new this.view.style.PropertyBag();
|
|
matchingSelectors.forEach(selector =>
|
|
selector.ruleset.declarations.forEach(declaration =>
|
|
newPropertyValues[declaration.property] = declaration.value));
|
|
Object.freeze(newPropertyValues);
|
|
|
|
const oldProperties = this._appliedPropertyValues;
|
|
for(const key in oldProperties) {
|
|
if (!(key in newPropertyValues)) {
|
|
if (key in this.view.style) {
|
|
this.view.style[`css:${key}`] = unsetValue;
|
|
} else {
|
|
// TRICKY: How do we unset local value?
|
|
}
|
|
}
|
|
}
|
|
for(const property in newPropertyValues) {
|
|
if (oldProperties && property in oldProperties && oldProperties[property] === newPropertyValues[property]) {
|
|
continue;
|
|
}
|
|
const value = newPropertyValues[property];
|
|
try {
|
|
if (property in this.view.style) {
|
|
this.view.style[`css:${property}`] = value;
|
|
} else {
|
|
this.view[property] = value;
|
|
}
|
|
} catch (e) {
|
|
traceWrite(`Failed to apply property [${property}] with value [${value}] to ${this.view}. ${e}`, traceCategories.Error, traceMessageType.error);
|
|
}
|
|
}
|
|
|
|
this._appliedPropertyValues = newPropertyValues;
|
|
}
|
|
|
|
private subscribeForDynamicUpdates(): void {
|
|
const changeMap = this._match.changeMap;
|
|
changeMap.forEach((changes, view) => {
|
|
if (changes.attributes) {
|
|
changes.attributes.forEach(attribute => {
|
|
view.addEventListener(attribute + "Change", this._onDynamicStateChangeHandler);
|
|
});
|
|
}
|
|
if (changes.pseudoClasses) {
|
|
changes.pseudoClasses.forEach(pseudoClass => {
|
|
let eventName = ":" + pseudoClass;
|
|
view.addEventListener(":" + pseudoClass, this._onDynamicStateChangeHandler);
|
|
if (view[eventName]) {
|
|
view[eventName](+1);
|
|
}
|
|
});
|
|
}
|
|
});
|
|
this._appliedChangeMap = changeMap;
|
|
}
|
|
|
|
private unsubscribeFromDynamicUpdates(): void {
|
|
this._appliedChangeMap.forEach((changes, view) => {
|
|
if (changes.attributes) {
|
|
changes.attributes.forEach(attribute => {
|
|
view.removeEventListener("onPropertyChanged:" + attribute, this._onDynamicStateChangeHandler);
|
|
});
|
|
}
|
|
if (changes.pseudoClasses) {
|
|
changes.pseudoClasses.forEach(pseudoClass => {
|
|
let eventName = ":" + pseudoClass;
|
|
view.removeEventListener(eventName, this._onDynamicStateChangeHandler);
|
|
if (view[eventName]) {
|
|
view[eventName](-1);
|
|
}
|
|
});
|
|
}
|
|
});
|
|
this._appliedChangeMap = CssState.emptyChangeMap;
|
|
}
|
|
|
|
toString(): string {
|
|
return `${this.view}._cssState`;
|
|
}
|
|
}
|
|
CssState.prototype._appliedChangeMap = CssState.emptyChangeMap;
|
|
CssState.prototype._appliedPropertyValues = CssState.emptyPropertyBag;
|
|
CssState.prototype._appliedAnimations = CssState.emptyAnimationArray;
|
|
CssState.prototype._matchInvalid = true;
|
|
|
|
export class StyleScope {
|
|
|
|
private _selectors: SelectorsMap;
|
|
|
|
// caches all the visual states by the key of the visual state selectors
|
|
private _statesByKey = {};
|
|
private _viewIdToKey = {};
|
|
|
|
private _css: string = "";
|
|
private _cssFileName: string;
|
|
private _mergedCssSelectors: RuleSet[];
|
|
private _localCssSelectors: RuleSet[] = [];
|
|
private _localCssSelectorVersion: number = 0;
|
|
private _localCssSelectorsAppliedVersion: number = 0;
|
|
private _applicationCssSelectorsAppliedVersion: number = 0;
|
|
private _keyframes = new Map<string, Keyframes>();
|
|
|
|
get css(): string {
|
|
return this._css;
|
|
}
|
|
|
|
set css(value: string) {
|
|
this._cssFileName = undefined;
|
|
this.setCss(value);
|
|
}
|
|
|
|
public addCss(cssString: string, cssFileName?: string): void {
|
|
this.appendCss(cssString, cssFileName)
|
|
}
|
|
|
|
public addCssFile(cssFileName: string): void {
|
|
this.appendCss(null, cssFileName);
|
|
}
|
|
|
|
@profile
|
|
private setCss(cssString: string, cssFileName?): void {
|
|
this._css = cssString;
|
|
this._reset();
|
|
|
|
const cssFile = CSSSource.fromSource(cssString, this._keyframes, cssFileName);
|
|
this._localCssSelectors = cssFile.selectors;
|
|
this._localCssSelectorVersion++;
|
|
this.ensureSelectors();
|
|
}
|
|
|
|
@profile
|
|
private appendCss(cssString: string, cssFileName?): void {
|
|
if (!cssString && !cssFileName) {
|
|
return;
|
|
}
|
|
|
|
this._reset();
|
|
let parsedCssSelectors = cssString ? CSSSource.fromSource(cssString, this._keyframes, cssFileName) : CSSSource.fromFile(cssFileName, this._keyframes);
|
|
this._css = this._css + parsedCssSelectors.source;
|
|
this._localCssSelectors.push.apply(this._localCssSelectors, parsedCssSelectors.selectors);
|
|
this._localCssSelectorVersion++;
|
|
this.ensureSelectors();
|
|
}
|
|
|
|
public getKeyframeAnimationWithName(animationName: string): kam.KeyframeAnimationInfo {
|
|
const cssKeyframes = this._keyframes[animationName];
|
|
if (!cssKeyframes) {
|
|
return;
|
|
}
|
|
|
|
ensureKeyframeAnimationModule();
|
|
const animation = new keyframeAnimationModule.KeyframeAnimationInfo();
|
|
ensureCssAnimationParserModule();
|
|
animation.keyframes = cssAnimationParserModule
|
|
.CssAnimationParser.keyframesArrayFromCSS(cssKeyframes.keyframes);
|
|
|
|
return animation;
|
|
}
|
|
|
|
public ensureSelectors(): number {
|
|
if (this._applicationCssSelectorsAppliedVersion !== applicationCssSelectorVersion ||
|
|
this._localCssSelectorVersion !== this._localCssSelectorsAppliedVersion ||
|
|
!this._mergedCssSelectors) {
|
|
|
|
this._createSelectors();
|
|
}
|
|
|
|
return this._getSelectorsVersion();
|
|
}
|
|
|
|
@profile
|
|
private _createSelectors() {
|
|
let toMerge: RuleSet[][] = [];
|
|
toMerge.push(applicationCssSelectors);
|
|
this._applicationCssSelectorsAppliedVersion = applicationCssSelectorVersion;
|
|
toMerge.push(this._localCssSelectors);
|
|
this._localCssSelectorsAppliedVersion = this._localCssSelectorVersion;
|
|
for (let keyframe in applicationKeyframes) {
|
|
this._keyframes[keyframe] = applicationKeyframes[keyframe];
|
|
}
|
|
|
|
if (toMerge.length > 0) {
|
|
this._mergedCssSelectors = toMerge.filter(m => !!m).reduce((merged, next) => merged.concat(next), []);
|
|
this._applyKeyframesOnSelectors();
|
|
this._selectors = new SelectorsMap(this._mergedCssSelectors);
|
|
}
|
|
}
|
|
|
|
@profile
|
|
public matchSelectors(view: ViewBase): SelectorsMatch<ViewBase> {
|
|
this.ensureSelectors();
|
|
return this._selectors.query(view);
|
|
}
|
|
|
|
public query(node: Node): SelectorCore[] {
|
|
this.ensureSelectors();
|
|
return this._selectors.query(node).selectors;
|
|
}
|
|
|
|
private _reset() {
|
|
this._statesByKey = {};
|
|
this._viewIdToKey = {};
|
|
}
|
|
|
|
private _getSelectorsVersion() {
|
|
// The counters can only go up. So we can return just appVersion + localVersion
|
|
// The 100000 * appVersion is just for easier debugging
|
|
return 100000 * this._applicationCssSelectorsAppliedVersion + this._localCssSelectorsAppliedVersion;
|
|
}
|
|
|
|
private _applyKeyframesOnSelectors() {
|
|
for (let i = this._mergedCssSelectors.length - 1; i >= 0; i--) {
|
|
let ruleset = this._mergedCssSelectors[i];
|
|
let animations: kam.KeyframeAnimationInfo[] = ruleset[animationsSymbol];
|
|
if (animations !== undefined && animations.length) {
|
|
ensureCssAnimationParserModule();
|
|
for (let animation of animations) {
|
|
const cssKeyframe = this._keyframes[animation.name];
|
|
if (cssKeyframe !== undefined) {
|
|
animation.keyframes = cssAnimationParserModule
|
|
.CssAnimationParser.keyframesArrayFromCSS(cssKeyframe.keyframes);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public getAnimations(ruleset: RuleSet): kam.KeyframeAnimationInfo[] {
|
|
return ruleset[animationsSymbol];
|
|
}
|
|
}
|
|
|
|
type KeyframesMap = Map<string, Keyframes>;
|
|
|
|
export function resolveFileNameFromUrl(url: string, appDirectory: string, fileExists: (name: string) => boolean): string {
|
|
let fileName: string = typeof url === "string" ? url.trim() : "";
|
|
|
|
if (fileName.indexOf("~/") === 0) {
|
|
fileName = fileName.replace("~/", "");
|
|
}
|
|
|
|
const isAbsolutePath = fileName.indexOf("/") === 0;
|
|
const absolutePath = isAbsolutePath ? fileName : path.join(appDirectory, fileName);
|
|
if (fileExists(absolutePath)) {
|
|
return absolutePath;
|
|
}
|
|
|
|
if (!isAbsolutePath) {
|
|
const external = path.join(appDirectory, "tns_modules", fileName);
|
|
if (fileExists(external)) {
|
|
return external;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
export const applyInlineStyle = profile(function applyInlineStyle(view: ViewBase, styleStr: string) {
|
|
let localStyle = `local { ${styleStr} }`;
|
|
let inlineRuleSet = CSSSource.fromSource(localStyle, new Map()).selectors;
|
|
const style = view.style;
|
|
|
|
inlineRuleSet[0].declarations.forEach(d => {
|
|
// Use the actual property name so that a local value is set.
|
|
let name = d.property;
|
|
try {
|
|
if (name in style) {
|
|
style[name] = d.value;
|
|
} else {
|
|
view[name] = d.value;
|
|
}
|
|
} catch (e) {
|
|
traceWrite(`Failed to apply property [${d.property}] with value [${d.value}] to ${view}. ${e}`, traceCategories.Error, traceMessageType.error);
|
|
}
|
|
});
|
|
});
|
|
|
|
function isKeyframe(node: CssNode): node is KeyframesDefinition {
|
|
return node.type === "keyframes";
|
|
}
|
|
|
|
class InlineSelector implements SelectorCore {
|
|
constructor(ruleSet: RuleSet) {
|
|
this.ruleset = ruleSet;
|
|
}
|
|
|
|
public specificity = 0x01000000;
|
|
public rarity = 0;
|
|
public dynamic: boolean = false;
|
|
public ruleset: RuleSet;
|
|
public match(node: Node): boolean { return true; }
|
|
}
|