@react-x11/components 0.14.0 → 0.16.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/code-editor/node.d.ts +13 -0
- package/dist/code-editor/node.d.ts.map +1 -1
- package/dist/code-editor/node.js +23 -5
- package/dist/code-editor/node.js.map +1 -1
- package/dist/html/css/cascade.d.ts +209 -23
- package/dist/html/css/cascade.d.ts.map +1 -1
- package/dist/html/css/cascade.js +873 -175
- package/dist/html/css/cascade.js.map +1 -1
- package/dist/html/css/color.d.ts +25 -0
- package/dist/html/css/color.d.ts.map +1 -1
- package/dist/html/css/color.js +307 -19
- package/dist/html/css/color.js.map +1 -1
- package/dist/html/css/content.d.ts +11 -6
- package/dist/html/css/content.d.ts.map +1 -1
- package/dist/html/css/content.js +171 -12
- package/dist/html/css/content.js.map +1 -1
- package/dist/html/css/parse.d.ts.map +1 -1
- package/dist/html/css/parse.js +62 -9
- package/dist/html/css/parse.js.map +1 -1
- package/dist/html/css/shapes.d.ts +62 -0
- package/dist/html/css/shapes.d.ts.map +1 -0
- package/dist/html/css/shapes.js +214 -0
- package/dist/html/css/shapes.js.map +1 -0
- package/dist/html/css/style.d.ts +42 -5
- package/dist/html/css/style.d.ts.map +1 -1
- package/dist/html/css/style.js +75 -27
- package/dist/html/css/style.js.map +1 -1
- package/dist/html/css/ua.js +15 -0
- package/dist/html/css/ua.js.map +1 -1
- package/dist/html/css/values.d.ts +5 -0
- package/dist/html/css/values.d.ts.map +1 -1
- package/dist/html/css/values.js +18 -9
- package/dist/html/css/values.js.map +1 -1
- package/dist/html/index.d.ts.map +1 -1
- package/dist/html/index.js +7 -0
- package/dist/html/index.js.map +1 -1
- package/dist/html/layout/block.d.ts +38 -7
- package/dist/html/layout/block.d.ts.map +1 -1
- package/dist/html/layout/block.js +160 -50
- package/dist/html/layout/block.js.map +1 -1
- package/dist/html/layout/boxes.d.ts +29 -2
- package/dist/html/layout/boxes.d.ts.map +1 -1
- package/dist/html/layout/boxes.js +159 -20
- package/dist/html/layout/boxes.js.map +1 -1
- package/dist/html/layout/css-grid.d.ts.map +1 -1
- package/dist/html/layout/css-grid.js +69 -19
- 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 +1648 -225
- package/dist/html/layout/flex.js.map +1 -1
- package/dist/html/layout/inline.d.ts +15 -3
- package/dist/html/layout/inline.d.ts.map +1 -1
- package/dist/html/layout/inline.js +23 -8
- package/dist/html/layout/inline.js.map +1 -1
- package/dist/html/layout/table.d.ts +27 -1
- package/dist/html/layout/table.d.ts.map +1 -1
- package/dist/html/layout/table.js +907 -323
- package/dist/html/layout/table.js.map +1 -1
- package/dist/html/node.d.ts +36 -4
- package/dist/html/node.d.ts.map +1 -1
- package/dist/html/node.js +174 -37
- package/dist/html/node.js.map +1 -1
- package/dist/html/paint.d.ts +4 -1
- package/dist/html/paint.d.ts.map +1 -1
- package/dist/html/paint.js +769 -204
- package/dist/html/paint.js.map +1 -1
- package/dist/html/resources.js +14 -1
- package/dist/html/resources.js.map +1 -1
- package/dist/html/svg.d.ts +50 -17
- package/dist/html/svg.d.ts.map +1 -1
- package/dist/html/svg.js +501 -83
- package/dist/html/svg.js.map +1 -1
- package/dist/html/widgets.d.ts.map +1 -1
- package/dist/html/widgets.js +57 -4
- package/dist/html/widgets.js.map +1 -1
- package/package.json +3 -3
- package/src/code-editor/node.ts +24 -5
- package/src/html/css/cascade.ts +987 -176
- package/src/html/css/color.ts +345 -18
- package/src/html/css/content.ts +173 -15
- package/src/html/css/parse.ts +60 -8
- package/src/html/css/shapes.ts +273 -0
- package/src/html/css/style.ts +123 -30
- package/src/html/css/ua.ts +15 -0
- package/src/html/css/values.ts +23 -9
- package/src/html/index.ts +7 -0
- package/src/html/layout/block.ts +169 -56
- package/src/html/layout/boxes.ts +198 -24
- package/src/html/layout/css-grid.ts +89 -22
- package/src/html/layout/flex.ts +1863 -219
- package/src/html/layout/inline.ts +27 -7
- package/src/html/layout/table.ts +989 -333
- package/src/html/node.ts +204 -37
- package/src/html/paint.ts +999 -252
- package/src/html/resources.ts +13 -1
- package/src/html/svg.ts +600 -78
- package/src/html/widgets.ts +61 -4
package/src/html/css/parse.ts
CHANGED
|
@@ -951,11 +951,13 @@ const PSEUDO_CLASSES = new Set([
|
|
|
951
951
|
/**
|
|
952
952
|
* Specificity, packed. Counted from the selector text rather than from a
|
|
953
953
|
* parse: `#a` is an id, `.a`/`[a]`/`:a` is a class, a bare name is a type,
|
|
954
|
-
* and a pseudo-*element* (`::before`) counts as a type.
|
|
955
|
-
*
|
|
956
|
-
* `:
|
|
957
|
-
*
|
|
958
|
-
*
|
|
954
|
+
* and a pseudo-*element* (`::before`) counts as a type. A pseudo-class
|
|
955
|
+
* that takes selectors counts as Selectors 4 (17) has it: `:is()`,
|
|
956
|
+
* `:not()` and `:has()` as the most specific selector in their list,
|
|
957
|
+
* `:where()` as nothing, and `:nth-child(An+B of S)` as a class and the
|
|
958
|
+
* most specific in S. Counted as a class each, `:where()` was not the
|
|
959
|
+
* zero a library writes it to be: a typography plugin's
|
|
960
|
+
* `.prose :where(p)` beat a page's own `.intro p`.
|
|
959
961
|
*/
|
|
960
962
|
const LEGACY_PSEUDO_ELEMENTS = /^(?:before|after|first-line|first-letter)$/i;
|
|
961
963
|
|
|
@@ -966,6 +968,8 @@ export function specificityOf(selector: string): number {
|
|
|
966
968
|
let ids = 0;
|
|
967
969
|
let classes = 0;
|
|
968
970
|
let types = 0;
|
|
971
|
+
/** What the selectors inside pseudo-classes come to, packed. */
|
|
972
|
+
let inner = 0;
|
|
969
973
|
let i = 0;
|
|
970
974
|
const n = selector.length;
|
|
971
975
|
while (i < n) {
|
|
@@ -985,14 +989,29 @@ export function specificityOf(selector: string): number {
|
|
|
985
989
|
i = identEnd(selector, i + 2);
|
|
986
990
|
} else {
|
|
987
991
|
const name = readIdent(selector, i + 1);
|
|
992
|
+
const lower = name.value.toLowerCase();
|
|
993
|
+
i = name.end;
|
|
994
|
+
let argument: string | null = null;
|
|
995
|
+
if (selector[i] === '(') {
|
|
996
|
+
const end = componentEnd(selector, i);
|
|
997
|
+
argument = selector.slice(i + 1, end - 1);
|
|
998
|
+
i = end;
|
|
999
|
+
}
|
|
988
1000
|
// CSS 2 spelled the four pseudo-elements it had with one colon
|
|
989
1001
|
if (LEGACY_PSEUDO_ELEMENTS.test(name.value)) {
|
|
990
1002
|
types += 1;
|
|
1003
|
+
} else if (argument !== null && SELECTOR_LIST_PSEUDOS.has(lower)) {
|
|
1004
|
+
inner += mostSpecific(argument);
|
|
1005
|
+
} else if (argument !== null && lower === 'where') {
|
|
1006
|
+
// nothing
|
|
991
1007
|
} else {
|
|
992
1008
|
classes += 1;
|
|
1009
|
+
if (argument !== null && NTH_OF_PSEUDOS.has(lower)) {
|
|
1010
|
+
const of = /\sof\s/i.exec(argument);
|
|
1011
|
+
if (of)
|
|
1012
|
+
inner += mostSpecific(argument.slice(of.index + of[0].length));
|
|
1013
|
+
}
|
|
993
1014
|
}
|
|
994
|
-
i = name.end;
|
|
995
|
-
if (selector[i] === '(') i = componentEnd(selector, i);
|
|
996
1015
|
}
|
|
997
1016
|
} else if (c === '*' || c === '|') {
|
|
998
1017
|
// the universal selector, and a namespace's bar, count nothing
|
|
@@ -1004,7 +1023,40 @@ export function specificityOf(selector: string): number {
|
|
|
1004
1023
|
i += 1;
|
|
1005
1024
|
}
|
|
1006
1025
|
}
|
|
1007
|
-
return ids * 1_000_000 + classes * 1_000 + types;
|
|
1026
|
+
return ids * 1_000_000 + classes * 1_000 + types + inner;
|
|
1027
|
+
}
|
|
1028
|
+
|
|
1029
|
+
/** The pseudo-classes whose specificity is their argument's most specific
|
|
1030
|
+
* selector, the old names of `:is()` among them. */
|
|
1031
|
+
const SELECTOR_LIST_PSEUDOS = new Set([
|
|
1032
|
+
'is',
|
|
1033
|
+
'not',
|
|
1034
|
+
'has',
|
|
1035
|
+
'matches',
|
|
1036
|
+
'-webkit-any',
|
|
1037
|
+
'-moz-any',
|
|
1038
|
+
]);
|
|
1039
|
+
|
|
1040
|
+
/** The pseudo-classes an `of S` may follow. */
|
|
1041
|
+
const NTH_OF_PSEUDOS = new Set(['nth-child', 'nth-last-child']);
|
|
1042
|
+
|
|
1043
|
+
/** The specificity of the most specific selector in a list, packed. */
|
|
1044
|
+
function mostSpecific(list: string): number {
|
|
1045
|
+
let most = 0;
|
|
1046
|
+
let depth = 0;
|
|
1047
|
+
let start = 0;
|
|
1048
|
+
for (let i = 0; i <= list.length; i += 1) {
|
|
1049
|
+
const c = list[i];
|
|
1050
|
+
if (c === '(' || c === '[') depth += 1;
|
|
1051
|
+
else if (c === ')' || c === ']') depth -= 1;
|
|
1052
|
+
else if (c === '"' || c === "'" || c === '\\') {
|
|
1053
|
+
i = componentEnd(list, i) - 1;
|
|
1054
|
+
} else if ((c === ',' && depth === 0) || i === list.length) {
|
|
1055
|
+
most = Math.max(most, specificityOf(list.slice(start, i)));
|
|
1056
|
+
start = i + 1;
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
1059
|
+
return most;
|
|
1008
1060
|
}
|
|
1009
1061
|
|
|
1010
1062
|
// --- the little scanner -----------------------------------------------------
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
// What a style sheet says of the shapes in a drawing.
|
|
2
|
+
//
|
|
3
|
+
// An inline `<svg>` is a replaced box: the box builder stops at it, and no
|
|
4
|
+
// computed style is made for what is inside. `fill`, `stroke` and their kin
|
|
5
|
+
// are properties all the same (SVG 2, 6.2 and 13.2), which a rule of the
|
|
6
|
+
// document's sets on a `<path>` as it would on a paragraph — a logo's
|
|
7
|
+
// `.dark .logo path { fill: #fff }` — and a drawing exported with a style
|
|
8
|
+
// sheet of its own, `.st0 { fill: #fff }` on `<path class="st0">`, has all
|
|
9
|
+
// its colours in one. So the cascade is run for them narrowly
|
|
10
|
+
// (`Cascade.shapeStyles`): over the rules that declare one of the
|
|
11
|
+
// properties here, and down to values `SvgView` reads from a `style`
|
|
12
|
+
// attribute, which is where the drawing's copy of an element carries them
|
|
13
|
+
// (`svg.ts`). A `ComputedStyle` per `<path>` would be a few hundred
|
|
14
|
+
// properties for the dozen a shape has, on a page of hundreds of icons.
|
|
15
|
+
import type { Element } from 'domhandler';
|
|
16
|
+
import { LIGHT_DARK, SYSTEM_COLOR, lightDark, systemColors } from './color.js';
|
|
17
|
+
import { inkColor, parseAlpha, parseColor, parseLength } from './values.js';
|
|
18
|
+
import type { UnitContext } from './values.js';
|
|
19
|
+
|
|
20
|
+
/** What the rules give one element of a drawing: each property they set,
|
|
21
|
+
* as `SvgView` reads it — `inherit` for one they put back to what the
|
|
22
|
+
* element inherits, over its own attribute. */
|
|
23
|
+
export type ShapeStyle = Readonly<Record<string, string>>;
|
|
24
|
+
|
|
25
|
+
/** What the rules give the elements of one drawing, and all of it as a
|
|
26
|
+
* string: what a drawing's tree was last made with (`SvgDrawing`). */
|
|
27
|
+
export interface ShapeStyles {
|
|
28
|
+
/** Each element's, where it stands. */
|
|
29
|
+
readonly of: ReadonlyMap<Element, ShapeStyle>;
|
|
30
|
+
/**
|
|
31
|
+
* What they give the copy a `<use>` draws, by the `<use>`, then by each
|
|
32
|
+
* element of the copy's original; null where they give none. A copy is
|
|
33
|
+
* styled in a tree of its own, which its original is the top of (SVG 2,
|
|
34
|
+
* 5.5.3, as Chrome has it): a rule finds no ancestor of that element's
|
|
35
|
+
* and none of the `<use>`'s, and no sibling of it, and what the top
|
|
36
|
+
* inherits is the `<use>`'s. So `symbol .line` reaches a sprite's line
|
|
37
|
+
* where `.sprite .line` does not, and an element a `<use>` draws may be
|
|
38
|
+
* styled otherwise than where it stands.
|
|
39
|
+
*/
|
|
40
|
+
readonly used: ReadonlyMap<Element, ReadonlyMap<Element, ShapeStyle>> | null;
|
|
41
|
+
readonly key: string;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** The id a `<use>` names the element it draws by: a fragment of its
|
|
45
|
+
* `href`. One in another document is none here, where nothing is fetched
|
|
46
|
+
* for a drawing. */
|
|
47
|
+
export function useHref(el: Element): string | null {
|
|
48
|
+
const href = el.attribs.href ?? el.attribs['xlink:href'] ?? '';
|
|
49
|
+
return href.length > 1 && href.startsWith('#') ? href.slice(1) : null;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Whether an element is a `<use>`, with a prefix or not. */
|
|
53
|
+
export function isUse(el: Element): boolean {
|
|
54
|
+
const name = el.name;
|
|
55
|
+
if (name === 'use') return true;
|
|
56
|
+
const i = name.indexOf(':');
|
|
57
|
+
return i >= 0 && name.slice(i + 1).toLowerCase() === 'use';
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Each property read, and whether it is inherited. */
|
|
61
|
+
const PROPS: Record<string, boolean> = {
|
|
62
|
+
fill: true,
|
|
63
|
+
stroke: true,
|
|
64
|
+
'stroke-width': true,
|
|
65
|
+
'stroke-linecap': true,
|
|
66
|
+
'stroke-linejoin': true,
|
|
67
|
+
'stroke-miterlimit': true,
|
|
68
|
+
'fill-rule': true,
|
|
69
|
+
'fill-opacity': true,
|
|
70
|
+
'stroke-opacity': true,
|
|
71
|
+
color: true,
|
|
72
|
+
visibility: true,
|
|
73
|
+
opacity: false,
|
|
74
|
+
display: false,
|
|
75
|
+
'stop-color': false,
|
|
76
|
+
'stop-opacity': false,
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
/** Whether a rule with this declaration is one to keep for drawings. */
|
|
80
|
+
export function isShapeProp(prop: string): boolean {
|
|
81
|
+
return PROPS[prop] !== undefined;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** The properties the drawing's box has from its computed style already:
|
|
85
|
+
* its paint, which `paintSvg` hands the drawing, and what makes it a box,
|
|
86
|
+
* shown and see-through as one. */
|
|
87
|
+
export const ROOT_BOX_PROPS: ReadonlySet<string> = new Set([
|
|
88
|
+
'fill',
|
|
89
|
+
'stroke',
|
|
90
|
+
'color',
|
|
91
|
+
'display',
|
|
92
|
+
'visibility',
|
|
93
|
+
'opacity',
|
|
94
|
+
]);
|
|
95
|
+
|
|
96
|
+
/** Each at its initial value (SVG 2, 13; CSS Color 4; CSS Display 3). */
|
|
97
|
+
const INITIAL: Record<string, string> = {
|
|
98
|
+
fill: '#000000',
|
|
99
|
+
stroke: 'none',
|
|
100
|
+
'stroke-width': '1',
|
|
101
|
+
'stroke-linecap': 'butt',
|
|
102
|
+
'stroke-linejoin': 'miter',
|
|
103
|
+
'stroke-miterlimit': '4',
|
|
104
|
+
'fill-rule': 'nonzero',
|
|
105
|
+
'fill-opacity': '1',
|
|
106
|
+
'stroke-opacity': '1',
|
|
107
|
+
visibility: 'visible',
|
|
108
|
+
opacity: '1',
|
|
109
|
+
display: 'inline',
|
|
110
|
+
'stop-color': '#000000',
|
|
111
|
+
'stop-opacity': '1',
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
/** The elements a rule's subject can be, where it names one by its type,
|
|
115
|
+
* for the rule to reach into a drawing: the ones `SvgView` draws, or
|
|
116
|
+
* reads a paint from. `a` is both languages'. */
|
|
117
|
+
export const SHAPE_TAGS: ReadonlySet<string> = new Set([
|
|
118
|
+
'svg',
|
|
119
|
+
'g',
|
|
120
|
+
'a',
|
|
121
|
+
'path',
|
|
122
|
+
'rect',
|
|
123
|
+
'circle',
|
|
124
|
+
'ellipse',
|
|
125
|
+
'line',
|
|
126
|
+
'polyline',
|
|
127
|
+
'polygon',
|
|
128
|
+
'text',
|
|
129
|
+
'tspan',
|
|
130
|
+
'use',
|
|
131
|
+
'symbol',
|
|
132
|
+
'switch',
|
|
133
|
+
'image',
|
|
134
|
+
'stop',
|
|
135
|
+
]);
|
|
136
|
+
|
|
137
|
+
/** What a shape's values are read against: the drawing's own `color` and
|
|
138
|
+
* colour scheme, and its font size for a length in `em`. */
|
|
139
|
+
export interface ShapeContext {
|
|
140
|
+
color: string;
|
|
141
|
+
scheme: 'light' | 'dark';
|
|
142
|
+
units: UnitContext;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* A `fill` or a `stroke` (SVG 2, 13.2): `none`, a colour, or a `url()`
|
|
147
|
+
* naming a gradient in the drawing, with what follows it, its fallback,
|
|
148
|
+
* left off. Null for anything else, `context-fill` and `context-stroke`
|
|
149
|
+
* among it, which only a marker or a `<use>` has a context for.
|
|
150
|
+
*/
|
|
151
|
+
export function svgPaint(value: string): string | null {
|
|
152
|
+
const v = value.trim();
|
|
153
|
+
if (v.toLowerCase() === 'none') return 'none';
|
|
154
|
+
const url = /^url\(\s*(['"]?)(#[^'")\s]+)\1\s*\)/i.exec(v);
|
|
155
|
+
if (url) return `url(${url[2]})`;
|
|
156
|
+
return parseColor(v);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const NUMBER = /^[+-]?(?:\d*\.\d+|\d+)(?:e[+-]?\d+)?$/i;
|
|
160
|
+
|
|
161
|
+
/** A word of a `display` that makes a box (CSS Display 3, 2). */
|
|
162
|
+
const DISPLAY =
|
|
163
|
+
/^(?:block|inline|run-in|flow|flow-root|table|flex|grid|ruby|list-item|contents|inline-[a-z]+|table-[a-z-]+|ruby-[a-z-]+|-webkit-[a-z-]+)$/;
|
|
164
|
+
|
|
165
|
+
const LINE_JOINS: Record<string, string> = {
|
|
166
|
+
miter: 'miter',
|
|
167
|
+
round: 'round',
|
|
168
|
+
bevel: 'bevel',
|
|
169
|
+
// the two a canvas has no join for end in a miter, clipped or not
|
|
170
|
+
'miter-clip': 'miter',
|
|
171
|
+
arcs: 'miter',
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
/** The properties of a shape that a colour is written in. */
|
|
175
|
+
const SHAPE_COLORS = new Set(['fill', 'stroke', 'color', 'stop-color']);
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* One declaration's value as a drawing reads it, or null for a value the
|
|
179
|
+
* property does not take, which leaves the declaration out of the cascade
|
|
180
|
+
* as any invalid one is left. `inherit` for a CSS-wide keyword that puts an
|
|
181
|
+
* inherited property back to what the element inherits.
|
|
182
|
+
*/
|
|
183
|
+
export function shapeValue(
|
|
184
|
+
prop: string,
|
|
185
|
+
raw: string,
|
|
186
|
+
ctx: ShapeContext,
|
|
187
|
+
): string | null {
|
|
188
|
+
let value = raw.trim();
|
|
189
|
+
const wide = value.toLowerCase();
|
|
190
|
+
if (wide === 'inherit') return PROPS[prop] ? 'inherit' : null;
|
|
191
|
+
if (wide === 'initial') {
|
|
192
|
+
return prop === 'color'
|
|
193
|
+
? (ctx.units.initial?.color ?? '#000000')
|
|
194
|
+
: INITIAL[prop];
|
|
195
|
+
}
|
|
196
|
+
if (wide === 'unset' || wide === 'revert' || wide === 'revert-layer') {
|
|
197
|
+
return PROPS[prop] ? 'inherit' : INITIAL[prop];
|
|
198
|
+
}
|
|
199
|
+
if (SHAPE_COLORS.has(prop) && SYSTEM_COLOR.test(value)) {
|
|
200
|
+
value = systemColors(value, ctx.units.systemColors);
|
|
201
|
+
}
|
|
202
|
+
if (LIGHT_DARK.test(value)) {
|
|
203
|
+
const picked = lightDark(value, ctx.scheme);
|
|
204
|
+
if (picked === null) return null;
|
|
205
|
+
value = picked;
|
|
206
|
+
}
|
|
207
|
+
switch (prop) {
|
|
208
|
+
case 'fill':
|
|
209
|
+
case 'stroke': {
|
|
210
|
+
const paint = svgPaint(value);
|
|
211
|
+
// `currentColor` is the shape's own `color`, which the drawing knows
|
|
212
|
+
return paint === null || paint === 'currentColor'
|
|
213
|
+
? paint
|
|
214
|
+
: inkColor(paint, ctx.color);
|
|
215
|
+
}
|
|
216
|
+
case 'color':
|
|
217
|
+
case 'stop-color': {
|
|
218
|
+
const color = parseColor(value);
|
|
219
|
+
return color === null ? null : inkColor(color, ctx.color);
|
|
220
|
+
}
|
|
221
|
+
case 'stroke-width': {
|
|
222
|
+
// a number is user units, as a length in pixels is; a percentage is
|
|
223
|
+
// of the viewport's diagonal, which only the drawing has
|
|
224
|
+
const px = NUMBER.test(value)
|
|
225
|
+
? Number(value)
|
|
226
|
+
: lengthIn(value, ctx.units);
|
|
227
|
+
return px !== null && px >= 0 ? String(px) : null;
|
|
228
|
+
}
|
|
229
|
+
case 'stroke-miterlimit': {
|
|
230
|
+
const n = NUMBER.test(value) ? Number(value) : NaN;
|
|
231
|
+
return n >= 1 ? String(n) : null;
|
|
232
|
+
}
|
|
233
|
+
case 'fill-opacity':
|
|
234
|
+
case 'stroke-opacity':
|
|
235
|
+
case 'stop-opacity':
|
|
236
|
+
case 'opacity': {
|
|
237
|
+
const a = parseAlpha(value);
|
|
238
|
+
return a === null ? null : String(a);
|
|
239
|
+
}
|
|
240
|
+
case 'stroke-linecap': {
|
|
241
|
+
const v = value.toLowerCase();
|
|
242
|
+
return v === 'butt' || v === 'round' || v === 'square' ? v : null;
|
|
243
|
+
}
|
|
244
|
+
case 'stroke-linejoin':
|
|
245
|
+
return LINE_JOINS[value.toLowerCase()] ?? null;
|
|
246
|
+
case 'fill-rule': {
|
|
247
|
+
const v = value.toLowerCase();
|
|
248
|
+
return v === 'nonzero' || v === 'evenodd' ? v : null;
|
|
249
|
+
}
|
|
250
|
+
case 'visibility': {
|
|
251
|
+
const v = value.toLowerCase();
|
|
252
|
+
if (v === 'visible') return v;
|
|
253
|
+
return v === 'hidden' || v === 'collapse' ? 'hidden' : null;
|
|
254
|
+
}
|
|
255
|
+
case 'display': {
|
|
256
|
+
// `none`, or some box: which one is nothing to a shape
|
|
257
|
+
const v = value.toLowerCase();
|
|
258
|
+
if (v === 'none') return v;
|
|
259
|
+
return v.split(/\s+/).every((word) => DISPLAY.test(word))
|
|
260
|
+
? 'inline'
|
|
261
|
+
: null;
|
|
262
|
+
}
|
|
263
|
+
default:
|
|
264
|
+
return null;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/** A length in user units, which are CSS pixels; null for none, and for a
|
|
269
|
+
* percentage. */
|
|
270
|
+
function lengthIn(value: string, units: UnitContext): number | null {
|
|
271
|
+
const length = parseLength(value, units);
|
|
272
|
+
return typeof length === 'number' ? length / units.scale : null;
|
|
273
|
+
}
|
package/src/html/css/style.ts
CHANGED
|
@@ -32,7 +32,6 @@ import {
|
|
|
32
32
|
import type { Len, Pct, UnitContext } from './values.js';
|
|
33
33
|
import { parseUrl, readIdent, startsIdent } from './parse.js';
|
|
34
34
|
import {
|
|
35
|
-
DEFAULT_QUOTES,
|
|
36
35
|
parseContent,
|
|
37
36
|
parseCounterList,
|
|
38
37
|
parseListStyleType,
|
|
@@ -42,7 +41,14 @@ import type { ContentItem, CounterChange } from './content.js';
|
|
|
42
41
|
import type { CustomProps } from './vars.js';
|
|
43
42
|
import { parseRotate, parseScale, parseTransform } from './transform.js';
|
|
44
43
|
import type { TransformFunction } from './transform.js';
|
|
45
|
-
import {
|
|
44
|
+
import {
|
|
45
|
+
LIGHT_DARK,
|
|
46
|
+
SYSTEM_COLOR,
|
|
47
|
+
lightDark,
|
|
48
|
+
systemColors,
|
|
49
|
+
usedColorScheme,
|
|
50
|
+
} from './color.js';
|
|
51
|
+
import { svgPaint } from './shapes.js';
|
|
46
52
|
|
|
47
53
|
export type Display =
|
|
48
54
|
| 'none'
|
|
@@ -456,7 +462,7 @@ export interface ComputedStyle {
|
|
|
456
462
|
* `text-align` on a `<table>` to behave. */
|
|
457
463
|
tableTextAlignSet: boolean;
|
|
458
464
|
/** The marks `open-quote` and `close-quote` write, pairs outermost first. */
|
|
459
|
-
quotes: readonly string[] | 'none';
|
|
465
|
+
quotes: readonly string[] | 'none' | 'auto';
|
|
460
466
|
/** Custom properties, `--name`, with their `var()`s replaced (`vars.ts`);
|
|
461
467
|
* null where none is set. Inherited as the same map. */
|
|
462
468
|
custom: CustomProps | null;
|
|
@@ -655,7 +661,8 @@ export interface ComputedStyle {
|
|
|
655
661
|
/** Its shadows front to back, the ones that can be seen; null for none. */
|
|
656
662
|
boxShadow: BoxShadow[] | null;
|
|
657
663
|
/** Its outline (CSS 2.1 18.4, CSS UI 4): drawn around the border box,
|
|
658
|
-
* grown by `outlineOffset`, taking no room; `auto` is
|
|
664
|
+
* grown by `outlineOffset`, taking no room; `auto` is the palette's
|
|
665
|
+
* focus ring (`settleOutline`), drawn solid. */
|
|
659
666
|
outlineStyle: BorderStyle | 'auto';
|
|
660
667
|
outlineWidth: number;
|
|
661
668
|
outlineColor: string;
|
|
@@ -760,16 +767,31 @@ export interface ComputedStyle {
|
|
|
760
767
|
| 'space-between'
|
|
761
768
|
| 'space-around'
|
|
762
769
|
| 'space-evenly';
|
|
763
|
-
|
|
770
|
+
/** `normal` is `stretch` in a flex box, and is kept apart from it only
|
|
771
|
+
* in a grid, where it does not stretch an item with a natural size or a
|
|
772
|
+
* ratio (`settleAlign`). */
|
|
773
|
+
alignItems:
|
|
774
|
+
'normal' | 'flex-start' | 'flex-end' | 'center' | 'stretch' | 'baseline';
|
|
764
775
|
alignSelf:
|
|
765
|
-
|
|
776
|
+
| 'auto'
|
|
777
|
+
| 'normal'
|
|
778
|
+
| 'flex-start'
|
|
779
|
+
| 'flex-end'
|
|
780
|
+
| 'center'
|
|
781
|
+
| 'stretch'
|
|
782
|
+
| 'baseline';
|
|
783
|
+
/** `start` and `end` are kept: a flex box that wraps in reverse puts
|
|
784
|
+
* them at the other ends of its lines from `flex-start` and `flex-end`. */
|
|
766
785
|
alignContent:
|
|
767
786
|
| 'flex-start'
|
|
768
787
|
| 'flex-end'
|
|
788
|
+
| 'start'
|
|
789
|
+
| 'end'
|
|
769
790
|
| 'center'
|
|
770
791
|
| 'stretch'
|
|
771
792
|
| 'space-between'
|
|
772
|
-
| 'space-around'
|
|
793
|
+
| 'space-around'
|
|
794
|
+
| 'space-evenly';
|
|
773
795
|
flexGrow: number;
|
|
774
796
|
flexShrink: number;
|
|
775
797
|
flexBasis: Len | 'auto' | 'content';
|
|
@@ -912,6 +934,51 @@ export interface RootLook {
|
|
|
912
934
|
* is in Chrome. Without it, `fontFamily`.
|
|
913
935
|
*/
|
|
914
936
|
controlFontFamily?: string;
|
|
937
|
+
/**
|
|
938
|
+
* The ring the palette's widgets draw round the one that has the focus —
|
|
939
|
+
* `theme.focusRing`, `focusRingWidth` and `focusRingOffset` — which is
|
|
940
|
+
* what an `outline-style: auto` is, the platform's own (CSS UI 4, 5.3),
|
|
941
|
+
* and what `-webkit-focus-ring-color` names. Without them, core's own
|
|
942
|
+
* defaults.
|
|
943
|
+
*/
|
|
944
|
+
focusRing?: string;
|
|
945
|
+
focusRingWidth?: number;
|
|
946
|
+
focusRingOffset?: number;
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
/** Core's focus ring, where a look names none (`DEFAULT_FOCUS_RING`). */
|
|
950
|
+
const FOCUS_RING = { color: '#2980b9', width: 2, offset: 1 };
|
|
951
|
+
|
|
952
|
+
/**
|
|
953
|
+
* An `outline-style: auto` as the platform's focus ring: the palette's
|
|
954
|
+
* width and, where the outline's colour is the text's or the keyword's,
|
|
955
|
+
* its colour, the palette's gap outside `outline-offset` — so a field the
|
|
956
|
+
* document draws is ringed as the window's own fields are. A browser
|
|
957
|
+
* draws `auto` in a style of its own the same way, whatever width it was
|
|
958
|
+
* given. `look` is the device one, as every length here is, and `scale`
|
|
959
|
+
* what makes core's defaults device lengths too.
|
|
960
|
+
*/
|
|
961
|
+
export function settleOutline(
|
|
962
|
+
style: ComputedStyle,
|
|
963
|
+
look: RootLook,
|
|
964
|
+
scale: number,
|
|
965
|
+
): void {
|
|
966
|
+
if (style.outlineStyle !== 'auto') return;
|
|
967
|
+
style.outlineWidth = look.focusRingWidth ?? FOCUS_RING.width * scale;
|
|
968
|
+
style.outlineOffset += look.focusRingOffset ?? FOCUS_RING.offset * scale;
|
|
969
|
+
if (style.outlineColor === 'currentColor') {
|
|
970
|
+
style.outlineColor = look.focusRing ?? FOCUS_RING.color;
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
/** `-webkit-focus-ring-color`, a colour of an outline's. */
|
|
975
|
+
function outlineColorOf(v: string, ctx: UnitContext): string | null {
|
|
976
|
+
const lower = v.trim().toLowerCase();
|
|
977
|
+
if (lower === 'invert') return 'currentColor';
|
|
978
|
+
if (lower === '-webkit-focus-ring-color') {
|
|
979
|
+
return ctx.focusRing ?? FOCUS_RING.color;
|
|
980
|
+
}
|
|
981
|
+
return parseColor(v);
|
|
915
982
|
}
|
|
916
983
|
|
|
917
984
|
export function initialStyle(look: RootLook, scale = 1): ComputedStyle {
|
|
@@ -967,7 +1034,7 @@ export function initialStyle(look: RootLook, scale = 1): ComputedStyle {
|
|
|
967
1034
|
captionSide: 'top',
|
|
968
1035
|
emptyCells: 'show',
|
|
969
1036
|
tableTextAlignSet: false,
|
|
970
|
-
quotes:
|
|
1037
|
+
quotes: 'auto',
|
|
971
1038
|
custom: null,
|
|
972
1039
|
|
|
973
1040
|
display: 'inline',
|
|
@@ -1113,7 +1180,7 @@ export function initialStyle(look: RootLook, scale = 1): ComputedStyle {
|
|
|
1113
1180
|
flexDirection: 'row',
|
|
1114
1181
|
flexWrap: 'nowrap',
|
|
1115
1182
|
justifyContent: 'normal',
|
|
1116
|
-
alignItems: '
|
|
1183
|
+
alignItems: 'normal',
|
|
1117
1184
|
alignSelf: 'auto',
|
|
1118
1185
|
alignContent: 'stretch',
|
|
1119
1186
|
flexGrow: 0,
|
|
@@ -1313,6 +1380,11 @@ export function firstLineParent(
|
|
|
1313
1380
|
// --- applying a declaration -------------------------------------------------
|
|
1314
1381
|
|
|
1315
1382
|
/** Longhands that take a colour and nothing else, by property name. */
|
|
1383
|
+
/** The properties a colour is written in, alone or in a shorthand, a
|
|
1384
|
+
* shadow, a gradient or an image's mask. */
|
|
1385
|
+
const TAKES_COLOR =
|
|
1386
|
+
/^(?:color|background|border|outline|box-shadow|text-shadow|text-decoration|text-emphasis|column-rule|fill|stroke|stop-color|flood-color|lighting-color|caret-color|accent-color|scrollbar-color|filter|mask|list-style|-webkit-text-(?:fill|stroke))/;
|
|
1387
|
+
|
|
1316
1388
|
const COLOR_PROPS: Record<string, keyof ComputedStyle> = {
|
|
1317
1389
|
color: 'color',
|
|
1318
1390
|
'background-color': 'backgroundColor',
|
|
@@ -1437,6 +1509,11 @@ export function applyDeclaration(
|
|
|
1437
1509
|
if (/!\s*important$/i.test(value)) {
|
|
1438
1510
|
value = value.replace(/!\s*important$/i, '').trim();
|
|
1439
1511
|
}
|
|
1512
|
+
// a system colour is a `light-dark()` of its own, in a property that
|
|
1513
|
+
// takes a colour: `Menu` is a font in `font`, and `Mark` a family's name
|
|
1514
|
+
if (TAKES_COLOR.test(name) && SYSTEM_COLOR.test(value)) {
|
|
1515
|
+
value = systemColors(value, ctx.systemColors);
|
|
1516
|
+
}
|
|
1440
1517
|
// the branch of each `light-dark()` this element's scheme picks, before
|
|
1441
1518
|
// anything reads the value: it stands wherever a colour can, in a
|
|
1442
1519
|
// shorthand, a shadow, a gradient or a mix, and most often arrives
|
|
@@ -2088,7 +2165,7 @@ export function applyDeclaration(
|
|
|
2088
2165
|
width = w;
|
|
2089
2166
|
continue;
|
|
2090
2167
|
}
|
|
2091
|
-
const c =
|
|
2168
|
+
const c = outlineColorOf(part, ctx);
|
|
2092
2169
|
if (c === null) return;
|
|
2093
2170
|
color = c;
|
|
2094
2171
|
}
|
|
@@ -2109,8 +2186,7 @@ export function applyDeclaration(
|
|
|
2109
2186
|
}
|
|
2110
2187
|
case 'outline-color': {
|
|
2111
2188
|
// `invert` is the text's colour where a browser cannot invert
|
|
2112
|
-
const
|
|
2113
|
-
const c = v === 'invert' ? 'currentColor' : parseColor(value);
|
|
2189
|
+
const c = outlineColorOf(value, ctx);
|
|
2114
2190
|
if (c !== null) style.outlineColor = c;
|
|
2115
2191
|
return;
|
|
2116
2192
|
}
|
|
@@ -2905,13 +2981,17 @@ export function applyDeclaration(
|
|
|
2905
2981
|
return;
|
|
2906
2982
|
}
|
|
2907
2983
|
case 'align-items': {
|
|
2908
|
-
const v =
|
|
2984
|
+
const v =
|
|
2985
|
+
value.trim().toLowerCase() === 'normal'
|
|
2986
|
+
? 'normal'
|
|
2987
|
+
: alignKeyword(value);
|
|
2909
2988
|
if (v) style.alignItems = v as ComputedStyle['alignItems'];
|
|
2910
2989
|
return;
|
|
2911
2990
|
}
|
|
2912
2991
|
case 'align-self': {
|
|
2913
|
-
|
|
2914
|
-
|
|
2992
|
+
const keyword = value.trim().toLowerCase();
|
|
2993
|
+
if (keyword === 'auto' || keyword === 'normal') {
|
|
2994
|
+
style.alignSelf = keyword;
|
|
2915
2995
|
return;
|
|
2916
2996
|
}
|
|
2917
2997
|
const v = alignKeyword(value);
|
|
@@ -2920,11 +3000,15 @@ export function applyDeclaration(
|
|
|
2920
3000
|
}
|
|
2921
3001
|
case 'align-content': {
|
|
2922
3002
|
// `normal` is `stretch` in a flex and a grid container, the only
|
|
2923
|
-
// boxes it moves anything in (CSS Box Alignment 3, 5.1)
|
|
3003
|
+
// boxes it moves anything in (CSS Box Alignment 3, 5.1); `start` and
|
|
3004
|
+
// `end` are the box's own edges, and stay those
|
|
3005
|
+
const plain = overflowAligned(value);
|
|
2924
3006
|
const v =
|
|
2925
3007
|
value.trim().toLowerCase() === 'normal'
|
|
2926
3008
|
? 'stretch'
|
|
2927
|
-
:
|
|
3009
|
+
: plain === 'start' || plain === 'end'
|
|
3010
|
+
? plain
|
|
3011
|
+
: alignKeyword(value);
|
|
2928
3012
|
if (v) style.alignContent = v as ComputedStyle['alignContent'];
|
|
2929
3013
|
return;
|
|
2930
3014
|
}
|
|
@@ -3372,6 +3456,28 @@ export function settleContentVisibility(style: ComputedStyle): void {
|
|
|
3372
3456
|
}
|
|
3373
3457
|
}
|
|
3374
3458
|
|
|
3459
|
+
/**
|
|
3460
|
+
* `align-items: normal` and `align-self: normal`, settled once the cascade
|
|
3461
|
+
* knows what the box and its parent are. In a grid `normal` stretches an
|
|
3462
|
+
* item only where it has neither a natural size nor a ratio, and `stretch`
|
|
3463
|
+
* stretches every item (CSS Grid 1, 6.2; CSS Box Alignment 3, 6.1), so the
|
|
3464
|
+
* grid keeps the two apart; everywhere else `normal` is `stretch`, and a
|
|
3465
|
+
* flex box reads nothing new. Unread, `normal` was dropped, and a grid could
|
|
3466
|
+
* not tell an unset `align-items` from `stretch`: an image under an explicit
|
|
3467
|
+
* `stretch` kept the height its ratio gave it.
|
|
3468
|
+
*/
|
|
3469
|
+
export function settleAlign(
|
|
3470
|
+
style: ComputedStyle,
|
|
3471
|
+
parent: ComputedStyle | null,
|
|
3472
|
+
): void {
|
|
3473
|
+
if (style.alignItems === 'normal' && !style.grid) {
|
|
3474
|
+
style.alignItems = 'stretch';
|
|
3475
|
+
}
|
|
3476
|
+
if (style.alignSelf === 'normal' && !parent?.grid) {
|
|
3477
|
+
style.alignSelf = 'stretch';
|
|
3478
|
+
}
|
|
3479
|
+
}
|
|
3480
|
+
|
|
3375
3481
|
export function settleOverflow(style: ComputedStyle): void {
|
|
3376
3482
|
const x = style.overflowX;
|
|
3377
3483
|
const y = style.overflowY;
|
|
@@ -5154,19 +5260,6 @@ const POINTER_EVENTS = new Set([
|
|
|
5154
5260
|
'stroke',
|
|
5155
5261
|
'all',
|
|
5156
5262
|
]);
|
|
5157
|
-
/**
|
|
5158
|
-
* A `fill` or a `stroke` (SVG 2, 13.2): `none`, a colour, or a `url()`
|
|
5159
|
-
* naming a gradient in the drawing, with what follows it, its fallback,
|
|
5160
|
-
* left off. Null for anything else, `context-fill` and `context-stroke`
|
|
5161
|
-
* among it, which only a marker or a `<use>` has a context for.
|
|
5162
|
-
*/
|
|
5163
|
-
function svgPaint(value: string): string | null {
|
|
5164
|
-
const v = value.trim();
|
|
5165
|
-
if (v.toLowerCase() === 'none') return 'none';
|
|
5166
|
-
const url = /^url\(\s*(['"]?)(#[^'")\s]+)\1\s*\)/i.exec(v);
|
|
5167
|
-
if (url) return `url(${url[2]})`;
|
|
5168
|
-
return parseColor(v);
|
|
5169
|
-
}
|
|
5170
5263
|
|
|
5171
5264
|
const OBJECT_FITS = new Set(['fill', 'contain', 'cover', 'none', 'scale-down']);
|
|
5172
5265
|
|
package/src/html/css/ua.ts
CHANGED
|
@@ -247,6 +247,17 @@ input, select, button, textarea {
|
|
|
247
247
|
text-indent: initial;
|
|
248
248
|
text-shadow: initial;
|
|
249
249
|
}
|
|
250
|
+
/* Chrome's ring round a control that shows it has the focus, and \`auto\` is
|
|
251
|
+
the platform's own: the palette's ring here (\`settleOutline\`), round
|
|
252
|
+
the element's border box, which the document draws — the field's widget
|
|
253
|
+
draws none, so a page's \`outline: none\`, or a ring of its own, is what
|
|
254
|
+
shows. Only a text field's widget tells the document it has the focus:
|
|
255
|
+
the other controls are core's components, which draw their own ring and
|
|
256
|
+
keep their focus to themselves. */
|
|
257
|
+
input:focus-visible, textarea:focus-visible {
|
|
258
|
+
outline: auto 1px -webkit-focus-ring-color;
|
|
259
|
+
outline-offset: 0;
|
|
260
|
+
}
|
|
250
261
|
input[type=checkbox] { margin: 3px 4px 3px 4px; }
|
|
251
262
|
input[type=radio] { margin: 3px 4px 3px 5px; }
|
|
252
263
|
input[type=hidden] { display: none; }
|
|
@@ -292,6 +303,9 @@ details[open] > summary:first-of-type { list-style-type: disclosure-open; }
|
|
|
292
303
|
* and border longhand, radius included — along with the native look it
|
|
293
304
|
* would lose in a browser. Nothing stays half the palette's and half the
|
|
294
305
|
* page's.
|
|
306
|
+
*
|
|
307
|
+
* A field's frame is its widget's, but its corners are the element's too:
|
|
308
|
+
* the focus ring the document draws round it is rounded as the frame is.
|
|
295
309
|
*/
|
|
296
310
|
function chromeText(look: RootLook): string {
|
|
297
311
|
return `
|
|
@@ -300,5 +314,6 @@ button {
|
|
|
300
314
|
border: ${look.controlBorder}px solid ${look.borderColor};
|
|
301
315
|
border-radius: ${look.controlRadius}px;
|
|
302
316
|
}
|
|
317
|
+
input, textarea { border-radius: ${look.controlRadius}px; }
|
|
303
318
|
`;
|
|
304
319
|
}
|