@nativescript/core
Version:
A JavaScript library providing an easy to use api for interacting with iOS and Android platform APIs.
1,199 lines • 53.8 kB
JavaScript
import { WrappedValue } from '../../../data/observable';
import { Trace } from '../../../trace';
import { profile } from '../../../profiling';
/**
* Value specifying that Property should be set to its initial value.
*/
export const unsetValue = new Object();
const cssPropertyNames = [];
const symbolPropertyMap = {};
const cssSymbolPropertyMap = {};
const inheritableProperties = new Array();
const inheritableCssProperties = new Array();
function print(map) {
const symbols = Object.getOwnPropertySymbols(map);
for (const symbol of symbols) {
const prop = map[symbol];
if (!prop.registered) {
console.log(`Property ${prop.name} not Registered!!!!!`);
}
}
}
function isCssUnsetValue(value) {
return value === 'unset' || value === 'revert';
}
function isResetValue(value) {
return value === unsetValue || value === 'initial' || value === 'inherit' || isCssUnsetValue(value);
}
export function _printUnregisteredProperties() {
print(symbolPropertyMap);
print(cssSymbolPropertyMap);
}
export function _getProperties() {
return getPropertiesFromMap(symbolPropertyMap);
}
export function _getStyleProperties() {
return getPropertiesFromMap(cssSymbolPropertyMap);
}
export function isCssVariable(property) {
return /^--[^,\s]+?$/.test(property);
}
export function isCssCalcExpression(value) {
return value.includes('calc(');
}
export function isCssVariableExpression(value) {
return value.includes('var(--');
}
export function isCssWideKeyword(value) {
return value === 'initial' || value === 'inherit' || isCssUnsetValue(value);
}
export function _evaluateCssVariableExpression(view, cssName, value) {
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;
while (lastValue !== output) {
lastValue = output;
const idx = output.lastIndexOf('var(');
if (idx === -1) {
continue;
}
const endIdx = output.indexOf(')', idx);
if (endIdx === -1) {
continue;
}
const matched = output
.substring(idx + 4, endIdx)
.split(',')
.map((v) => v.trim())
.filter((v) => !!v);
const cssVariableName = matched.shift();
let cssVariableValue = view.style.getCssVariable(cssVariableName);
if (cssVariableValue === null && matched.length) {
cssVariableValue = _evaluateCssVariableExpression(view, cssName, matched.join(', ')).split(',')[0];
}
if (!cssVariableValue) {
cssVariableValue = 'unset';
}
output = `${output.substring(0, idx)}${cssVariableValue}${output.substring(endIdx + 1)}`;
}
return output;
}
export function _evaluateCssCalcExpression(value) {
if (typeof value !== 'string') {
return value;
}
if (isCssCalcExpression(value)) {
return require('@csstools/css-calc').calc(_replaceKeywordsWithValues(_replaceDip(value)));
}
else {
return value;
}
}
function _replaceDip(value) {
return value.replace(/([0-9]+(\.[0-9]+)?)dip\b/g, '$1');
}
function _replaceKeywordsWithValues(value) {
let cssValue = value;
if (cssValue.includes('unset')) {
cssValue = cssValue.replace(/unset/g, '0');
}
if (cssValue.includes('infinity')) {
cssValue = cssValue.replace(/infinity/g, '999999');
}
return cssValue;
}
function getPropertiesFromMap(map) {
const props = [];
Object.getOwnPropertySymbols(map).forEach((symbol) => props.push(map[symbol]));
return props;
}
export class Property {
constructor(options) {
this.enumerable = true;
this.configurable = true;
const propertyName = options.name;
this.name = propertyName;
const key = Symbol(propertyName + ':propertyKey');
this.key = key;
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 = options.defaultValue;
this.defaultValue = defaultValue;
const eventName = propertyName + 'Change';
let equalityComparer = options.equalityComparer;
let affectsLayout = options.affectsLayout;
let valueChanged = options.valueChanged;
let valueConverter = options.valueConverter;
this.overrideHandlers = function (options) {
if (typeof options.equalityComparer !== 'undefined') {
equalityComparer = options.equalityComparer;
}
if (typeof options.affectsLayout !== 'undefined') {
affectsLayout = options.affectsLayout;
}
if (typeof options.valueChanged !== 'undefined') {
valueChanged = options.valueChanged;
}
if (typeof options.valueConverter !== 'undefined') {
valueConverter = options.valueConverter;
}
};
const property = this;
this.set = function (boxedValue) {
const reset = isResetValue(boxedValue);
let value;
let wrapped;
if (reset) {
value = defaultValue;
}
else {
wrapped = boxedValue && 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 = 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({
object: this,
eventName,
propertyName,
value,
oldValue,
});
}
if (this.domNode) {
if (reset) {
this.domNode.attributeRemoved(propertyName);
}
else {
this.domNode.attributeModified(propertyName, value);
}
}
}
};
this.get = function () {
return (key in this ? this[key] : defaultValue);
};
this.nativeValueChange = function (owner, value) {
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({
object: owner,
eventName,
propertyName,
value,
oldValue,
});
}
if (affectsLayout) {
owner.requestLayout();
}
if (owner.domNode) {
owner.domNode.attributeModified(propertyName, value);
}
}
};
symbolPropertyMap[key] = this;
}
register(cls) {
if (this.registered) {
throw new Error(`Property ${this.name} already registered.`);
}
this.registered = true;
Object.defineProperty(cls.prototype, this.name, this);
}
isSet(instance) {
return this.key in instance;
}
}
Property.prototype.isStyleProperty = false;
export class CoercibleProperty extends Property {
constructor(options) {
super(options);
const propertyName = options.name;
const key = this.key;
const getDefault = this.getDefault;
const setNative = this.setNative;
const defaultValueKey = this.defaultValueKey;
const defaultValue = this.defaultValue;
const coerceKey = Symbol(propertyName + ':coerceKey');
const eventName = propertyName + 'Change';
let affectsLayout = options.affectsLayout;
let equalityComparer = options.equalityComparer;
let valueChanged = options.valueChanged;
let valueConverter = options.valueConverter;
let coerceCallback = options.coerceValue;
const property = this;
this.overrideHandlers = function (options) {
if (typeof options.equalityComparer !== 'undefined') {
equalityComparer = options.equalityComparer;
}
if (typeof options.affectsLayout !== 'undefined') {
affectsLayout = options.affectsLayout;
}
if (typeof options.valueChanged !== 'undefined') {
valueChanged = options.valueChanged;
}
if (typeof options.valueConverter !== 'undefined') {
valueConverter = options.valueConverter;
}
if (typeof options.coerceValue !== 'undefined') {
coerceCallback = options.coerceValue;
}
};
this.coerce = function (target) {
const originalValue = (coerceKey in target ? target[coerceKey] : defaultValue);
// need that to make coercing but also fire change events
target[propertyName] = originalValue;
};
this.set = function (boxedValue) {
const reset = isResetValue(boxedValue);
let value;
let wrapped;
if (reset) {
value = defaultValue;
delete this[coerceKey];
}
else {
wrapped = boxedValue && 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 = 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({
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 extends Property {
constructor(options) {
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) => function (value) {
const that = this;
let unboxedValue;
let newValueSource;
if (isResetValue(value)) {
const parent = that.parent;
// If value is not initial or unset and view has a parent that has non-default value, use it as the reset value.
if (value !== 'initial' && parent && parent[sourceKey] !== 0 /* ValueSource.Default */) {
unboxedValue = parent[name];
newValueSource = 1 /* ValueSource.Inherited */;
}
else {
unboxedValue = defaultValue;
newValueSource = 0 /* 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 === 0 /* ValueSource.Default */;
that.eachChild((child) => {
const childValueSource = child[sourceKey] || 0 /* ValueSource.Default */;
if (reset) {
if (childValueSource === 1 /* ValueSource.Inherited */) {
setFunc.call(child, unsetValue);
}
}
else {
if (childValueSource <= 1 /* ValueSource.Inherited */) {
setInheritedValue.call(child, newValue);
}
}
return true;
});
}
};
const setInheritedValue = setFunc(1 /* ValueSource.Inherited */);
this.setInheritedValue = setInheritedValue;
this.set = setFunc(3 /* ValueSource.Local */);
inheritableProperties.push(this);
}
}
export class CssProperty {
constructor(options) {
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 = options.defaultValue;
this.defaultValue = defaultValue;
const eventName = propertyName + 'Change';
let affectsLayout = options.affectsLayout;
let equalityComparer = options.equalityComparer;
let valueChanged = options.valueChanged;
let valueConverter = options.valueConverter;
this.overrideHandlers = function (options) {
if (typeof options.equalityComparer !== 'undefined') {
equalityComparer = options.equalityComparer;
}
if (typeof options.affectsLayout !== 'undefined') {
affectsLayout = options.affectsLayout;
}
if (typeof options.valueChanged !== 'undefined') {
valueChanged = options.valueChanged;
}
if (typeof options.valueConverter !== 'undefined') {
valueConverter = options.valueConverter;
}
};
const property = this;
function setLocalValue(newValue) {
const view = this.viewRef.get();
if (!view) {
Trace.write(`${newValue} not set to view because ".viewRef" is cleared`, Trace.categories.Style, Trace.messageType.warn);
return;
}
const reset = isResetValue(newValue) || newValue === '';
let value;
if (reset) {
value = defaultValue;
delete this[sourceKey];
}
else {
this[sourceKey] = 3 /* ValueSource.Local */;
value = valueConverter && typeof newValue === 'string' ? valueConverter(newValue) : newValue;
}
const oldValue = (key in this ? this[key] : defaultValue);
const changed = 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({
object: this,
eventName,
propertyName,
value,
oldValue,
});
}
if (affectsLayout) {
view.requestLayout();
}
}
}
function setCssValue(newValue) {
const view = this.viewRef.get();
if (!view) {
Trace.write(`${newValue} not set to view because ".viewRef" is cleared`, Trace.categories.Style, Trace.messageType.warn);
return;
}
const currentValueSource = this[sourceKey] || 0 /* ValueSource.Default */;
// We have localValueSource - NOOP.
if (currentValueSource === 3 /* ValueSource.Local */) {
return;
}
const reset = isResetValue(newValue) || newValue === '';
let value;
if (reset) {
value = defaultValue;
delete this[sourceKey];
}
else {
value = valueConverter && typeof newValue === 'string' ? valueConverter(newValue) : newValue;
this[sourceKey] = 2 /* ValueSource.Css */;
}
const oldValue = (key in this ? this[key] : defaultValue);
const changed = 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({
object: this,
eventName,
propertyName,
value,
oldValue,
});
}
if (affectsLayout) {
view.requestLayout();
}
}
}
function get() {
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;
}
register(cls) {
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);
}
}
isSet(instance) {
return this.key in instance;
}
}
CssProperty.prototype.isStyleProperty = true;
export class CssAnimationProperty {
constructor(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 = options.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, propertySource, enumerable, configurable, getsComputed) {
return {
enumerable,
configurable,
get: getsComputed
? function () {
return this[computedValue];
}
: function () {
return this[symbol];
},
set(boxedValue) {
const view = this.viewRef.get();
if (!view) {
Trace.write(`${boxedValue} not set to view because ".viewRef" is cleared`, Trace.categories.Animation, Trace.messageType.warn);
return;
}
const oldValue = this[computedValue];
const oldSource = this[computedSource];
const wasSet = oldSource !== 0 /* ValueSource.Default */;
const reset = isResetValue(boxedValue) || boxedValue === '';
if (reset) {
this[symbol] = boxedValue;
if (this[computedSource] === propertySource) {
// Fallback to lower value source.
if (!isResetValue(this[styleValue])) {
this[computedSource] = 3 /* ValueSource.Local */;
this[computedValue] = this[styleValue];
}
else if (!isResetValue(this[cssValue])) {
this[computedSource] = 2 /* ValueSource.Css */;
this[computedValue] = this[cssValue];
}
else {
delete this[computedSource];
delete this[computedValue];
}
}
}
else {
if (options.valueConverter && typeof boxedValue === 'string') {
boxedValue = options.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 !== 0 /* ValueSource.Default */;
const computedValueChanged = oldValue !== value && (!options.equalityComparer || !options.equalityComparer(oldValue, value));
if (computedValueChanged && options.valueChanged) {
options.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]() : options.defaultValue;
}
view[setNative](value);
}
else if (wasSet) {
if (defaultValueKey in this) {
view[setNative](this[defaultValueKey]);
}
else {
view[setNative](options.defaultValue);
}
}
}
}
if (computedValueChanged && this.hasListeners(eventName)) {
this.notify({
object: this,
eventName,
propertyName,
value,
oldValue,
});
}
},
};
}
const defaultPropertyDescriptor = descriptor(defaultValueKey, 0 /* ValueSource.Default */, false, false, false);
const cssPropertyDescriptor = descriptor(cssValue, 2 /* ValueSource.Css */, false, false, false);
const stylePropertyDescriptor = descriptor(styleValue, 3 /* ValueSource.Local */, true, true, true);
const keyframePropertyDescriptor = descriptor(keyframeValue, 4 /* ValueSource.Keyframe */, false, false, false);
symbolPropertyMap[computedValue] = this;
cssSymbolPropertyMap[computedValue] = this;
this.register = (cls) => {
cls.prototype[computedValue] = options.defaultValue;
cls.prototype[computedSource] = 0 /* 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);
};
}
_initDefaultNativeValue(target) {
const view = target.viewRef.get();
if (!view) {
Trace.write(`_initDefaultNativeValue not executed to view because ".viewRef" is cleared`, Trace.categories.Animation, Trace.messageType.warn);
return;
}
const defaultValueKey = this.defaultValueKey;
if (!(defaultValueKey in target)) {
const getDefault = this.getDefault;
target[defaultValueKey] = view[getDefault] ? view[getDefault]() : this.defaultValue;
}
}
static _getByCssName(name) {
return this.properties[name];
}
static _getPropertyNames() {
return Object.keys(CssAnimationProperty.properties);
}
isSet(instance) {
return instance[this.source] !== 0 /* ValueSource.Default */;
}
}
CssAnimationProperty.properties = {};
CssAnimationProperty.prototype.isStyleProperty = true;
export class InheritedCssProperty extends CssProperty {
constructor(options) {
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 = options.defaultValue;
let affectsLayout = options.affectsLayout;
let equalityComparer = options.equalityComparer;
let valueChanged = options.valueChanged;
let valueConverter = options.valueConverter;
const property = this;
this.overrideHandlers = function (options) {
if (typeof options.equalityComparer !== 'undefined') {
equalityComparer = options.equalityComparer;
}
if (typeof options.affectsLayout !== 'undefined') {
affectsLayout = options.affectsLayout;
}
if (typeof options.valueChanged !== 'undefined') {
valueChanged = options.valueChanged;
}
if (typeof options.valueConverter !== 'undefined') {
valueConverter = options.valueConverter;
}
};
const setFunc = (valueSource) => function (boxedValue) {
const view = this.viewRef.get();
if (!view) {
Trace.write(`${boxedValue} not set to view's property because ".viewRef" is cleared`, Trace.categories.Style, Trace.messageType.warn);
return;
}
const reset = isResetValue(boxedValue) || boxedValue === '';
const currentValueSource = this[sourceKey] || 0 /* ValueSource.Default */;
if (reset) {
// If we want to reset cssValue and we have localValue - return;
if (valueSource === 2 /* ValueSource.Css */ && currentValueSource === 3 /* ValueSource.Local */) {
return;
}
}
else {
if (currentValueSource > valueSource) {
return;
}
}
const oldValue = key in this ? this[key] : defaultValue;
let value;
let unsetNativeValue = false;
if (reset) {
const parentStyle = view.parent ? view.parent.style : null;
// If value is not initial or unset and view has a parent that has non-default value, use it as the reset value.
if (boxedValue !== 'initial' && parentStyle && parentStyle[sourceKey] > 0 /* ValueSource.Default */) {
value = parentStyle[propertyName];
this[sourceKey] = 1 /* 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 = 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({
object: this,
eventName,
propertyName,
value,
oldValue,
});
}
if (affectsLayout) {
view.requestLayout();
}
view.eachChild((child) => {
const childStyle = child.style;
const childValueSource = childStyle[sourceKey] || 0 /* ValueSource.Default */;
if (reset) {
if (childValueSource === 1 /* ValueSource.Inherited */) {
setDefaultFunc.call(childStyle, unsetValue);
}
}
else {
if (childValueSource <= 1 /* ValueSource.Inherited */) {
setInheritedFunc.call(childStyle, value);
}
}
return true;
});
}
};
const setDefaultFunc = setFunc(0 /* ValueSource.Default */);
const setInheritedFunc = setFunc(1 /* ValueSource.Inherited */);
this.setInheritedValue = setInheritedFunc;
this.cssValueDescriptor.set = setFunc(2 /* ValueSource.Css */);
this.localValueDescriptor.set = setFunc(3 /* ValueSource.Local */);
inheritableCssProperties.push(this);
}
}
export class ShorthandProperty {
constructor(options) {
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(value) {
const view = this.viewRef.get();
if (!view) {
Trace.write(`setLocalValue not executed to view because ".viewRef" is cleared`, Trace.categories.Animation, Trace.messageType.warn);
return;
}
view._batchUpdate(() => {
for (const [p, v] of converter(value)) {
this[p.name] = v;
}
});
}
function setCssValue(value) {
const view = this.viewRef.get();
if (!view) {
Trace.write(`setCssValue not executed to view because ".viewRef" is cleared`, Trace.categories.Animation, Trace.messageType.warn);
return;
}
view._batchUpdate(() => {
for (const [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) {
converter(value).forEach(([property, value]) => {
this[property.cssLocalName] = value;
});
},
};
cssSymbolPropertyMap[key] = this;
}
register(cls) {
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) {
const array = new Array();
for (const prop of inheritableProperties) {
const sourceKey = prop.sourceKey;
const valueSource = view[sourceKey] || 0 /* ValueSource.Default */;
if (valueSource !== 0 /* 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) {
const array = new Array();
for (const prop of inheritableCssProperties) {
const sourceKey = prop.sourceKey;
const valueSource = style[sourceKey] || 0 /* ValueSource.Default */;
if (valueSource !== 0 /* 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;
}
export const initNativeView = profile('"properties".initNativeView', function initNativeView(view) {
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) {
// TODO: Check what happens if a view was suspended and its value was reset, or set back to default!
const suspendedUpdates = view._suspendedUpdates;
for (const propertyName in suspendedUpdates) {
const property = suspendedUpdates[propertyName];
const setNative = property.setNative;
if (view[setNative]) {
const { getDefault, isStyleProperty, defaultValueKey, defaultValue } = property;
let value;
if (isStyleProperty) {
const style = view.style;
if (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.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) {
let symbols = Object.getOwnPropertySymbols(view);
for (const symbol of symbols) {
const property = 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 (const symbol of symbols) {
const property = 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) {
let symbols = Object.getOwnPropertySymbols(view);
for (const symbol of symbols) {
const property = 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 (const symbol of symbols) {
const property = cssSymbolPropertyMap[symbol];
if (!property) {
continue;
}
if (view[property.setNative]) {
if (property.defaultValueKey in style) {
view[property.setNative](