53.1 kBJavaScriptView Raw
1// Types.
2import { WrappedValue } from '../../../data/observable';
3import { Trace } from '../../../trace';
4import { profile } from '../../../profiling';
5/**
6 * Value specifying that Property should be set to its initial value.
7 */
8export const unsetValue = new Object();
9const cssPropertyNames = [];
10const symbolPropertyMap = {};
11const cssSymbolPropertyMap = {};
12const inheritableProperties = new Array();
13const inheritableCssProperties = new Array();
14function print(map) {
15 const symbols = Object.getOwnPropertySymbols(map);
16 for (const symbol of symbols) {
17 const prop = map[symbol];
18 if (!prop.registered) {
19 console.log(`Property ${prop.name} not Registered!!!!!`);
20 }
21 }
22}
23export function _printUnregisteredProperties() {
24 print(symbolPropertyMap);
25 print(cssSymbolPropertyMap);
26}
27export function _getProperties() {
28 return getPropertiesFromMap(symbolPropertyMap);
29}
30export function _getStyleProperties() {
31 return getPropertiesFromMap(cssSymbolPropertyMap);
32}
33export function isCssVariable(property) {
34 return /^--[^,\s]+?$/.test(property);
35}
36export function isCssCalcExpression(value) {
37 return value.includes('calc(');
38}
39export function isCssVariableExpression(value) {
40 return value.includes('var(--');
41}
42export function _evaluateCssVariableExpression(view, cssName, value) {
43 if (typeof value !== 'string') {
44 return value;
45 }
46 if (!isCssVariableExpression(value)) {
47 // Value is not using css-variable(s)
48 return value;
49 }
50 let output = value.trim();
51 // Evaluate every (and nested) css-variables in the value.
52 let lastValue;
53 while (lastValue !== output) {
54 lastValue = output;
55 const idx = output.lastIndexOf('var(');
56 if (idx === -1) {
57 continue;
58 }
59 const endIdx = output.indexOf(')', idx);
60 if (endIdx === -1) {
61 continue;
62 }
63 const matched = output
64 .substring(idx + 4, endIdx)
65 .split(',')
66 .map((v) => v.trim())
67 .filter((v) => !!v);
68 const cssVariableName = matched.shift();
69 let cssVariableValue = view.style.getCssVariable(cssVariableName);
70 if (cssVariableValue === null && matched.length) {
71 cssVariableValue = _evaluateCssVariableExpression(view, cssName, matched.join(', ')).split(',')[0];
72 }
73 if (!cssVariableValue) {
74 cssVariableValue = 'unset';
75 }
76 output = `${output.substring(0, idx)}${cssVariableValue}${output.substring(endIdx + 1)}`;
77 }
78 return output;
79}
80export function _evaluateCssCalcExpression(value) {
81 if (typeof value !== 'string') {
82 return value;
83 }
84 if (isCssCalcExpression(value)) {
85 // WORKAROUND: reduce-css-calc can't handle the dip-unit.
86 return require('reduce-css-calc')(value.replace(/([0-9]+(\.[0-9]+)?)dip\b/g, '$1'));
87 }
88 else {
89 return value;
90 }
91}
92function getPropertiesFromMap(map) {
93 const props = [];
94 Object.getOwnPropertySymbols(map).forEach((symbol) => props.push(map[symbol]));
95 return props;
96}
97export class Property {
98 constructor(options) {
99 this.enumerable = true;
100 this.configurable = true;
101 const propertyName = options.name;
102 this.name = propertyName;
103 const key = Symbol(propertyName + ':propertyKey');
104 this.key = key;
105 const getDefault = Symbol(propertyName + ':getDefault');
106 this.getDefault = getDefault;
107 const setNative = Symbol(propertyName + ':setNative');
108 this.setNative = setNative;
109 const defaultValueKey = Symbol(propertyName + ':nativeDefaultValue');
110 this.defaultValueKey = defaultValueKey;
111 const defaultValue = options.defaultValue;
112 this.defaultValue = defaultValue;
113 const eventName = propertyName + 'Change';
114 let equalityComparer = options.equalityComparer;
115 let affectsLayout = options.affectsLayout;
116 let valueChanged = options.valueChanged;
117 let valueConverter = options.valueConverter;
118 this.overrideHandlers = function (options) {
119 if (typeof options.equalityComparer !== 'undefined') {
120 equalityComparer = options.equalityComparer;
121 }
122 if (typeof options.affectsLayout !== 'undefined') {
123 affectsLayout = options.affectsLayout;
124 }
125 if (typeof options.valueChanged !== 'undefined') {
126 valueChanged = options.valueChanged;
127 }
128 if (typeof options.valueConverter !== 'undefined') {
129 valueConverter = options.valueConverter;
130 }
131 };
132 const property = this;
133 this.set = function (boxedValue) {
134 const reset = boxedValue === unsetValue;
135 let value;
136 let wrapped;
137 if (reset) {
138 value = defaultValue;
139 }
140 else {
141 wrapped = boxedValue && boxedValue.wrapped;
142 value = wrapped ? WrappedValue.unwrap(boxedValue) : boxedValue;
143 if (valueConverter && typeof value === 'string') {
144 value = valueConverter(value);
145 }
146 }
147 const oldValue = (key in this ? this[key] : defaultValue);
148 const changed = equalityComparer ? !equalityComparer(oldValue, value) : oldValue !== value;
149 if (wrapped || changed) {
150 if (affectsLayout) {
151 this.requestLayout();
152 }
153 if (reset) {
154 delete this[key];
155 if (valueChanged) {
156 valueChanged(this, oldValue, value);
157 }
158 if (this[setNative]) {
159 if (this._suspendNativeUpdatesCount) {
160 if (this._suspendedUpdates) {
161 this._suspendedUpdates[propertyName] = property;
162 }
163 }
164 else {
165 if (defaultValueKey in this) {
166 this[setNative](this[defaultValueKey]);
167 delete this[defaultValueKey];
168 }
169 else {
170 this[setNative](defaultValue);
171 }
172 }
173 }
174 }
175 else {
176 this[key] = value;
177 if (valueChanged) {
178 valueChanged(this, oldValue, value);
179 }
180 if (this[setNative]) {
181 if (this._suspendNativeUpdatesCount) {
182 if (this._suspendedUpdates) {
183 this._suspendedUpdates[propertyName] = property;
184 }
185 }
186 else {
187 if (!(defaultValueKey in this)) {
188 this[defaultValueKey] = this[getDefault] ? this[getDefault]() : defaultValue;
189 }
190 this[setNative](value);
191 }
192 }
193 }
194 if (this.hasListeners(eventName)) {
195 this.notify({
196 object: this,
197 eventName,
198 propertyName,
199 value,
200 oldValue,
201 });
202 }
203 if (this.domNode) {
204 if (reset) {
205 this.domNode.attributeRemoved(propertyName);
206 }
207 else {
208 this.domNode.attributeModified(propertyName, value);
209 }
210 }
211 }
212 };
213 this.get = function () {
214 return (key in this ? this[key] : defaultValue);
215 };
216 this.nativeValueChange = function (owner, value) {
217 const oldValue = (key in owner ? owner[key] : defaultValue);
218 const changed = equalityComparer ? !equalityComparer(oldValue, value) : oldValue !== value;
219 if (changed) {
220 owner[key] = value;
221 if (valueChanged) {
222 valueChanged(owner, oldValue, value);
223 }
224 if (owner.nativeViewProtected && !(defaultValueKey in owner)) {
225 owner[defaultValueKey] = owner[getDefault] ? owner[getDefault]() : defaultValue;
226 }
227 if (owner.hasListeners(eventName)) {
228 owner.notify({
229 object: owner,
230 eventName,
231 propertyName,
232 value,
233 oldValue,
234 });
235 }
236 if (affectsLayout) {
237 owner.requestLayout();
238 }
239 if (owner.domNode) {
240 owner.domNode.attributeModified(propertyName, value);
241 }
242 }
243 };
244 symbolPropertyMap[key] = this;
245 }
246 register(cls) {
247 if (this.registered) {
248 throw new Error(`Property ${this.name} already registered.`);
249 }
250 this.registered = true;
251 Object.defineProperty(cls.prototype, this.name, this);
252 }
253 isSet(instance) {
254 return this.key in instance;
255 }
256}
257Property.prototype.isStyleProperty = false;
258export class CoercibleProperty extends Property {
259 constructor(options) {
260 super(options);
261 const propertyName = options.name;
262 const key = this.key;
263 const getDefault = this.getDefault;
264 const setNative = this.setNative;
265 const defaultValueKey = this.defaultValueKey;
266 const defaultValue = this.defaultValue;
267 const coerceKey = Symbol(propertyName + ':coerceKey');
268 const eventName = propertyName + 'Change';
269 let affectsLayout = options.affectsLayout;
270 let equalityComparer = options.equalityComparer;
271 let valueChanged = options.valueChanged;
272 let valueConverter = options.valueConverter;
273 let coerceCallback = options.coerceValue;
274 const property = this;
275 this.overrideHandlers = function (options) {
276 if (typeof options.equalityComparer !== 'undefined') {
277 equalityComparer = options.equalityComparer;
278 }
279 if (typeof options.affectsLayout !== 'undefined') {
280 affectsLayout = options.affectsLayout;
281 }
282 if (typeof options.valueChanged !== 'undefined') {
283 valueChanged = options.valueChanged;
284 }
285 if (typeof options.valueConverter !== 'undefined') {
286 valueConverter = options.valueConverter;
287 }
288 if (typeof options.coerceValue !== 'undefined') {
289 coerceCallback = options.coerceValue;
290 }
291 };
292 this.coerce = function (target) {
293 const originalValue = (coerceKey in target ? target[coerceKey] : defaultValue);
294 // need that to make coercing but also fire change events
295 target[propertyName] = originalValue;
296 };
297 this.set = function (boxedValue) {
298 const reset = boxedValue === unsetValue;
299 let value;
300 let wrapped;
301 if (reset) {
302 value = defaultValue;
303 delete this[coerceKey];
304 }
305 else {
306 wrapped = boxedValue && boxedValue.wrapped;
307 value = wrapped ? WrappedValue.unwrap(boxedValue) : boxedValue;
308 if (valueConverter && typeof value === 'string') {
309 value = valueConverter(value);
310 }
311 this[coerceKey] = value;
312 value = coerceCallback(this, value);
313 }
314 const oldValue = key in this ? this[key] : defaultValue;
315 const changed = equalityComparer ? !equalityComparer(oldValue, value) : oldValue !== value;
316 if (wrapped || changed) {
317 if (reset) {
318 delete this[key];
319 if (valueChanged) {
320 valueChanged(this, oldValue, value);
321 }
322 if (this[setNative]) {
323 if (this._suspendNativeUpdatesCount) {
324 if (this._suspendedUpdates) {
325 this._suspendedUpdates[propertyName] = property;
326 }
327 }
328 else {
329 if (defaultValueKey in this) {
330 this[setNative](this[defaultValueKey]);
331 delete this[defaultValueKey];
332 }
333 else {
334 this[setNative](defaultValue);
335 }
336 }
337 }
338 }
339 else {
340 this[key] = value;
341 if (valueChanged) {
342 valueChanged(this, oldValue, value);
343 }
344 if (this[setNative]) {
345 if (this._suspendNativeUpdatesCount) {
346 if (this._suspendedUpdates) {
347 this._suspendedUpdates[propertyName] = property;
348 }
349 }
350 else {
351 if (!(defaultValueKey in this)) {
352 this[defaultValueKey] = this[getDefault] ? this[getDefault]() : defaultValue;
353 }
354 this[setNative](value);
355 }
356 }
357 }
358 if (this.hasListeners(eventName)) {
359 this.notify({
360 object: this,
361 eventName,
362 propertyName,
363 value,
364 oldValue,
365 });
366 }
367 if (affectsLayout) {
368 this.requestLayout();
369 }
370 if (this.domNode) {
371 if (reset) {
372 this.domNode.attributeRemoved(propertyName);
373 }
374 else {
375 this.domNode.attributeModified(propertyName, value);
376 }
377 }
378 }
379 };
380 }
381}
382export class InheritedProperty extends Property {
383 constructor(options) {
384 super(options);
385 const name = options.name;
386 const key = this.key;
387 const defaultValue = options.defaultValue;
388 const sourceKey = Symbol(name + ':valueSourceKey');
389 this.sourceKey = sourceKey;
390 const setBase = this.set;
391 const setFunc = (valueSource) => function (value) {
392 const that = this;
393 let unboxedValue;
394 let newValueSource;
395 if (value === unsetValue) {
396 // If unsetValue - we want to reset the property.
397 const parent = that.parent;
398 // If we have parent and it has non-default value we use as our inherited value.
399 if (parent && parent[sourceKey] !== 0 /* ValueSource.Default */) {
400 unboxedValue = parent[name];
401 newValueSource = 1 /* ValueSource.Inherited */;
402 }
403 else {
404 unboxedValue = defaultValue;
405 newValueSource = 0 /* ValueSource.Default */;
406 }
407 }
408 else {
409 // else we are set through property set.
410 unboxedValue = value;
411 newValueSource = valueSource;
412 }
413 // take currentValue before calling base - base may change it.
414 const currentValue = that[key];
415 setBase.call(that, unboxedValue);
416 const newValue = that[key];
417 that[sourceKey] = newValueSource;
418 if (currentValue !== newValue) {
419 const reset = newValueSource === 0 /* ValueSource.Default */;
420 that.eachChild((child) => {
421 const childValueSource = child[sourceKey] || 0 /* ValueSource.Default */;
422 if (reset) {
423 if (childValueSource === 1 /* ValueSource.Inherited */) {
424 setFunc.call(child, unsetValue);
425 }
426 }
427 else {
428 if (childValueSource <= 1 /* ValueSource.Inherited */) {
429 setInheritedValue.call(child, newValue);
430 }
431 }
432 return true;
433 });
434 }
435 };
436 const setInheritedValue = setFunc(1 /* ValueSource.Inherited */);
437 this.setInheritedValue = setInheritedValue;
438 this.set = setFunc(3 /* ValueSource.Local */);
439 inheritableProperties.push(this);
440 }
441}
442export class CssProperty {
443 constructor(options) {
444 const propertyName = options.name;
445 this.name = propertyName;
446 cssPropertyNames.push(options.cssName);
447 this.cssName = `css:${options.cssName}`;
448 this.cssLocalName = options.cssName;
449 const key = Symbol(propertyName + ':propertyKey');
450 this.key = key;
451 const sourceKey = Symbol(propertyName + ':valueSourceKey');
452 this.sourceKey = sourceKey;
453 const getDefault = Symbol(propertyName + ':getDefault');
454 this.getDefault = getDefault;
455 const setNative = Symbol(propertyName + ':setNative');
456 this.setNative = setNative;
457 const defaultValueKey = Symbol(propertyName + ':nativeDefaultValue');
458 this.defaultValueKey = defaultValueKey;
459 const defaultValue = options.defaultValue;
460 this.defaultValue = defaultValue;
461 const eventName = propertyName + 'Change';
462 let affectsLayout = options.affectsLayout;
463 let equalityComparer = options.equalityComparer;
464 let valueChanged = options.valueChanged;
465 let valueConverter = options.valueConverter;
466 this.overrideHandlers = function (options) {
467 if (typeof options.equalityComparer !== 'undefined') {
468 equalityComparer = options.equalityComparer;
469 }
470 if (typeof options.affectsLayout !== 'undefined') {
471 affectsLayout = options.affectsLayout;
472 }
473 if (typeof options.valueChanged !== 'undefined') {
474 valueChanged = options.valueChanged;
475 }
476 if (typeof options.valueConverter !== 'undefined') {
477 valueConverter = options.valueConverter;
478 }
479 };
480 const property = this;
481 function setLocalValue(newValue) {
482 const view = this.viewRef.get();
483 if (!view) {
484 Trace.write(`${newValue} not set to view because ".viewRef" is cleared`, Trace.categories.Style, Trace.messageType.warn);
485 return;
486 }
487 const reset = newValue === unsetValue || newValue === '';
488 let value;
489 if (reset) {
490 value = defaultValue;
491 delete this[sourceKey];
492 }
493 else {
494 this[sourceKey] = 3 /* ValueSource.Local */;
495 value = valueConverter && typeof newValue === 'string' ? valueConverter(newValue) : newValue;
496 }
497 const oldValue = (key in this ? this[key] : defaultValue);
498 const changed = equalityComparer ? !equalityComparer(oldValue, value) : oldValue !== value;
499 if (changed) {
500 if (reset) {
501 delete this[key];
502 if (valueChanged) {
503 valueChanged(this, oldValue, value);
504 }
505 if (view[setNative]) {
506 if (view._suspendNativeUpdatesCount) {
507 if (view._suspendedUpdates) {
508 view._suspendedUpdates[propertyName] = property;
509 }
510 }
511 else {
512 if (defaultValueKey in this) {
513 view[setNative](this[defaultValueKey]);
514 delete this[defaultValueKey];
515 }
516 else {
517 view[setNative](defaultValue);
518 }
519 }
520 }
521 }
522 else {
523 this[key] = value;
524 if (valueChanged) {
525 valueChanged(this, oldValue, value);
526 }
527 if (view[setNative]) {
528 if (view._suspendNativeUpdatesCount) {
529 if (view._suspendedUpdates) {
530 view._suspendedUpdates[propertyName] = property;
531 }
532 }
533 else {
534 if (!(defaultValueKey in this)) {
535 this[defaultValueKey] = view[getDefault] ? view[getDefault]() : defaultValue;
536 }
537 view[setNative](value);
538 }
539 }
540 }
541 if (this.hasListeners(eventName)) {
542 this.notify({
543 object: this,
544 eventName,
545 propertyName,
546 value,
547 oldValue,
548 });
549 }
550 if (affectsLayout) {
551 view.requestLayout();
552 }
553 }
554 }
555 function setCssValue(newValue) {
556 const view = this.viewRef.get();
557 if (!view) {
558 Trace.write(`${newValue} not set to view because ".viewRef" is cleared`, Trace.categories.Style, Trace.messageType.warn);
559 return;
560 }
561 const currentValueSource = this[sourceKey] || 0 /* ValueSource.Default */;
562 // We have localValueSource - NOOP.
563 if (currentValueSource === 3 /* ValueSource.Local */) {
564 return;
565 }
566 const reset = newValue === unsetValue || newValue === '';
567 let value;
568 if (reset) {
569 value = defaultValue;
570 delete this[sourceKey];
571 }
572 else {
573 value = valueConverter && typeof newValue === 'string' ? valueConverter(newValue) : newValue;
574 this[sourceKey] = 2 /* ValueSource.Css */;
575 }
576 const oldValue = (key in this ? this[key] : defaultValue);
577 const changed = equalityComparer ? !equalityComparer(oldValue, value) : oldValue !== value;
578 if (changed) {
579 if (reset) {
580 delete this[key];
581 if (valueChanged) {
582 valueChanged(this, oldValue, value);
583 }
584 if (view[setNative]) {
585 if (view._suspendNativeUpdatesCount) {
586 if (view._suspendedUpdates) {
587 view._suspendedUpdates[propertyName] = property;
588 }
589 }
590 else {
591 if (defaultValueKey in this) {
592 view[setNative](this[defaultValueKey]);
593 delete this[defaultValueKey];
594 }
595 else {
596 view[setNative](defaultValue);
597 }
598 }
599 }
600 }
601 else {
602 this[key] = value;
603 if (valueChanged) {
604 valueChanged(this, oldValue, value);
605 }
606 if (view[setNative]) {
607 if (view._suspendNativeUpdatesCount) {
608 if (view._suspendedUpdates) {
609 view._suspendedUpdates[propertyName] = property;
610 }
611 }
612 else {
613 if (!(defaultValueKey in this)) {
614 this[defaultValueKey] = view[getDefault] ? view[getDefault]() : defaultValue;
615 }
616 view[setNative](value);
617 }
618 }
619 }
620 if (this.hasListeners(eventName)) {
621 this.notify({
622 object: this,
623 eventName,
624 propertyName,
625 value,
626 oldValue,
627 });
628 }
629 if (affectsLayout) {
630 view.requestLayout();
631 }
632 }
633 }
634 function get() {
635 return key in this ? this[key] : defaultValue;
636 }
637 this.cssValueDescriptor = {
638 enumerable: true,
639 configurable: true,
640 get: get,
641 set: setCssValue,
642 };
643 this.localValueDescriptor = {
644 enumerable: true,
645 configurable: true,
646 get: get,
647 set: setLocalValue,
648 };
649 cssSymbolPropertyMap[key] = this;
650 }
651 register(cls) {
652 if (this.registered) {
653 throw new Error(`Property ${this.name} already registered.`);
654 }
655 this.registered = true;
656 Object.defineProperty(cls.prototype, this.name, this.localValueDescriptor);
657 Object.defineProperty(cls.prototype, this.cssName, this.cssValueDescriptor);
658 if (this.cssLocalName !== this.cssName) {
659 Object.defineProperty(cls.prototype, this.cssLocalName, this.localValueDescriptor);
660 }
661 }
662 isSet(instance) {
663 return this.key in instance;
664 }
665}
666CssProperty.prototype.isStyleProperty = true;
667export class CssAnimationProperty {
668 constructor(options) {
669 const propertyName = options.name;
670 this.name = propertyName;
671 cssPropertyNames.push(options.cssName);
672 CssAnimationProperty.properties[propertyName] = this;
673 if (options.cssName && options.cssName !== propertyName) {
674 CssAnimationProperty.properties[options.cssName] = this;
675 }
676 this._valueConverter = options.valueConverter;
677 const cssLocalName = options.cssName || propertyName;
678 this.cssLocalName = cssLocalName;
679 const cssName = 'css:' + cssLocalName;
680 this.cssName = cssName;
681 const keyframeName = 'keyframe:' + propertyName;
682 this.keyframe = keyframeName;
683 const defaultName = 'default:' + propertyName;
684 const defaultValueKey = Symbol(defaultName);
685 this.defaultValueKey = defaultValueKey;
686 this.defaultValue = options.defaultValue;
687 const cssValue = Symbol(cssName);
688 const styleValue = Symbol(`local:${propertyName}`);
689 const keyframeValue = Symbol(keyframeName);
690 const computedValue = Symbol('computed-value:' + propertyName);
691 this.key = computedValue;
692 const computedSource = Symbol('computed-source:' + propertyName);
693 this.source = computedSource;
694 this.getDefault = Symbol(propertyName + ':getDefault');
695 const getDefault = this.getDefault;
696 const setNative = (this.setNative = Symbol(propertyName + ':setNative'));
697 const eventName = propertyName + 'Change';
698 const property = this;
699 function descriptor(symbol, propertySource, enumerable, configurable, getsComputed) {
700 return {
701 enumerable,
702 configurable,
703 get: getsComputed
704 ? function () {
705 return this[computedValue];
706 }
707 : function () {
708 return this[symbol];
709 },
710 set(boxedValue) {
711 const view = this.viewRef.get();
712 if (!view) {
713 Trace.write(`${boxedValue} not set to view because ".viewRef" is cleared`, Trace.categories.Animation, Trace.messageType.warn);
714 return;
715 }
716 const oldValue = this[computedValue];
717 const oldSource = this[computedSource];
718 const wasSet = oldSource !== 0 /* ValueSource.Default */;
719 const reset = boxedValue === unsetValue || boxedValue === '';
720 if (reset) {
721 this[symbol] = unsetValue;
722 if (this[computedSource] === propertySource) {
723 // Fallback to lower value source.
724 if (this[styleValue] !== unsetValue) {
725 this[computedSource] = 3 /* ValueSource.Local */;
726 this[computedValue] = this[styleValue];
727 }
728 else if (this[cssValue] !== unsetValue) {
729 this[computedSource] = 2 /* ValueSource.Css */;
730 this[computedValue] = this[cssValue];
731 }
732 else {
733 delete this[computedSource];
734 delete this[computedValue];
735 }
736 }
737 }
738 else {
739 if (options.valueConverter && typeof boxedValue === 'string') {
740 boxedValue = options.valueConverter(boxedValue);
741 }
742 this[symbol] = boxedValue;
743 if (this[computedSource] <= propertySource) {
744 this[computedSource] = propertySource;
745 this[computedValue] = boxedValue;
746 }
747 }
748 const value = this[computedValue];
749 const source = this[computedSource];
750 const isSet = source !== 0 /* ValueSource.Default */;
751 const computedValueChanged = oldValue !== value && (!options.equalityComparer || !options.equalityComparer(oldValue, value));
752 if (computedValueChanged && options.valueChanged) {
753 options.valueChanged(this, oldValue, value);
754 }
755 if (view[setNative] && (computedValueChanged || isSet !== wasSet)) {
756 if (view._suspendNativeUpdatesCount) {
757 if (view._suspendedUpdates) {
758 view._suspendedUpdates[propertyName] = property;
759 }
760 }
761 else {
762 if (isSet) {
763 if (!wasSet && !(defaultValueKey in this)) {
764 this[defaultValueKey] = view[getDefault] ? view[getDefault]() : options.defaultValue;
765 }
766 view[setNative](value);
767 }
768 else if (wasSet) {
769 if (defaultValueKey in this) {
770 view[setNative](this[defaultValueKey]);
771 }
772 else {
773 view[setNative](options.defaultValue);
774 }
775 }
776 }
777 }
778 if (computedValueChanged && this.hasListeners(eventName)) {
779 this.notify({
780 object: this,
781 eventName,
782 propertyName,
783 value,
784 oldValue,
785 });
786 }
787 },
788 };
789 }
790 const defaultPropertyDescriptor = descriptor(defaultValueKey, 0 /* ValueSource.Default */, false, false, false);
791 const cssPropertyDescriptor = descriptor(cssValue, 2 /* ValueSource.Css */, false, false, false);
792 const stylePropertyDescriptor = descriptor(styleValue, 3 /* ValueSource.Local */, true, true, true);
793 const keyframePropertyDescriptor = descriptor(keyframeValue, 4 /* ValueSource.Keyframe */, false, false, false);
794 symbolPropertyMap[computedValue] = this;
795 cssSymbolPropertyMap[computedValue] = this;
796 this.register = (cls) => {
797 cls.prototype[computedValue] = options.defaultValue;
798 cls.prototype[computedSource] = 0 /* ValueSource.Default */;
799 cls.prototype[cssValue] = unsetValue;
800 cls.prototype[styleValue] = unsetValue;
801 cls.prototype[keyframeValue] = unsetValue;
802 Object.defineProperty(cls.prototype, defaultName, defaultPropertyDescriptor);
803 Object.defineProperty(cls.prototype, cssName, cssPropertyDescriptor);
804 Object.defineProperty(cls.prototype, propertyName, stylePropertyDescriptor);
805 if (options.cssName && options.cssName !== options.name) {
806 Object.defineProperty(cls.prototype, options.cssName, stylePropertyDescriptor);
807 }
808 Object.defineProperty(cls.prototype, keyframeName, keyframePropertyDescriptor);
809 };
810 }
811 _initDefaultNativeValue(target) {
812 const view = target.viewRef.get();
813 if (!view) {
814 Trace.write(`_initDefaultNativeValue not executed to view because ".viewRef" is cleared`, Trace.categories.Animation, Trace.messageType.warn);
815 return;
816 }
817 const defaultValueKey = this.defaultValueKey;
818 if (!(defaultValueKey in target)) {
819 const getDefault = this.getDefault;
820 target[defaultValueKey] = view[getDefault] ? view[getDefault]() : this.defaultValue;
821 }
822 }
823 static _getByCssName(name) {
824 return this.properties[name];
825 }
826 static _getPropertyNames() {
827 return Object.keys(CssAnimationProperty.properties);
828 }
829 isSet(instance) {
830 return instance[this.source] !== 0 /* ValueSource.Default */;
831 }
832}
833CssAnimationProperty.properties = {};
834CssAnimationProperty.prototype.isStyleProperty = true;
835export class InheritedCssProperty extends CssProperty {
836 constructor(options) {
837 super(options);
838 const propertyName = options.name;
839 const key = this.key;
840 const sourceKey = this.sourceKey;
841 const getDefault = this.getDefault;
842 const setNative = this.setNative;
843 const defaultValueKey = this.defaultValueKey;
844 const eventName = propertyName + 'Change';
845 let defaultValue = options.defaultValue;
846 let affectsLayout = options.affectsLayout;
847 let equalityComparer = options.equalityComparer;
848 let valueChanged = options.valueChanged;
849 let valueConverter = options.valueConverter;
850 const property = this;
851 this.overrideHandlers = function (options) {
852 if (typeof options.equalityComparer !== 'undefined') {
853 equalityComparer = options.equalityComparer;
854 }
855 if (typeof options.affectsLayout !== 'undefined') {
856 affectsLayout = options.affectsLayout;
857 }
858 if (typeof options.valueChanged !== 'undefined') {
859 valueChanged = options.valueChanged;
860 }
861 if (typeof options.valueConverter !== 'undefined') {
862 valueConverter = options.valueConverter;
863 }
864 };
865 const setFunc = (valueSource) => function (boxedValue) {
866 const view = this.viewRef.get();
867 if (!view) {
868 Trace.write(`${boxedValue} not set to view's property because ".viewRef" is cleared`, Trace.categories.Style, Trace.messageType.warn);
869 return;
870 }
871 const reset = boxedValue === unsetValue || boxedValue === '';
872 const currentValueSource = this[sourceKey] || 0 /* ValueSource.Default */;
873 if (reset) {
874 // If we want to reset cssValue and we have localValue - return;
875 if (valueSource === 2 /* ValueSource.Css */ && currentValueSource === 3 /* ValueSource.Local */) {
876 return;
877 }
878 }
879 else {
880 if (currentValueSource > valueSource) {
881 return;
882 }
883 }
884 const oldValue = key in this ? this[key] : defaultValue;
885 let value;
886 let unsetNativeValue = false;
887 if (reset) {
888 // If unsetValue - we want to reset this property.
889 const parent = view.parent;
890 const style = parent ? parent.style : null;
891 // If we have parent and it has non-default value we use as our inherited value.
892 if (style && style[sourceKey] > 0 /* ValueSource.Default */) {
893 value = style[propertyName];
894 this[sourceKey] = 1 /* ValueSource.Inherited */;
895 this[key] = value;
896 }
897 else {
898 value = defaultValue;
899 delete this[sourceKey];
900 delete this[key];
901 unsetNativeValue = true;
902 }
903 }
904 else {
905 this[sourceKey] = valueSource;
906 if (valueConverter && typeof boxedValue === 'string') {
907 value = valueConverter(boxedValue);
908 }
909 else {
910 value = boxedValue;
911 }
912 this[key] = value;
913 }
914 const changed = equalityComparer ? !equalityComparer(oldValue, value) : oldValue !== value;
915 if (changed) {
916 if (valueChanged) {
917 valueChanged(this, oldValue, value);
918 }
919 if (view[setNative]) {
920 if (view._suspendNativeUpdatesCount) {
921 if (view._suspendedUpdates) {
922 view._suspendedUpdates[propertyName] = property;
923 }
924 }
925 else {
926 if (unsetNativeValue) {
927 if (defaultValueKey in this) {
928 view[setNative](this[defaultValueKey]);
929 delete this[defaultValueKey];
930 }
931 else {
932 view[setNative](defaultValue);
933 }
934 }
935 else {
936 if (!(defaultValueKey in this)) {
937 this[defaultValueKey] = view[getDefault] ? view[getDefault]() : defaultValue;
938 }
939 view[setNative](value);
940 }
941 }
942 }
943 if (this.hasListeners(eventName)) {
944 this.notify({
945 object: this,
946 eventName,
947 propertyName,
948 value,
949 oldValue,
950 });
951 }
952 if (affectsLayout) {
953 view.requestLayout();
954 }
955 view.eachChild((child) => {
956 const childStyle = child.style;
957 const childValueSource = childStyle[sourceKey] || 0 /* ValueSource.Default */;
958 if (reset) {
959 if (childValueSource === 1 /* ValueSource.Inherited */) {
960 setDefaultFunc.call(childStyle, unsetValue);
961 }
962 }
963 else {
964 if (childValueSource <= 1 /* ValueSource.Inherited */) {
965 setInheritedFunc.call(childStyle, value);
966 }
967 }
968 return true;
969 });
970 }
971 };
972 const setDefaultFunc = setFunc(0 /* ValueSource.Default */);
973 const setInheritedFunc = setFunc(1 /* ValueSource.Inherited */);
974 this.setInheritedValue = setInheritedFunc;
975 this.cssValueDescriptor.set = setFunc(2 /* ValueSource.Css */);
976 this.localValueDescriptor.set = setFunc(3 /* ValueSource.Local */);
977 inheritableCssProperties.push(this);
978 }
979}
980export class ShorthandProperty {
981 constructor(options) {
982 this.name = options.name;
983 const key = Symbol(this.name + ':propertyKey');
984 this.key = key;
985 this.cssName = `css:${options.cssName}`;
986 this.cssLocalName = `${options.cssName}`;
987 const converter = options.converter;
988 function setLocalValue(value) {
989 const view = this.viewRef.get();
990 if (!view) {
991 Trace.write(`setLocalValue not executed to view because ".viewRef" is cleared`, Trace.categories.Animation, Trace.messageType.warn);
992 return;
993 }
994 view._batchUpdate(() => {
995 for (const [p, v] of converter(value)) {
996 this[p.name] = v;
997 }
998 });
999 }
1000 function setCssValue(value) {
1001 const view = this.viewRef.get();
1002 if (!view) {
1003 Trace.write(`setCssValue not executed to view because ".viewRef" is cleared`, Trace.categories.Animation, Trace.messageType.warn);
1004 return;
1005 }
1006 view._batchUpdate(() => {
1007 for (const [p, v] of converter(value)) {
1008 this[p.cssName] = v;
1009 }
1010 });
1011 }
1012 this.cssValueDescriptor = {
1013 enumerable: true,
1014 configurable: true,
1015 get: options.getter,
1016 set: setCssValue,
1017 };
1018 this.localValueDescriptor = {
1019 enumerable: true,
1020 configurable: true,
1021 get: options.getter,
1022 set: setLocalValue,
1023 };
1024 this.propertyBagDescriptor = {
1025 enumerable: false,
1026 configurable: true,
1027 set(value) {
1028 converter(value).forEach(([property, value]) => {
1029 this[property.cssLocalName] = value;
1030 });
1031 },
1032 };
1033 cssSymbolPropertyMap[key] = this;
1034 }
1035 register(cls) {
1036 if (this.registered) {
1037 throw new Error(`Property ${this.name} already registered.`);
1038 }
1039 this.registered = true;
1040 Object.defineProperty(cls.prototype, this.name, this.localValueDescriptor);
1041 Object.defineProperty(cls.prototype, this.cssName, this.cssValueDescriptor);
1042 if (this.cssLocalName !== this.cssName) {
1043 Object.defineProperty(cls.prototype, this.cssLocalName, this.localValueDescriptor);
1044 }
1045 Object.defineProperty(cls.prototype.PropertyBag, this.cssLocalName, this.propertyBagDescriptor);
1046 }
1047}
1048function inheritablePropertyValuesOn(view) {
1049 const array = new Array();
1050 for (const prop of inheritableProperties) {
1051 const sourceKey = prop.sourceKey;
1052 const valueSource = view[sourceKey] || 0 /* ValueSource.Default */;
1053 if (valueSource !== 0 /* ValueSource.Default */) {
1054 // use prop.name as it will return value or default value.
1055 // prop.key will return undefined if property is set the same value as default one.
1056 array.push({ property: prop, value: view[prop.name] });
1057 }
1058 }
1059 return array;
1060}
1061function inheritableCssPropertyValuesOn(style) {
1062 const array = new Array();
1063 for (const prop of inheritableCssProperties) {
1064 const sourceKey = prop.sourceKey;
1065 const valueSource = style[sourceKey] || 0 /* ValueSource.Default */;
1066 if (valueSource !== 0 /* ValueSource.Default */) {
1067 // use prop.name as it will return value or default value.
1068 // prop.key will return undefined if property is set the same value as default one.
1069 array.push({ property: prop, value: style[prop.name] });
1070 }
1071 }
1072 return array;
1073}
1074export const initNativeView = profile('"properties".initNativeView', function initNativeView(view) {
1075 if (view._suspendedUpdates) {
1076 applyPendingNativeSetters(view);
1077 }
1078 else {
1079 applyAllNativeSetters(view);
1080 }
1081 // Would it be faster to delete all members of the old object?
1082 view._suspendedUpdates = {};
1083});
1084export function applyPendingNativeSetters(view) {
1085 // TODO: Check what happens if a view was suspended and its value was reset, or set back to default!
1086 const suspendedUpdates = view._suspendedUpdates;
1087 for (const propertyName in suspendedUpdates) {
1088 const property = suspendedUpdates[propertyName];
1089 const setNative = property.setNative;
1090 if (view[setNative]) {
1091 const { getDefault, isStyleProperty, defaultValueKey, defaultValue } = property;
1092 let value;
1093 if (isStyleProperty) {
1094 const style = view.style;
1095 if (property.isSet(view.style)) {
1096 if (!(defaultValueKey in style)) {
1097 style[defaultValueKey] = view[getDefault] ? view[getDefault]() : defaultValue;
1098 }
1099 value = view.style[propertyName];
1100 }
1101 else {
1102 value = style[defaultValueKey];
1103 }
1104 }
1105 else {
1106 if (property.isSet(view)) {
1107 if (!(defaultValueKey in view)) {
1108 view[defaultValueKey] = view[getDefault] ? view[getDefault]() : defaultValue;
1109 }
1110 value = view[propertyName];
1111 }
1112 else {
1113 value = view[defaultValueKey];
1114 }
1115 }
1116 // TODO: Only if value is different from the value before the scope was created.
1117 view[setNative](value);
1118 }
1119 }
1120}
1121export function applyAllNativeSetters(view) {
1122 let symbols = Object.getOwnPropertySymbols(view);
1123 for (const symbol of symbols) {
1124 const property = symbolPropertyMap[symbol];
1125 if (!property) {
1126 continue;
1127 }
1128 const setNative = property.setNative;
1129 const getDefault = property.getDefault;
1130 if (setNative in view) {
1131 const defaultValueKey = property.defaultValueKey;
1132 if (!(defaultValueKey in view)) {
1133 view[defaultValueKey] = view[getDefault] ? view[getDefault]() : property.defaultValue;
1134 }
1135 const value = view[symbol];
1136 view[setNative](value);
1137 }
1138 }
1139 const style = view.style;
1140 symbols = Object.getOwnPropertySymbols(style);
1141 for (const symbol of symbols) {
1142 const property = cssSymbolPropertyMap[symbol];
1143 if (!property) {
1144 continue;
1145 }
1146 if (view[property.setNative]) {
1147 const defaultValueKey = property.defaultValueKey;
1148 if (!(defaultValueKey in style)) {
1149 style[defaultValueKey] = view[property.getDefault] ? view[property.getDefault]() : property.defaultValue;
1150 }
1151 const value = style[symbol];
1152 view[property.setNative](value);
1153 }
1154 }
1155}
1156export function resetNativeView(view) {
1157 let symbols = Object.getOwnPropertySymbols(view);
1158 for (const symbol of symbols) {
1159 const property = symbolPropertyMap[symbol];
1160 if (!property) {
1161 continue;
1162 }
1163 if (view[property.setNative]) {
1164 if (property.defaultValueKey in view) {
1165 view[property.setNative](view[property.defaultValueKey]);
1166 delete view[property.defaultValueKey];
1167 }
1168 else {
1169 view[property.setNative](property.defaultValue);
1170 }
1171 }
1172 }
1173 const style = view.style;
1174 symbols = Object.getOwnPropertySymbols(style);
1175 for (const symbol of symbols) {
1176 const property = cssSymbolPropertyMap[symbol];
1177 if (!property) {
1178 continue;
1179 }
1180 if (view[property.setNative]) {
1181 if (property.defaultValueKey in style) {
1182 view[property.setNative](style[property.defaultValueKey]);
1183 delete style[property.defaultValueKey];
1184 }
1185 else {
1186 view[property.setNative](property.defaultValue);
1187 }
1188 }
1189 }
1190}
1191export function clearInheritedProperties(view) {
1192 for (const prop of inheritableProperties) {
1193 const sourceKey = prop.sourceKey;
1194 if (view[sourceKey] === 1 /* ValueSource.Inherited */) {
1195 prop.set.call(view, unsetValue);
1196 }
1197 }
1198 const style = view.style;
1199 for (const prop of inheritableCssProperties) {
1200 const sourceKey = prop.sourceKey;
1201 if (style[sourceKey] === 1 /* ValueSource.Inherited */) {
1202 prop.setInheritedValue.call(style, unsetValue);
1203 }
1204 }
1205}
1206export function resetCSSProperties(style) {
1207 const symbols = Object.getOwnPropertySymbols(style);
1208 for (const symbol of symbols) {
1209 let cssProperty;
1210 if ((cssProperty = cssSymbolPropertyMap[symbol])) {
1211 style[cssProperty.cssName] = unsetValue;
1212 if (cssProperty instanceof CssAnimationProperty) {
1213 style[cssProperty.keyframe] = unsetValue;
1214 }
1215 }
1216 }
1217}
1218export function propagateInheritableProperties(view, child) {
1219 const inheritablePropertyValues = inheritablePropertyValuesOn(view);
1220 for (const pair of inheritablePropertyValues) {
1221 const prop = pair.property;
1222 const sourceKey = prop.sourceKey;
1223 const currentValueSource = child[sourceKey] || 0 /* ValueSource.Default */;
1224 if (currentValueSource <= 1 /* ValueSource.Inherited */) {
1225 prop.setInheritedValue.call(child, pair.value);
1226 }
1227 }
1228}
1229export function propagateInheritableCssProperties(parentStyle, childStyle) {
1230 const inheritableCssPropertyValues = inheritableCssPropertyValuesOn(parentStyle);
1231 for (const pair of inheritableCssPropertyValues) {
1232 const prop = pair.property;
1233 const sourceKey = prop.sourceKey;
1234 const currentValueSource = childStyle[sourceKey] || 0 /* ValueSource.Default */;
1235 if (currentValueSource <= 1 /* ValueSource.Inherited */) {
1236 prop.setInheritedValue.call(childStyle, pair.value, 1 /* ValueSource.Inherited */);
1237 }
1238 }
1239}
1240export function makeValidator(...values) {
1241 const set = new Set(values);
1242 return (value) => set.has(value);
1243}
1244export function makeParser(isValid, allowNumbers = false) {
1245 return (value) => {
1246 const lower = value && value.toLowerCase();
1247 if (isValid(lower)) {
1248 return lower;
1249 }
1250 else {
1251 if (allowNumbers) {
1252 const convNumber = +value;
1253 if (!isNaN(convNumber)) {
1254 return value;
1255 }
1256 }
1257 throw new Error('Invalid value: ' + value);
1258 }
1259 };
1260}
1261export function getSetProperties(view) {
1262 const result = [];
1263 Object.getOwnPropertyNames(view).forEach((prop) => {
1264 result.push([prop, view[prop]]);
1265 });
1266 const symbols = Object.getOwnPropertySymbols(view);
1267 for (const symbol of symbols) {
1268 const property = symbolPropertyMap[symbol];
1269 if (!property) {
1270 continue;
1271 }
1272 const value = view[property.key];
1273 result.push([property.name, value]);
1274 }
1275 return result;
1276}
1277export function getComputedCssValues(view) {
1278 const result = [];
1279 const style = view.style;
1280 for (const prop of cssPropertyNames) {
1281 result.push([prop, style[prop]]);
1282 }
1283 // Add these to enable box model in chrome-devtools styles tab
1284 result.push(['top', 'auto']);
1285 result.push(['left', 'auto']);
1286 result.push(['bottom', 'auto']);
1287 result.push(['right', 'auto']);
1288 return result;
1289}
1290//# sourceMappingURL=index.js.map
\No newline at end of file