@react-x11/components 0.12.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.
- package/dist/html/controls.d.ts +12 -3
- package/dist/html/controls.d.ts.map +1 -1
- package/dist/html/controls.js +15 -55
- 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 +519 -122
- package/dist/html/css/cascade.js.map +1 -1
- package/dist/html/css/parse.d.ts +29 -2
- package/dist/html/css/parse.d.ts.map +1 -1
- package/dist/html/css/parse.js +277 -9
- package/dist/html/css/parse.js.map +1 -1
- package/dist/html/css/style.d.ts +14 -0
- package/dist/html/css/style.d.ts.map +1 -1
- package/dist/html/css/style.js +2 -3
- package/dist/html/css/style.js.map +1 -1
- 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 +59 -7
- package/dist/html/css/ua.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 +25 -1
- package/dist/html/fonts.d.ts.map +1 -1
- package/dist/html/fonts.js +99 -6
- 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/block.d.ts.map +1 -1
- package/dist/html/layout/block.js +5 -1
- package/dist/html/layout/block.js.map +1 -1
- package/dist/html/layout/boxes.d.ts +31 -1
- package/dist/html/layout/boxes.d.ts.map +1 -1
- package/dist/html/layout/boxes.js +52 -8
- package/dist/html/layout/boxes.js.map +1 -1
- package/dist/html/layout/flex.js +4 -1
- package/dist/html/layout/flex.js.map +1 -1
- package/dist/html/layout/inline.d.ts.map +1 -1
- package/dist/html/layout/inline.js +10 -0
- package/dist/html/layout/inline.js.map +1 -1
- package/dist/html/node.d.ts +87 -15
- package/dist/html/node.d.ts.map +1 -1
- package/dist/html/node.js +510 -152
- package/dist/html/node.js.map +1 -1
- package/dist/html/paint.d.ts +22 -1
- package/dist/html/paint.d.ts.map +1 -1
- package/dist/html/paint.js +290 -20
- package/dist/html/paint.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 +620 -0
- package/dist/html/widgets.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/package.json +2 -2
- package/src/html/controls.ts +29 -49
- package/src/html/css/cascade.ts +591 -140
- package/src/html/css/parse.ts +324 -9
- package/src/html/css/style.ts +24 -3
- package/src/html/css/ua.ts +62 -8
- package/src/html/dom.ts +3 -1
- package/src/html/fonts.ts +116 -6
- package/src/html/form.ts +962 -0
- package/src/html/index.ts +49 -338
- package/src/html/layout/block.ts +3 -0
- package/src/html/layout/boxes.ts +78 -11
- package/src/html/layout/flex.ts +4 -1
- package/src/html/layout/inline.ts +10 -0
- package/src/html/node.ts +498 -135
- package/src/html/paint.ts +330 -29
- package/src/html/widgets.ts +735 -0
- package/src/index.ts +1 -0
package/src/html/css/parse.ts
CHANGED
|
@@ -60,6 +60,14 @@ export interface Stylesheet {
|
|
|
60
60
|
* keeps these so a resize can tell "the layout changed" from "the
|
|
61
61
|
* *cascade* changed", and restyle only when it crossed one. */
|
|
62
62
|
breakpoints: number[];
|
|
63
|
+
/** Whether a `@media` rule in this sheet tests the viewport's height,
|
|
64
|
+
* which makes a document restyle when the height moves, as a `vh`
|
|
65
|
+
* does. */
|
|
66
|
+
readsHeight?: boolean;
|
|
67
|
+
/** Whether one tests its aspect ratio, which a width that crosses no
|
|
68
|
+
* breakpoint can change: the document restyles as one with a `vw`
|
|
69
|
+
* does. */
|
|
70
|
+
readsWidth?: boolean;
|
|
63
71
|
/** The `@font-face` rules, in order (see `fonts.ts`). */
|
|
64
72
|
fontFaces: FontFaceRule[];
|
|
65
73
|
/** `@counter-style` rules, in order: a name and its descriptors, which
|
|
@@ -94,12 +102,26 @@ export interface FontFaceSource {
|
|
|
94
102
|
format: string | null;
|
|
95
103
|
}
|
|
96
104
|
|
|
97
|
-
/** The tests this evaluates live: a width, a colour scheme
|
|
105
|
+
/** The tests this evaluates live: a width, a height, a colour scheme.
|
|
98
106
|
* Anything else — `orientation`, `print`, `prefers-reduced-motion` — is
|
|
99
107
|
* decided once, at parse time, by `staticPass`. */
|
|
100
108
|
export interface MediaCondition {
|
|
101
109
|
min?: number;
|
|
102
110
|
max?: number;
|
|
111
|
+
/** The viewport's height, as `min`/`max` its width: `min-height`,
|
|
112
|
+
* `max-height` and a range on `height` (Media Queries 4, 4.2). */
|
|
113
|
+
minHeight?: number;
|
|
114
|
+
maxHeight?: number;
|
|
115
|
+
/** The display's resolution in dots per CSS pixel, its scale: `min-`
|
|
116
|
+
* and `max-resolution`, and WebKit's `-webkit-min-device-pixel-ratio`,
|
|
117
|
+
* which a design writes to swap in its high-DPI images. */
|
|
118
|
+
minResolution?: number;
|
|
119
|
+
maxResolution?: number;
|
|
120
|
+
/** The viewport's width over its height: `aspect-ratio` and its `min-`
|
|
121
|
+
* and `max-`, the device's the viewport's too, and `orientation`, which
|
|
122
|
+
* is landscape where the width is the greater. */
|
|
123
|
+
minAspect?: number;
|
|
124
|
+
maxAspect?: number;
|
|
103
125
|
/** `prefers-color-scheme`, answered from the palette in force. */
|
|
104
126
|
scheme?: 'light' | 'dark';
|
|
105
127
|
/** Set when the query could not be evaluated as a width or scheme test:
|
|
@@ -229,6 +251,13 @@ export function parseStylesheet(
|
|
|
229
251
|
for (const c of conditions) {
|
|
230
252
|
if (c.min !== undefined) breakpoints.add(c.min);
|
|
231
253
|
if (c.max !== undefined) breakpoints.add(c.max + MAX_EDGE);
|
|
254
|
+
if (c.minHeight !== undefined || c.maxHeight !== undefined) {
|
|
255
|
+
sheet.readsHeight = true;
|
|
256
|
+
}
|
|
257
|
+
if (c.minAspect !== undefined || c.maxAspect !== undefined) {
|
|
258
|
+
sheet.readsHeight = true;
|
|
259
|
+
sheet.readsWidth = true;
|
|
260
|
+
}
|
|
232
261
|
}
|
|
233
262
|
// A nested `@media` intersects with the one above it; pushing a
|
|
234
263
|
// level rather than merging keeps "all of these blocks hold" exact
|
|
@@ -335,6 +364,13 @@ export function parseStylesheet(
|
|
|
335
364
|
for (const c of conditions) {
|
|
336
365
|
if (c.min !== undefined) breakpoints.add(c.min);
|
|
337
366
|
if (c.max !== undefined) breakpoints.add(c.max + MAX_EDGE);
|
|
367
|
+
if (c.minHeight !== undefined || c.maxHeight !== undefined) {
|
|
368
|
+
sheet.readsHeight = true;
|
|
369
|
+
}
|
|
370
|
+
if (c.minAspect !== undefined || c.maxAspect !== undefined) {
|
|
371
|
+
sheet.readsHeight = true;
|
|
372
|
+
sheet.readsWidth = true;
|
|
373
|
+
}
|
|
338
374
|
}
|
|
339
375
|
styleRule(
|
|
340
376
|
selectors,
|
|
@@ -1355,17 +1391,36 @@ function readAtRule(text: string, at: number): AtRule {
|
|
|
1355
1391
|
* divides a device width into (`640.5` at 2x) fall on the right side. */
|
|
1356
1392
|
const MAX_EDGE = 1 / 64;
|
|
1357
1393
|
|
|
1394
|
+
/**
|
|
1395
|
+
* The media features that are the viewport's width and its height here.
|
|
1396
|
+
* `device-width` and `device-height` are the Web-exposed screen area's
|
|
1397
|
+
* (Media Queries 4, appendix A), which a user agent may answer with the
|
|
1398
|
+
* viewport's (CSSOM View 2.3), and a document drawn into an element has no
|
|
1399
|
+
* screen of its own to answer with. Deprecated, and still what a page from
|
|
1400
|
+
* before `width` asks with: DuckDuckGo Lite's phone sheet is
|
|
1401
|
+
* `(max-device-width: 700px)`, and a query that went unread held, so a
|
|
1402
|
+
* desktop window got the phone's 12px dropdowns.
|
|
1403
|
+
*/
|
|
1404
|
+
const VIEWPORT_SIDES = {
|
|
1405
|
+
width: new Set(['width', 'device-width']),
|
|
1406
|
+
height: new Set(['height', 'device-height']),
|
|
1407
|
+
};
|
|
1408
|
+
|
|
1358
1409
|
/**
|
|
1359
1410
|
* A width range in Media Queries 4's syntax — `(width >= 48rem)`, `(60rem >
|
|
1360
1411
|
* width)`, `(40rem <= width < 60rem)` — as the widths it holds between, or
|
|
1361
1412
|
* null for a term that is not one. A strict bound is a sixty-fourth of a
|
|
1362
1413
|
* pixel inside the value, where a viewport's width never lands.
|
|
1363
1414
|
*/
|
|
1364
|
-
function widthRange(
|
|
1415
|
+
function widthRange(
|
|
1416
|
+
term: string,
|
|
1417
|
+
feature: 'width' | 'height' = 'width',
|
|
1418
|
+
): { min?: number; max?: number } | null {
|
|
1365
1419
|
const inner = /^\(\s*(.*?)\s*\)$/.exec(term)?.[1];
|
|
1366
1420
|
if (!inner || !/[<>=]/.test(inner)) return null;
|
|
1367
1421
|
const parts = inner.split(/\s*(<=|>=|<|>|=)\s*/);
|
|
1368
|
-
const isWidth = (part: string) =>
|
|
1422
|
+
const isWidth = (part: string) =>
|
|
1423
|
+
VIEWPORT_SIDES[feature].has(part.toLowerCase());
|
|
1369
1424
|
const out: { min?: number; max?: number } = {};
|
|
1370
1425
|
// `width OP value`, with the operator read from the width's side
|
|
1371
1426
|
const bound = (op: string, value: string): boolean => {
|
|
@@ -1760,8 +1815,15 @@ export function parseMediaQuery(prelude: string): MediaCondition[] {
|
|
|
1760
1815
|
const condition: MediaCondition = {};
|
|
1761
1816
|
let pass = true;
|
|
1762
1817
|
let sawWidth = false;
|
|
1818
|
+
let sawHeight = false;
|
|
1819
|
+
let sawResolution = false;
|
|
1820
|
+
let sawAspect = false;
|
|
1763
1821
|
const negated = /^\s*not\b/i.test(group);
|
|
1764
|
-
|
|
1822
|
+
// `and` needs white space after it, where a `(` would make it a
|
|
1823
|
+
// function, and none after the `)` it follows: DuckDuckGo writes
|
|
1824
|
+
// `(max-device-width: 701px)and (orientation: landscape)`, which read
|
|
1825
|
+
// as one term that was no feature, and held
|
|
1826
|
+
for (const part of group.split(/(?:\s+|(?<=\)))and\s+/i)) {
|
|
1765
1827
|
const term = part
|
|
1766
1828
|
.trim()
|
|
1767
1829
|
.replace(/^not\s+/i, '')
|
|
@@ -1780,17 +1842,61 @@ export function parseMediaQuery(prelude: string): MediaCondition[] {
|
|
|
1780
1842
|
sawWidth = true;
|
|
1781
1843
|
continue;
|
|
1782
1844
|
}
|
|
1845
|
+
const heights = widthRange(term, 'height');
|
|
1846
|
+
if (heights) {
|
|
1847
|
+
if (heights.min !== undefined) {
|
|
1848
|
+
condition.minHeight = Math.max(condition.minHeight ?? 0, heights.min);
|
|
1849
|
+
}
|
|
1850
|
+
if (heights.max !== undefined) {
|
|
1851
|
+
condition.maxHeight = Math.min(
|
|
1852
|
+
condition.maxHeight ?? Infinity,
|
|
1853
|
+
heights.max,
|
|
1854
|
+
);
|
|
1855
|
+
}
|
|
1856
|
+
sawHeight = true;
|
|
1857
|
+
continue;
|
|
1858
|
+
}
|
|
1783
1859
|
const feature = mediaFeature(term);
|
|
1784
1860
|
if (feature) {
|
|
1785
1861
|
const key = feature[1].toLowerCase();
|
|
1786
1862
|
const len = parseLength(feature[2], ZERO_UNITS);
|
|
1787
1863
|
const px = typeof len === 'number' ? len : null;
|
|
1788
|
-
if (
|
|
1864
|
+
if (
|
|
1865
|
+
(key === 'min-width' || key === 'min-device-width') &&
|
|
1866
|
+
px !== null
|
|
1867
|
+
) {
|
|
1789
1868
|
condition.min = Math.max(condition.min ?? 0, px);
|
|
1790
1869
|
sawWidth = true;
|
|
1791
|
-
} else if (
|
|
1870
|
+
} else if (
|
|
1871
|
+
(key === 'max-width' || key === 'max-device-width') &&
|
|
1872
|
+
px !== null
|
|
1873
|
+
) {
|
|
1792
1874
|
condition.max = Math.min(condition.max ?? Infinity, px);
|
|
1793
1875
|
sawWidth = true;
|
|
1876
|
+
} else if (
|
|
1877
|
+
px === null &&
|
|
1878
|
+
/^(?:min-|max-)?(?:device-)?(?:width|height)$/.test(key)
|
|
1879
|
+
) {
|
|
1880
|
+
// a size that is no length is a query that does not parse, which
|
|
1881
|
+
// Media Queries 4 (3.2) makes `not all`: `(min-width:0\0)`, the
|
|
1882
|
+
// hack that kept a block for Internet Explorer 9 and 10, held
|
|
1883
|
+
// here as a query that asked nothing, and its rules applied
|
|
1884
|
+
pass = false;
|
|
1885
|
+
} else if (
|
|
1886
|
+
(key === 'min-height' || key === 'min-device-height') &&
|
|
1887
|
+
px !== null
|
|
1888
|
+
) {
|
|
1889
|
+
// the viewport's height, answered live as its width is: a
|
|
1890
|
+
// design that sets its heading's size by the window's height
|
|
1891
|
+
// took the tallest step's at every height
|
|
1892
|
+
condition.minHeight = Math.max(condition.minHeight ?? 0, px);
|
|
1893
|
+
sawHeight = true;
|
|
1894
|
+
} else if (
|
|
1895
|
+
(key === 'max-height' || key === 'max-device-height') &&
|
|
1896
|
+
px !== null
|
|
1897
|
+
) {
|
|
1898
|
+
condition.maxHeight = Math.min(condition.maxHeight ?? Infinity, px);
|
|
1899
|
+
sawHeight = true;
|
|
1794
1900
|
} else if (key === 'prefers-color-scheme') {
|
|
1795
1901
|
// Answered live, from the palette in force: a document dropped
|
|
1796
1902
|
// into a dark application takes its dark branch, and follows the
|
|
@@ -1809,7 +1915,73 @@ export function parseMediaQuery(prelude: string): MediaCondition[] {
|
|
|
1809
1915
|
// renderer needs.
|
|
1810
1916
|
pass = false;
|
|
1811
1917
|
} else if (key === 'orientation') {
|
|
1812
|
-
|
|
1918
|
+
// the viewport's, as its aspect ratio: portrait where its height
|
|
1919
|
+
// is at least its width (Media Queries 4, 4.5)
|
|
1920
|
+
const orientation = feature[2].trim().toLowerCase();
|
|
1921
|
+
if (orientation === 'portrait') {
|
|
1922
|
+
condition.maxAspect = Math.min(condition.maxAspect ?? Infinity, 1);
|
|
1923
|
+
sawAspect = true;
|
|
1924
|
+
} else if (orientation === 'landscape') {
|
|
1925
|
+
condition.minAspect = Math.max(
|
|
1926
|
+
condition.minAspect ?? 0,
|
|
1927
|
+
1 + ASPECT_EDGE,
|
|
1928
|
+
);
|
|
1929
|
+
sawAspect = true;
|
|
1930
|
+
} else pass = false;
|
|
1931
|
+
} else if (ASPECT_FEATURES.test(key)) {
|
|
1932
|
+
const ratio = ratioOf(feature[2]);
|
|
1933
|
+
if (ratio === null) pass = false;
|
|
1934
|
+
else {
|
|
1935
|
+
if (!key.startsWith('max-')) {
|
|
1936
|
+
condition.minAspect = Math.max(condition.minAspect ?? 0, ratio);
|
|
1937
|
+
}
|
|
1938
|
+
if (!key.startsWith('min-')) {
|
|
1939
|
+
condition.maxAspect = Math.min(
|
|
1940
|
+
condition.maxAspect ?? Infinity,
|
|
1941
|
+
ratio,
|
|
1942
|
+
);
|
|
1943
|
+
}
|
|
1944
|
+
sawAspect = true;
|
|
1945
|
+
}
|
|
1946
|
+
} else if (RESOLUTION_FEATURES.has(key)) {
|
|
1947
|
+
const dppx = resolutionOf(feature[2], key.startsWith('-webkit-'));
|
|
1948
|
+
if (dppx === null) pass = false;
|
|
1949
|
+
else {
|
|
1950
|
+
if (!key.includes('max-')) {
|
|
1951
|
+
condition.minResolution = Math.max(
|
|
1952
|
+
condition.minResolution ?? 0,
|
|
1953
|
+
dppx,
|
|
1954
|
+
);
|
|
1955
|
+
}
|
|
1956
|
+
if (!key.includes('min-')) {
|
|
1957
|
+
condition.maxResolution = Math.min(
|
|
1958
|
+
condition.maxResolution ?? Infinity,
|
|
1959
|
+
dppx,
|
|
1960
|
+
);
|
|
1961
|
+
}
|
|
1962
|
+
sawResolution = true;
|
|
1963
|
+
}
|
|
1964
|
+
} else if (desktopFeature(key, feature[2]) !== true) {
|
|
1965
|
+
// any other feature as a desktop screen with a mouse answers
|
|
1966
|
+
// it, and one nothing knows is false (Media Queries 4, 3.2):
|
|
1967
|
+
// Firefox's and Opera's resolution features, which a design lists
|
|
1968
|
+
// beside WebKit's, held here, and a page at one dot to the pixel
|
|
1969
|
+
// took the rules it keeps for two
|
|
1970
|
+
pass = false;
|
|
1971
|
+
}
|
|
1972
|
+
continue;
|
|
1973
|
+
}
|
|
1974
|
+
const flag = /^\(\s*(-?[a-z][a-z0-9-]*)\s*\)$/i.exec(term);
|
|
1975
|
+
if (flag) {
|
|
1976
|
+
// a feature in the boolean context, `(hover)`: true where its value
|
|
1977
|
+
// is anything but zero or none
|
|
1978
|
+
const key = flag[1].toLowerCase();
|
|
1979
|
+
if (
|
|
1980
|
+
!BOOLEAN_TRUE.has(key) &&
|
|
1981
|
+
!RESOLUTION_FEATURES.has(key) &&
|
|
1982
|
+
desktopFeature(key, null) !== true
|
|
1983
|
+
) {
|
|
1984
|
+
pass = false;
|
|
1813
1985
|
}
|
|
1814
1986
|
continue;
|
|
1815
1987
|
}
|
|
@@ -1825,11 +1997,23 @@ export function parseMediaQuery(prelude: string): MediaCondition[] {
|
|
|
1825
1997
|
// `not` over a scheme is the other scheme. `not` over a width range
|
|
1826
1998
|
// is not expressible as one range; the honest reduction is to decide
|
|
1827
1999
|
// it statically rather than invert it wrongly.
|
|
1828
|
-
if (
|
|
2000
|
+
if (
|
|
2001
|
+
!sawWidth &&
|
|
2002
|
+
!sawHeight &&
|
|
2003
|
+
!sawResolution &&
|
|
2004
|
+
!sawAspect &&
|
|
2005
|
+
pass &&
|
|
2006
|
+
condition.scheme
|
|
2007
|
+
) {
|
|
1829
2008
|
out.push({ scheme: condition.scheme === 'dark' ? 'light' : 'dark' });
|
|
1830
2009
|
continue;
|
|
1831
2010
|
}
|
|
1832
|
-
out.push({
|
|
2011
|
+
out.push({
|
|
2012
|
+
staticPass:
|
|
2013
|
+
!sawWidth && !sawHeight && !sawResolution && !sawAspect && pass
|
|
2014
|
+
? false
|
|
2015
|
+
: !pass,
|
|
2016
|
+
});
|
|
1833
2017
|
continue;
|
|
1834
2018
|
}
|
|
1835
2019
|
if (!pass) {
|
|
@@ -1838,6 +2022,9 @@ export function parseMediaQuery(prelude: string): MediaCondition[] {
|
|
|
1838
2022
|
}
|
|
1839
2023
|
if (
|
|
1840
2024
|
!sawWidth &&
|
|
2025
|
+
!sawHeight &&
|
|
2026
|
+
!sawResolution &&
|
|
2027
|
+
!sawAspect &&
|
|
1841
2028
|
condition.scheme === undefined &&
|
|
1842
2029
|
condition.min === undefined &&
|
|
1843
2030
|
condition.max === undefined
|
|
@@ -1936,6 +2123,123 @@ export function supportsCondition(prelude: string): boolean | null {
|
|
|
1936
2123
|
* first `)` found no feature there — the term was passed over as though it
|
|
1937
2124
|
* said nothing, and every narrow-screen rule held at every width.
|
|
1938
2125
|
*/
|
|
2126
|
+
/** `aspect-ratio` and the device's, bare or `min-`/`max-`. */
|
|
2127
|
+
const ASPECT_FEATURES = /^(?:min-|max-)?(?:device-)?aspect-ratio$/;
|
|
2128
|
+
|
|
2129
|
+
/** How far past square a landscape viewport is: any width over the height. */
|
|
2130
|
+
const ASPECT_EDGE = 1e-9;
|
|
2131
|
+
|
|
2132
|
+
/** A ratio, `16/9` or `16 / 9` or `1.5`, as a number; null for anything
|
|
2133
|
+
* else, or a ratio of nothing. */
|
|
2134
|
+
function ratioOf(value: string): number | null {
|
|
2135
|
+
const m = /^\s*(\d*\.?\d+)\s*(?:\/\s*(\d*\.?\d+)\s*)?$/.exec(value);
|
|
2136
|
+
if (!m) return null;
|
|
2137
|
+
const ratio = Number(m[1]) / (m[2] === undefined ? 1 : Number(m[2]));
|
|
2138
|
+
return Number.isFinite(ratio) && ratio > 0 ? ratio : null;
|
|
2139
|
+
}
|
|
2140
|
+
|
|
2141
|
+
/** The resolution features, the standard's and WebKit's, which Chrome
|
|
2142
|
+
* answers; `min--moz-device-pixel-ratio` and `-o-min-device-pixel-ratio`
|
|
2143
|
+
* are no feature of its, and none of this. */
|
|
2144
|
+
const RESOLUTION_FEATURES = new Set([
|
|
2145
|
+
'resolution',
|
|
2146
|
+
'min-resolution',
|
|
2147
|
+
'max-resolution',
|
|
2148
|
+
'-webkit-device-pixel-ratio',
|
|
2149
|
+
'-webkit-min-device-pixel-ratio',
|
|
2150
|
+
'-webkit-max-device-pixel-ratio',
|
|
2151
|
+
]);
|
|
2152
|
+
|
|
2153
|
+
/** A resolution in dots per CSS pixel: `2dppx`, `2x`, `192dpi`, `75.6dpcm`,
|
|
2154
|
+
* or WebKit's device pixel ratio, a number. Null for anything else. */
|
|
2155
|
+
function resolutionOf(value: string, ratio: boolean): number | null {
|
|
2156
|
+
const m = /^\s*(\d*\.?\d+(?:e[+-]?\d+)?)\s*(dppx|x|dpi|dpcm)?\s*$/i.exec(
|
|
2157
|
+
value,
|
|
2158
|
+
);
|
|
2159
|
+
if (!m) return null;
|
|
2160
|
+
const n = Number(m[1]);
|
|
2161
|
+
const unit = (m[2] ?? '').toLowerCase();
|
|
2162
|
+
if (ratio) return unit ? null : n;
|
|
2163
|
+
if (unit === 'dppx' || unit === 'x') return n;
|
|
2164
|
+
if (unit === 'dpi') return n / 96;
|
|
2165
|
+
if (unit === 'dpcm') return (n * 2.54) / 96;
|
|
2166
|
+
return null;
|
|
2167
|
+
}
|
|
2168
|
+
|
|
2169
|
+
/** The features true in the boolean context whatever the viewport: a
|
|
2170
|
+
* size, a scheme, an orientation, there is always one. */
|
|
2171
|
+
const BOOLEAN_TRUE = new Set([
|
|
2172
|
+
'width',
|
|
2173
|
+
'height',
|
|
2174
|
+
'device-width',
|
|
2175
|
+
'device-height',
|
|
2176
|
+
'aspect-ratio',
|
|
2177
|
+
'orientation',
|
|
2178
|
+
'prefers-color-scheme',
|
|
2179
|
+
]);
|
|
2180
|
+
|
|
2181
|
+
/**
|
|
2182
|
+
* A media feature as a desktop screen driven by a mouse answers it — the
|
|
2183
|
+
* device `<Html>` draws on — for the features that do not depend on the
|
|
2184
|
+
* viewport: true, false, or null for a feature nothing knows, which is
|
|
2185
|
+
* false (Media Queries 4, 3.2). `value` is null in the boolean context.
|
|
2186
|
+
* The colour is eight bits a component on no palette and no grid; scripts
|
|
2187
|
+
* never run here, so `scripting` is `none`.
|
|
2188
|
+
*/
|
|
2189
|
+
function desktopFeature(key: string, value: string | null): boolean | null {
|
|
2190
|
+
const v = value?.trim().toLowerCase() ?? null;
|
|
2191
|
+
const numeric = (feature: string, has: number): boolean | null => {
|
|
2192
|
+
const bare = key.replace(/^(min|max)-/, '');
|
|
2193
|
+
if (bare !== feature) return null;
|
|
2194
|
+
if (v === null) return key === feature ? has !== 0 : null;
|
|
2195
|
+
const n = Number(v);
|
|
2196
|
+
if (!Number.isFinite(n)) return false;
|
|
2197
|
+
if (key.startsWith('min-')) return has >= n;
|
|
2198
|
+
if (key.startsWith('max-')) return has <= n;
|
|
2199
|
+
return has === n;
|
|
2200
|
+
};
|
|
2201
|
+
const keyword = (answers: Record<string, boolean>, bool: boolean) =>
|
|
2202
|
+
v === null ? bool : (answers[v] ?? false);
|
|
2203
|
+
switch (key) {
|
|
2204
|
+
case 'hover':
|
|
2205
|
+
case 'any-hover':
|
|
2206
|
+
return keyword({ hover: true, none: false }, true);
|
|
2207
|
+
case 'pointer':
|
|
2208
|
+
case 'any-pointer':
|
|
2209
|
+
return keyword({ fine: true, coarse: false, none: false }, true);
|
|
2210
|
+
case 'update':
|
|
2211
|
+
return keyword({ fast: true, slow: false, none: false }, true);
|
|
2212
|
+
case 'overflow-block':
|
|
2213
|
+
case 'overflow-inline':
|
|
2214
|
+
return keyword({ scroll: true }, true);
|
|
2215
|
+
case 'scan':
|
|
2216
|
+
return false;
|
|
2217
|
+
case 'color-gamut':
|
|
2218
|
+
return keyword({ srgb: true }, true);
|
|
2219
|
+
case 'dynamic-range':
|
|
2220
|
+
case 'video-dynamic-range':
|
|
2221
|
+
return keyword({ standard: true }, true);
|
|
2222
|
+
case 'prefers-contrast':
|
|
2223
|
+
case 'prefers-reduced-transparency':
|
|
2224
|
+
case 'prefers-reduced-data':
|
|
2225
|
+
return keyword({ 'no-preference': true }, false);
|
|
2226
|
+
case 'forced-colors':
|
|
2227
|
+
return keyword({ none: true }, false);
|
|
2228
|
+
case 'scripting':
|
|
2229
|
+
return keyword({ none: true }, false);
|
|
2230
|
+
case 'display-mode':
|
|
2231
|
+
return keyword({ browser: true }, true);
|
|
2232
|
+
case '-webkit-transform-3d':
|
|
2233
|
+
return v === null || v === '1';
|
|
2234
|
+
}
|
|
2235
|
+
return (
|
|
2236
|
+
numeric('color', 8) ??
|
|
2237
|
+
numeric('monochrome', 0) ??
|
|
2238
|
+
numeric('color-index', 0) ??
|
|
2239
|
+
numeric('grid', 0)
|
|
2240
|
+
);
|
|
2241
|
+
}
|
|
2242
|
+
|
|
1939
2243
|
function mediaFeature(term: string): [string, string, string] | null {
|
|
1940
2244
|
const m = /^\(\s*([a-z-]+)\s*:([\s\S]*)\)$/i.exec(term);
|
|
1941
2245
|
if (!m) return null;
|
|
@@ -1959,6 +2263,11 @@ export function mediaMatches(
|
|
|
1959
2263
|
media: MediaCondition[][] | null,
|
|
1960
2264
|
width: number,
|
|
1961
2265
|
scheme: 'light' | 'dark' = 'light',
|
|
2266
|
+
/** The viewport's height in CSS pixels; a test of it holds at no height
|
|
2267
|
+
* where none is given. */
|
|
2268
|
+
height = NaN,
|
|
2269
|
+
/** The display's scale, device pixels to the CSS pixel. */
|
|
2270
|
+
resolution = 1,
|
|
1962
2271
|
): boolean {
|
|
1963
2272
|
if (!media) return true;
|
|
1964
2273
|
for (const block of media) {
|
|
@@ -1971,6 +2280,12 @@ export function mediaMatches(
|
|
|
1971
2280
|
if (
|
|
1972
2281
|
(c.min === undefined || width >= c.min) &&
|
|
1973
2282
|
(c.max === undefined || width <= c.max) &&
|
|
2283
|
+
(c.minHeight === undefined || height >= c.minHeight) &&
|
|
2284
|
+
(c.maxHeight === undefined || height <= c.maxHeight) &&
|
|
2285
|
+
(c.minResolution === undefined || resolution >= c.minResolution) &&
|
|
2286
|
+
(c.minAspect === undefined || width >= c.minAspect * height) &&
|
|
2287
|
+
(c.maxAspect === undefined || width <= c.maxAspect * height) &&
|
|
2288
|
+
(c.maxResolution === undefined || resolution <= c.maxResolution) &&
|
|
1974
2289
|
(c.scheme === undefined || c.scheme === scheme)
|
|
1975
2290
|
) {
|
|
1976
2291
|
any = true;
|
package/src/html/css/style.ts
CHANGED
|
@@ -700,6 +700,20 @@ export interface RootLook {
|
|
|
700
700
|
controlPadY: number;
|
|
701
701
|
controlBorder: number;
|
|
702
702
|
controlRadius: number;
|
|
703
|
+
/**
|
|
704
|
+
* The size the palette's widgets set their text at, `theme.fontSize` — and
|
|
705
|
+
* so the size of a control's text, whatever the text around it is: a
|
|
706
|
+
* browser's controls take a system font rather than their parent's, and
|
|
707
|
+
* this is the system's. Without it, `fontSize`.
|
|
708
|
+
*/
|
|
709
|
+
controlFontSize?: number;
|
|
710
|
+
/**
|
|
711
|
+
* The family they set it in, `theme.fontFamily`, for the same reason: a
|
|
712
|
+
* browser's system font is its own, Arial in Chrome whatever the page is
|
|
713
|
+
* set in. A `<textarea>` is `monoFamily`, a browser's `monospace`, as it
|
|
714
|
+
* is in Chrome. Without it, `fontFamily`.
|
|
715
|
+
*/
|
|
716
|
+
controlFontFamily?: string;
|
|
703
717
|
}
|
|
704
718
|
|
|
705
719
|
export function initialStyle(look: RootLook, scale = 1): ComputedStyle {
|
|
@@ -2067,7 +2081,11 @@ export function applyDeclaration(
|
|
|
2067
2081
|
// handed a face at a size of millions shapes and caches glyphs that
|
|
2068
2082
|
// size, and a nest of `larger`s gets there on its own
|
|
2069
2083
|
const most = MAX_FONT_SIZE * ctx.scale;
|
|
2070
|
-
const kw = keywordFontSize(
|
|
2084
|
+
const kw = keywordFontSize(
|
|
2085
|
+
value,
|
|
2086
|
+
parent.fontSize,
|
|
2087
|
+
ctx.initial?.fontSize ?? ctx.rem,
|
|
2088
|
+
);
|
|
2071
2089
|
if (kw !== null) {
|
|
2072
2090
|
style.fontSize = Math.min(kw, most);
|
|
2073
2091
|
return;
|
|
@@ -4307,8 +4325,11 @@ function applyFontShorthand(
|
|
|
4307
4325
|
// dropped whole, as CSS drops any value it cannot read — a line height
|
|
4308
4326
|
// below nought included: `font: 4em/-2em serif` set the text at 4em.
|
|
4309
4327
|
const size =
|
|
4310
|
-
keywordFontSize(
|
|
4311
|
-
|
|
4328
|
+
keywordFontSize(
|
|
4329
|
+
sizeText ?? '',
|
|
4330
|
+
parent.fontSize,
|
|
4331
|
+
ctx.initial?.fontSize ?? ctx.rem,
|
|
4332
|
+
) ?? parseLength(sizeText ?? '', { ...ctx, em: parent.fontSize });
|
|
4312
4333
|
if (!family || size === null || size === AUTO) return;
|
|
4313
4334
|
if (lineText && negativeLength(lineText, ctx)) return;
|
|
4314
4335
|
// What the shorthand does not name goes back to its initial value rather
|
package/src/html/css/ua.ts
CHANGED
|
@@ -9,9 +9,20 @@
|
|
|
9
9
|
// author stylesheet still overrides all of it — that is what makes it a UA
|
|
10
10
|
// sheet and not a skin.
|
|
11
11
|
import { parseStylesheet } from './parse.js';
|
|
12
|
-
import type { Stylesheet } from './parse.js';
|
|
12
|
+
import type { Declaration, Stylesheet } from './parse.js';
|
|
13
13
|
import type { RootLook } from './style.js';
|
|
14
14
|
|
|
15
|
+
/**
|
|
16
|
+
* The declaration blocks that draw a control as the palette's widget rather
|
|
17
|
+
* than as the web's UA sheet has it. The cascade drops them from an element
|
|
18
|
+
* whose page styles its background or border (`Cascade`'s
|
|
19
|
+
* `dropPaletteChrome`), as Blink drops a control's native appearance
|
|
20
|
+
* (`LayoutTheme::IsControlStyled`), since a page that styles a control
|
|
21
|
+
* builds on the UA values a browser gives it. Keyed by the array, which is
|
|
22
|
+
* what a cascade candidate carries.
|
|
23
|
+
*/
|
|
24
|
+
export const PALETTE_CHROME = new WeakSet<Declaration[]>();
|
|
25
|
+
|
|
15
26
|
/** Cache key → parsed sheet. The text depends only on the look, so two
|
|
16
27
|
* documents in one themed window parse this once between them. */
|
|
17
28
|
const CACHE = new Map<string, Stylesheet>();
|
|
@@ -30,6 +41,8 @@ export function lookKey(look: RootLook): string {
|
|
|
30
41
|
look.controlPadY,
|
|
31
42
|
look.controlBorder,
|
|
32
43
|
look.controlRadius,
|
|
44
|
+
look.controlFontSize,
|
|
45
|
+
look.controlFontFamily,
|
|
33
46
|
].join('|');
|
|
34
47
|
}
|
|
35
48
|
|
|
@@ -38,6 +51,13 @@ export function uaStylesheet(look: RootLook): Stylesheet {
|
|
|
38
51
|
const hit = CACHE.get(key);
|
|
39
52
|
if (hit) return hit;
|
|
40
53
|
const sheet = parseStylesheet(uaText(look), -1_000_000);
|
|
54
|
+
// after the rest, so its values win over theirs while it applies
|
|
55
|
+
const last = sheet.rules[sheet.rules.length - 1];
|
|
56
|
+
const chrome = parseStylesheet(chromeText(look), last.order + 1);
|
|
57
|
+
for (const rule of chrome.rules) {
|
|
58
|
+
PALETTE_CHROME.add(rule.declarations);
|
|
59
|
+
sheet.rules.push(rule);
|
|
60
|
+
}
|
|
41
61
|
if (CACHE.size > 8) CACHE.clear();
|
|
42
62
|
CACHE.set(key, sheet);
|
|
43
63
|
return sheet;
|
|
@@ -181,11 +201,23 @@ col { display: table-column; }
|
|
|
181
201
|
makes a label and its input share a line. */
|
|
182
202
|
input, button, select, textarea, meter, progress {
|
|
183
203
|
display: inline-block;
|
|
184
|
-
font-family: ${look.fontFamily};
|
|
185
|
-
font-size: 1em;
|
|
186
204
|
vertical-align: middle;
|
|
187
205
|
margin: 3px 2px;
|
|
188
206
|
}
|
|
207
|
+
/* A control's text is a system font's, not its parent's: Chrome gives these
|
|
208
|
+
four \`font: -webkit-small-control\`, Arial at the default size less 2pt
|
|
209
|
+
(13.33px) whatever the text around it is set in, and Gecko \`-moz-field\`
|
|
210
|
+
the same. The system here is the palette, and its family and size are the
|
|
211
|
+
ones core's widgets are set in — so a document's form is its window's,
|
|
212
|
+
and the box a control is measured into is the size the widget mounted in
|
|
213
|
+
it draws. A <textarea> is then \`monospace\` in Chrome, the sheet's code
|
|
214
|
+
face here; a <meter> and a <progress> keep their parent's font, as they
|
|
215
|
+
do in Chrome. */
|
|
216
|
+
input, button, select, textarea {
|
|
217
|
+
font-family: ${look.controlFontFamily ?? look.fontFamily};
|
|
218
|
+
font-size: ${look.controlFontSize ?? look.fontSize}px;
|
|
219
|
+
}
|
|
220
|
+
textarea { font-family: ${mono}; }
|
|
189
221
|
/* Chrome's own UA margins for the checkables, near enough: they are the
|
|
190
222
|
controls that sit hard against their label text otherwise. */
|
|
191
223
|
/* HTML's rendering section (15.3.10): a control's text keeps none of the
|
|
@@ -208,13 +240,16 @@ input[type=hidden] { display: none; }
|
|
|
208
240
|
/* A <button> is drawn rather than mounted: its content is the document's —
|
|
209
241
|
an icon, a label in spans, a pill of the page's own design, which is what
|
|
210
242
|
most buttons on the web are — and the page restyles it as it restyles
|
|
211
|
-
anything.
|
|
212
|
-
|
|
243
|
+
anything. The edges here are Chrome's, which a page that styles its
|
|
244
|
+
buttons builds on: Codex gives Wikipedia's search button its side padding
|
|
245
|
+
and a 32px min-height, and leaves the 1px above and below the label, and
|
|
246
|
+
the border box the minimum holds, to the browser. One the page left alone
|
|
247
|
+
is the palette's control instead, below the rest of this sheet. */
|
|
213
248
|
button {
|
|
214
249
|
vertical-align: baseline;
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
border
|
|
250
|
+
box-sizing: border-box;
|
|
251
|
+
padding: 1px 6px;
|
|
252
|
+
border: 2px outset ${look.borderColor};
|
|
218
253
|
background-color: ${look.surface};
|
|
219
254
|
color: ${look.color};
|
|
220
255
|
text-align: center;
|
|
@@ -235,3 +270,22 @@ details[open] > summary:first-of-type { list-style-type: disclosure-open; }
|
|
|
235
270
|
[hidden] { display: none; }
|
|
236
271
|
`;
|
|
237
272
|
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* The palette's control, for a `<button>` whose page left its background and
|
|
276
|
+
* border alone: the chrome a mounted widget has, so a form in a document is
|
|
277
|
+
* the size of the window's own. The rules land in `PALETTE_CHROME`, and a
|
|
278
|
+
* page that styles either loses all of them — Blink counts every background
|
|
279
|
+
* and border longhand, radius included — along with the native look it
|
|
280
|
+
* would lose in a browser. Nothing stays half the palette's and half the
|
|
281
|
+
* page's.
|
|
282
|
+
*/
|
|
283
|
+
function chromeText(look: RootLook): string {
|
|
284
|
+
return `
|
|
285
|
+
button {
|
|
286
|
+
padding: ${look.controlPadY}px 0.75em;
|
|
287
|
+
border: ${look.controlBorder}px solid ${look.borderColor};
|
|
288
|
+
border-radius: ${look.controlRadius}px;
|
|
289
|
+
}
|
|
290
|
+
`;
|
|
291
|
+
}
|
package/src/html/dom.ts
CHANGED
|
@@ -342,7 +342,9 @@ export class HtmlSource {
|
|
|
342
342
|
tag === 'image' ||
|
|
343
343
|
tag === 'object' ||
|
|
344
344
|
tag === 'embed' ||
|
|
345
|
-
tag === 'video'
|
|
345
|
+
tag === 'video' ||
|
|
346
|
+
(tag === 'input' &&
|
|
347
|
+
(attr(el, 'type') ?? '').trim().toLowerCase() === 'image')
|
|
346
348
|
) {
|
|
347
349
|
if (imageUrlOf(el)) facts.resources.push(el);
|
|
348
350
|
} else if (tag === 'title' && facts.title === null) {
|