@nativescript/core 9.1.0-next.5 → 9.1.0-rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,7 @@
1
1
  import { getNativeScriptGlobals } from '../../globals/global-utils';
2
- import { _evaluateCssVariableExpression, _evaluateCssCalcExpression, isCssVariable, isCssVariableExpression, isCssCalcExpression } from '../core/properties';
2
+ import { _evaluateCssVariableExpression, _evaluateCssCalcExpression, _expandCssShorthand, _isCssPendingSubstitution, isCssVariable, isCssVariableExpression, isCssCalcExpression } from '../core/properties';
3
3
  import { unsetValue } from '../core/properties/property-shared';
4
- import { StyleSheetSelectorScope, SelectorsMatch, fromAstNode, matchMediaQueryString } from './css-selector';
4
+ import { StyleSheetSelectorScope, SelectorsMatch, fromAstNode, matchMediaQueryString, matchSelectorCandidates } from './css-selector';
5
5
  import { Trace } from './styling-shared';
6
6
  import { File, knownFolders, path } from '../../file-system';
7
7
  import { Application } from '../../application';
@@ -10,7 +10,6 @@ import { KeyframeAnimationInfo, KeyframeAnimation } from '../animation/keyframe-
10
10
  import { CssAnimationParser } from './css-animation-parser';
11
11
  import { sanitizeModuleName } from '../../utils/common';
12
12
  import { resolveModuleName } from '../../module-name-resolver';
13
- import { cleanupImportantFlags } from './css-utils';
14
13
  import { cssTreeParse } from '../../css/css-tree-parser';
15
14
  import { CSS3Parser } from '../../css/CSS3Parser';
16
15
  import { CSSNativeScript } from '../../css/CSSNativeScript';
@@ -34,16 +33,49 @@ catch (e) {
34
33
  let mergedApplicationCssSelectors = [];
35
34
  let applicationCssSelectors = [];
36
35
  const applicationAdditionalSelectors = [];
36
+ let mergedApplicationCssSelectorsInvalid = false;
37
37
  let mergedApplicationCssKeyframes = [];
38
38
  let applicationCssKeyframes = [];
39
39
  const applicationAdditionalKeyframes = [];
40
+ let mergedApplicationCssKeyframesInvalid = false;
40
41
  let applicationCssSelectorVersion = 0;
42
+ /**
43
+ * Shared index over the application stylesheets, built once for all style scopes.
44
+ * Tagged rules stay in it and are filtered out at match time - see `matchSelectorCandidates`.
45
+ */
46
+ let applicationSelectorScope = null;
47
+ let applicationSelectorScopeVersion = -1;
48
+ let applicationSelectorScopeRuleCount = 0;
49
+ let applicationSelectorsHaveScopedTags = false;
50
+ /** Bumped when the application rules change in a way an append cannot express. */
51
+ let applicationSelectorsResetVersion = 0;
52
+ let applicationSelectorScopeResetVersion = -1;
41
53
  const tagToScopeTag = new Map();
42
54
  let currentScopeTag = null;
43
55
  const animationsSymbol = Symbol('animations');
44
56
  const kebabCasePattern = /-([a-z])/g;
45
57
  const kebabCaseReplacementFunc = (g) => g[1].toUpperCase();
46
58
  const pattern = /('|")(.*?)\1/;
59
+ /**
60
+ * Parse a pending-substitution shorthand once its expression has been evaluated
61
+ * against the view; a value that does not survive evaluation leaves its longhands unset.
62
+ */
63
+ function resolvePendingSubstitution(view, pending) {
64
+ const value = evaluateCssExpressions(view, pending.shorthand, pending.value);
65
+ if (value === unsetValue) {
66
+ return CssState.emptyPropertyBag;
67
+ }
68
+ const expanded = _expandCssShorthand(pending.shorthand, value);
69
+ if (!expanded) {
70
+ Trace.write(`Failed to expand shorthand [${pending.shorthand}] resolved to [${value}] for ${view}.`, Trace.categories.Style, Trace.messageType.warn);
71
+ return CssState.emptyPropertyBag;
72
+ }
73
+ const resolved = {};
74
+ for (let i = 0, length = expanded.length; i < length; i++) {
75
+ resolved[expanded[i][0]] = expanded[i][1];
76
+ }
77
+ return resolved;
78
+ }
47
79
  /**
48
80
  * Evaluate css-variable and css-calc expressions
49
81
  */
@@ -62,13 +94,66 @@ function evaluateCssExpressions(view, property, value) {
62
94
  }
63
95
  return value;
64
96
  }
97
+ /**
98
+ * Only marks the merged list dirty - it is rebuilt on next read, since frameworks
99
+ * register stylesheets one call at a time.
100
+ */
65
101
  export function mergeCssSelectors() {
66
- mergedApplicationCssSelectors = applicationCssSelectors.slice();
67
- mergedApplicationCssSelectors.push(...applicationAdditionalSelectors);
102
+ mergedApplicationCssSelectorsInvalid = true;
68
103
  }
69
104
  export function mergeCssKeyframes() {
70
- mergedApplicationCssKeyframes = applicationCssKeyframes.slice();
71
- mergedApplicationCssKeyframes.push(...applicationAdditionalKeyframes);
105
+ mergedApplicationCssKeyframesInvalid = true;
106
+ }
107
+ function getMergedApplicationCssSelectors() {
108
+ if (mergedApplicationCssSelectorsInvalid) {
109
+ mergedApplicationCssSelectorsInvalid = false;
110
+ mergedApplicationCssSelectors = concatRuleSets(applicationCssSelectors, applicationAdditionalSelectors);
111
+ }
112
+ return mergedApplicationCssSelectors;
113
+ }
114
+ function getMergedApplicationCssKeyframes() {
115
+ if (mergedApplicationCssKeyframesInvalid) {
116
+ mergedApplicationCssKeyframesInvalid = false;
117
+ mergedApplicationCssKeyframes = concatRuleSets(applicationCssKeyframes, applicationAdditionalKeyframes);
118
+ }
119
+ return mergedApplicationCssKeyframes;
120
+ }
121
+ function getApplicationSelectorScope() {
122
+ if (applicationSelectorScopeVersion === applicationCssSelectorVersion) {
123
+ return applicationSelectorScope;
124
+ }
125
+ const rulesets = getMergedApplicationCssSelectors();
126
+ const canAppend = applicationSelectorScope && applicationSelectorScopeResetVersion === applicationSelectorsResetVersion && rulesets.length >= applicationSelectorScopeRuleCount;
127
+ if (canAppend) {
128
+ applicationSelectorScope.appendRulesets(rulesets, applicationSelectorScopeRuleCount);
129
+ }
130
+ else {
131
+ applicationSelectorScope = rulesets.length > 0 ? new StyleSheetSelectorScope(rulesets, 0 /* SelectorTier.Application */) : null;
132
+ applicationSelectorsHaveScopedTags = false;
133
+ applicationSelectorScopeRuleCount = 0;
134
+ }
135
+ for (let i = applicationSelectorScopeRuleCount, length = rulesets.length; i < length; i++) {
136
+ if (rulesets[i].scopedTag) {
137
+ applicationSelectorsHaveScopedTags = true;
138
+ break;
139
+ }
140
+ }
141
+ applicationSelectorScopeRuleCount = rulesets.length;
142
+ applicationSelectorScopeVersion = applicationCssSelectorVersion;
143
+ applicationSelectorScopeResetVersion = applicationSelectorsResetVersion;
144
+ return applicationSelectorScope;
145
+ }
146
+ /** Avoids `push(...arr)`, which blows the stack past the engine's argument limit. */
147
+ function concatRuleSets(base, additional) {
148
+ const baseLength = base.length;
149
+ const merged = new Array(baseLength + additional.length);
150
+ for (let i = 0; i < baseLength; i++) {
151
+ merged[i] = base[i];
152
+ }
153
+ for (let i = 0, length = additional.length; i < length; i++) {
154
+ merged[baseLength + i] = additional[i];
155
+ }
156
+ return merged;
72
157
  }
73
158
  class CSSSource {
74
159
  constructor(_ast, _url, _file, _source) {
@@ -192,7 +277,7 @@ class CSSSource {
192
277
  this._selectors = [];
193
278
  }
194
279
  }
195
- async parseCSSAst() {
280
+ parseCSSAst() {
196
281
  if (this._source) {
197
282
  if (__CSS_PARSER__ === 'css-tree') {
198
283
  this._ast = cssTreeParse(this._source, this._file);
@@ -245,7 +330,7 @@ __decorate([
245
330
  profile,
246
331
  __metadata("design:type", Function),
247
332
  __metadata("design:paramtypes", []),
248
- __metadata("design:returntype", Promise)
333
+ __metadata("design:returntype", void 0)
249
334
  ], CSSSource.prototype, "parseCSSAst", null);
250
335
  __decorate([
251
336
  profile,
@@ -339,6 +424,8 @@ export function removeTaggedAdditionalCSS(tag) {
339
424
  }
340
425
  }
341
426
  if (selectorsChanged) {
427
+ // Rules were dropped from the middle of the list, so the index has to be rebuilt.
428
+ applicationSelectorsResetVersion++;
342
429
  mergeCssSelectors();
343
430
  updated = true;
344
431
  }
@@ -426,6 +513,8 @@ const loadCss = profile(`"style-scope".loadCss`, (cssModule) => {
426
513
  let updated = false;
427
514
  // Check for existing application css selectors too in case the app is undergoing a live-sync
428
515
  if (selectors.length > 0 || applicationCssSelectors.length > 0) {
516
+ // The base stylesheet is replaced rather than appended to.
517
+ applicationSelectorsResetVersion++;
429
518
  applicationCssSelectors = selectors;
430
519
  mergeCssSelectors();
431
520
  updated = true;
@@ -461,6 +550,36 @@ if (Application.hasLaunched()) {
461
550
  else {
462
551
  getNativeScriptGlobals().events.on('loadAppCss', loadAppCSS);
463
552
  }
553
+ function trackedNamesEqual(a, b) {
554
+ if (a === b) {
555
+ return true;
556
+ }
557
+ if (!a || !b || a.size !== b.size) {
558
+ return false;
559
+ }
560
+ for (const name of a) {
561
+ if (!b.has(name)) {
562
+ return false;
563
+ }
564
+ }
565
+ return true;
566
+ }
567
+ /** Two empty maps compare equal even when they are different objects. */
568
+ function changeMapsEqual(applied, current) {
569
+ if (applied === current) {
570
+ return true;
571
+ }
572
+ if (applied.size !== current.size) {
573
+ return false;
574
+ }
575
+ for (const [view, changes] of applied) {
576
+ const currentChanges = current.get(view);
577
+ if (!currentChanges || !trackedNamesEqual(changes.attributes, currentChanges.attributes) || !trackedNamesEqual(changes.pseudoClasses, currentChanges.pseudoClasses)) {
578
+ return false;
579
+ }
580
+ }
581
+ return true;
582
+ }
464
583
  export class CssState {
465
584
  constructor(viewRef) {
466
585
  this.viewRef = viewRef;
@@ -474,9 +593,13 @@ export class CssState {
474
593
  onChange() {
475
594
  const view = this.viewRef.get();
476
595
  if (view && view.isLoaded) {
477
- this.unsubscribeFromDynamicUpdates();
596
+ // Matching does not read the subscriptions, so re-subscribe only when the
597
+ // dependencies actually changed - they are usually identical.
478
598
  this.updateMatch();
479
- this.subscribeForDynamicUpdates();
599
+ if (!changeMapsEqual(this._appliedChangeMap, this._match.changeMap)) {
600
+ this.unsubscribeFromDynamicUpdates();
601
+ this.subscribeForDynamicUpdates();
602
+ }
480
603
  this.updateDynamicState();
481
604
  }
482
605
  else {
@@ -606,46 +729,111 @@ export class CssState {
606
729
  const oldProperties = this._appliedPropertyValues;
607
730
  // Update values for the scope's css-variables
608
731
  view.style.resetScopedCssVariables();
609
- const valuesToApply = {};
610
- const cssExpsProperties = {};
732
+ let valuesToApply;
733
+ let cssExpsProperties;
734
+ let pendingProperties;
611
735
  for (const property in newPropertyValues) {
612
- const value = cleanupImportantFlags(newPropertyValues[property], property);
613
- const isCssExp = isCssVariableExpression(value) || isCssCalcExpression(value);
736
+ const value = newPropertyValues[property];
737
+ if (_isCssPendingSubstitution(value)) {
738
+ // Resolvable only after the css variables below are up to date.
739
+ if (!pendingProperties) {
740
+ pendingProperties = {};
741
+ }
742
+ pendingProperties[property] = value;
743
+ continue;
744
+ }
745
+ // Expanded shorthand values are already converted and may not be strings.
746
+ const isCssExp = typeof value === 'string' && (isCssVariableExpression(value) || isCssCalcExpression(value));
614
747
  if (isCssExp) {
615
748
  // we handle css exp separately because css vars must be evaluated first
749
+ if (!cssExpsProperties) {
750
+ cssExpsProperties = {};
751
+ }
616
752
  cssExpsProperties[property] = value;
617
753
  continue;
618
754
  }
619
- delete oldProperties[property];
620
- if (property in oldProperties && oldProperties[property] === value) {
621
- // Skip unchanged values
622
- continue;
755
+ // Whatever is left in oldProperties after these loops was removed and gets unset.
756
+ const hadOldValue = property in oldProperties;
757
+ const unchanged = hadOldValue && oldProperties[property] === value;
758
+ if (hadOldValue) {
759
+ delete oldProperties[property];
623
760
  }
624
761
  if (isCssVariable(property)) {
762
+ // The scoped css-variables were just reset, so they always have to be re-registered.
625
763
  view.style.setScopedCssVariable(property, value);
626
764
  delete newPropertyValues[property];
627
765
  continue;
628
766
  }
767
+ if (unchanged) {
768
+ continue;
769
+ }
770
+ if (!valuesToApply) {
771
+ valuesToApply = {};
772
+ }
629
773
  valuesToApply[property] = value;
630
774
  }
631
775
  //we need to parse CSS vars first before evaluating css expressions
632
776
  for (const property in cssExpsProperties) {
633
- delete oldProperties[property];
777
+ const hadOldValue = property in oldProperties;
778
+ const oldValue = hadOldValue ? oldProperties[property] : undefined;
779
+ if (hadOldValue) {
780
+ delete oldProperties[property];
781
+ }
634
782
  const value = evaluateCssExpressions(view, property, cssExpsProperties[property]);
635
- if (property in oldProperties && oldProperties[property] === value) {
636
- // Skip unchanged values
783
+ if (isCssVariable(property)) {
784
+ view.style.setScopedCssVariable(property, value);
785
+ delete newPropertyValues[property];
637
786
  continue;
638
787
  }
639
788
  if (value === unsetValue) {
640
789
  delete newPropertyValues[property];
641
790
  }
642
- if (isCssVariable(property)) {
643
- view.style.setScopedCssVariable(property, value);
791
+ else {
792
+ // Record the evaluated value - the next diff compares against it.
793
+ newPropertyValues[property] = value;
794
+ }
795
+ if (hadOldValue && oldValue === value) {
796
+ continue;
797
+ }
798
+ if (!valuesToApply) {
799
+ valuesToApply = {};
800
+ }
801
+ valuesToApply[property] = value;
802
+ }
803
+ // Each shorthand is resolved once, however many longhands point at it.
804
+ let resolvedShorthands;
805
+ for (const property in pendingProperties) {
806
+ const pending = pendingProperties[property];
807
+ if (!resolvedShorthands) {
808
+ resolvedShorthands = new Map();
809
+ }
810
+ let resolved = resolvedShorthands.get(pending);
811
+ if (!resolved) {
812
+ resolved = resolvePendingSubstitution(view, pending);
813
+ resolvedShorthands.set(pending, resolved);
814
+ }
815
+ const value = property in resolved ? resolved[property] : unsetValue;
816
+ const hadOldValue = property in oldProperties;
817
+ const oldValue = hadOldValue ? oldProperties[property] : undefined;
818
+ if (hadOldValue) {
819
+ delete oldProperties[property];
820
+ }
821
+ if (value === unsetValue) {
644
822
  delete newPropertyValues[property];
645
823
  }
824
+ else {
825
+ newPropertyValues[property] = value;
826
+ }
827
+ if (hadOldValue && oldValue === value) {
828
+ continue;
829
+ }
830
+ if (!valuesToApply) {
831
+ valuesToApply = {};
832
+ }
646
833
  valuesToApply[property] = value;
647
834
  }
648
- // Unset removed values
835
+ // Unset removed values - the bag is keyed by longhands only, so unsetting
836
+ // one entry cannot clear a value another one set.
649
837
  for (const property in oldProperties) {
650
838
  if (property in view.style) {
651
839
  view.style[`css:${property}`] = unsetValue;
@@ -749,12 +937,13 @@ CssState.prototype._matchInvalid = true;
749
937
  export class StyleScope {
750
938
  constructor() {
751
939
  this._css = '';
940
+ this._hasSelectors = false;
752
941
  this._localCssSelectors = [];
753
942
  this._localCssKeyframes = [];
754
943
  this._localCssSelectorVersion = 0;
755
944
  this._localCssSelectorsAppliedVersion = 0;
756
945
  this._applicationCssSelectorsAppliedVersion = 0;
757
- this._cssFiles = [];
946
+ this._cssFiles = new Set();
758
947
  }
759
948
  get css() {
760
949
  return this._css;
@@ -772,7 +961,7 @@ export class StyleScope {
772
961
  if (!cssFileName) {
773
962
  return;
774
963
  }
775
- this._cssFiles.push(cssFileName);
964
+ this._cssFiles.add(cssFileName);
776
965
  currentScopeTag = cssFileName;
777
966
  const cssFile = CSSSource.fromURI(cssFileName);
778
967
  currentScopeTag = null;
@@ -795,7 +984,7 @@ export class StyleScope {
795
984
  return;
796
985
  }
797
986
  if (cssFileName) {
798
- this._cssFiles.push(cssFileName);
987
+ this._cssFiles.add(cssFileName);
799
988
  currentScopeTag = cssFileName;
800
989
  }
801
990
  const cssFile = cssString ? CSSSource.fromSource(cssString, cssFileName) : CSSSource.fromURI(cssFileName);
@@ -816,7 +1005,7 @@ export class StyleScope {
816
1005
  return animation;
817
1006
  }
818
1007
  ensureSelectors() {
819
- if (!this.isApplicationCssSelectorsLatestVersionApplied() || !this.isLocalCssSelectorsLatestVersionApplied() || !this._mergedCssSelectors) {
1008
+ if (!this.isApplicationCssSelectorsLatestVersionApplied() || !this.isLocalCssSelectorsLatestVersionApplied()) {
820
1009
  this._createSelectors();
821
1010
  }
822
1011
  return this.getSelectorsVersion();
@@ -826,14 +1015,14 @@ export class StyleScope {
826
1015
  */
827
1016
  get hasAdjacentCombinatorSelectors() {
828
1017
  this.ensureSelectors();
829
- return this._selectorScope ? this._selectorScope.hasAdjacentCombinatorSelectors : false;
1018
+ return !!applicationSelectorScope?.hasAdjacentCombinatorSelectors || !!this._localSelectorScope?.hasAdjacentCombinatorSelectors;
830
1019
  }
831
1020
  /**
832
1021
  * True when any selector in the scope contains a general sibling ('~') combinator.
833
1022
  */
834
1023
  get hasSiblingCombinatorSelectors() {
835
1024
  this.ensureSelectors();
836
- return this._selectorScope ? this._selectorScope.hasSiblingCombinatorSelectors : false;
1025
+ return !!applicationSelectorScope?.hasSiblingCombinatorSelectors || !!this._localSelectorScope?.hasSiblingCombinatorSelectors;
837
1026
  }
838
1027
  /**
839
1028
  * Increase the application CSS selector version.
@@ -848,38 +1037,43 @@ export class StyleScope {
848
1037
  return this._localCssSelectorsAppliedVersion === this._localCssSelectorVersion;
849
1038
  }
850
1039
  _createSelectors() {
851
- const toMerge = [];
852
- const toMergeKeyframes = [];
853
- toMerge.push(...mergedApplicationCssSelectors.filter((v) => !v.scopedTag || this._cssFiles.indexOf(v.scopedTag) >= 0));
854
- toMergeKeyframes.push(...mergedApplicationCssKeyframes.filter((v) => !v.scopedTag || this._cssFiles.indexOf(v.scopedTag) >= 0));
1040
+ const cssFiles = this._cssFiles;
1041
+ const applicationScope = getApplicationSelectorScope();
855
1042
  this._applicationCssSelectorsAppliedVersion = applicationCssSelectorVersion;
856
- toMerge.push(...this._localCssSelectors);
857
- toMergeKeyframes.push(...this._localCssKeyframes);
1043
+ if (!this.isLocalCssSelectorsLatestVersionApplied() || (!this._localSelectorScope && this._localCssSelectors.length > 0)) {
1044
+ this._localSelectorScope = this._localCssSelectors.length > 0 ? new StyleSheetSelectorScope(this._localCssSelectors, 1 /* SelectorTier.Local */) : null;
1045
+ }
858
1046
  this._localCssSelectorsAppliedVersion = this._localCssSelectorVersion;
859
- if (toMerge.length > 0) {
860
- this._mergedCssSelectors = toMerge;
861
- this._selectorScope = new StyleSheetSelectorScope(this._mergedCssSelectors);
1047
+ this._hasSelectors = !!applicationScope || !!this._localSelectorScope;
1048
+ const toMergeKeyframes = [];
1049
+ const applicationKeyframes = getMergedApplicationCssKeyframes();
1050
+ for (let i = 0, length = applicationKeyframes.length; i < length; i++) {
1051
+ const keyframe = applicationKeyframes[i];
1052
+ if (!keyframe.scopedTag || cssFiles.has(keyframe.scopedTag)) {
1053
+ toMergeKeyframes.push(keyframe);
1054
+ }
862
1055
  }
863
- else {
864
- this._mergedCssSelectors = null;
865
- this._selectorScope = null;
1056
+ const localKeyframes = this._localCssKeyframes;
1057
+ for (let i = 0, length = localKeyframes.length; i < length; i++) {
1058
+ toMergeKeyframes.push(localKeyframes[i]);
866
1059
  }
867
1060
  this._mergedCssKeyframes = toMergeKeyframes.length > 0 ? toMergeKeyframes : null;
868
1061
  }
869
1062
  // HACK: This @profile decorator creates a circular dependency
870
1063
  // HACK: because the function parameter type is evaluated with 'typeof'
871
1064
  matchSelectors(view) {
872
- let match;
873
1065
  // should be (view: ViewBase): SelectorsMatch<ViewBase>
874
1066
  this.ensureSelectors();
875
- if (this._selectorScope) {
876
- match = this._selectorScope.query(view);
877
- // Make sure to re-apply keyframes to matching selectors as a media query keyframe might be applicable at this point
878
- this._applyKeyframesToSelectors(match.selectors);
879
- }
880
- else {
881
- match = null;
1067
+ if (!this._hasSelectors) {
1068
+ return null;
882
1069
  }
1070
+ // The cascade has to see the application and local candidates as a single ordered set.
1071
+ const candidates = [];
1072
+ applicationSelectorScope?.collectCandidates(view, candidates);
1073
+ this._localSelectorScope?.collectCandidates(view, candidates);
1074
+ const match = matchSelectorCandidates(view, candidates, applicationSelectorsHaveScopedTags ? this._cssFiles : undefined);
1075
+ // Make sure to re-apply keyframes to matching selectors as a media query keyframe might be applicable at this point
1076
+ this._applyKeyframesToSelectors(match.selectors);
883
1077
  return match;
884
1078
  }
885
1079
  query(node) {
@@ -1012,6 +1206,8 @@ export const applyInlineStyle = profile('applyInlineStyle', function applyInline
1012
1206
  view.style.setUnscopedCssVariable(property, d.value);
1013
1207
  }
1014
1208
  });
1209
+ // Pending-substitution longhands share one placeholder - resolve it once.
1210
+ let resolvedShorthands;
1015
1211
  inlineRuleSet[0].declarations.forEach((d) => {
1016
1212
  // Use the actual property name so that a local value is set.
1017
1213
  const property = d.property;
@@ -1020,7 +1216,21 @@ export const applyInlineStyle = profile('applyInlineStyle', function applyInline
1020
1216
  // Skip css-variables, they have been handled
1021
1217
  return;
1022
1218
  }
1023
- const value = evaluateCssExpressions(view, property, d.value);
1219
+ let value;
1220
+ if (_isCssPendingSubstitution(d.value)) {
1221
+ if (!resolvedShorthands) {
1222
+ resolvedShorthands = new Map();
1223
+ }
1224
+ let resolved = resolvedShorthands.get(d.value);
1225
+ if (!resolved) {
1226
+ resolved = resolvePendingSubstitution(view, d.value);
1227
+ resolvedShorthands.set(d.value, resolved);
1228
+ }
1229
+ value = property in resolved ? resolved[property] : unsetValue;
1230
+ }
1231
+ else {
1232
+ value = evaluateCssExpressions(view, property, d.value);
1233
+ }
1024
1234
  if (property in view.style) {
1025
1235
  view.style[property] = value;
1026
1236
  }