@react-x11/components 0.11.0 → 0.13.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.
Files changed (126) hide show
  1. package/dist/flow/node.d.ts +14 -6
  2. package/dist/flow/node.d.ts.map +1 -1
  3. package/dist/flow/node.js +82 -64
  4. package/dist/flow/node.js.map +1 -1
  5. package/dist/html/controls.d.ts +38 -10
  6. package/dist/html/controls.d.ts.map +1 -1
  7. package/dist/html/controls.js +43 -58
  8. package/dist/html/controls.js.map +1 -1
  9. package/dist/html/css/cascade.d.ts +134 -20
  10. package/dist/html/css/cascade.d.ts.map +1 -1
  11. package/dist/html/css/cascade.js +538 -128
  12. package/dist/html/css/cascade.js.map +1 -1
  13. package/dist/html/css/color.d.ts +19 -0
  14. package/dist/html/css/color.d.ts.map +1 -1
  15. package/dist/html/css/color.js +86 -0
  16. package/dist/html/css/color.js.map +1 -1
  17. package/dist/html/css/parse.d.ts +40 -2
  18. package/dist/html/css/parse.d.ts.map +1 -1
  19. package/dist/html/css/parse.js +391 -13
  20. package/dist/html/css/parse.js.map +1 -1
  21. package/dist/html/css/style.d.ts +52 -1
  22. package/dist/html/css/style.d.ts.map +1 -1
  23. package/dist/html/css/style.js +328 -11
  24. package/dist/html/css/style.js.map +1 -1
  25. package/dist/html/css/ua.d.ts +11 -1
  26. package/dist/html/css/ua.d.ts.map +1 -1
  27. package/dist/html/css/ua.js +73 -7
  28. package/dist/html/css/ua.js.map +1 -1
  29. package/dist/html/css/values.d.ts +10 -1
  30. package/dist/html/css/values.d.ts.map +1 -1
  31. package/dist/html/css/values.js +27 -2
  32. package/dist/html/css/values.js.map +1 -1
  33. package/dist/html/css/vars.d.ts +5 -0
  34. package/dist/html/css/vars.d.ts.map +1 -1
  35. package/dist/html/css/vars.js +2 -2
  36. package/dist/html/css/vars.js.map +1 -1
  37. package/dist/html/dom.d.ts.map +1 -1
  38. package/dist/html/dom.js +38 -3
  39. package/dist/html/dom.js.map +1 -1
  40. package/dist/html/fonts.d.ts +25 -1
  41. package/dist/html/fonts.d.ts.map +1 -1
  42. package/dist/html/fonts.js +99 -6
  43. package/dist/html/fonts.js.map +1 -1
  44. package/dist/html/form.d.ts +200 -0
  45. package/dist/html/form.d.ts.map +1 -0
  46. package/dist/html/form.js +801 -0
  47. package/dist/html/form.js.map +1 -0
  48. package/dist/html/index.d.ts +12 -0
  49. package/dist/html/index.d.ts.map +1 -1
  50. package/dist/html/index.js +30 -264
  51. package/dist/html/index.js.map +1 -1
  52. package/dist/html/layout/block.d.ts +11 -2
  53. package/dist/html/layout/block.d.ts.map +1 -1
  54. package/dist/html/layout/block.js +186 -19
  55. package/dist/html/layout/block.js.map +1 -1
  56. package/dist/html/layout/boxes.d.ts +65 -7
  57. package/dist/html/layout/boxes.d.ts.map +1 -1
  58. package/dist/html/layout/boxes.js +87 -22
  59. package/dist/html/layout/boxes.js.map +1 -1
  60. package/dist/html/layout/cache.d.ts +2 -2
  61. package/dist/html/layout/cache.d.ts.map +1 -1
  62. package/dist/html/layout/cache.js +8 -3
  63. package/dist/html/layout/cache.js.map +1 -1
  64. package/dist/html/layout/flex.js +4 -1
  65. package/dist/html/layout/flex.js.map +1 -1
  66. package/dist/html/layout/floats.d.ts +4 -0
  67. package/dist/html/layout/floats.d.ts.map +1 -1
  68. package/dist/html/layout/floats.js +26 -0
  69. package/dist/html/layout/floats.js.map +1 -1
  70. package/dist/html/layout/inline.d.ts +26 -2
  71. package/dist/html/layout/inline.d.ts.map +1 -1
  72. package/dist/html/layout/inline.js +1232 -115
  73. package/dist/html/layout/inline.js.map +1 -1
  74. package/dist/html/node.d.ts +120 -15
  75. package/dist/html/node.d.ts.map +1 -1
  76. package/dist/html/node.js +993 -174
  77. package/dist/html/node.js.map +1 -1
  78. package/dist/html/paint.d.ts +75 -2
  79. package/dist/html/paint.d.ts.map +1 -1
  80. package/dist/html/paint.js +836 -200
  81. package/dist/html/paint.js.map +1 -1
  82. package/dist/html/surfaces.d.ts +7 -2
  83. package/dist/html/surfaces.d.ts.map +1 -1
  84. package/dist/html/surfaces.js +16 -0
  85. package/dist/html/surfaces.js.map +1 -1
  86. package/dist/html/widgets.d.ts +29 -0
  87. package/dist/html/widgets.d.ts.map +1 -0
  88. package/dist/html/widgets.js +620 -0
  89. package/dist/html/widgets.js.map +1 -0
  90. package/dist/index.d.ts +1 -1
  91. package/dist/index.d.ts.map +1 -1
  92. package/dist/index.js.map +1 -1
  93. package/dist/internal/window.d.ts +22 -0
  94. package/dist/internal/window.d.ts.map +1 -1
  95. package/dist/internal/window.js +35 -8
  96. package/dist/internal/window.js.map +1 -1
  97. package/dist/richtext/node.d.ts +7 -0
  98. package/dist/richtext/node.d.ts.map +1 -1
  99. package/dist/richtext/node.js.map +1 -1
  100. package/package.json +6 -4
  101. package/src/flow/node.ts +80 -64
  102. package/src/html/controls.ts +79 -58
  103. package/src/html/css/cascade.ts +610 -146
  104. package/src/html/css/color.ts +87 -0
  105. package/src/html/css/parse.ts +431 -13
  106. package/src/html/css/style.ts +367 -11
  107. package/src/html/css/ua.ts +76 -8
  108. package/src/html/css/values.ts +31 -3
  109. package/src/html/css/vars.ts +2 -2
  110. package/src/html/dom.ts +41 -3
  111. package/src/html/fonts.ts +116 -6
  112. package/src/html/form.ts +962 -0
  113. package/src/html/index.ts +49 -300
  114. package/src/html/layout/block.ts +231 -16
  115. package/src/html/layout/boxes.ts +133 -32
  116. package/src/html/layout/cache.ts +8 -3
  117. package/src/html/layout/flex.ts +4 -1
  118. package/src/html/layout/floats.ts +24 -0
  119. package/src/html/layout/inline.ts +1445 -108
  120. package/src/html/node.ts +1010 -166
  121. package/src/html/paint.ts +980 -270
  122. package/src/html/surfaces.ts +21 -1
  123. package/src/html/widgets.ts +735 -0
  124. package/src/index.ts +1 -0
  125. package/src/internal/window.ts +36 -8
  126. package/src/richtext/node.ts +7 -0
@@ -24,6 +24,8 @@ import { asciiLower, escapeEnd, mediaMatches, readIdent, startsIdent, } from './
24
24
  import { applyDeclaration, blockify, settleContentVisibility, settleOverflow, settleClamp, copyStyle, decorate, inherit, initialOne, initialStyle, isInherited, } from './style.js';
25
25
  import { parseDeclarations } from './parse.js';
26
26
  import { CounterStyles, counterStyleRule } from './counter-styles.js';
27
+ import { PALETTE_CHROME } from './ua.js';
28
+ import { viewportUnit } from './values.js';
27
29
  import { customProperties, substituteIn } from './vars.js';
28
30
  /** Where a declaration came from. Higher wins before specificity is asked. */
29
31
  var Origin;
@@ -35,6 +37,22 @@ var Origin;
35
37
  Origin[Origin["AuthorImportant"] = 4] = "AuthorImportant";
36
38
  Origin[Origin["InlineImportant"] = 5] = "InlineImportant";
37
39
  })(Origin || (Origin = {}));
40
+ export const HOVER_UNTOUCHED = 0;
41
+ export const HOVER_TOUCHED = 1;
42
+ export const HOVER_FOLLOWED = 2;
43
+ /** Which pseudo-elements' rules test the pointer (`Cascade.hoverPseudo`). */
44
+ export const HOVER_PSEUDO_NONE = 0;
45
+ export const HOVER_PSEUDO_GENERATED = 1;
46
+ export const HOVER_PSEUDO_OTHER = 2;
47
+ /** The pseudo-elements a pointer move can restyle: `::selection`'s style
48
+ * is worked out as a selection is painted. */
49
+ const POINTER_PSEUDO_ELEMENTS = [
50
+ 'before',
51
+ 'after',
52
+ 'first-letter',
53
+ 'first-line',
54
+ 'marker',
55
+ ];
38
56
  /**
39
57
  * What makes a selector's match depend on more than the element's own tag and
40
58
  * attributes and its ancestors' — its siblings, where it sits among them,
@@ -61,6 +79,11 @@ class RuleIndex {
61
79
  /** Whether any rule in here is pointer-sensitive, so the renderer knows
62
80
  * whether a pointer move can change the cascade at all. */
63
81
  hoverSensitive = false;
82
+ /** The rules that test `:hover`, in buckets of their own: the ones whose
83
+ * answers a pointer move can change, which is all that is asked to know
84
+ * whether an element's did (`Cascade.pointerChanged`). Null until there
85
+ * is one. */
86
+ pointer = null;
64
87
  /** The buckets holding a rule an element cannot share its style under
65
88
  * (`UNSHAREABLE`): the ids, classes and tags whose elements compute
66
89
  * their own, and whether the universal bucket makes every element do so. */
@@ -119,20 +142,27 @@ class RuleIndex {
119
142
  else
120
143
  this.ownStyleEverywhere = true;
121
144
  }
122
- const bucket = key.kind === 'id'
145
+ this._bucket(key).push(indexed);
146
+ if (HOVER.test(rule.selector)) {
147
+ this.pointer ??= new RuleIndex();
148
+ this.pointer.size += 1;
149
+ this.pointer._bucket(key).push(indexed);
150
+ }
151
+ }
152
+ _bucket(key) {
153
+ return key.kind === 'id'
123
154
  ? mapBucket(this.byId, key.name)
124
155
  : key.kind === 'class'
125
156
  ? mapBucket(this.byClass, key.name)
126
157
  : key.kind === 'tag'
127
158
  ? mapBucket(this.byTag, key.name)
128
159
  : this.universal;
129
- bucket.push(indexed);
130
160
  }
131
161
  }
132
162
  /** A selector's trailing `::before`, `::after`, `::first-letter`,
133
- * `::first-line` or `::marker`, or CSS 2's single-colon spelling of any of
134
- * its four. */
135
- const PSEUDO_ELEMENT = /::?(before|after|first-letter|first-line)$|::marker$/i;
163
+ * `::first-line`, `::marker` or `::selection`, or CSS 2's single-colon
164
+ * spelling of any of its four. */
165
+ const PSEUDO_ELEMENT = /::?(before|after|first-letter|first-line)$|::(marker|selection)$/i;
136
166
  /**
137
167
  * A rule for a pseudo-element, as the pseudo-element it styles and a rule
138
168
  * for the element it hangs off, which is what gets matched. The specificity
@@ -152,22 +182,35 @@ function splitPseudoElement(rule) {
152
182
  ? `${trimmed} *`
153
183
  : trimmed;
154
184
  return {
155
- which: (m[1] ?? 'marker').toLowerCase(),
185
+ which: (m[1] ?? m[2]).toLowerCase(),
156
186
  rule: { ...rule, selector },
157
187
  };
158
188
  }
159
189
  /**
160
190
  * The compounds of a selector that test `:hover`, each without it (and
161
191
  * without an `:active` beside it), and whether one is followed by a sibling
162
- * combinator, which reaches the element's later siblings. `nested` where a
163
- * `:hover` sits inside a functional pseudo-class — `:not(:hover)`,
164
- * `:is(:hover)` — and reaches elements no compound names.
192
+ * combinator, which reaches the element's later siblings.
165
193
  *
166
- * A `:hover` in a compound's `:has()` is the one exception, since what it
167
- * reaches is known: the compound, without its `:has()`, is an ancestor of
194
+ * A `:hover` inside `:is()`, `:where()` or `:not()` is found where it is:
195
+ * each takes a selector list and matches the element it is written on, so
196
+ * a list entry of one compound tests that element — `a:not(:hover)` is a
197
+ * compound of `a`'s that tests the pointer — and an entry with combinators
198
+ * in it names an ancestor or an earlier sibling, as it would outside.
199
+ * Tailwind 4 writes every `group-hover:` that way, `:is(:where(.group):hover
200
+ * *)`, and a page of them built its document again on every move. Whatever
201
+ * the nesting, the element whose hover changed is one a compound found here
202
+ * matches, and what changes with it is itself, what is in it, and what
203
+ * follows it where a sibling combinator is in play: a selector reads an
204
+ * element, its ancestors and the siblings before each of them, and nothing
205
+ * else. `nested` is left for a function this cannot read a selector list
206
+ * in — `:nth-child(… of :hover)`, `:host()` — which reaches elements no
207
+ * compound names.
208
+ *
209
+ * A `:hover` in a compound's `:has()` is the other way about, since what
210
+ * it tests is below: the compound, without its `:has()`, is an ancestor of
168
211
  * the element the pointer is over — or an earlier sibling of it or of an
169
212
  * ancestor, where the argument starts at a sibling (Selectors 4, 4.5). So
170
- * it is given as an anchor (`has`) rather than making the rule `nested`.
213
+ * it is given as an anchor (`has`).
171
214
  *
172
215
  * `:active` is not the pointer's here: nothing sets it (`setPointer` is
173
216
  * handed none), so a selector that tests only it never changes as the
@@ -180,8 +223,17 @@ export function pointerCompounds(selector) {
180
223
  siblings: false,
181
224
  nested: false,
182
225
  has: [],
226
+ followed: [],
183
227
  };
184
- // the compounds at the top level, each with the combinator after it
228
+ scanComplex(selector, out);
229
+ return out;
230
+ }
231
+ /** The functions that take a selector list and match the element they are
232
+ * written on (Selectors 4, 4.2 to 4.4), under the names css-select and
233
+ * older sheets know them by. */
234
+ const MATCHES_ANY = /^(?:is|where|not|matches|any|-webkit-any|-moz-any)$/i;
235
+ /** The compounds of a complex selector, each with the combinator after it. */
236
+ function compoundsOf(selector) {
185
237
  const parts = [];
186
238
  let start = 0;
187
239
  let depth = 0;
@@ -214,94 +266,203 @@ export function pointerCompounds(selector) {
214
266
  }
215
267
  }
216
268
  split(selector.length, '');
217
- for (const { text, next } of parts) {
218
- let bare = '';
219
- let pointer = false;
220
- depth = 0;
221
- quote = '';
222
- /** Where a top-level `:has(` starts in `bare`, and its argument in
223
- * `text`, while one is open. */
224
- let hasAt = -1;
225
- let argAt = -1;
226
- let hasPointer = false;
227
- let anchored = false;
228
- const args = [];
229
- for (let i = 0; i < text.length; i += 1) {
230
- const c = text[i];
231
- if (quote) {
232
- if (c === quote && text[i - 1] !== '\\')
233
- quote = '';
234
- bare += c;
235
- continue;
269
+ return parts;
270
+ }
271
+ /** A selector list's entries: split at its top-level commas. */
272
+ function selectorsOf(list) {
273
+ const entries = [];
274
+ let start = 0;
275
+ let depth = 0;
276
+ let quote = '';
277
+ for (let i = 0; i < list.length; i += 1) {
278
+ const c = list[i];
279
+ if (quote) {
280
+ if (c === quote && list[i - 1] !== '\\')
281
+ quote = '';
282
+ }
283
+ else if (c === '\\')
284
+ i = escapeEnd(list, i) - 1;
285
+ else if (c === '"' || c === "'")
286
+ quote = c;
287
+ else if (c === '(' || c === '[')
288
+ depth += 1;
289
+ else if (c === ')' || c === ']')
290
+ depth = Math.max(0, depth - 1);
291
+ else if (c === ',' && depth === 0) {
292
+ entries.push(list.slice(start, i));
293
+ start = i + 1;
294
+ }
295
+ }
296
+ entries.push(list.slice(start));
297
+ return entries;
298
+ }
299
+ /** Where the parenthesis opened at `open` closes, or the text's end. */
300
+ function closeOf(text, open) {
301
+ let depth = 0;
302
+ let quote = '';
303
+ for (let i = open; i < text.length; i += 1) {
304
+ const c = text[i];
305
+ if (quote) {
306
+ if (c === quote && text[i - 1] !== '\\')
307
+ quote = '';
308
+ }
309
+ else if (c === '\\')
310
+ i = escapeEnd(text, i) - 1;
311
+ else if (c === '"' || c === "'")
312
+ quote = c;
313
+ else if (c === '(' || c === '[')
314
+ depth += 1;
315
+ else if (c === ')' || c === ']') {
316
+ depth -= 1;
317
+ if (depth === 0)
318
+ return i;
319
+ }
320
+ }
321
+ return text.length;
322
+ }
323
+ /** `pointerCompounds` for one complex selector, into `out`. */
324
+ function scanComplex(selector, out) {
325
+ for (const { text, next } of compoundsOf(selector)) {
326
+ const compoundsFrom = out.compounds.length;
327
+ const hasFrom = out.has.length;
328
+ const found = scanCompound(text, out);
329
+ const sibling = next === '+' || next === '~';
330
+ if (found.args.length) {
331
+ out.has.push({
332
+ anchor: found.bare.trim() || '*',
333
+ siblings: found.args.some((arg) => selectorsOf(arg).some((one) => /^\s*[+~]/.test(one))),
334
+ });
335
+ }
336
+ if (found.pointer)
337
+ out.compounds.push(found.bare.trim() || '*');
338
+ // what follows the element this compound matches follows one whose
339
+ // hover, or whose answer to a `:has()`, changed
340
+ // — the one this compound matches, or one a selector nested in it
341
+ // named, which is more than it reaches and never less
342
+ if (sibling && (found.pointer || found.below || found.args.length)) {
343
+ out.siblings = true;
344
+ for (let i = compoundsFrom; i < out.compounds.length; i += 1) {
345
+ out.followed.push(out.compounds[i]);
236
346
  }
237
- if (c === '\\') {
238
- const end = escapeEnd(text, i);
239
- bare += text.slice(i, end);
240
- i = end - 1;
347
+ for (let i = hasFrom; i < out.has.length; i += 1) {
348
+ out.followed.push(out.has[i].anchor);
349
+ }
350
+ }
351
+ }
352
+ }
353
+ /**
354
+ * What one compound says of the pointer: the compound without what tests
355
+ * it (`bare`), whether it tests the element's own hover (`pointer`), the
356
+ * arguments of each `:has()` of the element's that tests it below (`args`),
357
+ * and whether a selector nested in it named another element's (`below`),
358
+ * whose compounds went into `out`.
359
+ */
360
+ function scanCompound(text, out) {
361
+ let bare = '';
362
+ let pointer = false;
363
+ let below = false;
364
+ const args = [];
365
+ let bracket = 0;
366
+ let quote = '';
367
+ for (let i = 0; i < text.length; i += 1) {
368
+ const c = text[i];
369
+ if (quote) {
370
+ if (c === quote && text[i - 1] !== '\\')
371
+ quote = '';
372
+ bare += c;
373
+ continue;
374
+ }
375
+ if (c === '\\') {
376
+ const end = escapeEnd(text, i);
377
+ bare += text.slice(i, end);
378
+ i = end - 1;
379
+ continue;
380
+ }
381
+ if (c === '"' || c === "'")
382
+ quote = c;
383
+ else if (c === '[')
384
+ bracket += 1;
385
+ else if (c === ']')
386
+ bracket = Math.max(0, bracket - 1);
387
+ else if (c === ':' && bracket === 0 && text[i + 1] !== ':') {
388
+ const m = POINTER_PSEUDO_AT.exec(text.slice(i));
389
+ if (m) {
390
+ if (m[0].length === 6)
391
+ pointer = true;
392
+ i += m[0].length - 1;
241
393
  continue;
242
394
  }
243
- if (c === '"' || c === "'")
244
- quote = c;
245
- else if (c === '(' || c === '[') {
246
- if (depth === 0 && c === '(' && /:has$/i.test(bare)) {
247
- hasAt = bare.length - 4;
248
- argAt = i + 1;
249
- hasPointer = false;
395
+ const fn = FUNCTION_AT.exec(text.slice(i));
396
+ if (fn) {
397
+ const open = i + fn[0].length - 1;
398
+ const close = closeOf(text, open);
399
+ const arg = text.slice(open + 1, close);
400
+ const whole = text.slice(i, close + 1);
401
+ i = close;
402
+ if (!HOVER.test(arg)) {
403
+ bare += whole;
404
+ continue;
250
405
  }
251
- depth += 1;
252
- }
253
- else if (c === ')' || c === ']') {
254
- depth = Math.max(0, depth - 1);
255
- if (depth === 0 && hasAt >= 0 && c === ')') {
406
+ const name = fn[1].toLowerCase();
407
+ if (name === 'has') {
256
408
  // the compound is kept without it: its argument is what the
257
- // pointer changes
258
- if (hasPointer) {
259
- anchored = true;
260
- args.push(text.slice(argAt, i));
261
- bare = bare.slice(0, hasAt);
262
- }
263
- else
264
- bare += c;
265
- hasAt = -1;
266
- continue;
409
+ // pointer changes, and whatever holds the `:hover` in there, the
410
+ // element it tests is in the anchor's subtree or a later
411
+ // sibling's
412
+ args.push(arg);
267
413
  }
268
- }
269
- else if (c === ':' && text[i + 1] !== ':') {
270
- const m = POINTER_PSEUDO_AT.exec(text.slice(i));
271
- if (m) {
272
- const hover = m[0].length === 6;
273
- if (depth > 0) {
274
- // whatever holds it in the argument, the element it tests is
275
- // in the anchor's subtree or a later sibling's
276
- if (hover && hasAt >= 0)
277
- hasPointer = true;
278
- else if (hover)
279
- out.nested = true;
280
- }
281
- else {
282
- if (hover)
414
+ else if (MATCHES_ANY.test(name)) {
415
+ // Kept as what the entries that test this element's hover ask
416
+ // of it besides — `:where(a:hover)` is a compound of `a`'s, and
417
+ // so is `:not(a:hover)`, which only an `a` can change its answer
418
+ // to. Tailwind's typography writes its links `.prose
419
+ // :where(a:hover):not(:where(.not-prose, .not-prose *))`, and
420
+ // without the `a` that was a compound every element matched.
421
+ const own = [];
422
+ let any = false;
423
+ for (const entry of selectorsOf(arg)) {
424
+ if (!HOVER.test(entry))
425
+ continue;
426
+ const parts = compoundsOf(entry);
427
+ if (parts.length !== 1) {
428
+ scanComplex(entry, out);
429
+ below = true;
430
+ continue;
431
+ }
432
+ // of this element itself
433
+ const one = scanCompound(parts[0].text, out);
434
+ if (one.below)
435
+ below = true;
436
+ if (!one.pointer && !one.args.length)
437
+ continue;
438
+ if (one.pointer)
283
439
  pointer = true;
284
- i += m[0].length - 1;
285
- continue;
440
+ args.push(...one.args);
441
+ const rest = one.bare.trim();
442
+ if (rest && rest !== '*')
443
+ own.push(rest);
444
+ else
445
+ any = true;
286
446
  }
447
+ if (own.length && !any)
448
+ bare += `:is(${own.join(',')})`;
287
449
  }
450
+ else {
451
+ out.nested = true;
452
+ bare += whole;
453
+ }
454
+ continue;
288
455
  }
289
- bare += c;
290
- }
291
- if (anchored) {
292
- out.has.push({
293
- anchor: bare.trim() || '*',
294
- siblings: args.some((arg) => arg.split(',').some((one) => /^\s*[+~]/.test(one))),
295
- });
296
456
  }
297
- if (!pointer)
298
- continue;
299
- out.compounds.push(bare.trim() || '*');
300
- if (next === '+' || next === '~')
301
- out.siblings = true;
457
+ bare += c;
302
458
  }
303
- return out;
459
+ return { bare, pointer, below, args };
304
460
  }
461
+ /** A functional pseudo-class's name and its opening parenthesis, at the
462
+ * start of a string. */
463
+ const FUNCTION_AT = /^:([\w-]+)\(/;
464
+ /** `:hover` anywhere in a selector. */
465
+ const HOVER = /:hover(?![\w-])/i;
305
466
  /** `:hover` or `:active` at the start of a string, and nothing longer. */
306
467
  const POINTER_PSEUDO_AT = /^:(?:hover|active)(?![\w-])/i;
307
468
  /** `:hover` or `:active` anywhere in a selector. */
@@ -455,23 +616,22 @@ export class Cascade {
455
616
  'first-letter': new RuleIndex(),
456
617
  'first-line': new RuleIndex(),
457
618
  marker: new RuleIndex(),
619
+ selection: new RuleIndex(),
458
620
  };
459
621
  _adapter;
460
622
  /** The compounds of the selectors that test the pointer, each without
461
623
  * its `:hover` or `:active` (`pointerCompounds`), and their matchers,
462
624
  * compiled when a pointer move first asks (`hoverTouches`). */
463
- _hoverCompounds = new Set();
625
+ _hoverCompounds = new Map();
464
626
  _hoverMatchers = null;
465
627
  /** Whether a pointer move can be restyled where it happened
466
628
  * (`HtmlViewNode._hoverInPlace`): false where a selector tests the
467
629
  * pointer inside a functional pseudo-class, which reaches elements no
468
630
  * compound names. */
469
631
  hoverLocal = true;
470
- /** Whether a compound that tests the pointer is followed by a sibling
471
- * combinator, so an element's later siblings restyle with it. */
472
- hoverSiblings = false;
473
- /** The compounds whose `:has()` tests the pointer, each without it, and
474
- * whether its argument starts at a sibling (`hoverAnchors`). */
632
+ /** The compounds whose `:has()` tests the pointer, each without it,
633
+ * whether its argument starts at a sibling, and whether a sibling
634
+ * combinator follows it (`hoverAnchors`). */
475
635
  _hoverAnchors = new Map();
476
636
  _anchorMatchers = null;
477
637
  _pointer = NO_POINTER;
@@ -511,6 +671,12 @@ export class Cascade {
511
671
  /** The families the document loads itself (`fonts.ts`), or null for a
512
672
  * document with no `@font-face`. */
513
673
  _families;
674
+ /** The root element's computed style: its `font-size` is what a `rem`
675
+ * is and its line height what an `rlh` is (CSS Values 4, 6.1.1), in
676
+ * every element but the root, whose own declarations measure them from
677
+ * the initial values. Set as the root's style is computed, which a build
678
+ * does ahead of every element under it. */
679
+ _root = null;
514
680
  _mapFamilies;
515
681
  /** Whether any declaration has a length in `lh` or `rlh`: only then is
516
682
  * the line height settled ahead of the declarations that read it. */
@@ -552,6 +718,11 @@ export class Cascade {
552
718
  }
553
719
  for (const bp of sheet.breakpoints)
554
720
  breakpoints.add(bp);
721
+ // a query on the viewport's height reads it as a `vh` does
722
+ if (sheet.readsHeight)
723
+ this.readsViewportHeight = true;
724
+ if (sheet.readsWidth)
725
+ this.readsViewportWidth = true;
555
726
  }
556
727
  this.breakpoints = [...breakpoints].sort((a, b) => a - b);
557
728
  this.counterStyles = new CounterStyles(counterStyles);
@@ -583,7 +754,7 @@ export class Cascade {
583
754
  if (!VIEWPORT_UNIT.test(d.value))
584
755
  continue;
585
756
  for (const m of d.value.matchAll(VIEWPORT_UNITS)) {
586
- const unit = m[1].toLowerCase();
757
+ const unit = viewportUnit(m[1]);
587
758
  if (unit !== 'vh')
588
759
  this.readsViewportWidth = true;
589
760
  if (unit !== 'vw')
@@ -603,42 +774,78 @@ export class Cascade {
603
774
  setPointer(pointer) {
604
775
  this._pointer = pointer;
605
776
  }
777
+ /**
778
+ * Which pseudo-elements' rules test the pointer: none's
779
+ * (`HOVER_PSEUDO_NONE`); a `::before`'s or an `::after`'s only
780
+ * (`HOVER_PSEUDO_GENERATED`), whose boxes a move restyles where they are
781
+ * as it does an element's; or a `::first-line`'s, a `::first-letter`'s
782
+ * or a `::marker`'s (`HOVER_PSEUDO_OTHER`), which layout styles and a
783
+ * move cannot.
784
+ */
785
+ get hoverPseudo() {
786
+ if (this._pseudo['first-letter'].hoverSensitive ||
787
+ this._pseudo['first-line'].hoverSensitive ||
788
+ this._pseudo.marker.hoverSensitive) {
789
+ return HOVER_PSEUDO_OTHER;
790
+ }
791
+ return this._pseudo.before.hoverSensitive ||
792
+ this._pseudo.after.hoverSensitive
793
+ ? HOVER_PSEUDO_GENERATED
794
+ : HOVER_PSEUDO_NONE;
795
+ }
606
796
  /**
607
797
  * Whether the pointer entering or leaving `el` can change a style: one of
608
798
  * the compounds a selector tests the pointer in matches it, the pointer
609
799
  * aside. A move between two paragraphs of a page whose only such rule is
610
- * `a:hover` touches nothing, and restyles nothing.
800
+ * `a:hover` touches nothing, and restyles nothing. `HOVER_FOLLOWED`
801
+ * where a sibling combinator follows one that matches, so the element's
802
+ * later siblings can change with it — those rules' elements alone: a
803
+ * sheet with one `.peer:hover ~ *` in it does not make every row of a
804
+ * table restyle the rows after it.
611
805
  */
612
806
  hoverTouches(el) {
613
- this._hoverMatchers ??= [...this._hoverCompounds].flatMap((c) => {
614
- const match = this._compile(c);
615
- return match ? [match] : [];
807
+ // the followed ones first, so the first that matches is the answer
808
+ this._hoverMatchers ??= [...this._hoverCompounds]
809
+ .sort(([, a], [, b]) => Number(b) - Number(a))
810
+ .flatMap(([compound, followed]) => {
811
+ const match = this._compile(compound);
812
+ return match ? [{ match, followed }] : [];
616
813
  });
617
- for (const match of this._hoverMatchers)
814
+ for (const { match, followed } of this._hoverMatchers) {
618
815
  if (match(el))
619
- return true;
620
- return false;
816
+ return followed ? HOVER_FOLLOWED : HOVER_TOUCHED;
817
+ }
818
+ return HOVER_UNTOUCHED;
621
819
  }
622
820
  /**
623
821
  * The elements whose `:has()` may have changed its answer as `el` was
624
822
  * hovered or left: the ancestors of it a compound whose `:has()` tests
625
823
  * the pointer names, and their earlier siblings and its own where the
626
824
  * argument starts at a sibling — a superset, which is restyled and found
627
- * the same where it did not. Into `into`.
825
+ * the same where it did not. Into `into`, each with whether its later
826
+ * siblings can change with it (`hoverTouches`).
628
827
  */
629
828
  hoverAnchors(el, into) {
630
829
  if (!this._hoverAnchors.size)
631
830
  return;
632
- this._anchorMatchers ??= [...this._hoverAnchors].flatMap(([anchor, siblings]) => {
831
+ this._anchorMatchers ??= [...this._hoverAnchors]
832
+ .sort(([, a], [, b]) => Number(b.followed) - Number(a.followed))
833
+ .flatMap(([anchor, { siblings, followed }]) => {
633
834
  const match = this._compile(anchor);
634
- return match ? [{ match, siblings }] : [];
835
+ return match ? [{ match, siblings, followed }] : [];
635
836
  });
837
+ const note = (at, followed) => {
838
+ if (followed)
839
+ into.set(at, HOVER_FOLLOWED);
840
+ else if (!into.has(at))
841
+ into.set(at, HOVER_TOUCHED);
842
+ };
636
843
  const siblings = this._anchorMatchers.some((m) => m.siblings);
637
844
  for (let at = el; at;) {
638
845
  if (at !== el) {
639
- for (const { match } of this._anchorMatchers) {
846
+ for (const { match, followed } of this._anchorMatchers) {
640
847
  if (match(at)) {
641
- into.push(at);
848
+ note(at, followed);
642
849
  break;
643
850
  }
644
851
  }
@@ -647,9 +854,9 @@ export class Cascade {
647
854
  for (let s = at.prev; s; s = s.prev) {
648
855
  if (!isTag(s))
649
856
  continue;
650
- for (const { match, siblings: apart } of this._anchorMatchers) {
651
- if (apart && match(s)) {
652
- into.push(s);
857
+ for (const m of this._anchorMatchers) {
858
+ if (m.siblings && m.match(s)) {
859
+ note(s, m.followed);
653
860
  break;
654
861
  }
655
862
  }
@@ -665,12 +872,16 @@ export class Cascade {
665
872
  const found = pointerCompounds(selector);
666
873
  if (found.nested)
667
874
  this.hoverLocal = false;
668
- if (found.siblings)
669
- this.hoverSiblings = true;
670
- for (const c of found.compounds)
671
- this._hoverCompounds.add(c);
875
+ const followed = new Set(found.followed);
876
+ for (const c of found.compounds) {
877
+ this._hoverCompounds.set(c, followed.has(c) || (this._hoverCompounds.get(c) ?? false));
878
+ }
672
879
  for (const { anchor, siblings } of found.has) {
673
- this._hoverAnchors.set(anchor, siblings || (this._hoverAnchors.get(anchor) ?? false));
880
+ const was = this._hoverAnchors.get(anchor);
881
+ this._hoverAnchors.set(anchor, {
882
+ siblings: siblings || (was?.siblings ?? false),
883
+ followed: followed.has(anchor) || (was?.followed ?? false),
884
+ });
674
885
  }
675
886
  }
676
887
  /** A selector compiled as a rule's is, or null where css-select refuses
@@ -707,12 +918,34 @@ export class Cascade {
707
918
  _customs = new Map();
708
919
  _ids = new WeakMap();
709
920
  _nextId = 1;
710
- /** A box tree is about to be built: the styles shared in the last build
711
- * were computed against a pointer and a viewport that may have moved. */
712
- beginSharing() {
921
+ /** The styles kept from the build before, while a build that keeps them
922
+ * runs, and what each kept element shares under in this one. */
923
+ _kept = null;
924
+ _keptShared = new Map();
925
+ /**
926
+ * A box tree is about to be built: the styles shared in the last build
927
+ * were computed against a pointer and a viewport that may have moved.
928
+ *
929
+ * With `kept`, only its `restyle` elements are matched: a build the
930
+ * pointer asked for — a hover that moved something — changes the styles
931
+ * of the elements the move reached, and every other element's is what it
932
+ * was. Matching them all again was most of such a build: a frame of 550 ms
933
+ * on a page with a Tailwind sheet, and 150 without it.
934
+ */
935
+ beginSharing(kept = null) {
713
936
  this._shared.clear();
714
937
  this._sharedByMatch.clear();
715
- this._customs.clear();
938
+ // the kept styles' custom properties are the sets in here, which the
939
+ // elements styled under them find again
940
+ if (!kept)
941
+ this._customs.clear();
942
+ this._kept = kept;
943
+ this._keptShared.clear();
944
+ }
945
+ /** The build is over: nothing later is answered from what it kept. */
946
+ endSharing() {
947
+ this._kept = null;
948
+ this._keptShared.clear();
716
949
  }
717
950
  /**
718
951
  * An element's custom properties: its parent's, or its own over them —
@@ -794,6 +1027,23 @@ export class Cascade {
794
1027
  * does after the cascade, and `rootStyle` does not go through here.
795
1028
  */
796
1029
  sharedStyleFor(el, parentStyle, parentKey, inFlexContainer) {
1030
+ const kept = this._kept;
1031
+ if (kept !== null && !kept.restyle.has(el)) {
1032
+ // An element the change did not reach keeps its style, under a key
1033
+ // of its own: a key stands for an element's ancestors, which the
1034
+ // elements styled again under it have in common, and two kept
1035
+ // elements with one style need not — a hover restyled in place
1036
+ // leaves a hovered card's children the styles its neighbours' have.
1037
+ let shared = this._keptShared.get(el);
1038
+ if (shared !== undefined)
1039
+ return shared;
1040
+ const was = kept.styles.get(el);
1041
+ if (was !== undefined && was.inFlex === inFlexContainer) {
1042
+ shared = { style: was.style, key: this._nextShareKey++ };
1043
+ this._keptShared.set(el, shared);
1044
+ return shared;
1045
+ }
1046
+ }
797
1047
  let key = `${parentKey}\u0001${inFlexContainer ? 1 : 0}`;
798
1048
  const tag = tagOf(el);
799
1049
  key += `\u0001${tag.length}:${tag}`;
@@ -938,6 +1188,53 @@ export class Cascade {
938
1188
  out.unicodeBidi = 'isolate';
939
1189
  return out;
940
1190
  }
1191
+ /** Whether any rule styles a `::selection`: a document with none draws
1192
+ * a selection in the palette's colour, and asks nothing. */
1193
+ get hasSelection() {
1194
+ return this._pseudo.selection.size > 0;
1195
+ }
1196
+ /**
1197
+ * An element's `::selection`, over its parent's (`parent`, null for the
1198
+ * palette's own): the colour and the background the rules that reach it
1199
+ * set, and its parent's for either they leave alone. Both inherit along
1200
+ * the chain of highlights rather than the elements' (CSS Pseudo 4, 3.5,
1201
+ * "highlight inheritance"), which is what Chrome draws: a `<span>` in a
1202
+ * `div::selection { background: red }` is selected in red, and a `<p>`
1203
+ * whose own rule sets only a colour keeps the red under it.
1204
+ *
1205
+ * The palette's highlight is taken only where no rule has set either
1206
+ * (3.6, "paired defaults"): one that sets a colour and no background is
1207
+ * selected over none, as Chrome has it. A rule that sets neither — a
1208
+ * `text-shadow` only — leaves the element where its parent was.
1209
+ */
1210
+ selectionStyle(el, style, parent) {
1211
+ const index = this._pseudo.selection;
1212
+ if (!index.size || !index.reaches(el))
1213
+ return parent;
1214
+ const candidates = [];
1215
+ this._matchInto(index, el, candidates);
1216
+ let color = false;
1217
+ let background = false;
1218
+ for (const candidate of candidates) {
1219
+ for (const d of candidate.declarations) {
1220
+ if (d.prop === 'color')
1221
+ color = true;
1222
+ else if (d.prop === 'background' || d.prop === 'background-color') {
1223
+ background = true;
1224
+ }
1225
+ }
1226
+ }
1227
+ if (!color && !background)
1228
+ return parent;
1229
+ candidates.sort(byCascade);
1230
+ const own = this._computeStyle(el, style, false, candidates);
1231
+ return {
1232
+ color: color ? own.color : (parent?.color ?? null),
1233
+ background: background
1234
+ ? own.backgroundColor
1235
+ : (parent?.background ?? null),
1236
+ };
1237
+ }
941
1238
  /**
942
1239
  * An element's `::first-line` style, inheriting from its own, or null
943
1240
  * when no rule reaches it (CSS 2.1 5.12.1).
@@ -1018,9 +1315,11 @@ export class Cascade {
1018
1315
  // size, so a `font-size: 1.2em` in the cascade resolves against the
1019
1316
  // right em, and again after the font size is settled so every *other*
1020
1317
  // em-relative length in the same rule resolves against this element's.
1318
+ const root = isRootElement(el);
1319
+ const rootStyle = root ? this.initial : (this._root ?? this.initial);
1021
1320
  const ctxParent = {
1022
1321
  em: parentStyle.fontSize,
1023
- rem: this.initial.fontSize,
1322
+ rem: rootStyle.fontSize,
1024
1323
  initial: this.initial,
1025
1324
  vw: this.viewportWidth,
1026
1325
  vh: this.viewportHeight,
@@ -1029,20 +1328,22 @@ export class Cascade {
1029
1328
  ch: () => this._chOf(parentStyle),
1030
1329
  families: this._mapFamilies,
1031
1330
  lh: () => this._lineHeightOf(parentStyle),
1032
- rlh: () => this._lineHeightOf(this.initial),
1331
+ rlh: () => this._lineHeightOf(rootStyle),
1033
1332
  };
1034
1333
  // The family, the weight and the slant go with the size: together they
1035
1334
  // pick the face an `ex`, a `ch` or an `lh` in any declaration is
1036
1335
  // measured in, however the declarations are ordered. A `width: 10ex`
1037
1336
  // in a sheet under an inline `font-weight: 900` was measured in the
1038
- // family's regular face.
1337
+ // family's regular face. The colour scheme goes ahead of the rest for
1338
+ // the same reason: every `light-dark()` among them is read by it.
1039
1339
  let sized = false;
1040
1340
  let keyword = false;
1041
1341
  for (const c of candidates) {
1042
1342
  for (const d of pick(c)) {
1043
1343
  if (d.prop === 'font-family' ||
1044
1344
  d.prop === 'font-weight' ||
1045
- d.prop === 'font-style') {
1345
+ d.prop === 'font-style' ||
1346
+ d.prop === 'color-scheme') {
1046
1347
  this._apply(style, parentStyle, d, ctxParent);
1047
1348
  }
1048
1349
  else if (d.prop === 'font-size' || d.prop === 'font') {
@@ -1083,9 +1384,13 @@ export class Cascade {
1083
1384
  fontStyle: style.fontStyle,
1084
1385
  };
1085
1386
  let line = null;
1387
+ // the root's other declarations measure a `rem` from its own size
1388
+ if (root)
1389
+ this._root = style;
1086
1390
  const ctx = {
1087
1391
  ...ctxParent,
1088
1392
  em: style.fontSize,
1393
+ rem: root ? style.fontSize : ctxParent.rem,
1089
1394
  ex: () => this._exOf(face),
1090
1395
  ch: () => this._chOf(face),
1091
1396
  lh: () => this._lineHeightOf(line ?? style),
@@ -1208,18 +1513,55 @@ export class Cascade {
1208
1513
  // the root is still the initial containing block.
1209
1514
  return { style: asRoot(this.styleFor(synthetic, parent, false)), html };
1210
1515
  }
1516
+ /**
1517
+ * Whether the pointer's move from `was` to where it is changed which
1518
+ * rules match `el`, or its `::before`, `::after` or another of its
1519
+ * pseudo-elements: the rules that test `:hover` asked under both, and
1520
+ * nothing else, since no other rule's answer is the pointer's. An element
1521
+ * they answer the same for, under a parent whose style is what it was, has
1522
+ * the style it had — which is most of what a move reaches: the rows of a
1523
+ * hovered table body, what is in a hovered card.
1524
+ */
1525
+ pointerChanged(el, was) {
1526
+ const now = this._pointer;
1527
+ const after = this._pointerAnswers(el);
1528
+ this._pointer = was;
1529
+ const before = this._pointerAnswers(el);
1530
+ this._pointer = now;
1531
+ return before !== after;
1532
+ }
1533
+ /** The ids of the rules testing the pointer that match `el` as things
1534
+ * stand, the element's and each pseudo-element's. */
1535
+ _pointerAnswers(el) {
1536
+ let answers = '';
1537
+ const ask = (index, name) => {
1538
+ const pointer = index.pointer;
1539
+ if (!pointer || !pointer.reaches(el))
1540
+ return;
1541
+ const matched = [];
1542
+ this._matchInto(pointer, el, null, matched);
1543
+ if (matched.length)
1544
+ answers += `${name}${matched.join(',')};`;
1545
+ };
1546
+ ask(this._index, '');
1547
+ for (const which of POINTER_PSEUDO_ELEMENTS)
1548
+ ask(this._pseudo[which], which);
1549
+ return answers;
1550
+ }
1211
1551
  /** The rules of `index` that match `el`, pushed onto `out` as candidates,
1212
1552
  * and their ids onto `matched` in the order they were tried. */
1213
1553
  _matchInto(index, el, out, matched) {
1214
1554
  // A media query's width is CSS pixels; the viewport is kept in device.
1215
1555
  const width = this.viewportWidth / this.scale;
1556
+ const height = this.viewportHeight / this.scale;
1216
1557
  const consider = (bucket) => {
1217
1558
  if (!bucket)
1218
1559
  return;
1219
1560
  for (const indexed of bucket) {
1220
1561
  const rule = indexed.rule;
1221
- if (!mediaMatches(rule.media, width, this.look.colorScheme))
1562
+ if (!mediaMatches(rule.media, width, this.look.colorScheme, height, this.scale)) {
1222
1563
  continue;
1564
+ }
1223
1565
  if (!indexed.compiled) {
1224
1566
  indexed.compiled = true;
1225
1567
  try {
@@ -1235,6 +1577,8 @@ export class Cascade {
1235
1577
  if (!indexed.match || !indexed.match(el))
1236
1578
  continue;
1237
1579
  matched?.push(indexed.id);
1580
+ if (out === null)
1581
+ continue;
1238
1582
  const origin = rule.order < 0 ? Origin.UserAgent : Origin.Author;
1239
1583
  pushRule(out, rule, origin);
1240
1584
  }
@@ -1301,9 +1645,57 @@ export class Cascade {
1301
1645
  }
1302
1646
  }
1303
1647
  out.sort(byCascade);
1648
+ dropPaletteChrome(out);
1304
1649
  return out;
1305
1650
  }
1306
1651
  }
1652
+ /**
1653
+ * Takes the palette's control chrome (`PALETTE_CHROME`) out of an element's
1654
+ * candidates where the page styles its background or border, so what shows
1655
+ * through is the web's UA values the page was written against. The rule is
1656
+ * Blink's (`LayoutTheme::IsControlStyled`): a background or border longhand
1657
+ * set by the page, radius included, drops the control's native appearance;
1658
+ * so does `appearance: none`. `out` is in cascade order, so the UA's
1659
+ * candidates are the ones before the first of any other origin.
1660
+ */
1661
+ function dropPaletteChrome(out) {
1662
+ let chrome = false;
1663
+ let i = 0;
1664
+ for (; i < out.length && out[i].origin === Origin.UserAgent; i += 1) {
1665
+ if (PALETTE_CHROME.has(out[i].declarations))
1666
+ chrome = true;
1667
+ }
1668
+ if (!chrome)
1669
+ return;
1670
+ let styled = false;
1671
+ for (let j = i; j < out.length && !styled; j += 1) {
1672
+ styled = pick(out[j]).some(stylesChrome);
1673
+ }
1674
+ if (!styled)
1675
+ return;
1676
+ let kept = 0;
1677
+ for (const c of out) {
1678
+ if (c.origin === Origin.UserAgent && PALETTE_CHROME.has(c.declarations)) {
1679
+ continue;
1680
+ }
1681
+ out[kept++] = c;
1682
+ }
1683
+ out.length = kept;
1684
+ }
1685
+ /** Whether a declaration styles what a control's native look draws: Blink's
1686
+ * `is_background` and `is_border` properties and their shorthands, and an
1687
+ * `appearance` of `none`. */
1688
+ function stylesChrome(d) {
1689
+ const prop = d.prop;
1690
+ if (prop.startsWith('background')) {
1691
+ return prop !== 'background-repeat' && prop !== 'background-blend-mode';
1692
+ }
1693
+ if (prop.startsWith('border')) {
1694
+ return prop !== 'border-collapse' && prop !== 'border-spacing';
1695
+ }
1696
+ return ((prop === 'appearance' || prop === '-webkit-appearance') &&
1697
+ d.value.trim().toLowerCase() === 'none');
1698
+ }
1307
1699
  /** An implied element's style as the root box's: its look, not its role —
1308
1700
  * the root is still a block, in flow, and the initial containing block. */
1309
1701
  function asRoot(style) {
@@ -1314,9 +1706,19 @@ function asRoot(style) {
1314
1706
  }
1315
1707
  /** `:root` is the `<html>` element: the one a browser implies around a
1316
1708
  * fragment, whose style the root box takes (`rootStyle`), and never a
1317
- * fragment's top-level elements, which css-select would take for it. */
1709
+ * fragment's top-level elements, which css-select would take for it.
1710
+ *
1711
+ * `:focus` and its two kin match nothing: no element of the document takes
1712
+ * the focus — a control's widget does, beside it. css-select has none of
1713
+ * the three and throws on them, which dropped every rule that named one,
1714
+ * `:not(:focus)` among them: Wikipedia's skip link hides with
1715
+ * `.mw-jump-link:not(:focus)`, and without it stood in the flow at the top
1716
+ * of every page. */
1318
1717
  const PSEUDOS = {
1319
1718
  root: (el) => el.name === 'html' && !(el.parent && isTag(el.parent)),
1719
+ focus: () => false,
1720
+ 'focus-visible': () => false,
1721
+ 'focus-within': () => false,
1320
1722
  // css-select's, but its ranges and tags lower-cased as ASCII has it, and
1321
1723
  // no other script (CSS 2.1 4.1.3): Unicode's took `:lang(\u212Al)`, a
1322
1724
  // Kelvin sign for the K, for `:lang(kl)`
@@ -1351,8 +1753,8 @@ const PSEUDOS = {
1351
1753
  };
1352
1754
  /** A length in a unit of the viewport: a number, then the unit, and no
1353
1755
  * more of a name after it. */
1354
- const VIEWPORT_UNIT = /\d(?:vw|vh|vmin|vmax)(?![\w-])/i;
1355
- const VIEWPORT_UNITS = /\d(vw|vh|vmin|vmax)(?![\w-])/gi;
1756
+ const VIEWPORT_UNIT = /\d[sld]?v(?:w|h|i|b|min|max)(?![\w-])/i;
1757
+ const VIEWPORT_UNITS = /\d([sld]?v(?:w|h|i|b|min|max))(?![\w-])/gi;
1356
1758
  /**
1357
1759
  * A selector with its `[attr~=""]` made one that matches nothing: an empty
1358
1760
  * string is no word of a list, and such a selector represents nothing
@@ -1625,7 +2027,10 @@ function presentationHints(el) {
1625
2027
  // `width`/`height` are lengths on the replaced and table elements and mean
1626
2028
  // nothing anywhere else, which is what stops a `<input width>` from
1627
2029
  // becoming a CSS width the widget then disagrees with.
1628
- if (SIZED.has(tag)) {
2030
+ if (SIZED.has(tag) ||
2031
+ // an image button's are its image's, as an `<img>`'s are (HTML 15.4.3)
2032
+ (tag === 'input' &&
2033
+ (attr(el, 'type') ?? '').trim().toLowerCase() === 'image')) {
1629
2034
  const width = attr(el, 'width');
1630
2035
  if (width)
1631
2036
  push('width', lengthAttr(width));
@@ -1865,4 +2270,9 @@ const ABSOLUTE_SIZES = new Set([
1865
2270
  'xxx-large',
1866
2271
  'initial',
1867
2272
  ]);
2273
+ /** Whether an element is the document's root: its parent the document, or
2274
+ * none, as the `<html>` the cascade supplies where the markup has none. */
2275
+ function isRootElement(el) {
2276
+ return !el.parent || el.parent.type === 'root';
2277
+ }
1868
2278
  //# sourceMappingURL=cascade.js.map