@ng-native/fabric 0.4.0 → 0.5.0
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.
- package/dist/color-mix.js +1 -16
- package/dist/color-mix.js.map +1 -1
- package/dist/css.d.ts +96 -1
- package/dist/css.js +332 -4
- package/dist/css.js.map +1 -1
- package/dist/engine.d.ts +105 -1
- package/dist/engine.js +489 -22
- package/dist/engine.js.map +1 -1
- package/dist/inline-token.js +3 -4
- package/dist/inline-token.js.map +1 -1
- package/dist/native-drive.d.ts +21 -0
- package/dist/native-drive.js +60 -0
- package/dist/native-drive.js.map +1 -1
- package/dist/premultiplied-stops.d.ts +7 -0
- package/dist/premultiplied-stops.js +131 -0
- package/dist/premultiplied-stops.js.map +1 -0
- package/dist/scroll-animation.d.ts +11 -0
- package/dist/scroll-animation.js +29 -0
- package/dist/scroll-animation.js.map +1 -1
- package/dist/transition.d.ts +15 -0
- package/dist/transition.js +42 -1
- package/dist/transition.js.map +1 -1
- package/package.json +1 -1
package/dist/css.js
CHANGED
|
@@ -89,6 +89,80 @@ function drawNoBorder(own) {
|
|
|
89
89
|
* The cost is one integer increment per comparison, against a Set lookup and several property
|
|
90
90
|
* reads, so it does not distort what it measures.
|
|
91
91
|
*/
|
|
92
|
+
/**
|
|
93
|
+
* What a sheet asks about a pressed element. `own` is whether a rule styles one, `.button:active`,
|
|
94
|
+
* which matching the pressed elements again answers. `elsewhere` is each compound that asks about
|
|
95
|
+
* a pressed element from another one: `.card:active .title`, `.a:active + .b`, or `:active`
|
|
96
|
+
* inside `:not()`, `:is()` or `:has()`. An element such a compound could be is restyled with
|
|
97
|
+
* everything under it when it is pressed, as what it changes is not itself alone.
|
|
98
|
+
*/
|
|
99
|
+
export function usesActive(sheet) {
|
|
100
|
+
const known = ACTIVE_USE.get(sheet);
|
|
101
|
+
if (known)
|
|
102
|
+
return known;
|
|
103
|
+
let own = false;
|
|
104
|
+
const elsewhere = [];
|
|
105
|
+
for (const rule of sheet.rules) {
|
|
106
|
+
rule.compounds.forEach((compound, at) => {
|
|
107
|
+
const subject = at === rule.compounds.length - 1;
|
|
108
|
+
// Asked of the element itself, on the compound or in its `:is()` and `:not()`: the
|
|
109
|
+
// subject's is its own, and any other compound's is of the element that compound is.
|
|
110
|
+
if (activeItself(compound, elsewhere)) {
|
|
111
|
+
if (subject)
|
|
112
|
+
own = true;
|
|
113
|
+
else
|
|
114
|
+
elsewhere.push(compound);
|
|
115
|
+
}
|
|
116
|
+
activeAround(compound, elsewhere);
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
const use = { own, elsewhere };
|
|
120
|
+
ACTIVE_USE.set(sheet, use);
|
|
121
|
+
return use;
|
|
122
|
+
}
|
|
123
|
+
const isActive = (compound) => compound.pseudo?.includes('active') === true;
|
|
124
|
+
/**
|
|
125
|
+
* Whether a compound asks about the pressed state of the element it matches: on itself, in a
|
|
126
|
+
* `:not()`, or as the last compound of an `:is()` argument. The compounds before the last in
|
|
127
|
+
* such an argument are other elements, and are collected as they are.
|
|
128
|
+
*/
|
|
129
|
+
function activeItself(compound, elsewhere) {
|
|
130
|
+
let itself = isActive(compound);
|
|
131
|
+
for (const inner of compound.not ?? []) {
|
|
132
|
+
if (activeItself(inner, elsewhere))
|
|
133
|
+
itself = true;
|
|
134
|
+
activeAround(inner, elsewhere);
|
|
135
|
+
}
|
|
136
|
+
for (const chain of compound.is ?? []) {
|
|
137
|
+
chain.forEach((inner, at) => {
|
|
138
|
+
const asked = activeItself(inner, elsewhere);
|
|
139
|
+
if (asked && at === chain.length - 1)
|
|
140
|
+
itself = true;
|
|
141
|
+
else if (asked)
|
|
142
|
+
elsewhere.push(inner);
|
|
143
|
+
activeAround(inner, elsewhere);
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
return itself;
|
|
147
|
+
}
|
|
148
|
+
/** Each compound that asks about a pressed element around the one a compound matches. */
|
|
149
|
+
function activeAround(compound, elsewhere) {
|
|
150
|
+
const collect = (value) => {
|
|
151
|
+
if (!value || typeof value !== 'object')
|
|
152
|
+
return;
|
|
153
|
+
if (Array.isArray(value))
|
|
154
|
+
return value.forEach(collect);
|
|
155
|
+
if (isActive(value))
|
|
156
|
+
elsewhere.push(value);
|
|
157
|
+
for (const inside of Object.values(value))
|
|
158
|
+
collect(inside);
|
|
159
|
+
};
|
|
160
|
+
for (const [key, inside] of Object.entries(compound)) {
|
|
161
|
+
if (key !== 'is' && key !== 'not' && key !== 'pseudo')
|
|
162
|
+
collect(inside);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
const ACTIVE_USE = new WeakMap();
|
|
92
166
|
export const styleStats = {
|
|
93
167
|
/** Calls to `matchesCompound`, the innermost unit of matching work. */
|
|
94
168
|
compoundTests: 0,
|
|
@@ -190,6 +264,21 @@ function matchesCompound(node, compound, sheet) {
|
|
|
190
264
|
return false;
|
|
191
265
|
}
|
|
192
266
|
}
|
|
267
|
+
if (compound.has) {
|
|
268
|
+
for (const test of compound.has) {
|
|
269
|
+
if (!hasBeneath(node, test, sheet))
|
|
270
|
+
return false;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
if (compound.parents) {
|
|
274
|
+
const parent = node.parent;
|
|
275
|
+
if (!parent)
|
|
276
|
+
return false;
|
|
277
|
+
for (const above of compound.parents) {
|
|
278
|
+
if (!matchesCompound(parent, above, sheet))
|
|
279
|
+
return false;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
193
282
|
if (compound.ancestors) {
|
|
194
283
|
for (const ancestor of compound.ancestors) {
|
|
195
284
|
let found = false;
|
|
@@ -212,6 +301,18 @@ function matchesCompound(node, compound, sheet) {
|
|
|
212
301
|
}
|
|
213
302
|
return true;
|
|
214
303
|
}
|
|
304
|
+
/** Whether some element beneath `node`, or some child of it for `:has(> x)`, matches the test. */
|
|
305
|
+
function hasBeneath(node, test, sheet) {
|
|
306
|
+
for (const child of node.children ?? []) {
|
|
307
|
+
if (child.kind !== undefined && child.kind !== 'element')
|
|
308
|
+
continue;
|
|
309
|
+
if (test.any.some((option) => matchesCompound(child, option, sheet)))
|
|
310
|
+
return true;
|
|
311
|
+
if (!test.child && hasBeneath(child, test, sheet))
|
|
312
|
+
return true;
|
|
313
|
+
}
|
|
314
|
+
return false;
|
|
315
|
+
}
|
|
215
316
|
/**
|
|
216
317
|
* Right-to-left matching, as every CSS engine does it: the rightmost compound is the cheapest
|
|
217
318
|
* filter and rejects almost everything immediately.
|
|
@@ -341,6 +442,133 @@ function overriddenBy(deferred, declarations, important) {
|
|
|
341
442
|
}
|
|
342
443
|
return kept.length ? kept : null;
|
|
343
444
|
}
|
|
445
|
+
/**
|
|
446
|
+
* The logical property and the physical one for the same corner or edge, left to right. CSS
|
|
447
|
+
* settles the two as one property, the later declaration winning. A native view has a prop for
|
|
448
|
+
* each and takes one whatever their order: React Native the physical corner, Yoga the logical
|
|
449
|
+
* edge. So a declaration of either takes the other out of the cascade.
|
|
450
|
+
*/
|
|
451
|
+
const SIDES_LTR = [
|
|
452
|
+
['borderStartStartRadius', 'borderTopLeftRadius'],
|
|
453
|
+
['borderStartEndRadius', 'borderTopRightRadius'],
|
|
454
|
+
['borderEndStartRadius', 'borderBottomLeftRadius'],
|
|
455
|
+
['borderEndEndRadius', 'borderBottomRightRadius'],
|
|
456
|
+
...['margin', 'padding'].flatMap((box) => [
|
|
457
|
+
[`${box}Start`, `${box}Left`],
|
|
458
|
+
[`${box}End`, `${box}Right`],
|
|
459
|
+
]),
|
|
460
|
+
...['Width', 'Color'].flatMap((part) => [
|
|
461
|
+
[`borderStart${part}`, `borderLeft${part}`],
|
|
462
|
+
[`borderEnd${part}`, `borderRight${part}`],
|
|
463
|
+
]),
|
|
464
|
+
['start', 'left'],
|
|
465
|
+
['end', 'right'],
|
|
466
|
+
];
|
|
467
|
+
const mirrored = (physical) => physical.includes('Left') || physical === 'left'
|
|
468
|
+
? physical.replace('Left', 'Right').replace('left', 'right')
|
|
469
|
+
: physical.replace('Right', 'Left').replace('right', 'left');
|
|
470
|
+
/** Each property's other form, both ways, in a layout of each direction. */
|
|
471
|
+
const twins = (pairs) => Object.fromEntries(pairs.flatMap(([logical, physical]) => [
|
|
472
|
+
[logical, physical],
|
|
473
|
+
[physical, logical],
|
|
474
|
+
]));
|
|
475
|
+
const TWIN = {
|
|
476
|
+
ltr: twins(SIDES_LTR),
|
|
477
|
+
rtl: twins(SIDES_LTR.map(([logical, physical]) => [logical, mirrored(physical)])),
|
|
478
|
+
};
|
|
479
|
+
const sidedOf = new WeakMap();
|
|
480
|
+
function sided(rule) {
|
|
481
|
+
let found = sidedOf.get(rule);
|
|
482
|
+
if (found !== undefined)
|
|
483
|
+
return found;
|
|
484
|
+
const twin = TWIN.ltr;
|
|
485
|
+
const plain = Object.keys(rule.declarations).filter((key) => key in twin);
|
|
486
|
+
const important = rule.important ? Object.keys(rule.important).filter((key) => key in twin) : [];
|
|
487
|
+
for (const declaration of rule.deferred ?? []) {
|
|
488
|
+
for (const prop of declaration.props) {
|
|
489
|
+
if (prop in twin)
|
|
490
|
+
(declaration.important ? important : plain).push(prop);
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
found = plain.length || important.length ? { plain, important } : null;
|
|
494
|
+
sidedOf.set(rule, found);
|
|
495
|
+
return found;
|
|
496
|
+
}
|
|
497
|
+
/** Of `keys`, in the order a rule wrote them, each whose other form comes after it. */
|
|
498
|
+
function writtenEarlier(keys, twin) {
|
|
499
|
+
return keys.filter((key, at) => keys.indexOf(twin[key]) > at);
|
|
500
|
+
}
|
|
501
|
+
/** The other form of each of `keys`, as the record `overriddenBy` reads. */
|
|
502
|
+
function twinsOf(keys, twin) {
|
|
503
|
+
const out = {};
|
|
504
|
+
for (const key of keys)
|
|
505
|
+
out[twin[key]] = true;
|
|
506
|
+
return out;
|
|
507
|
+
}
|
|
508
|
+
/**
|
|
509
|
+
* One cascade's settling of the corners and edges set in both forms: each rule's are taken out of
|
|
510
|
+
* what the rules before it left, in the form the rule does not use.
|
|
511
|
+
*/
|
|
512
|
+
class OtherForms {
|
|
513
|
+
/** Worked out for the first rule that sets a corner or an edge, which most elements have none of. */
|
|
514
|
+
twin = null;
|
|
515
|
+
overImportant = null;
|
|
516
|
+
direction;
|
|
517
|
+
constructor(direction) {
|
|
518
|
+
this.direction = direction;
|
|
519
|
+
}
|
|
520
|
+
/** Before `rule` is applied: the other form of what it sets goes, as the same property would. */
|
|
521
|
+
before(rule, normal, important, deferred) {
|
|
522
|
+
const sides = sided(rule);
|
|
523
|
+
if (!sides)
|
|
524
|
+
return deferred;
|
|
525
|
+
const twin = (this.twin ??= TWIN[this.direction()]);
|
|
526
|
+
const plain = twinsOf(sides.plain, twin);
|
|
527
|
+
for (const key in plain)
|
|
528
|
+
delete normal[key];
|
|
529
|
+
let kept = deferred && overriddenBy(deferred, plain, false);
|
|
530
|
+
if (!sides.important.length)
|
|
531
|
+
return kept;
|
|
532
|
+
const gone = twinsOf(sides.important, twin);
|
|
533
|
+
for (const key in gone)
|
|
534
|
+
delete important[key];
|
|
535
|
+
kept &&= overriddenBy(kept, gone, true);
|
|
536
|
+
Object.assign((this.overImportant ??= {}), gone);
|
|
537
|
+
return kept;
|
|
538
|
+
}
|
|
539
|
+
/**
|
|
540
|
+
* Once `rule` is applied, its deferred values among `deferred`: where it wrote both forms, the
|
|
541
|
+
* one it wrote later stands, among its plain declarations and among its important ones. The
|
|
542
|
+
* order is the rule's own, sent with it, since a value settled on device is in another list.
|
|
543
|
+
*/
|
|
544
|
+
within(rule, normal, important, deferred) {
|
|
545
|
+
if (!rule.sides && !rule.importantSides)
|
|
546
|
+
return deferred;
|
|
547
|
+
const twin = (this.twin ??= TWIN[this.direction()]);
|
|
548
|
+
let kept = deferred;
|
|
549
|
+
const settle = (order, into, tier) => {
|
|
550
|
+
const lost = {};
|
|
551
|
+
for (const key of writtenEarlier(order, twin)) {
|
|
552
|
+
delete into[key];
|
|
553
|
+
lost[key] = true;
|
|
554
|
+
}
|
|
555
|
+
kept &&= overriddenBy(kept, lost, tier);
|
|
556
|
+
};
|
|
557
|
+
if (rule.sides)
|
|
558
|
+
settle(rule.sides, normal, false);
|
|
559
|
+
if (rule.importantSides)
|
|
560
|
+
settle(rule.importantSides, important, true);
|
|
561
|
+
return kept;
|
|
562
|
+
}
|
|
563
|
+
/** An important one stands over the other form wherever that was set, as over its own. */
|
|
564
|
+
after(normal, deferred) {
|
|
565
|
+
if (!this.overImportant)
|
|
566
|
+
return deferred;
|
|
567
|
+
for (const key in this.overImportant)
|
|
568
|
+
delete normal[key];
|
|
569
|
+
return deferred && overriddenBy(deferred, this.overImportant, false);
|
|
570
|
+
}
|
|
571
|
+
}
|
|
344
572
|
/**
|
|
345
573
|
* The deferred values still standing once `rule` has been applied, plus its own.
|
|
346
574
|
*
|
|
@@ -801,6 +1029,9 @@ export class StyleResolver {
|
|
|
801
1029
|
cached.epoch = epoch;
|
|
802
1030
|
return cached;
|
|
803
1031
|
}
|
|
1032
|
+
const kept = this.kept(node, parentContext, epoch);
|
|
1033
|
+
if (kept)
|
|
1034
|
+
return kept;
|
|
804
1035
|
const parentInherited = parent ? parent.inherited : EMPTY;
|
|
805
1036
|
// A box the engine made around loose text is in no template, so no rule matches it.
|
|
806
1037
|
if (node.anonymous || this.hasNoRules(node)) {
|
|
@@ -817,11 +1048,70 @@ export class StyleResolver {
|
|
|
817
1048
|
context: {},
|
|
818
1049
|
parentContext,
|
|
819
1050
|
...styled,
|
|
1051
|
+
...this.record(matched),
|
|
820
1052
|
};
|
|
821
1053
|
node.styleCache = cache;
|
|
822
1054
|
node.styleDirty = false;
|
|
1055
|
+
node.hasDirty = false;
|
|
1056
|
+
node.stateDirty = false;
|
|
823
1057
|
return cache;
|
|
824
1058
|
}
|
|
1059
|
+
/** The rules a node matched, for its cache to keep, while a sheet uses `:has()`. */
|
|
1060
|
+
record(matched) {
|
|
1061
|
+
return this.tracksHas ? { matched } : {};
|
|
1062
|
+
}
|
|
1063
|
+
/**
|
|
1064
|
+
* Set by the engine once a sheet uses `:has()`. Until then no cache keeps the rules it matched,
|
|
1065
|
+
* so an app that never asks pays nothing for it.
|
|
1066
|
+
*/
|
|
1067
|
+
tracksHas = false;
|
|
1068
|
+
/**
|
|
1069
|
+
* The cache of a node whose subtree changed, and nothing else, when it matches the rules it
|
|
1070
|
+
* did: keeping the object is what leaves everything under it alone. Nothing when it has to be
|
|
1071
|
+
* resolved, as it does with no record of what it matched, made before any sheet asked.
|
|
1072
|
+
*/
|
|
1073
|
+
keptBeneath(node, parentContext, epoch) {
|
|
1074
|
+
const cached = node.styleCache;
|
|
1075
|
+
const stands = (node.hasDirty === true || node.stateDirty === true) &&
|
|
1076
|
+
cached?.matched !== undefined &&
|
|
1077
|
+
!node.styleDirty &&
|
|
1078
|
+
cached.parentContext === parentContext &&
|
|
1079
|
+
cached.generation === this.generation;
|
|
1080
|
+
if (!stands)
|
|
1081
|
+
return null;
|
|
1082
|
+
const candidates = this.rulesFor(node);
|
|
1083
|
+
const now = this.matched(node, node.styled ? ELEMENT_ENTRIES.concat(candidates) : candidates);
|
|
1084
|
+
const before = cached.matched;
|
|
1085
|
+
if (now.length !== before.length || now.some((rule, i) => rule !== before[i]))
|
|
1086
|
+
return null;
|
|
1087
|
+
node.hasDirty = false;
|
|
1088
|
+
node.stateDirty = false;
|
|
1089
|
+
cached.epoch = epoch;
|
|
1090
|
+
return cached;
|
|
1091
|
+
}
|
|
1092
|
+
/** A cache that still stands for a node marked for its subtree or its state alone. */
|
|
1093
|
+
kept(node, parentContext, epoch) {
|
|
1094
|
+
return (this.keptBeneath(node, parentContext, epoch) ?? this.keptUnmatched(node, parentContext, epoch));
|
|
1095
|
+
}
|
|
1096
|
+
/**
|
|
1097
|
+
* The cache of a node whose pressed state changed and nothing else, where no rule can match
|
|
1098
|
+
* it at all: it has no style of its own to change, so it and all under it stay as they are.
|
|
1099
|
+
*/
|
|
1100
|
+
keptUnmatched(node, parentContext, epoch) {
|
|
1101
|
+
const cached = node.styleCache;
|
|
1102
|
+
const stands = node.stateDirty === true &&
|
|
1103
|
+
cached !== null &&
|
|
1104
|
+
!node.styleDirty &&
|
|
1105
|
+
!node.hasDirty &&
|
|
1106
|
+
cached.parentContext === parentContext &&
|
|
1107
|
+
cached.generation === this.generation &&
|
|
1108
|
+
(node.anonymous === true || this.hasNoRules(node));
|
|
1109
|
+
if (!stands)
|
|
1110
|
+
return null;
|
|
1111
|
+
node.stateDirty = false;
|
|
1112
|
+
cached.epoch = epoch;
|
|
1113
|
+
return cached;
|
|
1114
|
+
}
|
|
825
1115
|
/**
|
|
826
1116
|
* Whether a node's last answer still stands under a parent that resolved to `parentContext`,
|
|
827
1117
|
* without resolving it. What lets a commit step over a clean child in one comparison.
|
|
@@ -836,6 +1126,8 @@ export class StyleResolver {
|
|
|
836
1126
|
reusable(cached, node, parentContext) {
|
|
837
1127
|
return (cached !== null &&
|
|
838
1128
|
!node.styleDirty &&
|
|
1129
|
+
!node.hasDirty &&
|
|
1130
|
+
!node.stateDirty &&
|
|
839
1131
|
cached.parentContext === parentContext &&
|
|
840
1132
|
cached.generation === this.generation);
|
|
841
1133
|
}
|
|
@@ -1022,7 +1314,7 @@ export class StyleResolver {
|
|
|
1022
1314
|
...matched,
|
|
1023
1315
|
{ declarations: inline ?? EMPTY, ...(bound && { deferred: bound }) },
|
|
1024
1316
|
]
|
|
1025
|
-
: matched);
|
|
1317
|
+
: matched, parentInherited);
|
|
1026
1318
|
// Tokens are in scope for this node's own declarations as well as its descendants', so they
|
|
1027
1319
|
// are merged before any `var()` here is resolved.
|
|
1028
1320
|
const tokens = tokensInScope(parentTokens, result.tokens, node.customProperties);
|
|
@@ -1048,14 +1340,16 @@ export class StyleResolver {
|
|
|
1048
1340
|
return out;
|
|
1049
1341
|
}
|
|
1050
1342
|
/** The declarations of the rules a node matched. The list is sorted, so later simply wins. */
|
|
1051
|
-
cascade(matched) {
|
|
1343
|
+
cascade(matched, parentInherited) {
|
|
1052
1344
|
const normal = {};
|
|
1053
1345
|
const important = {};
|
|
1054
1346
|
let hasImportant = false;
|
|
1055
1347
|
let tokens = null;
|
|
1056
1348
|
let importantTokens = null;
|
|
1057
1349
|
let deferred = null;
|
|
1350
|
+
const forms = new OtherForms(() => this.directionOf(matched, parentInherited));
|
|
1058
1351
|
for (const rule of this.layered ? byImportance(matched, this.layerPlaces) : matched) {
|
|
1352
|
+
deferred = forms.before(rule, normal, important, deferred);
|
|
1059
1353
|
Object.assign(normal, rule.declarations);
|
|
1060
1354
|
if (rule.important) {
|
|
1061
1355
|
Object.assign(important, rule.important);
|
|
@@ -1067,10 +1361,11 @@ export class StyleResolver {
|
|
|
1067
1361
|
if (rule.importantTokens) {
|
|
1068
1362
|
Object.assign((importantTokens ??= {}), pick(rule.tokens, rule.importantTokens));
|
|
1069
1363
|
}
|
|
1070
|
-
deferred = carryDeferred(deferred, rule);
|
|
1364
|
+
deferred = forms.within(rule, normal, important, carryDeferred(deferred, rule));
|
|
1071
1365
|
}
|
|
1072
1366
|
if (importantTokens)
|
|
1073
1367
|
Object.assign(tokens, importantTokens);
|
|
1368
|
+
deferred = forms.after(normal, deferred);
|
|
1074
1369
|
return {
|
|
1075
1370
|
declarations: hasImportant ? { ...normal, ...important } : normal,
|
|
1076
1371
|
important: hasImportant ? important : null,
|
|
@@ -1078,6 +1373,37 @@ export class StyleResolver {
|
|
|
1078
1373
|
deferred,
|
|
1079
1374
|
};
|
|
1080
1375
|
}
|
|
1376
|
+
/**
|
|
1377
|
+
* An inline style over the style the cascade settled, both already in `style`: a corner or an
|
|
1378
|
+
* edge the inline style sets takes the other form of it out, as a later rule would.
|
|
1379
|
+
*/
|
|
1380
|
+
overOtherForms(inline, style) {
|
|
1381
|
+
let twin = null;
|
|
1382
|
+
const written = Object.keys(inline);
|
|
1383
|
+
for (const [at, key] of written.entries()) {
|
|
1384
|
+
if (!(key in TWIN.ltr))
|
|
1385
|
+
continue;
|
|
1386
|
+
twin ??= TWIN[(style['direction'] ?? this.conditions.direction) === 'rtl' ? 'rtl' : 'ltr'];
|
|
1387
|
+
const other = twin[key];
|
|
1388
|
+
// Where the inline style sets both, the one it set later stands.
|
|
1389
|
+
if (written.indexOf(other) < at)
|
|
1390
|
+
delete style[other];
|
|
1391
|
+
}
|
|
1392
|
+
}
|
|
1393
|
+
/**
|
|
1394
|
+
* The direction an element is laid out in: the last `direction` its own rules give it, the one
|
|
1395
|
+
* it inherits, or the app's. A logical corner is a different physical one in each.
|
|
1396
|
+
*/
|
|
1397
|
+
directionOf(matched, parentInherited) {
|
|
1398
|
+
let plain;
|
|
1399
|
+
let important;
|
|
1400
|
+
for (const rule of matched) {
|
|
1401
|
+
plain = rule.declarations['direction'] ?? plain;
|
|
1402
|
+
important = rule.important?.['direction'] ?? important;
|
|
1403
|
+
}
|
|
1404
|
+
const direction = important ?? plain ?? parentInherited['direction'] ?? this.conditions.direction;
|
|
1405
|
+
return direction === 'rtl' ? 'rtl' : 'ltr';
|
|
1406
|
+
}
|
|
1081
1407
|
/**
|
|
1082
1408
|
* Whether a rule's media query holds, memoised per rule.
|
|
1083
1409
|
*
|
|
@@ -1115,7 +1441,9 @@ export class StyleResolver {
|
|
|
1115
1441
|
}
|
|
1116
1442
|
/** What the device answers for a feature that is a keyword rather than a measurement. */
|
|
1117
1443
|
preference(feature) {
|
|
1118
|
-
const { width, height, colorScheme, reducedMotion } = this.conditions;
|
|
1444
|
+
const { width, height, colorScheme, reducedMotion, direction } = this.conditions;
|
|
1445
|
+
if (feature === 'direction')
|
|
1446
|
+
return direction ?? 'ltr';
|
|
1119
1447
|
if (feature === 'orientation')
|
|
1120
1448
|
return width > height ? 'landscape' : 'portrait';
|
|
1121
1449
|
if (feature === 'prefers-color-scheme')
|