@react-x11/components 0.12.0 → 0.14.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/html/controls.d.ts +41 -5
- package/dist/html/controls.d.ts.map +1 -1
- package/dist/html/controls.js +85 -57
- package/dist/html/controls.js.map +1 -1
- package/dist/html/css/cascade.d.ts +134 -20
- package/dist/html/css/cascade.d.ts.map +1 -1
- package/dist/html/css/cascade.js +545 -126
- package/dist/html/css/cascade.js.map +1 -1
- package/dist/html/css/color.d.ts +18 -0
- package/dist/html/css/color.d.ts.map +1 -1
- package/dist/html/css/color.js +27 -0
- package/dist/html/css/color.js.map +1 -1
- package/dist/html/css/parse.d.ts +47 -6
- package/dist/html/css/parse.d.ts.map +1 -1
- package/dist/html/css/parse.js +336 -30
- package/dist/html/css/parse.js.map +1 -1
- package/dist/html/css/style.d.ts +142 -15
- package/dist/html/css/style.d.ts.map +1 -1
- package/dist/html/css/style.js +563 -147
- package/dist/html/css/style.js.map +1 -1
- package/dist/html/css/transform.d.ts +85 -0
- package/dist/html/css/transform.d.ts.map +1 -0
- package/dist/html/css/transform.js +425 -0
- package/dist/html/css/transform.js.map +1 -0
- package/dist/html/css/ua.d.ts +11 -1
- package/dist/html/css/ua.d.ts.map +1 -1
- package/dist/html/css/ua.js +73 -9
- package/dist/html/css/ua.js.map +1 -1
- package/dist/html/css/values.d.ts +4 -0
- package/dist/html/css/values.d.ts.map +1 -1
- package/dist/html/css/values.js.map +1 -1
- package/dist/html/dom.d.ts.map +1 -1
- package/dist/html/dom.js +3 -1
- package/dist/html/dom.js.map +1 -1
- package/dist/html/fonts.d.ts +78 -4
- package/dist/html/fonts.d.ts.map +1 -1
- package/dist/html/fonts.js +373 -19
- package/dist/html/fonts.js.map +1 -1
- package/dist/html/form.d.ts +200 -0
- package/dist/html/form.d.ts.map +1 -0
- package/dist/html/form.js +801 -0
- package/dist/html/form.js.map +1 -0
- package/dist/html/index.d.ts +12 -0
- package/dist/html/index.d.ts.map +1 -1
- package/dist/html/index.js +30 -301
- package/dist/html/index.js.map +1 -1
- package/dist/html/layout/axes.d.ts +14 -0
- package/dist/html/layout/axes.d.ts.map +1 -0
- package/dist/html/layout/axes.js +28 -0
- package/dist/html/layout/axes.js.map +1 -0
- package/dist/html/layout/block.d.ts +93 -12
- package/dist/html/layout/block.d.ts.map +1 -1
- package/dist/html/layout/block.js +393 -39
- package/dist/html/layout/block.js.map +1 -1
- package/dist/html/layout/boxes.d.ts +90 -2
- package/dist/html/layout/boxes.d.ts.map +1 -1
- package/dist/html/layout/boxes.js +74 -10
- package/dist/html/layout/boxes.js.map +1 -1
- package/dist/html/layout/cache.d.ts +1 -1
- package/dist/html/layout/cache.d.ts.map +1 -1
- package/dist/html/layout/cache.js +6 -0
- package/dist/html/layout/cache.js.map +1 -1
- package/dist/html/layout/css-grid.d.ts.map +1 -1
- package/dist/html/layout/css-grid.js +90 -18
- package/dist/html/layout/css-grid.js.map +1 -1
- package/dist/html/layout/flex.d.ts.map +1 -1
- package/dist/html/layout/flex.js +574 -84
- package/dist/html/layout/flex.js.map +1 -1
- package/dist/html/layout/inline.d.ts +3 -0
- package/dist/html/layout/inline.d.ts.map +1 -1
- package/dist/html/layout/inline.js +154 -54
- package/dist/html/layout/inline.js.map +1 -1
- package/dist/html/layout/multicol.d.ts +42 -0
- package/dist/html/layout/multicol.d.ts.map +1 -0
- package/dist/html/layout/multicol.js +897 -0
- package/dist/html/layout/multicol.js.map +1 -0
- package/dist/html/layout/shaping.d.ts +7 -0
- package/dist/html/layout/shaping.d.ts.map +1 -1
- package/dist/html/layout/shaping.js +25 -20
- package/dist/html/layout/shaping.js.map +1 -1
- package/dist/html/layout/table.d.ts.map +1 -1
- package/dist/html/layout/table.js +62 -18
- package/dist/html/layout/table.js.map +1 -1
- package/dist/html/node.d.ts +90 -15
- package/dist/html/node.d.ts.map +1 -1
- package/dist/html/node.js +771 -186
- package/dist/html/node.js.map +1 -1
- package/dist/html/paint.d.ts +101 -5
- package/dist/html/paint.d.ts.map +1 -1
- package/dist/html/paint.js +1502 -160
- package/dist/html/paint.js.map +1 -1
- package/dist/html/svg.d.ts +15 -2
- package/dist/html/svg.d.ts.map +1 -1
- package/dist/html/svg.js +249 -17
- package/dist/html/svg.js.map +1 -1
- package/dist/html/widgets.d.ts +29 -0
- package/dist/html/widgets.d.ts.map +1 -0
- package/dist/html/widgets.js +700 -0
- package/dist/html/widgets.js.map +1 -0
- package/dist/html/woff2.d.ts +14 -0
- package/dist/html/woff2.d.ts.map +1 -0
- package/dist/html/woff2.js +608 -0
- package/dist/html/woff2.js.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/maps/controller.d.ts +17 -1
- package/dist/maps/controller.d.ts.map +1 -1
- package/dist/maps/controller.js +27 -7
- package/dist/maps/controller.js.map +1 -1
- package/dist/richtext/node.d.ts +8 -1
- package/dist/richtext/node.d.ts.map +1 -1
- package/dist/richtext/node.js +5 -1
- package/dist/richtext/node.js.map +1 -1
- package/dist/richtext/runs.d.ts +25 -6
- package/dist/richtext/runs.d.ts.map +1 -1
- package/dist/richtext/runs.js +240 -24
- package/dist/richtext/runs.js.map +1 -1
- package/package.json +2 -2
- package/src/html/controls.ts +114 -51
- package/src/html/css/cascade.ts +614 -140
- package/src/html/css/color.ts +29 -0
- package/src/html/css/parse.ts +388 -33
- package/src/html/css/style.ts +688 -150
- package/src/html/css/transform.ts +459 -0
- package/src/html/css/ua.ts +77 -10
- package/src/html/css/values.ts +4 -0
- package/src/html/dom.ts +3 -1
- package/src/html/fonts.ts +417 -24
- package/src/html/form.ts +962 -0
- package/src/html/index.ts +49 -338
- package/src/html/layout/axes.ts +62 -0
- package/src/html/layout/block.ts +447 -37
- package/src/html/layout/boxes.ts +143 -14
- package/src/html/layout/cache.ts +6 -0
- package/src/html/layout/css-grid.ts +95 -22
- package/src/html/layout/flex.ts +675 -78
- package/src/html/layout/inline.ts +159 -55
- package/src/html/layout/multicol.ts +977 -0
- package/src/html/layout/shaping.ts +27 -18
- package/src/html/layout/table.ts +62 -19
- package/src/html/node.ts +798 -172
- package/src/html/paint.ts +1742 -241
- package/src/html/svg.ts +277 -19
- package/src/html/widgets.ts +821 -0
- package/src/html/woff2.ts +612 -0
- package/src/index.ts +1 -0
- package/src/maps/controller.ts +28 -7
- package/src/richtext/node.ts +13 -2
- package/src/richtext/runs.ts +313 -35
package/src/html/css/cascade.ts
CHANGED
|
@@ -33,6 +33,7 @@ import type { Declaration, StyleRule, Stylesheet } from './parse.js';
|
|
|
33
33
|
import {
|
|
34
34
|
applyDeclaration,
|
|
35
35
|
blockify,
|
|
36
|
+
settleButton,
|
|
36
37
|
settleContentVisibility,
|
|
37
38
|
settleOverflow,
|
|
38
39
|
settleClamp,
|
|
@@ -48,6 +49,7 @@ import type { FontFamilies } from '../fonts.js';
|
|
|
48
49
|
import { parseDeclarations } from './parse.js';
|
|
49
50
|
import { CounterStyles, counterStyleRule } from './counter-styles.js';
|
|
50
51
|
import type { CounterStyleRule } from './counter-styles.js';
|
|
52
|
+
import { PALETTE_CHROME } from './ua.js';
|
|
51
53
|
import type { UnitContext } from './values.js';
|
|
52
54
|
import { viewportUnit } from './values.js';
|
|
53
55
|
import { customProperties, substituteIn } from './vars.js';
|
|
@@ -93,6 +95,41 @@ export interface SharedStyle {
|
|
|
93
95
|
key: number;
|
|
94
96
|
}
|
|
95
97
|
|
|
98
|
+
/**
|
|
99
|
+
* The styles of the build before, for a build that only some elements'
|
|
100
|
+
* styles can differ in (`Cascade.beginSharing`): what each element's came
|
|
101
|
+
* to, and the elements to style again — `restyle`, each with everything in
|
|
102
|
+
* it, since a style is its parent's and the rules', and a rule reads an
|
|
103
|
+
* element's ancestors.
|
|
104
|
+
*/
|
|
105
|
+
export interface KeptStyles {
|
|
106
|
+
styles: ReadonlyMap<Element, { style: ComputedStyle; inFlex: boolean }>;
|
|
107
|
+
restyle: ReadonlySet<Element>;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** What the pointer entering or leaving an element can change
|
|
111
|
+
* (`Cascade.hoverTouches`): nothing, the element and what is in it, or
|
|
112
|
+
* those and the siblings after it. */
|
|
113
|
+
export type HoverTouch = 0 | 1 | 2;
|
|
114
|
+
export const HOVER_UNTOUCHED = 0;
|
|
115
|
+
export const HOVER_TOUCHED = 1;
|
|
116
|
+
export const HOVER_FOLLOWED = 2;
|
|
117
|
+
|
|
118
|
+
/** Which pseudo-elements' rules test the pointer (`Cascade.hoverPseudo`). */
|
|
119
|
+
export const HOVER_PSEUDO_NONE = 0;
|
|
120
|
+
export const HOVER_PSEUDO_GENERATED = 1;
|
|
121
|
+
export const HOVER_PSEUDO_OTHER = 2;
|
|
122
|
+
|
|
123
|
+
/** The pseudo-elements a pointer move can restyle: `::selection`'s style
|
|
124
|
+
* is worked out as a selection is painted. */
|
|
125
|
+
const POINTER_PSEUDO_ELEMENTS = [
|
|
126
|
+
'before',
|
|
127
|
+
'after',
|
|
128
|
+
'first-letter',
|
|
129
|
+
'first-line',
|
|
130
|
+
'marker',
|
|
131
|
+
] as const;
|
|
132
|
+
|
|
96
133
|
/** A compiled matcher, kept beside the rule it came from. */
|
|
97
134
|
interface IndexedRule {
|
|
98
135
|
rule: StyleRule;
|
|
@@ -132,6 +169,11 @@ class RuleIndex {
|
|
|
132
169
|
/** Whether any rule in here is pointer-sensitive, so the renderer knows
|
|
133
170
|
* whether a pointer move can change the cascade at all. */
|
|
134
171
|
hoverSensitive = false;
|
|
172
|
+
/** The rules that test `:hover`, in buckets of their own: the ones whose
|
|
173
|
+
* answers a pointer move can change, which is all that is asked to know
|
|
174
|
+
* whether an element's did (`Cascade.pointerChanged`). Null until there
|
|
175
|
+
* is one. */
|
|
176
|
+
pointer: RuleIndex | null = null;
|
|
135
177
|
/** The buckets holding a rule an element cannot share its style under
|
|
136
178
|
* (`UNSHAREABLE`): the ids, classes and tags whose elements compute
|
|
137
179
|
* their own, and whether the universal bucket makes every element do so. */
|
|
@@ -185,26 +227,44 @@ class RuleIndex {
|
|
|
185
227
|
else if (key.kind === 'tag') this.ownStyleTags.add(key.name);
|
|
186
228
|
else this.ownStyleEverywhere = true;
|
|
187
229
|
}
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
230
|
+
this._bucket(key).push(indexed);
|
|
231
|
+
if (HOVER.test(rule.selector)) {
|
|
232
|
+
this.pointer ??= new RuleIndex();
|
|
233
|
+
this.pointer.size += 1;
|
|
234
|
+
this.pointer._bucket(key).push(indexed);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
private _bucket(key: ReturnType<typeof rightmostKey>): IndexedRule[] {
|
|
239
|
+
return key.kind === 'id'
|
|
240
|
+
? mapBucket(this.byId, key.name)
|
|
241
|
+
: key.kind === 'class'
|
|
242
|
+
? mapBucket(this.byClass, key.name)
|
|
243
|
+
: key.kind === 'tag'
|
|
244
|
+
? mapBucket(this.byTag, key.name)
|
|
245
|
+
: this.universal;
|
|
197
246
|
}
|
|
198
247
|
}
|
|
199
248
|
|
|
200
249
|
/** The pseudo-elements a rule can style here. */
|
|
201
250
|
type PseudoElement =
|
|
202
|
-
'before' | 'after' | 'first-letter' | 'first-line' | 'marker';
|
|
251
|
+
'before' | 'after' | 'first-letter' | 'first-line' | 'marker' | 'selection';
|
|
203
252
|
|
|
204
253
|
/** A selector's trailing `::before`, `::after`, `::first-letter`,
|
|
205
|
-
* `::first-line` or `::
|
|
206
|
-
* its four. */
|
|
207
|
-
const PSEUDO_ELEMENT =
|
|
254
|
+
* `::first-line`, `::marker` or `::selection`, or CSS 2's single-colon
|
|
255
|
+
* spelling of any of its four. */
|
|
256
|
+
const PSEUDO_ELEMENT =
|
|
257
|
+
/::?(before|after|first-letter|first-line)$|::(marker|selection)$/i;
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* What a `::selection` makes of the text it covers (CSS Pseudo 4, 3.2): the
|
|
261
|
+
* colour the text is drawn in, null for its own, and the band under it,
|
|
262
|
+
* null for none.
|
|
263
|
+
*/
|
|
264
|
+
export interface SelectionStyle {
|
|
265
|
+
color: string | null;
|
|
266
|
+
background: string | null;
|
|
267
|
+
}
|
|
208
268
|
|
|
209
269
|
/**
|
|
210
270
|
* A rule for a pseudo-element, as the pseudo-element it styles and a rule
|
|
@@ -226,7 +286,7 @@ function splitPseudoElement(
|
|
|
226
286
|
? `${trimmed} *`
|
|
227
287
|
: trimmed;
|
|
228
288
|
return {
|
|
229
|
-
which: (m[1] ??
|
|
289
|
+
which: (m[1] ?? m[2]).toLowerCase() as PseudoElement,
|
|
230
290
|
rule: { ...rule, selector },
|
|
231
291
|
};
|
|
232
292
|
}
|
|
@@ -234,34 +294,63 @@ function splitPseudoElement(
|
|
|
234
294
|
/**
|
|
235
295
|
* The compounds of a selector that test `:hover`, each without it (and
|
|
236
296
|
* without an `:active` beside it), and whether one is followed by a sibling
|
|
237
|
-
* combinator, which reaches the element's later siblings.
|
|
238
|
-
*
|
|
239
|
-
* `:is(
|
|
297
|
+
* combinator, which reaches the element's later siblings.
|
|
298
|
+
*
|
|
299
|
+
* A `:hover` inside `:is()`, `:where()` or `:not()` is found where it is:
|
|
300
|
+
* each takes a selector list and matches the element it is written on, so
|
|
301
|
+
* a list entry of one compound tests that element — `a:not(:hover)` is a
|
|
302
|
+
* compound of `a`'s that tests the pointer — and an entry with combinators
|
|
303
|
+
* in it names an ancestor or an earlier sibling, as it would outside.
|
|
304
|
+
* Tailwind 4 writes every `group-hover:` that way, `:is(:where(.group):hover
|
|
305
|
+
* *)`, and a page of them built its document again on every move. Whatever
|
|
306
|
+
* the nesting, the element whose hover changed is one a compound found here
|
|
307
|
+
* matches, and what changes with it is itself, what is in it, and what
|
|
308
|
+
* follows it where a sibling combinator is in play: a selector reads an
|
|
309
|
+
* element, its ancestors and the siblings before each of them, and nothing
|
|
310
|
+
* else. `nested` is left for a function this cannot read a selector list
|
|
311
|
+
* in — `:nth-child(… of :hover)`, `:host()` — which reaches elements no
|
|
312
|
+
* compound names.
|
|
240
313
|
*
|
|
241
|
-
* A `:hover` in a compound's `:has()` is the
|
|
242
|
-
*
|
|
314
|
+
* A `:hover` in a compound's `:has()` is the other way about, since what
|
|
315
|
+
* it tests is below: the compound, without its `:has()`, is an ancestor of
|
|
243
316
|
* the element the pointer is over — or an earlier sibling of it or of an
|
|
244
317
|
* ancestor, where the argument starts at a sibling (Selectors 4, 4.5). So
|
|
245
|
-
* it is given as an anchor (`has`)
|
|
318
|
+
* it is given as an anchor (`has`).
|
|
246
319
|
*
|
|
247
320
|
* `:active` is not the pointer's here: nothing sets it (`setPointer` is
|
|
248
321
|
* handed none), so a selector that tests only it never changes as the
|
|
249
322
|
* pointer moves — Wikipedia's buttons' `:focus:not(:active)` among them.
|
|
250
323
|
* A press that sets it would have to be counted here too.
|
|
251
324
|
*/
|
|
252
|
-
export function pointerCompounds(selector: string): {
|
|
325
|
+
export function pointerCompounds(selector: string): PointerCompounds {
|
|
326
|
+
const out: PointerCompounds = {
|
|
327
|
+
compounds: [],
|
|
328
|
+
siblings: false,
|
|
329
|
+
nested: false,
|
|
330
|
+
has: [],
|
|
331
|
+
followed: [],
|
|
332
|
+
};
|
|
333
|
+
scanComplex(selector, out);
|
|
334
|
+
return out;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
export interface PointerCompounds {
|
|
253
338
|
compounds: string[];
|
|
254
339
|
siblings: boolean;
|
|
255
340
|
nested: boolean;
|
|
256
341
|
has: { anchor: string; siblings: boolean }[];
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
342
|
+
/** The compounds and anchors a sibling combinator follows: the ones
|
|
343
|
+
* whose elements' later siblings change with them. */
|
|
344
|
+
followed: string[];
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/** The functions that take a selector list and match the element they are
|
|
348
|
+
* written on (Selectors 4, 4.2 to 4.4), under the names css-select and
|
|
349
|
+
* older sheets know them by. */
|
|
350
|
+
const MATCHES_ANY = /^(?:is|where|not|matches|any|-webkit-any|-moz-any)$/i;
|
|
351
|
+
|
|
352
|
+
/** The compounds of a complex selector, each with the combinator after it. */
|
|
353
|
+
function compoundsOf(selector: string): { text: string; next: string }[] {
|
|
265
354
|
const parts: { text: string; next: string }[] = [];
|
|
266
355
|
let start = 0;
|
|
267
356
|
let depth = 0;
|
|
@@ -287,85 +376,188 @@ export function pointerCompounds(selector: string): {
|
|
|
287
376
|
}
|
|
288
377
|
}
|
|
289
378
|
split(selector.length, '');
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
379
|
+
return parts;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
/** A selector list's entries: split at its top-level commas. */
|
|
383
|
+
function selectorsOf(list: string): string[] {
|
|
384
|
+
const entries: string[] = [];
|
|
385
|
+
let start = 0;
|
|
386
|
+
let depth = 0;
|
|
387
|
+
let quote = '';
|
|
388
|
+
for (let i = 0; i < list.length; i += 1) {
|
|
389
|
+
const c = list[i];
|
|
390
|
+
if (quote) {
|
|
391
|
+
if (c === quote && list[i - 1] !== '\\') quote = '';
|
|
392
|
+
} else if (c === '\\') i = escapeEnd(list, i) - 1;
|
|
393
|
+
else if (c === '"' || c === "'") quote = c;
|
|
394
|
+
else if (c === '(' || c === '[') depth += 1;
|
|
395
|
+
else if (c === ')' || c === ']') depth = Math.max(0, depth - 1);
|
|
396
|
+
else if (c === ',' && depth === 0) {
|
|
397
|
+
entries.push(list.slice(start, i));
|
|
398
|
+
start = i + 1;
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
entries.push(list.slice(start));
|
|
402
|
+
return entries;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
/** Where the parenthesis opened at `open` closes, or the text's end. */
|
|
406
|
+
function closeOf(text: string, open: number): number {
|
|
407
|
+
let depth = 0;
|
|
408
|
+
let quote = '';
|
|
409
|
+
for (let i = open; i < text.length; i += 1) {
|
|
410
|
+
const c = text[i];
|
|
411
|
+
if (quote) {
|
|
412
|
+
if (c === quote && text[i - 1] !== '\\') quote = '';
|
|
413
|
+
} else if (c === '\\') i = escapeEnd(text, i) - 1;
|
|
414
|
+
else if (c === '"' || c === "'") quote = c;
|
|
415
|
+
else if (c === '(' || c === '[') depth += 1;
|
|
416
|
+
else if (c === ')' || c === ']') {
|
|
417
|
+
depth -= 1;
|
|
418
|
+
if (depth === 0) return i;
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
return text.length;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
/** `pointerCompounds` for one complex selector, into `out`. */
|
|
425
|
+
function scanComplex(selector: string, out: PointerCompounds): void {
|
|
426
|
+
for (const { text, next } of compoundsOf(selector)) {
|
|
427
|
+
const compoundsFrom = out.compounds.length;
|
|
428
|
+
const hasFrom = out.has.length;
|
|
429
|
+
const found = scanCompound(text, out);
|
|
430
|
+
const sibling = next === '+' || next === '~';
|
|
431
|
+
if (found.args.length) {
|
|
432
|
+
out.has.push({
|
|
433
|
+
anchor: found.bare.trim() || '*',
|
|
434
|
+
siblings: found.args.some((arg) =>
|
|
435
|
+
selectorsOf(arg).some((one) => /^\s*[+~]/.test(one)),
|
|
436
|
+
),
|
|
437
|
+
});
|
|
438
|
+
}
|
|
439
|
+
if (found.pointer) out.compounds.push(found.bare.trim() || '*');
|
|
440
|
+
// what follows the element this compound matches follows one whose
|
|
441
|
+
// hover, or whose answer to a `:has()`, changed
|
|
442
|
+
// — the one this compound matches, or one a selector nested in it
|
|
443
|
+
// named, which is more than it reaches and never less
|
|
444
|
+
if (sibling && (found.pointer || found.below || found.args.length)) {
|
|
445
|
+
out.siblings = true;
|
|
446
|
+
for (let i = compoundsFrom; i < out.compounds.length; i += 1) {
|
|
447
|
+
out.followed.push(out.compounds[i]);
|
|
308
448
|
}
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
449
|
+
for (let i = hasFrom; i < out.has.length; i += 1) {
|
|
450
|
+
out.followed.push(out.has[i].anchor);
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
/**
|
|
457
|
+
* What one compound says of the pointer: the compound without what tests
|
|
458
|
+
* it (`bare`), whether it tests the element's own hover (`pointer`), the
|
|
459
|
+
* arguments of each `:has()` of the element's that tests it below (`args`),
|
|
460
|
+
* and whether a selector nested in it named another element's (`below`),
|
|
461
|
+
* whose compounds went into `out`.
|
|
462
|
+
*/
|
|
463
|
+
function scanCompound(
|
|
464
|
+
text: string,
|
|
465
|
+
out: PointerCompounds,
|
|
466
|
+
): { bare: string; pointer: boolean; below: boolean; args: string[] } {
|
|
467
|
+
let bare = '';
|
|
468
|
+
let pointer = false;
|
|
469
|
+
let below = false;
|
|
470
|
+
const args: string[] = [];
|
|
471
|
+
let bracket = 0;
|
|
472
|
+
let quote = '';
|
|
473
|
+
for (let i = 0; i < text.length; i += 1) {
|
|
474
|
+
const c = text[i];
|
|
475
|
+
if (quote) {
|
|
476
|
+
if (c === quote && text[i - 1] !== '\\') quote = '';
|
|
477
|
+
bare += c;
|
|
478
|
+
continue;
|
|
479
|
+
}
|
|
480
|
+
if (c === '\\') {
|
|
481
|
+
const end = escapeEnd(text, i);
|
|
482
|
+
bare += text.slice(i, end);
|
|
483
|
+
i = end - 1;
|
|
484
|
+
continue;
|
|
485
|
+
}
|
|
486
|
+
if (c === '"' || c === "'") quote = c;
|
|
487
|
+
else if (c === '[') bracket += 1;
|
|
488
|
+
else if (c === ']') bracket = Math.max(0, bracket - 1);
|
|
489
|
+
else if (c === ':' && bracket === 0 && text[i + 1] !== ':') {
|
|
490
|
+
const m = POINTER_PSEUDO_AT.exec(text.slice(i));
|
|
491
|
+
if (m) {
|
|
492
|
+
if (m[0].length === 6) pointer = true;
|
|
493
|
+
i += m[0].length - 1;
|
|
313
494
|
continue;
|
|
314
495
|
}
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
depth = Math.max(0, depth - 1);
|
|
325
|
-
if (depth === 0 && hasAt >= 0 && c === ')') {
|
|
326
|
-
// the compound is kept without it: its argument is what the
|
|
327
|
-
// pointer changes
|
|
328
|
-
if (hasPointer) {
|
|
329
|
-
anchored = true;
|
|
330
|
-
args.push(text.slice(argAt, i));
|
|
331
|
-
bare = bare.slice(0, hasAt);
|
|
332
|
-
} else bare += c;
|
|
333
|
-
hasAt = -1;
|
|
496
|
+
const fn = FUNCTION_AT.exec(text.slice(i));
|
|
497
|
+
if (fn) {
|
|
498
|
+
const open = i + fn[0].length - 1;
|
|
499
|
+
const close = closeOf(text, open);
|
|
500
|
+
const arg = text.slice(open + 1, close);
|
|
501
|
+
const whole = text.slice(i, close + 1);
|
|
502
|
+
i = close;
|
|
503
|
+
if (!HOVER.test(arg)) {
|
|
504
|
+
bare += whole;
|
|
334
505
|
continue;
|
|
335
506
|
}
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
507
|
+
const name = fn[1].toLowerCase();
|
|
508
|
+
if (name === 'has') {
|
|
509
|
+
// the compound is kept without it: its argument is what the
|
|
510
|
+
// pointer changes, and whatever holds the `:hover` in there, the
|
|
511
|
+
// element it tests is in the anchor's subtree or a later
|
|
512
|
+
// sibling's
|
|
513
|
+
args.push(arg);
|
|
514
|
+
} else if (MATCHES_ANY.test(name)) {
|
|
515
|
+
// Kept as what the entries that test this element's hover ask
|
|
516
|
+
// of it besides — `:where(a:hover)` is a compound of `a`'s, and
|
|
517
|
+
// so is `:not(a:hover)`, which only an `a` can change its answer
|
|
518
|
+
// to. Tailwind's typography writes its links `.prose
|
|
519
|
+
// :where(a:hover):not(:where(.not-prose, .not-prose *))`, and
|
|
520
|
+
// without the `a` that was a compound every element matched.
|
|
521
|
+
const own: string[] = [];
|
|
522
|
+
let any = false;
|
|
523
|
+
for (const entry of selectorsOf(arg)) {
|
|
524
|
+
if (!HOVER.test(entry)) continue;
|
|
525
|
+
const parts = compoundsOf(entry);
|
|
526
|
+
if (parts.length !== 1) {
|
|
527
|
+
scanComplex(entry, out);
|
|
528
|
+
below = true;
|
|
529
|
+
continue;
|
|
530
|
+
}
|
|
531
|
+
// of this element itself
|
|
532
|
+
const one = scanCompound(parts[0].text, out);
|
|
533
|
+
if (one.below) below = true;
|
|
534
|
+
if (!one.pointer && !one.args.length) continue;
|
|
535
|
+
if (one.pointer) pointer = true;
|
|
536
|
+
args.push(...one.args);
|
|
537
|
+
const rest = one.bare.trim();
|
|
538
|
+
if (rest && rest !== '*') own.push(rest);
|
|
539
|
+
else any = true;
|
|
349
540
|
}
|
|
541
|
+
if (own.length && !any) bare += `:is(${own.join(',')})`;
|
|
542
|
+
} else {
|
|
543
|
+
out.nested = true;
|
|
544
|
+
bare += whole;
|
|
350
545
|
}
|
|
546
|
+
continue;
|
|
351
547
|
}
|
|
352
|
-
bare += c;
|
|
353
548
|
}
|
|
354
|
-
|
|
355
|
-
out.has.push({
|
|
356
|
-
anchor: bare.trim() || '*',
|
|
357
|
-
siblings: args.some((arg) =>
|
|
358
|
-
arg.split(',').some((one) => /^\s*[+~]/.test(one)),
|
|
359
|
-
),
|
|
360
|
-
});
|
|
361
|
-
}
|
|
362
|
-
if (!pointer) continue;
|
|
363
|
-
out.compounds.push(bare.trim() || '*');
|
|
364
|
-
if (next === '+' || next === '~') out.siblings = true;
|
|
549
|
+
bare += c;
|
|
365
550
|
}
|
|
366
|
-
return
|
|
551
|
+
return { bare, pointer, below, args };
|
|
367
552
|
}
|
|
368
553
|
|
|
554
|
+
/** A functional pseudo-class's name and its opening parenthesis, at the
|
|
555
|
+
* start of a string. */
|
|
556
|
+
const FUNCTION_AT = /^:([\w-]+)\(/;
|
|
557
|
+
|
|
558
|
+
/** `:hover` anywhere in a selector. */
|
|
559
|
+
const HOVER = /:hover(?![\w-])/i;
|
|
560
|
+
|
|
369
561
|
/** `:hover` or `:active` at the start of a string, and nothing longer. */
|
|
370
562
|
const POINTER_PSEUDO_AT = /^:(?:hover|active)(?![\w-])/i;
|
|
371
563
|
|
|
@@ -569,26 +761,34 @@ export class Cascade {
|
|
|
569
761
|
'first-letter': new RuleIndex(),
|
|
570
762
|
'first-line': new RuleIndex(),
|
|
571
763
|
marker: new RuleIndex(),
|
|
764
|
+
selection: new RuleIndex(),
|
|
572
765
|
};
|
|
573
766
|
private _adapter: CssSelectAdapter;
|
|
574
767
|
/** The compounds of the selectors that test the pointer, each without
|
|
575
768
|
* its `:hover` or `:active` (`pointerCompounds`), and their matchers,
|
|
576
769
|
* compiled when a pointer move first asks (`hoverTouches`). */
|
|
577
|
-
private _hoverCompounds = new
|
|
578
|
-
private _hoverMatchers:
|
|
770
|
+
private _hoverCompounds = new Map<string, boolean>();
|
|
771
|
+
private _hoverMatchers:
|
|
772
|
+
{ match: (el: Element) => boolean; followed: boolean }[] | null = null;
|
|
579
773
|
/** Whether a pointer move can be restyled where it happened
|
|
580
774
|
* (`HtmlViewNode._hoverInPlace`): false where a selector tests the
|
|
581
775
|
* pointer inside a functional pseudo-class, which reaches elements no
|
|
582
776
|
* compound names. */
|
|
583
777
|
hoverLocal = true;
|
|
584
|
-
/**
|
|
585
|
-
*
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
778
|
+
/** The compounds whose `:has()` tests the pointer, each without it,
|
|
779
|
+
* whether its argument starts at a sibling, and whether a sibling
|
|
780
|
+
* combinator follows it (`hoverAnchors`). */
|
|
781
|
+
private _hoverAnchors = new Map<
|
|
782
|
+
string,
|
|
783
|
+
{ siblings: boolean; followed: boolean }
|
|
784
|
+
>();
|
|
590
785
|
private _anchorMatchers:
|
|
591
|
-
|
|
786
|
+
| {
|
|
787
|
+
match: (el: Element) => boolean;
|
|
788
|
+
siblings: boolean;
|
|
789
|
+
followed: boolean;
|
|
790
|
+
}[]
|
|
791
|
+
| null = null;
|
|
592
792
|
private _pointer: PointerState = NO_POINTER;
|
|
593
793
|
readonly initial: ComputedStyle;
|
|
594
794
|
readonly look: RootLook;
|
|
@@ -627,6 +827,12 @@ export class Cascade {
|
|
|
627
827
|
/** The families the document loads itself (`fonts.ts`), or null for a
|
|
628
828
|
* document with no `@font-face`. */
|
|
629
829
|
private _families: FontFamilies | null;
|
|
830
|
+
/** The root element's computed style: its `font-size` is what a `rem`
|
|
831
|
+
* is and its line height what an `rlh` is (CSS Values 4, 6.1.1), in
|
|
832
|
+
* every element but the root, whose own declarations measure them from
|
|
833
|
+
* the initial values. Set as the root's style is computed, which a build
|
|
834
|
+
* does ahead of every element under it. */
|
|
835
|
+
private _root: ComputedStyle | null = null;
|
|
630
836
|
private _mapFamilies: ((list: string) => string) | undefined;
|
|
631
837
|
/** Whether any declaration has a length in `lh` or `rlh`: only then is
|
|
632
838
|
* the line height settled ahead of the declarations that read it. */
|
|
@@ -673,6 +879,9 @@ export class Cascade {
|
|
|
673
879
|
this._noteHover((pseudo?.rule ?? rule).selector);
|
|
674
880
|
}
|
|
675
881
|
for (const bp of sheet.breakpoints) breakpoints.add(bp);
|
|
882
|
+
// a query on the viewport's height reads it as a `vh` does
|
|
883
|
+
if (sheet.readsHeight) this.readsViewportHeight = true;
|
|
884
|
+
if (sheet.readsWidth) this.readsViewportWidth = true;
|
|
676
885
|
}
|
|
677
886
|
this.breakpoints = [...breakpoints].sort((a, b) => a - b);
|
|
678
887
|
this.counterStyles = new CounterStyles(counterStyles);
|
|
@@ -727,19 +936,50 @@ export class Cascade {
|
|
|
727
936
|
this._pointer = pointer;
|
|
728
937
|
}
|
|
729
938
|
|
|
939
|
+
/**
|
|
940
|
+
* Which pseudo-elements' rules test the pointer: none's
|
|
941
|
+
* (`HOVER_PSEUDO_NONE`); a `::before`'s or an `::after`'s only
|
|
942
|
+
* (`HOVER_PSEUDO_GENERATED`), whose boxes a move restyles where they are
|
|
943
|
+
* as it does an element's; or a `::first-line`'s, a `::first-letter`'s
|
|
944
|
+
* or a `::marker`'s (`HOVER_PSEUDO_OTHER`), which layout styles and a
|
|
945
|
+
* move cannot.
|
|
946
|
+
*/
|
|
947
|
+
get hoverPseudo(): 0 | 1 | 2 {
|
|
948
|
+
if (
|
|
949
|
+
this._pseudo['first-letter'].hoverSensitive ||
|
|
950
|
+
this._pseudo['first-line'].hoverSensitive ||
|
|
951
|
+
this._pseudo.marker.hoverSensitive
|
|
952
|
+
) {
|
|
953
|
+
return HOVER_PSEUDO_OTHER;
|
|
954
|
+
}
|
|
955
|
+
return this._pseudo.before.hoverSensitive ||
|
|
956
|
+
this._pseudo.after.hoverSensitive
|
|
957
|
+
? HOVER_PSEUDO_GENERATED
|
|
958
|
+
: HOVER_PSEUDO_NONE;
|
|
959
|
+
}
|
|
960
|
+
|
|
730
961
|
/**
|
|
731
962
|
* Whether the pointer entering or leaving `el` can change a style: one of
|
|
732
963
|
* the compounds a selector tests the pointer in matches it, the pointer
|
|
733
964
|
* aside. A move between two paragraphs of a page whose only such rule is
|
|
734
|
-
* `a:hover` touches nothing, and restyles nothing.
|
|
965
|
+
* `a:hover` touches nothing, and restyles nothing. `HOVER_FOLLOWED`
|
|
966
|
+
* where a sibling combinator follows one that matches, so the element's
|
|
967
|
+
* later siblings can change with it — those rules' elements alone: a
|
|
968
|
+
* sheet with one `.peer:hover ~ *` in it does not make every row of a
|
|
969
|
+
* table restyle the rows after it.
|
|
735
970
|
*/
|
|
736
|
-
hoverTouches(el: Element):
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
971
|
+
hoverTouches(el: Element): HoverTouch {
|
|
972
|
+
// the followed ones first, so the first that matches is the answer
|
|
973
|
+
this._hoverMatchers ??= [...this._hoverCompounds]
|
|
974
|
+
.sort(([, a], [, b]) => Number(b) - Number(a))
|
|
975
|
+
.flatMap(([compound, followed]) => {
|
|
976
|
+
const match = this._compile(compound);
|
|
977
|
+
return match ? [{ match, followed }] : [];
|
|
978
|
+
});
|
|
979
|
+
for (const { match, followed } of this._hoverMatchers) {
|
|
980
|
+
if (match(el)) return followed ? HOVER_FOLLOWED : HOVER_TOUCHED;
|
|
981
|
+
}
|
|
982
|
+
return HOVER_UNTOUCHED;
|
|
743
983
|
}
|
|
744
984
|
|
|
745
985
|
/**
|
|
@@ -747,22 +987,27 @@ export class Cascade {
|
|
|
747
987
|
* hovered or left: the ancestors of it a compound whose `:has()` tests
|
|
748
988
|
* the pointer names, and their earlier siblings and its own where the
|
|
749
989
|
* argument starts at a sibling — a superset, which is restyled and found
|
|
750
|
-
* the same where it did not. Into `into
|
|
990
|
+
* the same where it did not. Into `into`, each with whether its later
|
|
991
|
+
* siblings can change with it (`hoverTouches`).
|
|
751
992
|
*/
|
|
752
|
-
hoverAnchors(el: Element, into: Element
|
|
993
|
+
hoverAnchors(el: Element, into: Map<Element, HoverTouch>): void {
|
|
753
994
|
if (!this._hoverAnchors.size) return;
|
|
754
|
-
this._anchorMatchers ??= [...this._hoverAnchors]
|
|
755
|
-
([
|
|
995
|
+
this._anchorMatchers ??= [...this._hoverAnchors]
|
|
996
|
+
.sort(([, a], [, b]) => Number(b.followed) - Number(a.followed))
|
|
997
|
+
.flatMap(([anchor, { siblings, followed }]) => {
|
|
756
998
|
const match = this._compile(anchor);
|
|
757
|
-
return match ? [{ match, siblings }] : [];
|
|
758
|
-
}
|
|
759
|
-
)
|
|
999
|
+
return match ? [{ match, siblings, followed }] : [];
|
|
1000
|
+
});
|
|
1001
|
+
const note = (at: Element, followed: boolean): void => {
|
|
1002
|
+
if (followed) into.set(at, HOVER_FOLLOWED);
|
|
1003
|
+
else if (!into.has(at)) into.set(at, HOVER_TOUCHED);
|
|
1004
|
+
};
|
|
760
1005
|
const siblings = this._anchorMatchers.some((m) => m.siblings);
|
|
761
1006
|
for (let at: Element | null = el; at;) {
|
|
762
1007
|
if (at !== el) {
|
|
763
|
-
for (const { match } of this._anchorMatchers) {
|
|
1008
|
+
for (const { match, followed } of this._anchorMatchers) {
|
|
764
1009
|
if (match(at)) {
|
|
765
|
-
|
|
1010
|
+
note(at, followed);
|
|
766
1011
|
break;
|
|
767
1012
|
}
|
|
768
1013
|
}
|
|
@@ -770,9 +1015,9 @@ export class Cascade {
|
|
|
770
1015
|
if (siblings) {
|
|
771
1016
|
for (let s = at.prev; s; s = s.prev) {
|
|
772
1017
|
if (!isTag(s as Element)) continue;
|
|
773
|
-
for (const
|
|
774
|
-
if (
|
|
775
|
-
|
|
1018
|
+
for (const m of this._anchorMatchers) {
|
|
1019
|
+
if (m.siblings && m.match(s as Element)) {
|
|
1020
|
+
note(s as Element, m.followed);
|
|
776
1021
|
break;
|
|
777
1022
|
}
|
|
778
1023
|
}
|
|
@@ -787,14 +1032,20 @@ export class Cascade {
|
|
|
787
1032
|
if (!/:(?:hover|active)/i.test(selector)) return;
|
|
788
1033
|
const found = pointerCompounds(selector);
|
|
789
1034
|
if (found.nested) this.hoverLocal = false;
|
|
790
|
-
|
|
791
|
-
for (const c of found.compounds)
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
siblings || (this._hoverAnchors.get(anchor) ?? false),
|
|
1035
|
+
const followed = new Set(found.followed);
|
|
1036
|
+
for (const c of found.compounds) {
|
|
1037
|
+
this._hoverCompounds.set(
|
|
1038
|
+
c,
|
|
1039
|
+
followed.has(c) || (this._hoverCompounds.get(c) ?? false),
|
|
796
1040
|
);
|
|
797
1041
|
}
|
|
1042
|
+
for (const { anchor, siblings } of found.has) {
|
|
1043
|
+
const was = this._hoverAnchors.get(anchor);
|
|
1044
|
+
this._hoverAnchors.set(anchor, {
|
|
1045
|
+
siblings: siblings || (was?.siblings ?? false),
|
|
1046
|
+
followed: followed.has(anchor) || (was?.followed ?? false),
|
|
1047
|
+
});
|
|
1048
|
+
}
|
|
798
1049
|
}
|
|
799
1050
|
|
|
800
1051
|
/** A selector compiled as a rule's is, or null where css-select refuses
|
|
@@ -831,12 +1082,35 @@ export class Cascade {
|
|
|
831
1082
|
private _ids = new WeakMap<object, number>();
|
|
832
1083
|
private _nextId = 1;
|
|
833
1084
|
|
|
834
|
-
/**
|
|
835
|
-
*
|
|
836
|
-
|
|
1085
|
+
/** The styles kept from the build before, while a build that keeps them
|
|
1086
|
+
* runs, and what each kept element shares under in this one. */
|
|
1087
|
+
private _kept: KeptStyles | null = null;
|
|
1088
|
+
private _keptShared = new Map<Element, SharedStyle>();
|
|
1089
|
+
|
|
1090
|
+
/**
|
|
1091
|
+
* A box tree is about to be built: the styles shared in the last build
|
|
1092
|
+
* were computed against a pointer and a viewport that may have moved.
|
|
1093
|
+
*
|
|
1094
|
+
* With `kept`, only its `restyle` elements are matched: a build the
|
|
1095
|
+
* pointer asked for — a hover that moved something — changes the styles
|
|
1096
|
+
* of the elements the move reached, and every other element's is what it
|
|
1097
|
+
* was. Matching them all again was most of such a build: a frame of 550 ms
|
|
1098
|
+
* on a page with a Tailwind sheet, and 150 without it.
|
|
1099
|
+
*/
|
|
1100
|
+
beginSharing(kept: KeptStyles | null = null): void {
|
|
837
1101
|
this._shared.clear();
|
|
838
1102
|
this._sharedByMatch.clear();
|
|
839
|
-
|
|
1103
|
+
// the kept styles' custom properties are the sets in here, which the
|
|
1104
|
+
// elements styled under them find again
|
|
1105
|
+
if (!kept) this._customs.clear();
|
|
1106
|
+
this._kept = kept;
|
|
1107
|
+
this._keptShared.clear();
|
|
1108
|
+
}
|
|
1109
|
+
|
|
1110
|
+
/** The build is over: nothing later is answered from what it kept. */
|
|
1111
|
+
endSharing(): void {
|
|
1112
|
+
this._kept = null;
|
|
1113
|
+
this._keptShared.clear();
|
|
840
1114
|
}
|
|
841
1115
|
|
|
842
1116
|
/**
|
|
@@ -924,6 +1198,22 @@ export class Cascade {
|
|
|
924
1198
|
parentKey: number,
|
|
925
1199
|
inFlexContainer: boolean,
|
|
926
1200
|
): SharedStyle {
|
|
1201
|
+
const kept = this._kept;
|
|
1202
|
+
if (kept !== null && !kept.restyle.has(el)) {
|
|
1203
|
+
// An element the change did not reach keeps its style, under a key
|
|
1204
|
+
// of its own: a key stands for an element's ancestors, which the
|
|
1205
|
+
// elements styled again under it have in common, and two kept
|
|
1206
|
+
// elements with one style need not — a hover restyled in place
|
|
1207
|
+
// leaves a hovered card's children the styles its neighbours' have.
|
|
1208
|
+
let shared = this._keptShared.get(el);
|
|
1209
|
+
if (shared !== undefined) return shared;
|
|
1210
|
+
const was = kept.styles.get(el);
|
|
1211
|
+
if (was !== undefined && was.inFlex === inFlexContainer) {
|
|
1212
|
+
shared = { style: was.style, key: this._nextShareKey++ };
|
|
1213
|
+
this._keptShared.set(el, shared);
|
|
1214
|
+
return shared;
|
|
1215
|
+
}
|
|
1216
|
+
}
|
|
927
1217
|
let key = `${parentKey}\u0001${inFlexContainer ? 1 : 0}`;
|
|
928
1218
|
const tag = tagOf(el);
|
|
929
1219
|
key += `\u0001${tag.length}:${tag}`;
|
|
@@ -948,6 +1238,7 @@ export class Cascade {
|
|
|
948
1238
|
parentStyle,
|
|
949
1239
|
inFlexContainer,
|
|
950
1240
|
candidates,
|
|
1241
|
+
true,
|
|
951
1242
|
),
|
|
952
1243
|
key: this._nextShareKey++,
|
|
953
1244
|
};
|
|
@@ -1004,6 +1295,7 @@ export class Cascade {
|
|
|
1004
1295
|
parentStyle,
|
|
1005
1296
|
inFlexContainer,
|
|
1006
1297
|
this._candidates(el),
|
|
1298
|
+
true,
|
|
1007
1299
|
);
|
|
1008
1300
|
}
|
|
1009
1301
|
|
|
@@ -1087,6 +1379,56 @@ export class Cascade {
|
|
|
1087
1379
|
return out;
|
|
1088
1380
|
}
|
|
1089
1381
|
|
|
1382
|
+
/** Whether any rule styles a `::selection`: a document with none draws
|
|
1383
|
+
* a selection in the palette's colour, and asks nothing. */
|
|
1384
|
+
get hasSelection(): boolean {
|
|
1385
|
+
return this._pseudo.selection.size > 0;
|
|
1386
|
+
}
|
|
1387
|
+
|
|
1388
|
+
/**
|
|
1389
|
+
* An element's `::selection`, over its parent's (`parent`, null for the
|
|
1390
|
+
* palette's own): the colour and the background the rules that reach it
|
|
1391
|
+
* set, and its parent's for either they leave alone. Both inherit along
|
|
1392
|
+
* the chain of highlights rather than the elements' (CSS Pseudo 4, 3.5,
|
|
1393
|
+
* "highlight inheritance"), which is what Chrome draws: a `<span>` in a
|
|
1394
|
+
* `div::selection { background: red }` is selected in red, and a `<p>`
|
|
1395
|
+
* whose own rule sets only a colour keeps the red under it.
|
|
1396
|
+
*
|
|
1397
|
+
* The palette's highlight is taken only where no rule has set either
|
|
1398
|
+
* (3.6, "paired defaults"): one that sets a colour and no background is
|
|
1399
|
+
* selected over none, as Chrome has it. A rule that sets neither — a
|
|
1400
|
+
* `text-shadow` only — leaves the element where its parent was.
|
|
1401
|
+
*/
|
|
1402
|
+
selectionStyle(
|
|
1403
|
+
el: Element,
|
|
1404
|
+
style: ComputedStyle,
|
|
1405
|
+
parent: SelectionStyle | null,
|
|
1406
|
+
): SelectionStyle | null {
|
|
1407
|
+
const index = this._pseudo.selection;
|
|
1408
|
+
if (!index.size || !index.reaches(el)) return parent;
|
|
1409
|
+
const candidates: Candidate[] = [];
|
|
1410
|
+
this._matchInto(index, el, candidates);
|
|
1411
|
+
let color = false;
|
|
1412
|
+
let background = false;
|
|
1413
|
+
for (const candidate of candidates) {
|
|
1414
|
+
for (const d of candidate.declarations) {
|
|
1415
|
+
if (d.prop === 'color') color = true;
|
|
1416
|
+
else if (d.prop === 'background' || d.prop === 'background-color') {
|
|
1417
|
+
background = true;
|
|
1418
|
+
}
|
|
1419
|
+
}
|
|
1420
|
+
}
|
|
1421
|
+
if (!color && !background) return parent;
|
|
1422
|
+
candidates.sort(byCascade);
|
|
1423
|
+
const own = this._computeStyle(el, style, false, candidates);
|
|
1424
|
+
return {
|
|
1425
|
+
color: color ? own.color : (parent?.color ?? null),
|
|
1426
|
+
background: background
|
|
1427
|
+
? own.backgroundColor
|
|
1428
|
+
: (parent?.background ?? null),
|
|
1429
|
+
};
|
|
1430
|
+
}
|
|
1431
|
+
|
|
1090
1432
|
/**
|
|
1091
1433
|
* An element's `::first-line` style, inheriting from its own, or null
|
|
1092
1434
|
* when no rule reaches it (CSS 2.1 5.12.1).
|
|
@@ -1170,6 +1512,9 @@ export class Cascade {
|
|
|
1170
1512
|
parentStyle: ComputedStyle,
|
|
1171
1513
|
inFlexContainer: boolean,
|
|
1172
1514
|
candidates: Candidate[],
|
|
1515
|
+
/** Whether the style is the element's own, and not that of one of its
|
|
1516
|
+
* pseudo-elements. */
|
|
1517
|
+
own = false,
|
|
1173
1518
|
): ComputedStyle {
|
|
1174
1519
|
const style = inherit(parentStyle, this.initial);
|
|
1175
1520
|
// custom properties first, in cascade order, so every `var()` in the
|
|
@@ -1180,10 +1525,16 @@ export class Cascade {
|
|
|
1180
1525
|
// size, so a `font-size: 1.2em` in the cascade resolves against the
|
|
1181
1526
|
// right em, and again after the font size is settled so every *other*
|
|
1182
1527
|
// em-relative length in the same rule resolves against this element's.
|
|
1528
|
+
const root = isRootElement(el);
|
|
1529
|
+
const rootStyle = root ? this.initial : (this._root ?? this.initial);
|
|
1530
|
+
// whose declaration is being applied: the UA sheet's families are the
|
|
1531
|
+
// host's own faces, and its lists end where they end
|
|
1532
|
+
let authored = true;
|
|
1183
1533
|
const ctxParent: UnitContext = {
|
|
1184
1534
|
em: parentStyle.fontSize,
|
|
1185
|
-
rem:
|
|
1535
|
+
rem: rootStyle.fontSize,
|
|
1186
1536
|
initial: this.initial,
|
|
1537
|
+
fallbackFamily: () => (authored ? this.initial.fontFamily : null),
|
|
1187
1538
|
vw: this.viewportWidth,
|
|
1188
1539
|
vh: this.viewportHeight,
|
|
1189
1540
|
scale: this.scale,
|
|
@@ -1191,7 +1542,7 @@ export class Cascade {
|
|
|
1191
1542
|
ch: () => this._chOf(parentStyle),
|
|
1192
1543
|
families: this._mapFamilies,
|
|
1193
1544
|
lh: () => this._lineHeightOf(parentStyle),
|
|
1194
|
-
rlh: () => this._lineHeightOf(
|
|
1545
|
+
rlh: () => this._lineHeightOf(rootStyle),
|
|
1195
1546
|
};
|
|
1196
1547
|
// The family, the weight and the slant go with the size: together they
|
|
1197
1548
|
// pick the face an `ex`, a `ch` or an `lh` in any declaration is
|
|
@@ -1202,6 +1553,7 @@ export class Cascade {
|
|
|
1202
1553
|
let sized = false;
|
|
1203
1554
|
let keyword = false;
|
|
1204
1555
|
for (const c of candidates) {
|
|
1556
|
+
authored = c.origin !== Origin.UserAgent;
|
|
1205
1557
|
for (const d of pick(c)) {
|
|
1206
1558
|
if (
|
|
1207
1559
|
d.prop === 'font-family' ||
|
|
@@ -1249,9 +1601,12 @@ export class Cascade {
|
|
|
1249
1601
|
fontStyle: style.fontStyle,
|
|
1250
1602
|
};
|
|
1251
1603
|
let line: LineSource | null = null;
|
|
1604
|
+
// the root's other declarations measure a `rem` from its own size
|
|
1605
|
+
if (root) this._root = style;
|
|
1252
1606
|
const ctx: UnitContext = {
|
|
1253
1607
|
...ctxParent,
|
|
1254
1608
|
em: style.fontSize,
|
|
1609
|
+
rem: root ? style.fontSize : ctxParent.rem,
|
|
1255
1610
|
ex: () => this._exOf(face),
|
|
1256
1611
|
ch: () => this._chOf(face),
|
|
1257
1612
|
lh: () => this._lineHeightOf(line ?? style),
|
|
@@ -1262,6 +1617,7 @@ export class Cascade {
|
|
|
1262
1617
|
// them. A `font` sets the size as well, which the first pass settled.
|
|
1263
1618
|
if (this._lh) {
|
|
1264
1619
|
for (const c of candidates) {
|
|
1620
|
+
authored = c.origin !== Origin.UserAgent;
|
|
1265
1621
|
for (const d of pick(c)) {
|
|
1266
1622
|
if (d.prop === 'line-height' || d.prop === 'font') {
|
|
1267
1623
|
this._apply(style, parentStyle, d, ctx);
|
|
@@ -1277,6 +1633,7 @@ export class Cascade {
|
|
|
1277
1633
|
}
|
|
1278
1634
|
const settled = style.fontSize;
|
|
1279
1635
|
for (const c of candidates) {
|
|
1636
|
+
authored = c.origin !== Origin.UserAgent;
|
|
1280
1637
|
for (const d of pick(c)) {
|
|
1281
1638
|
if (d.prop === 'font-size' || d.custom) continue;
|
|
1282
1639
|
this._apply(style, parentStyle, d, ctx);
|
|
@@ -1304,6 +1661,10 @@ export class Cascade {
|
|
|
1304
1661
|
settleOverflow(style);
|
|
1305
1662
|
settleContentVisibility(style);
|
|
1306
1663
|
blockify(style, inFlexContainer);
|
|
1664
|
+
// a button is laid out as one, whatever `display` it was given
|
|
1665
|
+
if (own && el.name.length === 6 && tagOf(el) === 'button') {
|
|
1666
|
+
settleButton(style);
|
|
1667
|
+
}
|
|
1307
1668
|
decorate(style);
|
|
1308
1669
|
// which faces of the document's own families this family, weight and
|
|
1309
1670
|
// slant ask for — known only now, with all three computed
|
|
@@ -1382,22 +1743,68 @@ export class Cascade {
|
|
|
1382
1743
|
return { style: asRoot(this.styleFor(synthetic, parent, false)), html };
|
|
1383
1744
|
}
|
|
1384
1745
|
|
|
1746
|
+
/**
|
|
1747
|
+
* Whether the pointer's move from `was` to where it is changed which
|
|
1748
|
+
* rules match `el`, or its `::before`, `::after` or another of its
|
|
1749
|
+
* pseudo-elements: the rules that test `:hover` asked under both, and
|
|
1750
|
+
* nothing else, since no other rule's answer is the pointer's. An element
|
|
1751
|
+
* they answer the same for, under a parent whose style is what it was, has
|
|
1752
|
+
* the style it had — which is most of what a move reaches: the rows of a
|
|
1753
|
+
* hovered table body, what is in a hovered card.
|
|
1754
|
+
*/
|
|
1755
|
+
pointerChanged(el: Element, was: PointerState): boolean {
|
|
1756
|
+
const now = this._pointer;
|
|
1757
|
+
const after = this._pointerAnswers(el);
|
|
1758
|
+
this._pointer = was;
|
|
1759
|
+
const before = this._pointerAnswers(el);
|
|
1760
|
+
this._pointer = now;
|
|
1761
|
+
return before !== after;
|
|
1762
|
+
}
|
|
1763
|
+
|
|
1764
|
+
/** The ids of the rules testing the pointer that match `el` as things
|
|
1765
|
+
* stand, the element's and each pseudo-element's. */
|
|
1766
|
+
private _pointerAnswers(el: Element): string {
|
|
1767
|
+
let answers = '';
|
|
1768
|
+
const ask = (index: RuleIndex, name: string): void => {
|
|
1769
|
+
const pointer = index.pointer;
|
|
1770
|
+
if (!pointer || !pointer.reaches(el)) return;
|
|
1771
|
+
const matched: number[] = [];
|
|
1772
|
+
this._matchInto(pointer, el, null, matched);
|
|
1773
|
+
if (matched.length) answers += `${name}${matched.join(',')};`;
|
|
1774
|
+
};
|
|
1775
|
+
ask(this._index, '');
|
|
1776
|
+
for (const which of POINTER_PSEUDO_ELEMENTS)
|
|
1777
|
+
ask(this._pseudo[which], which);
|
|
1778
|
+
return answers;
|
|
1779
|
+
}
|
|
1780
|
+
|
|
1385
1781
|
/** The rules of `index` that match `el`, pushed onto `out` as candidates,
|
|
1386
1782
|
* and their ids onto `matched` in the order they were tried. */
|
|
1387
1783
|
private _matchInto(
|
|
1388
1784
|
index: RuleIndex,
|
|
1389
1785
|
el: Element,
|
|
1390
|
-
out: Candidate[],
|
|
1786
|
+
out: Candidate[] | null,
|
|
1391
1787
|
matched?: number[],
|
|
1392
1788
|
): void {
|
|
1393
1789
|
// A media query's width is CSS pixels; the viewport is kept in device.
|
|
1394
1790
|
const width = this.viewportWidth / this.scale;
|
|
1791
|
+
const height = this.viewportHeight / this.scale;
|
|
1395
1792
|
|
|
1396
1793
|
const consider = (bucket: IndexedRule[] | undefined): void => {
|
|
1397
1794
|
if (!bucket) return;
|
|
1398
1795
|
for (const indexed of bucket) {
|
|
1399
1796
|
const rule = indexed.rule;
|
|
1400
|
-
if (
|
|
1797
|
+
if (
|
|
1798
|
+
!mediaMatches(
|
|
1799
|
+
rule.media,
|
|
1800
|
+
width,
|
|
1801
|
+
this.look.colorScheme,
|
|
1802
|
+
height,
|
|
1803
|
+
this.scale,
|
|
1804
|
+
)
|
|
1805
|
+
) {
|
|
1806
|
+
continue;
|
|
1807
|
+
}
|
|
1401
1808
|
if (!indexed.compiled) {
|
|
1402
1809
|
indexed.compiled = true;
|
|
1403
1810
|
try {
|
|
@@ -1411,6 +1818,7 @@ export class Cascade {
|
|
|
1411
1818
|
}
|
|
1412
1819
|
if (!indexed.match || !indexed.match(el)) continue;
|
|
1413
1820
|
matched?.push(indexed.id);
|
|
1821
|
+
if (out === null) continue;
|
|
1414
1822
|
const origin = rule.order < 0 ? Origin.UserAgent : Origin.Author;
|
|
1415
1823
|
pushRule(out, rule, origin);
|
|
1416
1824
|
}
|
|
@@ -1478,10 +1886,59 @@ export class Cascade {
|
|
|
1478
1886
|
}
|
|
1479
1887
|
|
|
1480
1888
|
out.sort(byCascade);
|
|
1889
|
+
dropPaletteChrome(out);
|
|
1481
1890
|
return out;
|
|
1482
1891
|
}
|
|
1483
1892
|
}
|
|
1484
1893
|
|
|
1894
|
+
/**
|
|
1895
|
+
* Takes the palette's control chrome (`PALETTE_CHROME`) out of an element's
|
|
1896
|
+
* candidates where the page styles its background or border, so what shows
|
|
1897
|
+
* through is the web's UA values the page was written against. The rule is
|
|
1898
|
+
* Blink's (`LayoutTheme::IsControlStyled`): a background or border longhand
|
|
1899
|
+
* set by the page, radius included, drops the control's native appearance;
|
|
1900
|
+
* so does `appearance: none`. `out` is in cascade order, so the UA's
|
|
1901
|
+
* candidates are the ones before the first of any other origin.
|
|
1902
|
+
*/
|
|
1903
|
+
function dropPaletteChrome(out: Candidate[]): void {
|
|
1904
|
+
let chrome = false;
|
|
1905
|
+
let i = 0;
|
|
1906
|
+
for (; i < out.length && out[i].origin === Origin.UserAgent; i += 1) {
|
|
1907
|
+
if (PALETTE_CHROME.has(out[i].declarations)) chrome = true;
|
|
1908
|
+
}
|
|
1909
|
+
if (!chrome) return;
|
|
1910
|
+
let styled = false;
|
|
1911
|
+
for (let j = i; j < out.length && !styled; j += 1) {
|
|
1912
|
+
styled = pick(out[j]).some(stylesChrome);
|
|
1913
|
+
}
|
|
1914
|
+
if (!styled) return;
|
|
1915
|
+
let kept = 0;
|
|
1916
|
+
for (const c of out) {
|
|
1917
|
+
if (c.origin === Origin.UserAgent && PALETTE_CHROME.has(c.declarations)) {
|
|
1918
|
+
continue;
|
|
1919
|
+
}
|
|
1920
|
+
out[kept++] = c;
|
|
1921
|
+
}
|
|
1922
|
+
out.length = kept;
|
|
1923
|
+
}
|
|
1924
|
+
|
|
1925
|
+
/** Whether a declaration styles what a control's native look draws: Blink's
|
|
1926
|
+
* `is_background` and `is_border` properties and their shorthands, and an
|
|
1927
|
+
* `appearance` of `none`. */
|
|
1928
|
+
function stylesChrome(d: Declaration): boolean {
|
|
1929
|
+
const prop = d.prop;
|
|
1930
|
+
if (prop.startsWith('background')) {
|
|
1931
|
+
return prop !== 'background-repeat' && prop !== 'background-blend-mode';
|
|
1932
|
+
}
|
|
1933
|
+
if (prop.startsWith('border')) {
|
|
1934
|
+
return prop !== 'border-collapse' && prop !== 'border-spacing';
|
|
1935
|
+
}
|
|
1936
|
+
return (
|
|
1937
|
+
(prop === 'appearance' || prop === '-webkit-appearance') &&
|
|
1938
|
+
d.value.trim().toLowerCase() === 'none'
|
|
1939
|
+
);
|
|
1940
|
+
}
|
|
1941
|
+
|
|
1485
1942
|
/** An implied element's style as the root box's: its look, not its role —
|
|
1486
1943
|
* the root is still a block, in flow, and the initial containing block. */
|
|
1487
1944
|
function asRoot(style: ComputedStyle): ComputedStyle {
|
|
@@ -1807,7 +2264,12 @@ function presentationHints(el: Element): Declaration[] {
|
|
|
1807
2264
|
// `width`/`height` are lengths on the replaced and table elements and mean
|
|
1808
2265
|
// nothing anywhere else, which is what stops a `<input width>` from
|
|
1809
2266
|
// becoming a CSS width the widget then disagrees with.
|
|
1810
|
-
if (
|
|
2267
|
+
if (
|
|
2268
|
+
SIZED.has(tag) ||
|
|
2269
|
+
// an image button's are its image's, as an `<img>`'s are (HTML 15.4.3)
|
|
2270
|
+
(tag === 'input' &&
|
|
2271
|
+
(attr(el, 'type') ?? '').trim().toLowerCase() === 'image')
|
|
2272
|
+
) {
|
|
1811
2273
|
const width = attr(el, 'width');
|
|
1812
2274
|
if (width) push('width', lengthAttr(width));
|
|
1813
2275
|
const height = attr(el, 'height');
|
|
@@ -1830,6 +2292,12 @@ function presentationHints(el: Element): Declaration[] {
|
|
|
1830
2292
|
const height = attr(el, 'height');
|
|
1831
2293
|
const h = height ? svgSizeHint(height) : null;
|
|
1832
2294
|
if (h) push('height', h);
|
|
2295
|
+
// and its paint is its `fill` and `stroke` properties' (13.2), which a
|
|
2296
|
+
// rule of the document's then sets over the attribute
|
|
2297
|
+
const fill = attr(el, 'fill');
|
|
2298
|
+
if (fill) push('fill', fill);
|
|
2299
|
+
const stroke = attr(el, 'stroke');
|
|
2300
|
+
if (stroke) push('stroke', stroke);
|
|
1833
2301
|
}
|
|
1834
2302
|
|
|
1835
2303
|
if (tag === 'table') {
|
|
@@ -2047,3 +2515,9 @@ const ABSOLUTE_SIZES = new Set([
|
|
|
2047
2515
|
'xxx-large',
|
|
2048
2516
|
'initial',
|
|
2049
2517
|
]);
|
|
2518
|
+
|
|
2519
|
+
/** Whether an element is the document's root: its parent the document, or
|
|
2520
|
+
* none, as the `<html>` the cascade supplies where the markup has none. */
|
|
2521
|
+
function isRootElement(el: Element): boolean {
|
|
2522
|
+
return !el.parent || el.parent.type === 'root';
|
|
2523
|
+
}
|