@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/color.ts
CHANGED
|
@@ -773,6 +773,24 @@ export function lightDark(
|
|
|
773
773
|
value: string,
|
|
774
774
|
scheme: 'light' | 'dark',
|
|
775
775
|
): string | null {
|
|
776
|
+
// kept by the value: the UA sheet's link colour is one, so every link of
|
|
777
|
+
// every document asks, and a theme's are a few asked by every element
|
|
778
|
+
const kept = PICKED[scheme];
|
|
779
|
+
let picked = kept.get(value);
|
|
780
|
+
if (picked === undefined) {
|
|
781
|
+
picked = pickScheme(value, scheme);
|
|
782
|
+
if (kept.size >= 512) kept.clear();
|
|
783
|
+
kept.set(value, picked);
|
|
784
|
+
}
|
|
785
|
+
return picked;
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
const PICKED = {
|
|
789
|
+
light: new Map<string, string | null>(),
|
|
790
|
+
dark: new Map<string, string | null>(),
|
|
791
|
+
};
|
|
792
|
+
|
|
793
|
+
function pickScheme(value: string, scheme: 'light' | 'dark'): string | null {
|
|
776
794
|
let out = value;
|
|
777
795
|
// a branch can hold another, which the next round replaces
|
|
778
796
|
for (let round = 0; round < 32; round += 1) {
|
|
@@ -827,6 +845,17 @@ export function usedColorScheme(
|
|
|
827
845
|
return first ?? preferred;
|
|
828
846
|
}
|
|
829
847
|
|
|
848
|
+
/**
|
|
849
|
+
* A browser's own colours in each scheme — the system colours `Canvas`,
|
|
850
|
+
* `CanvasText` and `LinkText` (CSS Color 4, 6.2), as Chrome has them: what
|
|
851
|
+
* a document is drawn in where its root's scheme is not the palette's,
|
|
852
|
+
* which has the colours of its own scheme and of no other.
|
|
853
|
+
*/
|
|
854
|
+
export const SCHEME_COLORS = {
|
|
855
|
+
light: { canvas: '#ffffff', text: '#000000', link: '#0000ee' },
|
|
856
|
+
dark: { canvas: '#121212', text: '#ffffff', link: '#9e9eff' },
|
|
857
|
+
} as const;
|
|
858
|
+
|
|
830
859
|
const SCHEME_IDENT = /^-?(?:[a-z_]|--)[\w-]*$/;
|
|
831
860
|
/** What a scheme's name cannot be: the keywords the grammar has, and the
|
|
832
861
|
* CSS-wide ones, which reach here only in a list. */
|
package/src/html/css/parse.ts
CHANGED
|
@@ -56,10 +56,22 @@ export interface Stylesheet {
|
|
|
56
56
|
* resource seam and splices the result in ahead of this sheet. Absolute
|
|
57
57
|
* when the sheet was parsed with a base. */
|
|
58
58
|
imports: string[];
|
|
59
|
+
/** Each import's media query list, where it has one: the condition the
|
|
60
|
+
* sheet it brings in is under, as though an `@media` block were around
|
|
61
|
+
* all of it (CSS Cascade 4, 2). Null for an import with none. */
|
|
62
|
+
importConditions: (MediaCondition[] | null)[];
|
|
59
63
|
/** Every width a `@media` rule in this sheet switches on. The renderer
|
|
60
64
|
* keeps these so a resize can tell "the layout changed" from "the
|
|
61
65
|
* *cascade* changed", and restyle only when it crossed one. */
|
|
62
66
|
breakpoints: number[];
|
|
67
|
+
/** Whether a `@media` rule in this sheet tests the viewport's height,
|
|
68
|
+
* which makes a document restyle when the height moves, as a `vh`
|
|
69
|
+
* does. */
|
|
70
|
+
readsHeight?: boolean;
|
|
71
|
+
/** Whether one tests its aspect ratio, which a width that crosses no
|
|
72
|
+
* breakpoint can change: the document restyles as one with a `vw`
|
|
73
|
+
* does. */
|
|
74
|
+
readsWidth?: boolean;
|
|
63
75
|
/** The `@font-face` rules, in order (see `fonts.ts`). */
|
|
64
76
|
fontFaces: FontFaceRule[];
|
|
65
77
|
/** `@counter-style` rules, in order: a name and its descriptors, which
|
|
@@ -76,8 +88,9 @@ export interface Stylesheet {
|
|
|
76
88
|
export interface FontFaceRule {
|
|
77
89
|
/** The family as the document names it, unquoted. */
|
|
78
90
|
family: string;
|
|
79
|
-
/** The
|
|
80
|
-
*
|
|
91
|
+
/** The entries of `src`, in order, which is the order they are tried in:
|
|
92
|
+
* a `url()` with its `format()` hint lowercased, or the name a `local()`
|
|
93
|
+
* asks the system for. */
|
|
81
94
|
sources: FontFaceSource[];
|
|
82
95
|
/** The weights the face covers: `[400, 400]` for a static regular, a
|
|
83
96
|
* range for a variable face. */
|
|
@@ -89,17 +102,31 @@ export interface FontFaceRule {
|
|
|
89
102
|
media: MediaCondition[][] | null;
|
|
90
103
|
}
|
|
91
104
|
|
|
92
|
-
export
|
|
93
|
-
url: string;
|
|
94
|
-
|
|
95
|
-
}
|
|
105
|
+
export type FontFaceSource =
|
|
106
|
+
| { url: string; format: string | null }
|
|
107
|
+
/** `local(Arial)`: a face the system has, by name and unquoted. */
|
|
108
|
+
| { local: string };
|
|
96
109
|
|
|
97
|
-
/** The tests this evaluates live: a width, a colour scheme
|
|
110
|
+
/** The tests this evaluates live: a width, a height, a colour scheme.
|
|
98
111
|
* Anything else — `orientation`, `print`, `prefers-reduced-motion` — is
|
|
99
112
|
* decided once, at parse time, by `staticPass`. */
|
|
100
113
|
export interface MediaCondition {
|
|
101
114
|
min?: number;
|
|
102
115
|
max?: number;
|
|
116
|
+
/** The viewport's height, as `min`/`max` its width: `min-height`,
|
|
117
|
+
* `max-height` and a range on `height` (Media Queries 4, 4.2). */
|
|
118
|
+
minHeight?: number;
|
|
119
|
+
maxHeight?: number;
|
|
120
|
+
/** The display's resolution in dots per CSS pixel, its scale: `min-`
|
|
121
|
+
* and `max-resolution`, and WebKit's `-webkit-min-device-pixel-ratio`,
|
|
122
|
+
* which a design writes to swap in its high-DPI images. */
|
|
123
|
+
minResolution?: number;
|
|
124
|
+
maxResolution?: number;
|
|
125
|
+
/** The viewport's width over its height: `aspect-ratio` and its `min-`
|
|
126
|
+
* and `max-`, the device's the viewport's too, and `orientation`, which
|
|
127
|
+
* is landscape where the width is the greater. */
|
|
128
|
+
minAspect?: number;
|
|
129
|
+
maxAspect?: number;
|
|
103
130
|
/** `prefers-color-scheme`, answered from the palette in force. */
|
|
104
131
|
scheme?: 'light' | 'dark';
|
|
105
132
|
/** Set when the query could not be evaluated as a width or scheme test:
|
|
@@ -154,16 +181,38 @@ export function parseStylesheet(
|
|
|
154
181
|
* the document's for a `<style>` — or null to leave them as written.
|
|
155
182
|
* Every `url()`, `@import` and `@font-face` source comes out absolute. */
|
|
156
183
|
base: string | null = null,
|
|
184
|
+
/** The media queries the whole sheet is under, outermost first: the ones
|
|
185
|
+
* of the `@import` that brought it in, and of the imports that brought
|
|
186
|
+
* that sheet in. Every rule and face in it is under them, as one inside
|
|
187
|
+
* an `@media` block is under the block's. */
|
|
188
|
+
under: MediaCondition[][] | null = null,
|
|
157
189
|
): Stylesheet {
|
|
158
190
|
text = withoutComments(text);
|
|
159
191
|
const sheet: Stylesheet = {
|
|
160
192
|
rules: [],
|
|
161
193
|
imports: [],
|
|
194
|
+
importConditions: [],
|
|
162
195
|
breakpoints: [],
|
|
163
196
|
fontFaces: [],
|
|
164
197
|
};
|
|
165
198
|
let order = startOrder;
|
|
166
199
|
const breakpoints = new Set<number>();
|
|
200
|
+
/** What a media query list tests that a resize has to restyle for: the
|
|
201
|
+
* widths it changes its mind at, and whether it reads the height. */
|
|
202
|
+
const note = (conditions: MediaCondition[]): void => {
|
|
203
|
+
for (const c of conditions) {
|
|
204
|
+
if (c.min !== undefined) breakpoints.add(c.min);
|
|
205
|
+
if (c.max !== undefined) breakpoints.add(c.max + MAX_EDGE);
|
|
206
|
+
if (c.minHeight !== undefined || c.maxHeight !== undefined) {
|
|
207
|
+
sheet.readsHeight = true;
|
|
208
|
+
}
|
|
209
|
+
if (c.minAspect !== undefined || c.maxAspect !== undefined) {
|
|
210
|
+
sheet.readsHeight = true;
|
|
211
|
+
sheet.readsWidth = true;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
};
|
|
215
|
+
for (const conditions of under ?? []) note(conditions);
|
|
167
216
|
// `@import` counts only ahead of every other rule, `@charset` aside
|
|
168
217
|
let importsAllowed = true;
|
|
169
218
|
|
|
@@ -211,10 +260,12 @@ export function parseStylesheet(
|
|
|
211
260
|
const name = at.name.toLowerCase();
|
|
212
261
|
if (name === 'charset') continue;
|
|
213
262
|
if (name === 'import') {
|
|
214
|
-
if (importsAllowed && media ===
|
|
263
|
+
if (importsAllowed && media === under) {
|
|
215
264
|
const url = importUrl(at.prelude);
|
|
216
|
-
|
|
265
|
+
const conditions = importConditions(at.prelude);
|
|
266
|
+
if (url && conditions !== false) {
|
|
217
267
|
sheet.imports.push(resolveUrl(url, base));
|
|
268
|
+
sheet.importConditions.push(conditions);
|
|
218
269
|
}
|
|
219
270
|
}
|
|
220
271
|
continue;
|
|
@@ -226,10 +277,7 @@ export function parseStylesheet(
|
|
|
226
277
|
}
|
|
227
278
|
if (name === 'media' && at.block !== null) {
|
|
228
279
|
const conditions = parseMediaQuery(at.prelude);
|
|
229
|
-
|
|
230
|
-
if (c.min !== undefined) breakpoints.add(c.min);
|
|
231
|
-
if (c.max !== undefined) breakpoints.add(c.max + MAX_EDGE);
|
|
232
|
-
}
|
|
280
|
+
note(conditions);
|
|
233
281
|
// A nested `@media` intersects with the one above it; pushing a
|
|
234
282
|
// level rather than merging keeps "all of these blocks hold" exact
|
|
235
283
|
// when two of them overlap.
|
|
@@ -335,6 +383,13 @@ export function parseStylesheet(
|
|
|
335
383
|
for (const c of conditions) {
|
|
336
384
|
if (c.min !== undefined) breakpoints.add(c.min);
|
|
337
385
|
if (c.max !== undefined) breakpoints.add(c.max + MAX_EDGE);
|
|
386
|
+
if (c.minHeight !== undefined || c.maxHeight !== undefined) {
|
|
387
|
+
sheet.readsHeight = true;
|
|
388
|
+
}
|
|
389
|
+
if (c.minAspect !== undefined || c.maxAspect !== undefined) {
|
|
390
|
+
sheet.readsHeight = true;
|
|
391
|
+
sheet.readsWidth = true;
|
|
392
|
+
}
|
|
338
393
|
}
|
|
339
394
|
styleRule(
|
|
340
395
|
selectors,
|
|
@@ -378,7 +433,7 @@ export function parseStylesheet(
|
|
|
378
433
|
}
|
|
379
434
|
};
|
|
380
435
|
|
|
381
|
-
walk(text,
|
|
436
|
+
walk(text, under);
|
|
382
437
|
sheet.breakpoints = [...breakpoints].sort((a, b) => a - b);
|
|
383
438
|
return sheet;
|
|
384
439
|
}
|
|
@@ -1355,17 +1410,36 @@ function readAtRule(text: string, at: number): AtRule {
|
|
|
1355
1410
|
* divides a device width into (`640.5` at 2x) fall on the right side. */
|
|
1356
1411
|
const MAX_EDGE = 1 / 64;
|
|
1357
1412
|
|
|
1413
|
+
/**
|
|
1414
|
+
* The media features that are the viewport's width and its height here.
|
|
1415
|
+
* `device-width` and `device-height` are the Web-exposed screen area's
|
|
1416
|
+
* (Media Queries 4, appendix A), which a user agent may answer with the
|
|
1417
|
+
* viewport's (CSSOM View 2.3), and a document drawn into an element has no
|
|
1418
|
+
* screen of its own to answer with. Deprecated, and still what a page from
|
|
1419
|
+
* before `width` asks with: DuckDuckGo Lite's phone sheet is
|
|
1420
|
+
* `(max-device-width: 700px)`, and a query that went unread held, so a
|
|
1421
|
+
* desktop window got the phone's 12px dropdowns.
|
|
1422
|
+
*/
|
|
1423
|
+
const VIEWPORT_SIDES = {
|
|
1424
|
+
width: new Set(['width', 'device-width']),
|
|
1425
|
+
height: new Set(['height', 'device-height']),
|
|
1426
|
+
};
|
|
1427
|
+
|
|
1358
1428
|
/**
|
|
1359
1429
|
* A width range in Media Queries 4's syntax — `(width >= 48rem)`, `(60rem >
|
|
1360
1430
|
* width)`, `(40rem <= width < 60rem)` — as the widths it holds between, or
|
|
1361
1431
|
* null for a term that is not one. A strict bound is a sixty-fourth of a
|
|
1362
1432
|
* pixel inside the value, where a viewport's width never lands.
|
|
1363
1433
|
*/
|
|
1364
|
-
function widthRange(
|
|
1434
|
+
function widthRange(
|
|
1435
|
+
term: string,
|
|
1436
|
+
feature: 'width' | 'height' = 'width',
|
|
1437
|
+
): { min?: number; max?: number } | null {
|
|
1365
1438
|
const inner = /^\(\s*(.*?)\s*\)$/.exec(term)?.[1];
|
|
1366
1439
|
if (!inner || !/[<>=]/.test(inner)) return null;
|
|
1367
1440
|
const parts = inner.split(/\s*(<=|>=|<|>|=)\s*/);
|
|
1368
|
-
const isWidth = (part: string) =>
|
|
1441
|
+
const isWidth = (part: string) =>
|
|
1442
|
+
VIEWPORT_SIDES[feature].has(part.toLowerCase());
|
|
1369
1443
|
const out: { min?: number; max?: number } = {};
|
|
1370
1444
|
// `width OP value`, with the operator read from the width's side
|
|
1371
1445
|
const bound = (op: string, value: string): boolean => {
|
|
@@ -1536,18 +1610,28 @@ function importUrl(prelude: string): string | null {
|
|
|
1536
1610
|
return m ? m[1] : null;
|
|
1537
1611
|
}
|
|
1538
1612
|
|
|
1539
|
-
/**
|
|
1540
|
-
*
|
|
1541
|
-
*
|
|
1542
|
-
*
|
|
1543
|
-
|
|
1613
|
+
/**
|
|
1614
|
+
* An `@import`'s media query list, the rest of its prelude after the URL:
|
|
1615
|
+
* null where it has none, and false where no query in it can ever hold
|
|
1616
|
+
* here — a list that only names other media, `print` or `braille` — which
|
|
1617
|
+
* leaves the sheet out, unasked for (CSS 2.1 6.3). Any other list is the
|
|
1618
|
+
* condition the sheet it imports is under (CSS Cascade 4, 2): the sheet is
|
|
1619
|
+
* fetched once, and its rules hold at the widths the list does. Taken to
|
|
1620
|
+
* hold at every width, as it was, a design that imports a sheet a
|
|
1621
|
+
* breakpoint — the Zen Garden's 219, `@import url("219-1367.css") all and
|
|
1622
|
+
* (min-width: 1367px)` — had all of them at once.
|
|
1623
|
+
*/
|
|
1624
|
+
function importConditions(prelude: string): MediaCondition[] | null | false {
|
|
1544
1625
|
const m =
|
|
1545
1626
|
/^\s*(?:url\(\s*(?:"[^"]*"|'[^']*'|[^)]*)\s*\)|"[^"]*"|'[^']*')(.*)$/s.exec(
|
|
1546
1627
|
prelude,
|
|
1547
1628
|
);
|
|
1548
1629
|
const list = m?.[1].trim();
|
|
1549
|
-
if (!list) return
|
|
1550
|
-
|
|
1630
|
+
if (!list) return null;
|
|
1631
|
+
const conditions = parseMediaQuery(list);
|
|
1632
|
+
if (!conditions.some((c) => c.staticPass !== false)) return false;
|
|
1633
|
+
// one that always holds is no condition
|
|
1634
|
+
return conditions.some((c) => c.staticPass === true) ? null : conditions;
|
|
1551
1635
|
}
|
|
1552
1636
|
|
|
1553
1637
|
/**
|
|
@@ -1629,10 +1713,13 @@ const GENERIC_FAMILIES = new Set([
|
|
|
1629
1713
|
]);
|
|
1630
1714
|
|
|
1631
1715
|
/**
|
|
1632
|
-
* A `@font-face` block, or null when it names no family
|
|
1633
|
-
*
|
|
1634
|
-
*
|
|
1635
|
-
*
|
|
1716
|
+
* A `@font-face` block, or null when it names no family or no source — a
|
|
1717
|
+
* rule CSS Fonts 4 (4.3) drops whole. A rule whose sources are all
|
|
1718
|
+
* `local()`s is a rule like any other: its family is the document's, and
|
|
1719
|
+
* left out here, `"GeistSans Fallback"` — next/font's `src: local("Arial")`
|
|
1720
|
+
* with the metrics adjusted — reached the text engine as a name to look up,
|
|
1721
|
+
* where fontconfig's nearest guess for a family nobody has set a site's
|
|
1722
|
+
* headings in Gill Sans Ultra Bold.
|
|
1636
1723
|
*/
|
|
1637
1724
|
function parseFontFace(
|
|
1638
1725
|
block: string,
|
|
@@ -1665,6 +1752,14 @@ function parseFontFace(
|
|
|
1665
1752
|
const sources: FontFaceSource[] = [];
|
|
1666
1753
|
for (const part of splitTopLevel(src, ',')) {
|
|
1667
1754
|
const item = part.trim();
|
|
1755
|
+
// `local(Gentium Bold)`, `local("Gentium Bold")`: a name as a family's
|
|
1756
|
+
// is written, and like one never a keyword (`local(inherit)`)
|
|
1757
|
+
const local = /^local\(([\s\S]*)\)$/i.exec(item);
|
|
1758
|
+
if (local) {
|
|
1759
|
+
const name = faceFamily(local[1]);
|
|
1760
|
+
if (name) sources.push({ local: name });
|
|
1761
|
+
continue;
|
|
1762
|
+
}
|
|
1668
1763
|
if (!/^url\(/i.test(item)) continue;
|
|
1669
1764
|
const end = urlEnd(item, 3);
|
|
1670
1765
|
const url = parseUrl(item.slice(0, end));
|
|
@@ -1760,8 +1855,15 @@ export function parseMediaQuery(prelude: string): MediaCondition[] {
|
|
|
1760
1855
|
const condition: MediaCondition = {};
|
|
1761
1856
|
let pass = true;
|
|
1762
1857
|
let sawWidth = false;
|
|
1858
|
+
let sawHeight = false;
|
|
1859
|
+
let sawResolution = false;
|
|
1860
|
+
let sawAspect = false;
|
|
1763
1861
|
const negated = /^\s*not\b/i.test(group);
|
|
1764
|
-
|
|
1862
|
+
// `and` needs white space after it, where a `(` would make it a
|
|
1863
|
+
// function, and none after the `)` it follows: DuckDuckGo writes
|
|
1864
|
+
// `(max-device-width: 701px)and (orientation: landscape)`, which read
|
|
1865
|
+
// as one term that was no feature, and held
|
|
1866
|
+
for (const part of group.split(/(?:\s+|(?<=\)))and\s+/i)) {
|
|
1765
1867
|
const term = part
|
|
1766
1868
|
.trim()
|
|
1767
1869
|
.replace(/^not\s+/i, '')
|
|
@@ -1780,17 +1882,61 @@ export function parseMediaQuery(prelude: string): MediaCondition[] {
|
|
|
1780
1882
|
sawWidth = true;
|
|
1781
1883
|
continue;
|
|
1782
1884
|
}
|
|
1885
|
+
const heights = widthRange(term, 'height');
|
|
1886
|
+
if (heights) {
|
|
1887
|
+
if (heights.min !== undefined) {
|
|
1888
|
+
condition.minHeight = Math.max(condition.minHeight ?? 0, heights.min);
|
|
1889
|
+
}
|
|
1890
|
+
if (heights.max !== undefined) {
|
|
1891
|
+
condition.maxHeight = Math.min(
|
|
1892
|
+
condition.maxHeight ?? Infinity,
|
|
1893
|
+
heights.max,
|
|
1894
|
+
);
|
|
1895
|
+
}
|
|
1896
|
+
sawHeight = true;
|
|
1897
|
+
continue;
|
|
1898
|
+
}
|
|
1783
1899
|
const feature = mediaFeature(term);
|
|
1784
1900
|
if (feature) {
|
|
1785
1901
|
const key = feature[1].toLowerCase();
|
|
1786
1902
|
const len = parseLength(feature[2], ZERO_UNITS);
|
|
1787
1903
|
const px = typeof len === 'number' ? len : null;
|
|
1788
|
-
if (
|
|
1904
|
+
if (
|
|
1905
|
+
(key === 'min-width' || key === 'min-device-width') &&
|
|
1906
|
+
px !== null
|
|
1907
|
+
) {
|
|
1789
1908
|
condition.min = Math.max(condition.min ?? 0, px);
|
|
1790
1909
|
sawWidth = true;
|
|
1791
|
-
} else if (
|
|
1910
|
+
} else if (
|
|
1911
|
+
(key === 'max-width' || key === 'max-device-width') &&
|
|
1912
|
+
px !== null
|
|
1913
|
+
) {
|
|
1792
1914
|
condition.max = Math.min(condition.max ?? Infinity, px);
|
|
1793
1915
|
sawWidth = true;
|
|
1916
|
+
} else if (
|
|
1917
|
+
px === null &&
|
|
1918
|
+
/^(?:min-|max-)?(?:device-)?(?:width|height)$/.test(key)
|
|
1919
|
+
) {
|
|
1920
|
+
// a size that is no length is a query that does not parse, which
|
|
1921
|
+
// Media Queries 4 (3.2) makes `not all`: `(min-width:0\0)`, the
|
|
1922
|
+
// hack that kept a block for Internet Explorer 9 and 10, held
|
|
1923
|
+
// here as a query that asked nothing, and its rules applied
|
|
1924
|
+
pass = false;
|
|
1925
|
+
} else if (
|
|
1926
|
+
(key === 'min-height' || key === 'min-device-height') &&
|
|
1927
|
+
px !== null
|
|
1928
|
+
) {
|
|
1929
|
+
// the viewport's height, answered live as its width is: a
|
|
1930
|
+
// design that sets its heading's size by the window's height
|
|
1931
|
+
// took the tallest step's at every height
|
|
1932
|
+
condition.minHeight = Math.max(condition.minHeight ?? 0, px);
|
|
1933
|
+
sawHeight = true;
|
|
1934
|
+
} else if (
|
|
1935
|
+
(key === 'max-height' || key === 'max-device-height') &&
|
|
1936
|
+
px !== null
|
|
1937
|
+
) {
|
|
1938
|
+
condition.maxHeight = Math.min(condition.maxHeight ?? Infinity, px);
|
|
1939
|
+
sawHeight = true;
|
|
1794
1940
|
} else if (key === 'prefers-color-scheme') {
|
|
1795
1941
|
// Answered live, from the palette in force: a document dropped
|
|
1796
1942
|
// into a dark application takes its dark branch, and follows the
|
|
@@ -1809,7 +1955,73 @@ export function parseMediaQuery(prelude: string): MediaCondition[] {
|
|
|
1809
1955
|
// renderer needs.
|
|
1810
1956
|
pass = false;
|
|
1811
1957
|
} else if (key === 'orientation') {
|
|
1812
|
-
|
|
1958
|
+
// the viewport's, as its aspect ratio: portrait where its height
|
|
1959
|
+
// is at least its width (Media Queries 4, 4.5)
|
|
1960
|
+
const orientation = feature[2].trim().toLowerCase();
|
|
1961
|
+
if (orientation === 'portrait') {
|
|
1962
|
+
condition.maxAspect = Math.min(condition.maxAspect ?? Infinity, 1);
|
|
1963
|
+
sawAspect = true;
|
|
1964
|
+
} else if (orientation === 'landscape') {
|
|
1965
|
+
condition.minAspect = Math.max(
|
|
1966
|
+
condition.minAspect ?? 0,
|
|
1967
|
+
1 + ASPECT_EDGE,
|
|
1968
|
+
);
|
|
1969
|
+
sawAspect = true;
|
|
1970
|
+
} else pass = false;
|
|
1971
|
+
} else if (ASPECT_FEATURES.test(key)) {
|
|
1972
|
+
const ratio = ratioOf(feature[2]);
|
|
1973
|
+
if (ratio === null) pass = false;
|
|
1974
|
+
else {
|
|
1975
|
+
if (!key.startsWith('max-')) {
|
|
1976
|
+
condition.minAspect = Math.max(condition.minAspect ?? 0, ratio);
|
|
1977
|
+
}
|
|
1978
|
+
if (!key.startsWith('min-')) {
|
|
1979
|
+
condition.maxAspect = Math.min(
|
|
1980
|
+
condition.maxAspect ?? Infinity,
|
|
1981
|
+
ratio,
|
|
1982
|
+
);
|
|
1983
|
+
}
|
|
1984
|
+
sawAspect = true;
|
|
1985
|
+
}
|
|
1986
|
+
} else if (RESOLUTION_FEATURES.has(key)) {
|
|
1987
|
+
const dppx = resolutionOf(feature[2], key.startsWith('-webkit-'));
|
|
1988
|
+
if (dppx === null) pass = false;
|
|
1989
|
+
else {
|
|
1990
|
+
if (!key.includes('max-')) {
|
|
1991
|
+
condition.minResolution = Math.max(
|
|
1992
|
+
condition.minResolution ?? 0,
|
|
1993
|
+
dppx,
|
|
1994
|
+
);
|
|
1995
|
+
}
|
|
1996
|
+
if (!key.includes('min-')) {
|
|
1997
|
+
condition.maxResolution = Math.min(
|
|
1998
|
+
condition.maxResolution ?? Infinity,
|
|
1999
|
+
dppx,
|
|
2000
|
+
);
|
|
2001
|
+
}
|
|
2002
|
+
sawResolution = true;
|
|
2003
|
+
}
|
|
2004
|
+
} else if (desktopFeature(key, feature[2]) !== true) {
|
|
2005
|
+
// any other feature as a desktop screen with a mouse answers
|
|
2006
|
+
// it, and one nothing knows is false (Media Queries 4, 3.2):
|
|
2007
|
+
// Firefox's and Opera's resolution features, which a design lists
|
|
2008
|
+
// beside WebKit's, held here, and a page at one dot to the pixel
|
|
2009
|
+
// took the rules it keeps for two
|
|
2010
|
+
pass = false;
|
|
2011
|
+
}
|
|
2012
|
+
continue;
|
|
2013
|
+
}
|
|
2014
|
+
const flag = /^\(\s*(-?[a-z][a-z0-9-]*)\s*\)$/i.exec(term);
|
|
2015
|
+
if (flag) {
|
|
2016
|
+
// a feature in the boolean context, `(hover)`: true where its value
|
|
2017
|
+
// is anything but zero or none
|
|
2018
|
+
const key = flag[1].toLowerCase();
|
|
2019
|
+
if (
|
|
2020
|
+
!BOOLEAN_TRUE.has(key) &&
|
|
2021
|
+
!RESOLUTION_FEATURES.has(key) &&
|
|
2022
|
+
desktopFeature(key, null) !== true
|
|
2023
|
+
) {
|
|
2024
|
+
pass = false;
|
|
1813
2025
|
}
|
|
1814
2026
|
continue;
|
|
1815
2027
|
}
|
|
@@ -1825,11 +2037,23 @@ export function parseMediaQuery(prelude: string): MediaCondition[] {
|
|
|
1825
2037
|
// `not` over a scheme is the other scheme. `not` over a width range
|
|
1826
2038
|
// is not expressible as one range; the honest reduction is to decide
|
|
1827
2039
|
// it statically rather than invert it wrongly.
|
|
1828
|
-
if (
|
|
2040
|
+
if (
|
|
2041
|
+
!sawWidth &&
|
|
2042
|
+
!sawHeight &&
|
|
2043
|
+
!sawResolution &&
|
|
2044
|
+
!sawAspect &&
|
|
2045
|
+
pass &&
|
|
2046
|
+
condition.scheme
|
|
2047
|
+
) {
|
|
1829
2048
|
out.push({ scheme: condition.scheme === 'dark' ? 'light' : 'dark' });
|
|
1830
2049
|
continue;
|
|
1831
2050
|
}
|
|
1832
|
-
out.push({
|
|
2051
|
+
out.push({
|
|
2052
|
+
staticPass:
|
|
2053
|
+
!sawWidth && !sawHeight && !sawResolution && !sawAspect && pass
|
|
2054
|
+
? false
|
|
2055
|
+
: !pass,
|
|
2056
|
+
});
|
|
1833
2057
|
continue;
|
|
1834
2058
|
}
|
|
1835
2059
|
if (!pass) {
|
|
@@ -1838,6 +2062,9 @@ export function parseMediaQuery(prelude: string): MediaCondition[] {
|
|
|
1838
2062
|
}
|
|
1839
2063
|
if (
|
|
1840
2064
|
!sawWidth &&
|
|
2065
|
+
!sawHeight &&
|
|
2066
|
+
!sawResolution &&
|
|
2067
|
+
!sawAspect &&
|
|
1841
2068
|
condition.scheme === undefined &&
|
|
1842
2069
|
condition.min === undefined &&
|
|
1843
2070
|
condition.max === undefined
|
|
@@ -1936,6 +2163,123 @@ export function supportsCondition(prelude: string): boolean | null {
|
|
|
1936
2163
|
* first `)` found no feature there — the term was passed over as though it
|
|
1937
2164
|
* said nothing, and every narrow-screen rule held at every width.
|
|
1938
2165
|
*/
|
|
2166
|
+
/** `aspect-ratio` and the device's, bare or `min-`/`max-`. */
|
|
2167
|
+
const ASPECT_FEATURES = /^(?:min-|max-)?(?:device-)?aspect-ratio$/;
|
|
2168
|
+
|
|
2169
|
+
/** How far past square a landscape viewport is: any width over the height. */
|
|
2170
|
+
const ASPECT_EDGE = 1e-9;
|
|
2171
|
+
|
|
2172
|
+
/** A ratio, `16/9` or `16 / 9` or `1.5`, as a number; null for anything
|
|
2173
|
+
* else, or a ratio of nothing. */
|
|
2174
|
+
function ratioOf(value: string): number | null {
|
|
2175
|
+
const m = /^\s*(\d*\.?\d+)\s*(?:\/\s*(\d*\.?\d+)\s*)?$/.exec(value);
|
|
2176
|
+
if (!m) return null;
|
|
2177
|
+
const ratio = Number(m[1]) / (m[2] === undefined ? 1 : Number(m[2]));
|
|
2178
|
+
return Number.isFinite(ratio) && ratio > 0 ? ratio : null;
|
|
2179
|
+
}
|
|
2180
|
+
|
|
2181
|
+
/** The resolution features, the standard's and WebKit's, which Chrome
|
|
2182
|
+
* answers; `min--moz-device-pixel-ratio` and `-o-min-device-pixel-ratio`
|
|
2183
|
+
* are no feature of its, and none of this. */
|
|
2184
|
+
const RESOLUTION_FEATURES = new Set([
|
|
2185
|
+
'resolution',
|
|
2186
|
+
'min-resolution',
|
|
2187
|
+
'max-resolution',
|
|
2188
|
+
'-webkit-device-pixel-ratio',
|
|
2189
|
+
'-webkit-min-device-pixel-ratio',
|
|
2190
|
+
'-webkit-max-device-pixel-ratio',
|
|
2191
|
+
]);
|
|
2192
|
+
|
|
2193
|
+
/** A resolution in dots per CSS pixel: `2dppx`, `2x`, `192dpi`, `75.6dpcm`,
|
|
2194
|
+
* or WebKit's device pixel ratio, a number. Null for anything else. */
|
|
2195
|
+
function resolutionOf(value: string, ratio: boolean): number | null {
|
|
2196
|
+
const m = /^\s*(\d*\.?\d+(?:e[+-]?\d+)?)\s*(dppx|x|dpi|dpcm)?\s*$/i.exec(
|
|
2197
|
+
value,
|
|
2198
|
+
);
|
|
2199
|
+
if (!m) return null;
|
|
2200
|
+
const n = Number(m[1]);
|
|
2201
|
+
const unit = (m[2] ?? '').toLowerCase();
|
|
2202
|
+
if (ratio) return unit ? null : n;
|
|
2203
|
+
if (unit === 'dppx' || unit === 'x') return n;
|
|
2204
|
+
if (unit === 'dpi') return n / 96;
|
|
2205
|
+
if (unit === 'dpcm') return (n * 2.54) / 96;
|
|
2206
|
+
return null;
|
|
2207
|
+
}
|
|
2208
|
+
|
|
2209
|
+
/** The features true in the boolean context whatever the viewport: a
|
|
2210
|
+
* size, a scheme, an orientation, there is always one. */
|
|
2211
|
+
const BOOLEAN_TRUE = new Set([
|
|
2212
|
+
'width',
|
|
2213
|
+
'height',
|
|
2214
|
+
'device-width',
|
|
2215
|
+
'device-height',
|
|
2216
|
+
'aspect-ratio',
|
|
2217
|
+
'orientation',
|
|
2218
|
+
'prefers-color-scheme',
|
|
2219
|
+
]);
|
|
2220
|
+
|
|
2221
|
+
/**
|
|
2222
|
+
* A media feature as a desktop screen driven by a mouse answers it — the
|
|
2223
|
+
* device `<Html>` draws on — for the features that do not depend on the
|
|
2224
|
+
* viewport: true, false, or null for a feature nothing knows, which is
|
|
2225
|
+
* false (Media Queries 4, 3.2). `value` is null in the boolean context.
|
|
2226
|
+
* The colour is eight bits a component on no palette and no grid; scripts
|
|
2227
|
+
* never run here, so `scripting` is `none`.
|
|
2228
|
+
*/
|
|
2229
|
+
function desktopFeature(key: string, value: string | null): boolean | null {
|
|
2230
|
+
const v = value?.trim().toLowerCase() ?? null;
|
|
2231
|
+
const numeric = (feature: string, has: number): boolean | null => {
|
|
2232
|
+
const bare = key.replace(/^(min|max)-/, '');
|
|
2233
|
+
if (bare !== feature) return null;
|
|
2234
|
+
if (v === null) return key === feature ? has !== 0 : null;
|
|
2235
|
+
const n = Number(v);
|
|
2236
|
+
if (!Number.isFinite(n)) return false;
|
|
2237
|
+
if (key.startsWith('min-')) return has >= n;
|
|
2238
|
+
if (key.startsWith('max-')) return has <= n;
|
|
2239
|
+
return has === n;
|
|
2240
|
+
};
|
|
2241
|
+
const keyword = (answers: Record<string, boolean>, bool: boolean) =>
|
|
2242
|
+
v === null ? bool : (answers[v] ?? false);
|
|
2243
|
+
switch (key) {
|
|
2244
|
+
case 'hover':
|
|
2245
|
+
case 'any-hover':
|
|
2246
|
+
return keyword({ hover: true, none: false }, true);
|
|
2247
|
+
case 'pointer':
|
|
2248
|
+
case 'any-pointer':
|
|
2249
|
+
return keyword({ fine: true, coarse: false, none: false }, true);
|
|
2250
|
+
case 'update':
|
|
2251
|
+
return keyword({ fast: true, slow: false, none: false }, true);
|
|
2252
|
+
case 'overflow-block':
|
|
2253
|
+
case 'overflow-inline':
|
|
2254
|
+
return keyword({ scroll: true }, true);
|
|
2255
|
+
case 'scan':
|
|
2256
|
+
return false;
|
|
2257
|
+
case 'color-gamut':
|
|
2258
|
+
return keyword({ srgb: true }, true);
|
|
2259
|
+
case 'dynamic-range':
|
|
2260
|
+
case 'video-dynamic-range':
|
|
2261
|
+
return keyword({ standard: true }, true);
|
|
2262
|
+
case 'prefers-contrast':
|
|
2263
|
+
case 'prefers-reduced-transparency':
|
|
2264
|
+
case 'prefers-reduced-data':
|
|
2265
|
+
return keyword({ 'no-preference': true }, false);
|
|
2266
|
+
case 'forced-colors':
|
|
2267
|
+
return keyword({ none: true }, false);
|
|
2268
|
+
case 'scripting':
|
|
2269
|
+
return keyword({ none: true }, false);
|
|
2270
|
+
case 'display-mode':
|
|
2271
|
+
return keyword({ browser: true }, true);
|
|
2272
|
+
case '-webkit-transform-3d':
|
|
2273
|
+
return v === null || v === '1';
|
|
2274
|
+
}
|
|
2275
|
+
return (
|
|
2276
|
+
numeric('color', 8) ??
|
|
2277
|
+
numeric('monochrome', 0) ??
|
|
2278
|
+
numeric('color-index', 0) ??
|
|
2279
|
+
numeric('grid', 0)
|
|
2280
|
+
);
|
|
2281
|
+
}
|
|
2282
|
+
|
|
1939
2283
|
function mediaFeature(term: string): [string, string, string] | null {
|
|
1940
2284
|
const m = /^\(\s*([a-z-]+)\s*:([\s\S]*)\)$/i.exec(term);
|
|
1941
2285
|
if (!m) return null;
|
|
@@ -1959,6 +2303,11 @@ export function mediaMatches(
|
|
|
1959
2303
|
media: MediaCondition[][] | null,
|
|
1960
2304
|
width: number,
|
|
1961
2305
|
scheme: 'light' | 'dark' = 'light',
|
|
2306
|
+
/** The viewport's height in CSS pixels; a test of it holds at no height
|
|
2307
|
+
* where none is given. */
|
|
2308
|
+
height = NaN,
|
|
2309
|
+
/** The display's scale, device pixels to the CSS pixel. */
|
|
2310
|
+
resolution = 1,
|
|
1962
2311
|
): boolean {
|
|
1963
2312
|
if (!media) return true;
|
|
1964
2313
|
for (const block of media) {
|
|
@@ -1971,6 +2320,12 @@ export function mediaMatches(
|
|
|
1971
2320
|
if (
|
|
1972
2321
|
(c.min === undefined || width >= c.min) &&
|
|
1973
2322
|
(c.max === undefined || width <= c.max) &&
|
|
2323
|
+
(c.minHeight === undefined || height >= c.minHeight) &&
|
|
2324
|
+
(c.maxHeight === undefined || height <= c.maxHeight) &&
|
|
2325
|
+
(c.minResolution === undefined || resolution >= c.minResolution) &&
|
|
2326
|
+
(c.minAspect === undefined || width >= c.minAspect * height) &&
|
|
2327
|
+
(c.maxAspect === undefined || width <= c.maxAspect * height) &&
|
|
2328
|
+
(c.maxResolution === undefined || resolution <= c.maxResolution) &&
|
|
1974
2329
|
(c.scheme === undefined || c.scheme === scheme)
|
|
1975
2330
|
) {
|
|
1976
2331
|
any = true;
|