mirror of
https://github.com/NativeScript/NativeScript.git
synced 2025-08-15 19:26:42 +08:00

* chore: move tns-core-modules to nativescript-core * chore: preparing compat generate script * chore: add missing definitions * chore: no need for http-request to be private * chore: packages chore * test: generate tests for tns-core-modules * chore: add anroid module for consistency * chore: add .npmignore * chore: added privateModulesWhitelist * chore(webpack): added bundle-entry-points * chore: scripts * chore: tests changed to use @ns/core * test: add scoped-packages test project * test: fix types * test: update test project * chore: build scripts * chore: update build script * chore: npm scripts cleanup * chore: make the compat pgk work with old wp config * test: generate diff friendly tests * chore: create barrel exports * chore: move files after rebase * chore: typedoc config * chore: compat mode * chore: review of barrels * chore: remove tns-core-modules import after rebase * chore: dev workflow setup * chore: update developer-workflow * docs: experiment with API extractor * chore: api-extractor and barrel exports * chore: api-extractor configs * chore: generate d.ts rollup with api-extractor * refactor: move methods inside Frame * chore: fic tests to use Frame static methods * refactor: create Builder class * refactor: use Builder class in tests * refactor: include Style in ui barrel * chore: separate compat build script * chore: fix tslint errors * chore: update NATIVESCRIPT_CORE_ARGS * chore: fix compat pack * chore: fix ui-test-app build with linked modules * chore: Application, ApplicationSettings, Connectivity and Http * chore: export Trace, Profiling and Utils * refactor: Static create methods for ImageSource * chore: fix deprecated usages of ImageSource * chore: move Span and FormattedString to ui * chore: add events-args and ImageSource to index files * chore: check for CLI >= 6.2 when building for IOS * chore: update travis build * chore: copy Pod file to compat package * chore: update error msg ui-tests-app * refactor: Apply suggestions from code review Co-Authored-By: Martin Yankov <m.i.yankov@gmail.com> * chore: typings and refs * chore: add missing d.ts files for public API * chore: adress code review FB * chore: update api-report * chore: dev-workflow for other apps * chore: api update * chore: update api-report
1444 lines
53 KiB
TypeScript
1444 lines
53 KiB
TypeScript
import * as reduceCSSCalc from "reduce-css-calc";
|
|
|
|
// Definitions.
|
|
import * as definitions from "../view-base";
|
|
import { ViewBase } from "../view-base";
|
|
|
|
// Types.
|
|
import { WrappedValue, PropertyChangeData } from "../../../data/observable";
|
|
import {
|
|
write as traceWrite,
|
|
categories as traceCategories,
|
|
messageType as traceMessageType,
|
|
} from "../../../trace";
|
|
|
|
import { Style } from "../../styling/style";
|
|
|
|
import { profile } from "../../../profiling";
|
|
|
|
export { Style };
|
|
|
|
export const unsetValue: any = new Object();
|
|
|
|
let cssPropertyNames: string[] = [];
|
|
let symbolPropertyMap = {};
|
|
let cssSymbolPropertyMap = {};
|
|
|
|
let inheritableProperties = new Array<InheritedProperty<any, any>>();
|
|
let inheritableCssProperties = new Array<InheritedCssProperty<any, any>>();
|
|
|
|
function print(map) {
|
|
let symbols = Object.getOwnPropertySymbols(map);
|
|
for (let symbol of symbols) {
|
|
const prop = map[symbol];
|
|
if (!prop.registered) {
|
|
console.log(`Property ${prop.name} not Registered!!!!!`);
|
|
}
|
|
}
|
|
}
|
|
|
|
export function _printUnregisteredProperties(): void {
|
|
print(symbolPropertyMap);
|
|
print(cssSymbolPropertyMap);
|
|
}
|
|
|
|
const enum ValueSource {
|
|
Default = 0,
|
|
Inherited = 1,
|
|
Css = 2,
|
|
Local = 3,
|
|
Keyframe = 4
|
|
}
|
|
|
|
export function _getProperties(): Property<any, any>[] {
|
|
return getPropertiesFromMap(symbolPropertyMap) as Property<any, any>[];
|
|
}
|
|
|
|
export function _getStyleProperties(): CssProperty<any, any>[] {
|
|
return getPropertiesFromMap(cssSymbolPropertyMap) as CssProperty<any, any>[];
|
|
}
|
|
|
|
const cssVariableExpressionRegexp = /\bvar\(\s*(--[^,\s]+?)(?:\s*,\s*(.+))?\s*\)/;
|
|
const cssVariableAllExpressionsRegexp = /\bvar\(\s*(--[^,\s]+?)(?:\s*,\s*(.+))?\s*\)/g;
|
|
|
|
export function isCssVariable(property: string) {
|
|
return /^--[^,\s]+?$/.test(property);
|
|
}
|
|
|
|
export function isCssCalcExpression(value: string) {
|
|
return /\bcalc\(/.test(value);
|
|
}
|
|
|
|
export function isCssVariableExpression(value: string) {
|
|
return cssVariableExpressionRegexp.test(value);
|
|
}
|
|
|
|
export function _evaluateCssVariableExpression(view: ViewBase, cssName: string, value: string): string {
|
|
if (typeof value !== "string") {
|
|
return value;
|
|
}
|
|
|
|
if (!isCssVariableExpression(value)) {
|
|
// Value is not using css-variable(s)
|
|
return value;
|
|
}
|
|
|
|
let output = value.trim();
|
|
|
|
// Evaluate every (and nested) css-variables in the value.
|
|
let lastValue: string;
|
|
while (lastValue !== output) {
|
|
lastValue = output;
|
|
|
|
output = output.replace(cssVariableAllExpressionsRegexp, (matchStr, cssVariableName: string, fallbackStr: string) => {
|
|
const cssVariableValue = view.style.getCssVariable(cssVariableName);
|
|
if (cssVariableValue !== null) {
|
|
return cssVariableValue;
|
|
}
|
|
|
|
if (fallbackStr) {
|
|
// css-variable not found, using fallback-string.
|
|
const fallbackOutput = _evaluateCssVariableExpression(view, cssName, fallbackStr);
|
|
if (fallbackOutput) {
|
|
// If the fallback have multiple values, return the first of them.
|
|
return fallbackOutput.split(",")[0];
|
|
}
|
|
}
|
|
|
|
// Couldn't find a value for the css-variable or the fallback, return "unset"
|
|
traceWrite(`Failed to get value for css-variable "${cssVariableName}" used in "${cssName}"=[${value}] to ${view}`, traceCategories.Style, traceMessageType.error);
|
|
|
|
return "unset";
|
|
});
|
|
}
|
|
|
|
return output;
|
|
}
|
|
|
|
export function _evaluateCssCalcExpression(value: string) {
|
|
if (typeof value !== "string") {
|
|
return value;
|
|
}
|
|
|
|
if (isCssCalcExpression(value)) {
|
|
// WORKAROUND: reduce-css-calc can't handle the dip-unit.
|
|
return reduceCSSCalc(value.replace(/([0-9]+(\.[0-9]+)?)dip\b/g, "$1"));
|
|
} else {
|
|
return value;
|
|
}
|
|
}
|
|
|
|
function getPropertiesFromMap(map): Property<any, any>[] | CssProperty<any, any>[] {
|
|
const props = [];
|
|
Object.getOwnPropertySymbols(map).forEach(symbol => props.push(map[symbol]));
|
|
|
|
return props;
|
|
}
|
|
|
|
export class Property<T extends ViewBase, U> implements TypedPropertyDescriptor<U>, definitions.Property<T, U> {
|
|
private registered: boolean;
|
|
|
|
public readonly name: string;
|
|
public readonly key: symbol;
|
|
|
|
public readonly getDefault: symbol;
|
|
public readonly setNative: symbol;
|
|
|
|
public readonly defaultValueKey: symbol;
|
|
public readonly defaultValue: U;
|
|
public readonly nativeValueChange: (owner: T, value: U) => void;
|
|
|
|
public isStyleProperty: boolean;
|
|
|
|
public get: () => U;
|
|
public set: (value: U) => void;
|
|
public enumerable: boolean = true;
|
|
public configurable: boolean = true;
|
|
|
|
constructor(options: definitions.PropertyOptions<T, U>) {
|
|
const propertyName = options.name;
|
|
this.name = propertyName;
|
|
|
|
const key = Symbol(propertyName + ":propertyKey");
|
|
this.key = key;
|
|
|
|
const getDefault: symbol = Symbol(propertyName + ":getDefault");
|
|
this.getDefault = getDefault;
|
|
|
|
const setNative: symbol = Symbol(propertyName + ":setNative");
|
|
this.setNative = setNative;
|
|
|
|
const defaultValueKey = Symbol(propertyName + ":nativeDefaultValue");
|
|
this.defaultValueKey = defaultValueKey;
|
|
|
|
const defaultValue: U = options.defaultValue;
|
|
this.defaultValue = defaultValue;
|
|
|
|
const eventName = propertyName + "Change";
|
|
const equalityComparer = options.equalityComparer;
|
|
const affectsLayout: boolean = options.affectsLayout;
|
|
const valueChanged = options.valueChanged;
|
|
const valueConverter = options.valueConverter;
|
|
|
|
const property = this;
|
|
|
|
this.set = function (this: T, boxedValue: U): void {
|
|
const reset = boxedValue === unsetValue;
|
|
let value: U;
|
|
let wrapped: boolean;
|
|
if (reset) {
|
|
value = defaultValue;
|
|
} else {
|
|
wrapped = boxedValue && (<any>boxedValue).wrapped;
|
|
value = wrapped ? WrappedValue.unwrap(boxedValue) : boxedValue;
|
|
|
|
if (valueConverter && typeof value === "string") {
|
|
value = valueConverter(value);
|
|
}
|
|
}
|
|
|
|
const oldValue = key in this ? this[key] : defaultValue;
|
|
const changed: boolean = equalityComparer ? !equalityComparer(oldValue, value) : oldValue !== value;
|
|
|
|
if (wrapped || changed) {
|
|
if (affectsLayout) {
|
|
this.requestLayout();
|
|
}
|
|
|
|
if (reset) {
|
|
delete this[key];
|
|
if (valueChanged) {
|
|
valueChanged(this, oldValue, value);
|
|
}
|
|
if (this[setNative]) {
|
|
if (this._suspendNativeUpdatesCount) {
|
|
if (this._suspendedUpdates) {
|
|
this._suspendedUpdates[propertyName] = property;
|
|
}
|
|
} else {
|
|
if (defaultValueKey in this) {
|
|
this[setNative](this[defaultValueKey]);
|
|
delete this[defaultValueKey];
|
|
} else {
|
|
this[setNative](defaultValue);
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
this[key] = value;
|
|
if (valueChanged) {
|
|
valueChanged(this, oldValue, value);
|
|
}
|
|
if (this[setNative]) {
|
|
if (this._suspendNativeUpdatesCount) {
|
|
if (this._suspendedUpdates) {
|
|
this._suspendedUpdates[propertyName] = property;
|
|
}
|
|
} else {
|
|
if (!(defaultValueKey in this)) {
|
|
this[defaultValueKey] = this[getDefault] ? this[getDefault]() : defaultValue;
|
|
}
|
|
this[setNative](value);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (this.hasListeners(eventName)) {
|
|
this.notify<PropertyChangeData>({ object: this, eventName, propertyName, value, oldValue });
|
|
}
|
|
|
|
if (this.domNode) {
|
|
if (reset) {
|
|
this.domNode.attributeRemoved(propertyName);
|
|
} else {
|
|
this.domNode.attributeModified(propertyName, value);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
this.get = function (this: T): U {
|
|
return key in this ? this[key] : defaultValue;
|
|
};
|
|
|
|
this.nativeValueChange = function (owner: T, value: U): void {
|
|
const oldValue = key in owner ? owner[key] : defaultValue;
|
|
const changed = equalityComparer ? !equalityComparer(oldValue, value) : oldValue !== value;
|
|
if (changed) {
|
|
owner[key] = value;
|
|
if (valueChanged) {
|
|
valueChanged(owner, oldValue, value);
|
|
}
|
|
|
|
if (owner.nativeViewProtected && !(defaultValueKey in owner)) {
|
|
owner[defaultValueKey] = owner[getDefault] ? owner[getDefault]() : defaultValue;
|
|
}
|
|
|
|
if (owner.hasListeners(eventName)) {
|
|
owner.notify<PropertyChangeData>({ object: owner, eventName, propertyName, value, oldValue });
|
|
}
|
|
|
|
if (affectsLayout) {
|
|
owner.requestLayout();
|
|
}
|
|
|
|
if (owner.domNode) {
|
|
owner.domNode.attributeModified(propertyName, value);
|
|
}
|
|
}
|
|
};
|
|
|
|
symbolPropertyMap[key] = this;
|
|
}
|
|
|
|
public register(cls: { prototype: T }): void {
|
|
if (this.registered) {
|
|
throw new Error(`Property ${this.name} already registered.`);
|
|
}
|
|
this.registered = true;
|
|
Object.defineProperty(cls.prototype, this.name, this);
|
|
}
|
|
|
|
public isSet(instance: T): boolean {
|
|
return this.key in instance;
|
|
}
|
|
}
|
|
Property.prototype.isStyleProperty = false;
|
|
|
|
export class CoercibleProperty<T extends ViewBase, U> extends Property<T, U> implements definitions.CoercibleProperty<T, U> {
|
|
public readonly coerce: (target: T) => void;
|
|
|
|
constructor(options: definitions.CoerciblePropertyOptions<T, U>) {
|
|
super(options);
|
|
|
|
const propertyName = options.name;
|
|
const key = this.key;
|
|
const getDefault: symbol = this.getDefault;
|
|
const setNative: symbol = this.setNative;
|
|
const defaultValueKey = this.defaultValueKey;
|
|
const defaultValue: U = this.defaultValue;
|
|
|
|
const coerceKey = Symbol(propertyName + ":coerceKey");
|
|
|
|
const eventName = propertyName + "Change";
|
|
const affectsLayout: boolean = options.affectsLayout;
|
|
const equalityComparer = options.equalityComparer;
|
|
const valueChanged = options.valueChanged;
|
|
const valueConverter = options.valueConverter;
|
|
const coerceCallback = options.coerceValue;
|
|
|
|
const property = this;
|
|
|
|
this.coerce = function (target: T): void {
|
|
const originalValue: U = coerceKey in target ? target[coerceKey] : defaultValue;
|
|
// need that to make coercing but also fire change events
|
|
target[propertyName] = originalValue;
|
|
};
|
|
|
|
this.set = function (this: T, boxedValue: U): void {
|
|
const reset = boxedValue === unsetValue;
|
|
let value: U;
|
|
let wrapped: boolean;
|
|
if (reset) {
|
|
value = defaultValue;
|
|
delete this[coerceKey];
|
|
} else {
|
|
wrapped = boxedValue && (<any>boxedValue).wrapped;
|
|
value = wrapped ? WrappedValue.unwrap(boxedValue) : boxedValue;
|
|
|
|
if (valueConverter && typeof value === "string") {
|
|
value = valueConverter(value);
|
|
}
|
|
|
|
this[coerceKey] = value;
|
|
value = coerceCallback(this, value);
|
|
}
|
|
|
|
const oldValue = key in this ? this[key] : defaultValue;
|
|
const changed: boolean = equalityComparer ? !equalityComparer(oldValue, value) : oldValue !== value;
|
|
|
|
if (wrapped || changed) {
|
|
if (reset) {
|
|
delete this[key];
|
|
if (valueChanged) {
|
|
valueChanged(this, oldValue, value);
|
|
}
|
|
|
|
if (this[setNative]) {
|
|
if (this._suspendNativeUpdatesCount) {
|
|
if (this._suspendedUpdates) {
|
|
this._suspendedUpdates[propertyName] = property;
|
|
}
|
|
} else {
|
|
if (defaultValueKey in this) {
|
|
this[setNative](this[defaultValueKey]);
|
|
delete this[defaultValueKey];
|
|
} else {
|
|
this[setNative](defaultValue);
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
this[key] = value;
|
|
if (valueChanged) {
|
|
valueChanged(this, oldValue, value);
|
|
}
|
|
|
|
if (this[setNative]) {
|
|
if (this._suspendNativeUpdatesCount) {
|
|
if (this._suspendedUpdates) {
|
|
this._suspendedUpdates[propertyName] = property;
|
|
}
|
|
} else {
|
|
if (!(defaultValueKey in this)) {
|
|
this[defaultValueKey] = this[getDefault] ? this[getDefault]() : defaultValue;
|
|
}
|
|
this[setNative](value);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (this.hasListeners(eventName)) {
|
|
this.notify<PropertyChangeData>({ object: this, eventName, propertyName, value, oldValue });
|
|
}
|
|
|
|
if (affectsLayout) {
|
|
this.requestLayout();
|
|
}
|
|
|
|
if (this.domNode) {
|
|
if (reset) {
|
|
this.domNode.attributeRemoved(propertyName);
|
|
} else {
|
|
this.domNode.attributeModified(propertyName, value);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
}
|
|
}
|
|
|
|
export class InheritedProperty<T extends ViewBase, U> extends Property<T, U> implements definitions.InheritedProperty<T, U> {
|
|
public readonly sourceKey: symbol;
|
|
public readonly setInheritedValue: (value: U) => void;
|
|
|
|
constructor(options: definitions.PropertyOptions<T, U>) {
|
|
super(options);
|
|
const name = options.name;
|
|
const key = this.key;
|
|
const defaultValue = options.defaultValue;
|
|
|
|
const sourceKey = Symbol(name + ":valueSourceKey");
|
|
this.sourceKey = sourceKey;
|
|
|
|
const setBase = this.set;
|
|
const setFunc = (valueSource: ValueSource) => function (value: U): void {
|
|
const that = <T>this;
|
|
|
|
let unboxedValue: U;
|
|
let newValueSource: number;
|
|
|
|
if (value === unsetValue) {
|
|
// If unsetValue - we want to reset the property.
|
|
const parent: ViewBase = that.parent;
|
|
// If we have parent and it has non-default value we use as our inherited value.
|
|
if (parent && parent[sourceKey] !== ValueSource.Default) {
|
|
unboxedValue = parent[name];
|
|
newValueSource = ValueSource.Inherited;
|
|
}
|
|
else {
|
|
unboxedValue = defaultValue;
|
|
newValueSource = ValueSource.Default;
|
|
}
|
|
} else {
|
|
// else we are set through property set.
|
|
unboxedValue = value;
|
|
newValueSource = valueSource;
|
|
}
|
|
|
|
// take currentValue before calling base - base may change it.
|
|
const currentValue = that[key];
|
|
setBase.call(that, unboxedValue);
|
|
|
|
const newValue = that[key];
|
|
that[sourceKey] = newValueSource;
|
|
|
|
if (currentValue !== newValue) {
|
|
const reset = newValueSource === ValueSource.Default;
|
|
that.eachChild((child) => {
|
|
const childValueSource = child[sourceKey] || ValueSource.Default;
|
|
if (reset) {
|
|
if (childValueSource === ValueSource.Inherited) {
|
|
setFunc.call(child, unsetValue);
|
|
}
|
|
} else {
|
|
if (childValueSource <= ValueSource.Inherited) {
|
|
setInheritedValue.call(child, newValue);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
});
|
|
}
|
|
};
|
|
|
|
const setInheritedValue = setFunc(ValueSource.Inherited);
|
|
this.setInheritedValue = setInheritedValue;
|
|
|
|
this.set = setFunc(ValueSource.Local);
|
|
|
|
inheritableProperties.push(this);
|
|
}
|
|
}
|
|
|
|
export class CssProperty<T extends Style, U> implements definitions.CssProperty<T, U> {
|
|
private registered: boolean;
|
|
|
|
public readonly name: string;
|
|
public readonly cssName: string;
|
|
public readonly cssLocalName: string;
|
|
|
|
protected readonly cssValueDescriptor: PropertyDescriptor;
|
|
protected readonly localValueDescriptor: PropertyDescriptor;
|
|
|
|
public isStyleProperty: boolean;
|
|
|
|
public readonly key: symbol;
|
|
public readonly getDefault: symbol;
|
|
public readonly setNative: symbol;
|
|
public readonly sourceKey: symbol;
|
|
public readonly defaultValueKey: symbol;
|
|
public readonly defaultValue: U;
|
|
|
|
constructor(options: definitions.CssPropertyOptions<T, U>) {
|
|
const propertyName = options.name;
|
|
this.name = propertyName;
|
|
|
|
cssPropertyNames.push(options.cssName);
|
|
|
|
this.cssName = `css:${options.cssName}`;
|
|
this.cssLocalName = options.cssName;
|
|
|
|
const key = Symbol(propertyName + ":propertyKey");
|
|
this.key = key;
|
|
|
|
const sourceKey = Symbol(propertyName + ":valueSourceKey");
|
|
this.sourceKey = sourceKey;
|
|
|
|
const getDefault = Symbol(propertyName + ":getDefault");
|
|
this.getDefault = getDefault;
|
|
|
|
const setNative = Symbol(propertyName + ":setNative");
|
|
this.setNative = setNative;
|
|
|
|
const defaultValueKey = Symbol(propertyName + ":nativeDefaultValue");
|
|
this.defaultValueKey = defaultValueKey;
|
|
|
|
const defaultValue: U = options.defaultValue;
|
|
this.defaultValue = defaultValue;
|
|
|
|
const eventName = propertyName + "Change";
|
|
const affectsLayout: boolean = options.affectsLayout;
|
|
const equalityComparer = options.equalityComparer;
|
|
const valueChanged = options.valueChanged;
|
|
const valueConverter = options.valueConverter;
|
|
|
|
const property = this;
|
|
|
|
function setLocalValue(this: T, newValue: U | string): void {
|
|
const view = this.viewRef.get();
|
|
if (!view) {
|
|
traceWrite(`${newValue} not set to view because ".viewRef" is cleared`, traceCategories.Style, traceMessageType.warn);
|
|
|
|
return;
|
|
}
|
|
|
|
const reset = newValue === unsetValue || newValue === "";
|
|
let value: U;
|
|
if (reset) {
|
|
value = defaultValue;
|
|
delete this[sourceKey];
|
|
} else {
|
|
this[sourceKey] = ValueSource.Local;
|
|
value = (valueConverter && typeof newValue === "string") ?
|
|
valueConverter(newValue) :
|
|
<U>newValue;
|
|
}
|
|
|
|
const oldValue: U = key in this ? this[key] : defaultValue;
|
|
const changed: boolean = equalityComparer ? !equalityComparer(oldValue, value) : oldValue !== value;
|
|
|
|
if (changed) {
|
|
if (reset) {
|
|
delete this[key];
|
|
if (valueChanged) {
|
|
valueChanged(this, oldValue, value);
|
|
}
|
|
|
|
if (view[setNative]) {
|
|
if (view._suspendNativeUpdatesCount) {
|
|
if (view._suspendedUpdates) {
|
|
view._suspendedUpdates[propertyName] = property;
|
|
}
|
|
} else {
|
|
if (defaultValueKey in this) {
|
|
view[setNative](this[defaultValueKey]);
|
|
delete this[defaultValueKey];
|
|
} else {
|
|
view[setNative](defaultValue);
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
this[key] = value;
|
|
if (valueChanged) {
|
|
valueChanged(this, oldValue, value);
|
|
}
|
|
|
|
if (view[setNative]) {
|
|
if (view._suspendNativeUpdatesCount) {
|
|
if (view._suspendedUpdates) {
|
|
view._suspendedUpdates[propertyName] = property;
|
|
}
|
|
} else {
|
|
if (!(defaultValueKey in this)) {
|
|
this[defaultValueKey] = view[getDefault] ? view[getDefault]() : defaultValue;
|
|
}
|
|
view[setNative](value);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (this.hasListeners(eventName)) {
|
|
this.notify<PropertyChangeData>({ object: this, eventName, propertyName, value, oldValue });
|
|
}
|
|
|
|
if (affectsLayout) {
|
|
view.requestLayout();
|
|
}
|
|
}
|
|
}
|
|
|
|
function setCssValue(this: T, newValue: U | string): void {
|
|
const view = this.viewRef.get();
|
|
if (!view) {
|
|
traceWrite(`${newValue} not set to view because ".viewRef" is cleared`, traceCategories.Style, traceMessageType.warn);
|
|
|
|
return;
|
|
}
|
|
|
|
const currentValueSource: number = this[sourceKey] || ValueSource.Default;
|
|
|
|
// We have localValueSource - NOOP.
|
|
if (currentValueSource === ValueSource.Local) {
|
|
return;
|
|
}
|
|
|
|
const reset = newValue === unsetValue || newValue === "";
|
|
let value: U;
|
|
if (reset) {
|
|
value = defaultValue;
|
|
delete this[sourceKey];
|
|
} else {
|
|
value = valueConverter && typeof newValue === "string" ?
|
|
valueConverter(newValue) :
|
|
<U>newValue;
|
|
this[sourceKey] = ValueSource.Css;
|
|
}
|
|
|
|
const oldValue: U = key in this ? this[key] : defaultValue;
|
|
const changed: boolean = equalityComparer ? !equalityComparer(oldValue, value) : oldValue !== value;
|
|
|
|
if (changed) {
|
|
if (reset) {
|
|
delete this[key];
|
|
if (valueChanged) {
|
|
valueChanged(this, oldValue, value);
|
|
}
|
|
|
|
if (view[setNative]) {
|
|
if (view._suspendNativeUpdatesCount) {
|
|
if (view._suspendedUpdates) {
|
|
view._suspendedUpdates[propertyName] = property;
|
|
}
|
|
} else {
|
|
if (defaultValueKey in this) {
|
|
view[setNative](this[defaultValueKey]);
|
|
delete this[defaultValueKey];
|
|
} else {
|
|
view[setNative](defaultValue);
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
this[key] = value;
|
|
if (valueChanged) {
|
|
valueChanged(this, oldValue, value);
|
|
}
|
|
|
|
if (view[setNative]) {
|
|
if (view._suspendNativeUpdatesCount) {
|
|
if (view._suspendedUpdates) {
|
|
view._suspendedUpdates[propertyName] = property;
|
|
}
|
|
} else {
|
|
if (!(defaultValueKey in this)) {
|
|
this[defaultValueKey] = view[getDefault] ? view[getDefault]() : defaultValue;
|
|
}
|
|
view[setNative](value);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (this.hasListeners(eventName)) {
|
|
this.notify<PropertyChangeData>({ object: this, eventName, propertyName, value, oldValue });
|
|
}
|
|
|
|
if (affectsLayout) {
|
|
view.requestLayout();
|
|
}
|
|
}
|
|
}
|
|
|
|
function get(): U {
|
|
return key in this ? this[key] : defaultValue;
|
|
}
|
|
|
|
this.cssValueDescriptor = {
|
|
enumerable: true,
|
|
configurable: true,
|
|
get: get,
|
|
set: setCssValue
|
|
};
|
|
|
|
this.localValueDescriptor = {
|
|
enumerable: true,
|
|
configurable: true,
|
|
get: get,
|
|
set: setLocalValue
|
|
};
|
|
|
|
cssSymbolPropertyMap[key] = this;
|
|
}
|
|
|
|
public register(cls: { prototype: T }): void {
|
|
if (this.registered) {
|
|
throw new Error(`Property ${this.name} already registered.`);
|
|
}
|
|
this.registered = true;
|
|
Object.defineProperty(cls.prototype, this.name, this.localValueDescriptor);
|
|
Object.defineProperty(cls.prototype, this.cssName, this.cssValueDescriptor);
|
|
if (this.cssLocalName !== this.cssName) {
|
|
Object.defineProperty(cls.prototype, this.cssLocalName, this.localValueDescriptor);
|
|
}
|
|
}
|
|
|
|
public isSet(instance: T): boolean {
|
|
return this.key in instance;
|
|
}
|
|
}
|
|
CssProperty.prototype.isStyleProperty = true;
|
|
|
|
export class CssAnimationProperty<T extends Style, U> implements definitions.CssAnimationProperty<T, U> {
|
|
public readonly name: string;
|
|
public readonly cssName: string;
|
|
public readonly cssLocalName: string;
|
|
|
|
public readonly getDefault: symbol;
|
|
public readonly setNative: symbol;
|
|
|
|
public readonly register: (cls: { prototype }) => void;
|
|
|
|
public readonly keyframe: string;
|
|
public readonly defaultValueKey: symbol;
|
|
public readonly key: symbol;
|
|
private readonly source: symbol;
|
|
|
|
public readonly defaultValue: U;
|
|
|
|
public isStyleProperty: boolean;
|
|
|
|
private static properties: { [cssName: string]: CssAnimationProperty<any, any> } = {};
|
|
|
|
public _valueConverter?: (value: string) => any;
|
|
|
|
constructor(options: definitions.CssAnimationPropertyOptions<T, U>) {
|
|
const { valueConverter, equalityComparer, valueChanged, defaultValue } = options;
|
|
const propertyName = options.name;
|
|
this.name = propertyName;
|
|
|
|
cssPropertyNames.push(options.cssName);
|
|
|
|
CssAnimationProperty.properties[propertyName] = this;
|
|
if (options.cssName && options.cssName !== propertyName) {
|
|
CssAnimationProperty.properties[options.cssName] = this;
|
|
}
|
|
|
|
this._valueConverter = options.valueConverter;
|
|
|
|
const cssLocalName = (options.cssName || propertyName);
|
|
this.cssLocalName = cssLocalName;
|
|
const cssName = "css:" + cssLocalName;
|
|
this.cssName = cssName;
|
|
|
|
const keyframeName = "keyframe:" + propertyName;
|
|
this.keyframe = keyframeName;
|
|
const defaultName = "default:" + propertyName;
|
|
|
|
const defaultValueKey = Symbol(defaultName);
|
|
this.defaultValueKey = defaultValueKey;
|
|
|
|
this.defaultValue = defaultValue;
|
|
|
|
const cssValue = Symbol(cssName);
|
|
const styleValue = Symbol(`local:${propertyName}`);
|
|
const keyframeValue = Symbol(keyframeName);
|
|
const computedValue = Symbol("computed-value:" + propertyName);
|
|
this.key = computedValue;
|
|
const computedSource = Symbol("computed-source:" + propertyName);
|
|
this.source = computedSource;
|
|
|
|
this.getDefault = Symbol(propertyName + ":getDefault");
|
|
const getDefault = this.getDefault;
|
|
const setNative = this.setNative = Symbol(propertyName + ":setNative");
|
|
const eventName = propertyName + "Change";
|
|
|
|
const property = this;
|
|
|
|
function descriptor(symbol: symbol, propertySource: ValueSource, enumerable: boolean, configurable: boolean, getsComputed: boolean): PropertyDescriptor {
|
|
return {
|
|
enumerable, configurable,
|
|
get: getsComputed ? function (this: T) { return this[computedValue]; } : function (this: T) { return this[symbol]; },
|
|
set(this: T, boxedValue: U | string) {
|
|
const view = this.viewRef.get();
|
|
if (!view) {
|
|
traceWrite(`${boxedValue} not set to view because ".viewRef" is cleared`, traceCategories.Animation, traceMessageType.warn);
|
|
|
|
return;
|
|
}
|
|
|
|
const oldValue = this[computedValue];
|
|
const oldSource = this[computedSource];
|
|
const wasSet = oldSource !== ValueSource.Default;
|
|
const reset = boxedValue === unsetValue || boxedValue === "";
|
|
|
|
if (reset) {
|
|
this[symbol] = unsetValue;
|
|
if (this[computedSource] === propertySource) {
|
|
// Fallback to lower value source.
|
|
if (this[styleValue] !== unsetValue) {
|
|
this[computedSource] = ValueSource.Local;
|
|
this[computedValue] = this[styleValue];
|
|
} else if (this[cssValue] !== unsetValue) {
|
|
this[computedSource] = ValueSource.Css;
|
|
this[computedValue] = this[cssValue];
|
|
} else {
|
|
delete this[computedSource];
|
|
delete this[computedValue];
|
|
}
|
|
}
|
|
} else {
|
|
if (valueConverter && typeof boxedValue === "string") {
|
|
boxedValue = valueConverter(boxedValue);
|
|
}
|
|
this[symbol] = boxedValue;
|
|
if (this[computedSource] <= propertySource) {
|
|
this[computedSource] = propertySource;
|
|
this[computedValue] = boxedValue;
|
|
}
|
|
}
|
|
|
|
const value = this[computedValue];
|
|
const source = this[computedSource];
|
|
const isSet = source !== ValueSource.Default;
|
|
|
|
const computedValueChanged = oldValue !== value && (!equalityComparer || !equalityComparer(oldValue, value));
|
|
|
|
if (computedValueChanged && valueChanged) {
|
|
valueChanged(this, oldValue, value);
|
|
}
|
|
|
|
if (view[setNative] && (computedValueChanged || isSet !== wasSet)) {
|
|
if (view._suspendNativeUpdatesCount) {
|
|
if (view._suspendedUpdates) {
|
|
view._suspendedUpdates[propertyName] = property;
|
|
}
|
|
} else {
|
|
if (isSet) {
|
|
if (!wasSet && !(defaultValueKey in this)) {
|
|
this[defaultValueKey] = view[getDefault] ? view[getDefault]() : defaultValue;
|
|
}
|
|
view[setNative](value);
|
|
} else if (wasSet) {
|
|
if (defaultValueKey in this) {
|
|
view[setNative](this[defaultValueKey]);
|
|
} else {
|
|
view[setNative](defaultValue);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (computedValueChanged && this.hasListeners(eventName)) {
|
|
this.notify<PropertyChangeData>({ object: this, eventName, propertyName, value, oldValue });
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
const defaultPropertyDescriptor = descriptor(defaultValueKey, ValueSource.Default, false, false, false);
|
|
const cssPropertyDescriptor = descriptor(cssValue, ValueSource.Css, false, false, false);
|
|
const stylePropertyDescriptor = descriptor(styleValue, ValueSource.Local, true, true, true);
|
|
const keyframePropertyDescriptor = descriptor(keyframeValue, ValueSource.Keyframe, false, false, false);
|
|
|
|
symbolPropertyMap[computedValue] = this;
|
|
cssSymbolPropertyMap[computedValue] = this;
|
|
|
|
this.register = (cls: { prototype: T }) => {
|
|
cls.prototype[computedValue] = options.defaultValue;
|
|
cls.prototype[computedSource] = ValueSource.Default;
|
|
|
|
cls.prototype[cssValue] = unsetValue;
|
|
cls.prototype[styleValue] = unsetValue;
|
|
cls.prototype[keyframeValue] = unsetValue;
|
|
|
|
Object.defineProperty(cls.prototype, defaultName, defaultPropertyDescriptor);
|
|
Object.defineProperty(cls.prototype, cssName, cssPropertyDescriptor);
|
|
Object.defineProperty(cls.prototype, propertyName, stylePropertyDescriptor);
|
|
if (options.cssName && options.cssName !== options.name) {
|
|
Object.defineProperty(cls.prototype, options.cssName, stylePropertyDescriptor);
|
|
}
|
|
Object.defineProperty(cls.prototype, keyframeName, keyframePropertyDescriptor);
|
|
};
|
|
}
|
|
|
|
public _initDefaultNativeValue(target: T): void {
|
|
const view = target.viewRef.get();
|
|
if (!view) {
|
|
traceWrite(`_initDefaultNativeValue not executed to view because ".viewRef" is cleared`, traceCategories.Animation, traceMessageType.warn);
|
|
|
|
return;
|
|
}
|
|
|
|
const defaultValueKey = this.defaultValueKey;
|
|
|
|
if (!(defaultValueKey in target)) {
|
|
const getDefault = this.getDefault;
|
|
target[defaultValueKey] = view[getDefault] ? view[getDefault]() : this.defaultValue;
|
|
}
|
|
}
|
|
|
|
public static _getByCssName(name: string): CssAnimationProperty<any, any> {
|
|
return this.properties[name];
|
|
}
|
|
|
|
public static _getPropertyNames(): string[] {
|
|
return Object.keys(CssAnimationProperty.properties);
|
|
}
|
|
|
|
public isSet(instance: T): boolean {
|
|
return instance[this.source] !== ValueSource.Default;
|
|
}
|
|
}
|
|
CssAnimationProperty.prototype.isStyleProperty = true;
|
|
|
|
export class InheritedCssProperty<T extends Style, U> extends CssProperty<T, U> implements definitions.InheritedCssProperty<T, U> {
|
|
public setInheritedValue: (value: U) => void;
|
|
|
|
constructor(options: definitions.CssPropertyOptions<T, U>) {
|
|
super(options);
|
|
const propertyName = options.name;
|
|
|
|
const key = this.key;
|
|
const sourceKey = this.sourceKey;
|
|
const getDefault = this.getDefault;
|
|
const setNative = this.setNative;
|
|
const defaultValueKey = this.defaultValueKey;
|
|
|
|
const eventName = propertyName + "Change";
|
|
const defaultValue: U = options.defaultValue;
|
|
const affectsLayout: boolean = options.affectsLayout;
|
|
const equalityComparer = options.equalityComparer;
|
|
const valueChanged = options.valueChanged;
|
|
const valueConverter = options.valueConverter;
|
|
|
|
const property = this;
|
|
|
|
const setFunc = (valueSource: ValueSource) => function (this: T, boxedValue: any): void {
|
|
const view = this.viewRef.get();
|
|
if (!view) {
|
|
traceWrite(`${boxedValue} not set to view's property because ".viewRef" is cleared`, traceCategories.Style, traceMessageType.warn);
|
|
|
|
return;
|
|
}
|
|
|
|
const reset = boxedValue === unsetValue || boxedValue === "";
|
|
const currentValueSource: number = this[sourceKey] || ValueSource.Default;
|
|
if (reset) {
|
|
// If we want to reset cssValue and we have localValue - return;
|
|
if (valueSource === ValueSource.Css && currentValueSource === ValueSource.Local) {
|
|
return;
|
|
}
|
|
} else {
|
|
if (currentValueSource > valueSource) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
const oldValue: U = key in this ? this[key] : defaultValue;
|
|
let value: U;
|
|
let unsetNativeValue = false;
|
|
if (reset) {
|
|
// If unsetValue - we want to reset this property.
|
|
let parent = view.parent;
|
|
let style = parent ? parent.style : null;
|
|
// If we have parent and it has non-default value we use as our inherited value.
|
|
if (style && style[sourceKey] > ValueSource.Default) {
|
|
value = style[propertyName];
|
|
this[sourceKey] = ValueSource.Inherited;
|
|
this[key] = value;
|
|
} else {
|
|
value = defaultValue;
|
|
delete this[sourceKey];
|
|
delete this[key];
|
|
unsetNativeValue = true;
|
|
}
|
|
} else {
|
|
this[sourceKey] = valueSource;
|
|
if (valueConverter && typeof boxedValue === "string") {
|
|
value = valueConverter(boxedValue);
|
|
} else {
|
|
value = boxedValue;
|
|
}
|
|
this[key] = value;
|
|
}
|
|
|
|
const changed: boolean = equalityComparer ? !equalityComparer(oldValue, value) : oldValue !== value;
|
|
|
|
if (changed) {
|
|
if (valueChanged) {
|
|
valueChanged(this, oldValue, value);
|
|
}
|
|
|
|
if (view[setNative]) {
|
|
if (view._suspendNativeUpdatesCount) {
|
|
if (view._suspendedUpdates) {
|
|
view._suspendedUpdates[propertyName] = property;
|
|
}
|
|
} else {
|
|
if (unsetNativeValue) {
|
|
if (defaultValueKey in this) {
|
|
view[setNative](this[defaultValueKey]);
|
|
delete this[defaultValueKey];
|
|
} else {
|
|
view[setNative](defaultValue);
|
|
}
|
|
} else {
|
|
if (!(defaultValueKey in this)) {
|
|
this[defaultValueKey] = view[getDefault] ? view[getDefault]() : defaultValue;
|
|
}
|
|
|
|
view[setNative](value);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (this.hasListeners(eventName)) {
|
|
this.notify<PropertyChangeData>({ object: this, eventName, propertyName, value, oldValue });
|
|
}
|
|
|
|
if (affectsLayout) {
|
|
view.requestLayout();
|
|
}
|
|
|
|
view.eachChild((child) => {
|
|
const childStyle = child.style;
|
|
const childValueSource = childStyle[sourceKey] || ValueSource.Default;
|
|
if (reset) {
|
|
if (childValueSource === ValueSource.Inherited) {
|
|
setDefaultFunc.call(childStyle, unsetValue);
|
|
}
|
|
} else {
|
|
if (childValueSource <= ValueSource.Inherited) {
|
|
setInheritedFunc.call(childStyle, value);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
});
|
|
}
|
|
};
|
|
|
|
const setDefaultFunc = setFunc(ValueSource.Default);
|
|
const setInheritedFunc = setFunc(ValueSource.Inherited);
|
|
|
|
this.setInheritedValue = setInheritedFunc;
|
|
this.cssValueDescriptor.set = setFunc(ValueSource.Css);
|
|
this.localValueDescriptor.set = setFunc(ValueSource.Local);
|
|
|
|
inheritableCssProperties.push(this);
|
|
}
|
|
}
|
|
|
|
export class ShorthandProperty<T extends Style, P> implements definitions.ShorthandProperty<T, P> {
|
|
private registered: boolean;
|
|
|
|
public readonly key: symbol;
|
|
public readonly name: string;
|
|
public readonly cssName: string;
|
|
public readonly cssLocalName: string;
|
|
|
|
protected readonly cssValueDescriptor: PropertyDescriptor;
|
|
protected readonly localValueDescriptor: PropertyDescriptor;
|
|
protected readonly propertyBagDescriptor: PropertyDescriptor;
|
|
|
|
public readonly sourceKey: symbol;
|
|
|
|
constructor(options: definitions.ShorthandPropertyOptions<P>) {
|
|
this.name = options.name;
|
|
|
|
const key = Symbol(this.name + ":propertyKey");
|
|
this.key = key;
|
|
|
|
this.cssName = `css:${options.cssName}`;
|
|
this.cssLocalName = `${options.cssName}`;
|
|
|
|
const converter = options.converter;
|
|
|
|
function setLocalValue(this: T, value: string | P): void {
|
|
const view = this.viewRef.get();
|
|
if (!view) {
|
|
traceWrite(`setLocalValue not executed to view because ".viewRef" is cleared`, traceCategories.Animation, traceMessageType.warn);
|
|
|
|
return;
|
|
}
|
|
|
|
view._batchUpdate(() => {
|
|
for (let [p, v] of converter(value)) {
|
|
this[p.name] = v;
|
|
}
|
|
});
|
|
}
|
|
|
|
function setCssValue(this: T, value: string): void {
|
|
const view = this.viewRef.get();
|
|
if (!view) {
|
|
traceWrite(`setCssValue not executed to view because ".viewRef" is cleared`, traceCategories.Animation, traceMessageType.warn);
|
|
|
|
return;
|
|
}
|
|
|
|
view._batchUpdate(() => {
|
|
for (let [p, v] of converter(value)) {
|
|
this[p.cssName] = v;
|
|
}
|
|
});
|
|
}
|
|
|
|
this.cssValueDescriptor = {
|
|
enumerable: true,
|
|
configurable: true,
|
|
get: options.getter,
|
|
set: setCssValue
|
|
};
|
|
|
|
this.localValueDescriptor = {
|
|
enumerable: true,
|
|
configurable: true,
|
|
get: options.getter,
|
|
set: setLocalValue
|
|
};
|
|
|
|
this.propertyBagDescriptor = {
|
|
enumerable: false,
|
|
configurable: true,
|
|
set(value: string) {
|
|
converter(value).forEach(([property, value]) => {
|
|
this[property.cssLocalName] = value;
|
|
});
|
|
}
|
|
};
|
|
|
|
cssSymbolPropertyMap[key] = this;
|
|
}
|
|
|
|
public register(cls: typeof Style): void {
|
|
if (this.registered) {
|
|
throw new Error(`Property ${this.name} already registered.`);
|
|
}
|
|
|
|
this.registered = true;
|
|
Object.defineProperty(cls.prototype, this.name, this.localValueDescriptor);
|
|
Object.defineProperty(cls.prototype, this.cssName, this.cssValueDescriptor);
|
|
if (this.cssLocalName !== this.cssName) {
|
|
Object.defineProperty(cls.prototype, this.cssLocalName, this.localValueDescriptor);
|
|
}
|
|
|
|
Object.defineProperty(cls.prototype.PropertyBag, this.cssLocalName, this.propertyBagDescriptor);
|
|
}
|
|
}
|
|
|
|
function inheritablePropertyValuesOn(view: ViewBase): Array<{ property: InheritedProperty<any, any>, value: any }> {
|
|
const array = new Array<{ property: InheritedProperty<any, any>, value: any }>();
|
|
for (let prop of inheritableProperties) {
|
|
const sourceKey = prop.sourceKey;
|
|
const valueSource: number = view[sourceKey] || ValueSource.Default;
|
|
if (valueSource !== ValueSource.Default) {
|
|
// use prop.name as it will return value or default value.
|
|
// prop.key will return undefined if property is set the same value as default one.
|
|
array.push({ property: prop, value: view[prop.name] });
|
|
}
|
|
}
|
|
|
|
return array;
|
|
}
|
|
|
|
function inheritableCssPropertyValuesOn(style: Style): Array<{ property: InheritedCssProperty<any, any>, value: any }> {
|
|
const array = new Array<{ property: InheritedCssProperty<any, any>, value: any }>();
|
|
for (let prop of inheritableCssProperties) {
|
|
const sourceKey = prop.sourceKey;
|
|
const valueSource: number = style[sourceKey] || ValueSource.Default;
|
|
if (valueSource !== ValueSource.Default) {
|
|
// use prop.name as it will return value or default value.
|
|
// prop.key will return undefined if property is set the same value as default one.
|
|
array.push({ property: prop, value: style[prop.name] });
|
|
}
|
|
}
|
|
|
|
return array;
|
|
}
|
|
|
|
type PropertyInterface = Property<ViewBase, any> | CssProperty<Style, any> | CssAnimationProperty<Style, any>;
|
|
|
|
export const initNativeView = profile("\"properties\".initNativeView", function initNativeView(view: ViewBase): void {
|
|
if (view._suspendedUpdates) {
|
|
applyPendingNativeSetters(view);
|
|
} else {
|
|
applyAllNativeSetters(view);
|
|
}
|
|
// Would it be faster to delete all members of the old object?
|
|
view._suspendedUpdates = {};
|
|
});
|
|
|
|
export function applyPendingNativeSetters(view: ViewBase): void {
|
|
// TODO: Check what happens if a view was suspended and its value was reset, or set back to default!
|
|
const suspendedUpdates = view._suspendedUpdates;
|
|
for (let propertyName in suspendedUpdates) {
|
|
const property = <PropertyInterface>suspendedUpdates[propertyName];
|
|
const setNative = property.setNative;
|
|
if (view[setNative]) {
|
|
const { getDefault, isStyleProperty, defaultValueKey, defaultValue } = property;
|
|
let value;
|
|
if (isStyleProperty) {
|
|
const style = view.style;
|
|
if ((<CssProperty<Style, any> | CssAnimationProperty<Style, any>>property).isSet(view.style)) {
|
|
if (!(defaultValueKey in style)) {
|
|
style[defaultValueKey] = view[getDefault] ? view[getDefault]() : defaultValue;
|
|
}
|
|
value = view.style[propertyName];
|
|
} else {
|
|
value = style[defaultValueKey];
|
|
}
|
|
} else {
|
|
if ((<Property<ViewBase, any>>property).isSet(view)) {
|
|
if (!(defaultValueKey in view)) {
|
|
view[defaultValueKey] = view[getDefault] ? view[getDefault]() : defaultValue;
|
|
}
|
|
value = view[propertyName];
|
|
} else {
|
|
value = view[defaultValueKey];
|
|
}
|
|
}
|
|
// TODO: Only if value is different from the value before the scope was created.
|
|
view[setNative](value);
|
|
}
|
|
}
|
|
}
|
|
|
|
export function applyAllNativeSetters(view: ViewBase): void {
|
|
let symbols = Object.getOwnPropertySymbols(view);
|
|
for (let symbol of symbols) {
|
|
const property: Property<any, any> = symbolPropertyMap[symbol];
|
|
if (!property) {
|
|
continue;
|
|
}
|
|
|
|
const setNative = property.setNative;
|
|
const getDefault = property.getDefault;
|
|
if (setNative in view) {
|
|
const defaultValueKey = property.defaultValueKey;
|
|
if (!(defaultValueKey in view)) {
|
|
view[defaultValueKey] = view[getDefault] ? view[getDefault]() : property.defaultValue;
|
|
}
|
|
|
|
const value = view[symbol];
|
|
view[setNative](value);
|
|
}
|
|
}
|
|
|
|
const style = view.style;
|
|
symbols = Object.getOwnPropertySymbols(style);
|
|
for (let symbol of symbols) {
|
|
const property: CssProperty<any, any> = cssSymbolPropertyMap[symbol];
|
|
if (!property) {
|
|
continue;
|
|
}
|
|
|
|
if (view[property.setNative]) {
|
|
const defaultValueKey = property.defaultValueKey;
|
|
if (!(defaultValueKey in style)) {
|
|
style[defaultValueKey] = view[property.getDefault] ? view[property.getDefault]() : property.defaultValue;
|
|
}
|
|
|
|
const value = style[symbol];
|
|
view[property.setNative](value);
|
|
}
|
|
}
|
|
}
|
|
|
|
export function resetNativeView(view: ViewBase): void {
|
|
let symbols = Object.getOwnPropertySymbols(view);
|
|
for (let symbol of symbols) {
|
|
const property: Property<any, any> = symbolPropertyMap[symbol];
|
|
if (!property) {
|
|
continue;
|
|
}
|
|
|
|
if (view[property.setNative]) {
|
|
if (property.defaultValueKey in view) {
|
|
view[property.setNative](view[property.defaultValueKey]);
|
|
delete view[property.defaultValueKey];
|
|
} else {
|
|
view[property.setNative](property.defaultValue);
|
|
}
|
|
}
|
|
}
|
|
|
|
const style = view.style;
|
|
|
|
symbols = Object.getOwnPropertySymbols(style);
|
|
for (let symbol of symbols) {
|
|
const property: CssProperty<any, any> = cssSymbolPropertyMap[symbol];
|
|
if (!property) {
|
|
continue;
|
|
}
|
|
|
|
if (view[property.setNative]) {
|
|
if (property.defaultValueKey in style) {
|
|
view[property.setNative](style[property.defaultValueKey]);
|
|
delete style[property.defaultValueKey];
|
|
} else {
|
|
view[property.setNative](property.defaultValue);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
export function clearInheritedProperties(view: ViewBase): void {
|
|
for (let prop of inheritableProperties) {
|
|
const sourceKey = prop.sourceKey;
|
|
if (view[sourceKey] === ValueSource.Inherited) {
|
|
prop.set.call(view, unsetValue);
|
|
}
|
|
}
|
|
|
|
const style = view.style;
|
|
for (let prop of inheritableCssProperties) {
|
|
const sourceKey = prop.sourceKey;
|
|
if (style[sourceKey] === ValueSource.Inherited) {
|
|
prop.setInheritedValue.call(style, unsetValue);
|
|
}
|
|
}
|
|
}
|
|
|
|
export function resetCSSProperties(style: Style): void {
|
|
let symbols = Object.getOwnPropertySymbols(style);
|
|
for (let symbol of symbols) {
|
|
let cssProperty;
|
|
if (cssProperty = cssSymbolPropertyMap[symbol]) {
|
|
style[cssProperty.cssName] = unsetValue;
|
|
if (cssProperty instanceof CssAnimationProperty) {
|
|
style[cssProperty.keyframe] = unsetValue;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
export function propagateInheritableProperties(view: ViewBase, child: ViewBase): void {
|
|
const inheritablePropertyValues = inheritablePropertyValuesOn(view);
|
|
for (let pair of inheritablePropertyValues) {
|
|
const prop = pair.property;
|
|
const sourceKey = prop.sourceKey;
|
|
const currentValueSource: number = child[sourceKey] || ValueSource.Default;
|
|
if (currentValueSource <= ValueSource.Inherited) {
|
|
prop.setInheritedValue.call(child, pair.value);
|
|
}
|
|
}
|
|
}
|
|
|
|
export function propagateInheritableCssProperties(parentStyle: Style, childStyle: Style): void {
|
|
const inheritableCssPropertyValues = inheritableCssPropertyValuesOn(parentStyle);
|
|
for (let pair of inheritableCssPropertyValues) {
|
|
const prop = pair.property;
|
|
const sourceKey = prop.sourceKey;
|
|
const currentValueSource: number = childStyle[sourceKey] || ValueSource.Default;
|
|
if (currentValueSource <= ValueSource.Inherited) {
|
|
prop.setInheritedValue.call(childStyle, pair.value, ValueSource.Inherited);
|
|
}
|
|
}
|
|
}
|
|
|
|
export function makeValidator<T>(...values: T[]): (value: any) => value is T {
|
|
const set = new Set(values);
|
|
|
|
return (value: any): value is T => set.has(value);
|
|
}
|
|
|
|
export function makeParser<T>(isValid: (value: any) => boolean): (value: any) => T {
|
|
return value => {
|
|
const lower = value && value.toLowerCase();
|
|
if (isValid(lower)) {
|
|
return lower;
|
|
} else {
|
|
throw new Error("Invalid value: " + value);
|
|
}
|
|
};
|
|
}
|
|
|
|
export function getSetProperties(view: ViewBase): [string, any][] {
|
|
const result = [];
|
|
|
|
Object.getOwnPropertyNames(view).forEach(prop => {
|
|
result.push([prop, view[prop]]);
|
|
});
|
|
|
|
let symbols = Object.getOwnPropertySymbols(view);
|
|
for (let symbol of symbols) {
|
|
const property = symbolPropertyMap[symbol];
|
|
if (!property) {
|
|
continue;
|
|
}
|
|
|
|
const value = view[property.key];
|
|
result.push([property.name, value]);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
export function getComputedCssValues(view: ViewBase): [string, any][] {
|
|
const result = [];
|
|
const style = view.style;
|
|
for (let prop of cssPropertyNames) {
|
|
result.push([prop, style[prop]]);
|
|
}
|
|
|
|
// Add these to enable box model in chrome-devtools styles tab
|
|
result.push(["top", "auto"]);
|
|
result.push(["left", "auto"]);
|
|
result.push(["bottom", "auto"]);
|
|
result.push(["right", "auto"]);
|
|
|
|
return result;
|
|
}
|