@react-x11/components 0.14.1 → 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 +171 -26
- package/dist/html/css/cascade.d.ts.map +1 -1
- package/dist/html/css/cascade.js +616 -216
- 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 +18 -0
- package/dist/html/css/shapes.d.ts.map +1 -1
- package/dist/html/css/shapes.js +21 -1
- package/dist/html/css/shapes.js.map +1 -1
- 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 +74 -12
- 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 +29 -9
- package/dist/html/layout/block.d.ts.map +1 -1
- package/dist/html/layout/block.js +141 -52
- package/dist/html/layout/block.js.map +1 -1
- package/dist/html/layout/boxes.d.ts +16 -2
- package/dist/html/layout/boxes.d.ts.map +1 -1
- package/dist/html/layout/boxes.js +144 -23
- 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 +11 -9
- 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 +1631 -221
- 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 +886 -324
- package/dist/html/layout/table.js.map +1 -1
- package/dist/html/node.d.ts +35 -4
- package/dist/html/node.d.ts.map +1 -1
- package/dist/html/node.js +118 -26
- package/dist/html/node.js.map +1 -1
- package/dist/html/paint.d.ts.map +1 -1
- package/dist/html/paint.js +558 -178
- 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 +23 -7
- package/dist/html/svg.d.ts.map +1 -1
- package/dist/html/svg.js +264 -59
- 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 +697 -221
- 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 +35 -1
- package/src/html/css/style.ts +122 -17
- 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 +149 -58
- package/src/html/layout/boxes.ts +171 -27
- package/src/html/layout/css-grid.ts +12 -9
- package/src/html/layout/flex.ts +1843 -212
- package/src/html/layout/inline.ts +27 -7
- package/src/html/layout/table.ts +961 -332
- package/src/html/node.ts +151 -27
- package/src/html/paint.ts +692 -234
- package/src/html/resources.ts +13 -1
- package/src/html/svg.ts +299 -56
- 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 -----------------------------------------------------
|
package/src/html/css/shapes.ts
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
// (`svg.ts`). A `ComputedStyle` per `<path>` would be a few hundred
|
|
14
14
|
// properties for the dozen a shape has, on a page of hundreds of icons.
|
|
15
15
|
import type { Element } from 'domhandler';
|
|
16
|
-
import { LIGHT_DARK, lightDark } from './color.js';
|
|
16
|
+
import { LIGHT_DARK, SYSTEM_COLOR, lightDark, systemColors } from './color.js';
|
|
17
17
|
import { inkColor, parseAlpha, parseColor, parseLength } from './values.js';
|
|
18
18
|
import type { UnitContext } from './values.js';
|
|
19
19
|
|
|
@@ -25,10 +25,38 @@ export type ShapeStyle = Readonly<Record<string, string>>;
|
|
|
25
25
|
/** What the rules give the elements of one drawing, and all of it as a
|
|
26
26
|
* string: what a drawing's tree was last made with (`SvgDrawing`). */
|
|
27
27
|
export interface ShapeStyles {
|
|
28
|
+
/** Each element's, where it stands. */
|
|
28
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;
|
|
29
41
|
readonly key: string;
|
|
30
42
|
}
|
|
31
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
|
+
|
|
32
60
|
/** Each property read, and whether it is inherited. */
|
|
33
61
|
const PROPS: Record<string, boolean> = {
|
|
34
62
|
fill: true,
|
|
@@ -143,6 +171,9 @@ const LINE_JOINS: Record<string, string> = {
|
|
|
143
171
|
arcs: 'miter',
|
|
144
172
|
};
|
|
145
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
|
+
|
|
146
177
|
/**
|
|
147
178
|
* One declaration's value as a drawing reads it, or null for a value the
|
|
148
179
|
* property does not take, which leaves the declaration out of the cascade
|
|
@@ -165,6 +196,9 @@ export function shapeValue(
|
|
|
165
196
|
if (wide === 'unset' || wide === 'revert' || wide === 'revert-layer') {
|
|
166
197
|
return PROPS[prop] ? 'inherit' : INITIAL[prop];
|
|
167
198
|
}
|
|
199
|
+
if (SHAPE_COLORS.has(prop) && SYSTEM_COLOR.test(value)) {
|
|
200
|
+
value = systemColors(value, ctx.units.systemColors);
|
|
201
|
+
}
|
|
168
202
|
if (LIGHT_DARK.test(value)) {
|
|
169
203
|
const picked = lightDark(value, ctx.scheme);
|
|
170
204
|
if (picked === null) return null;
|
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,13 @@ 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';
|
|
46
51
|
import { svgPaint } from './shapes.js';
|
|
47
52
|
|
|
48
53
|
export type Display =
|
|
@@ -457,7 +462,7 @@ export interface ComputedStyle {
|
|
|
457
462
|
* `text-align` on a `<table>` to behave. */
|
|
458
463
|
tableTextAlignSet: boolean;
|
|
459
464
|
/** The marks `open-quote` and `close-quote` write, pairs outermost first. */
|
|
460
|
-
quotes: readonly string[] | 'none';
|
|
465
|
+
quotes: readonly string[] | 'none' | 'auto';
|
|
461
466
|
/** Custom properties, `--name`, with their `var()`s replaced (`vars.ts`);
|
|
462
467
|
* null where none is set. Inherited as the same map. */
|
|
463
468
|
custom: CustomProps | null;
|
|
@@ -656,7 +661,8 @@ export interface ComputedStyle {
|
|
|
656
661
|
/** Its shadows front to back, the ones that can be seen; null for none. */
|
|
657
662
|
boxShadow: BoxShadow[] | null;
|
|
658
663
|
/** Its outline (CSS 2.1 18.4, CSS UI 4): drawn around the border box,
|
|
659
|
-
* 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. */
|
|
660
666
|
outlineStyle: BorderStyle | 'auto';
|
|
661
667
|
outlineWidth: number;
|
|
662
668
|
outlineColor: string;
|
|
@@ -761,16 +767,31 @@ export interface ComputedStyle {
|
|
|
761
767
|
| 'space-between'
|
|
762
768
|
| 'space-around'
|
|
763
769
|
| 'space-evenly';
|
|
764
|
-
|
|
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';
|
|
765
775
|
alignSelf:
|
|
766
|
-
|
|
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`. */
|
|
767
785
|
alignContent:
|
|
768
786
|
| 'flex-start'
|
|
769
787
|
| 'flex-end'
|
|
788
|
+
| 'start'
|
|
789
|
+
| 'end'
|
|
770
790
|
| 'center'
|
|
771
791
|
| 'stretch'
|
|
772
792
|
| 'space-between'
|
|
773
|
-
| 'space-around'
|
|
793
|
+
| 'space-around'
|
|
794
|
+
| 'space-evenly';
|
|
774
795
|
flexGrow: number;
|
|
775
796
|
flexShrink: number;
|
|
776
797
|
flexBasis: Len | 'auto' | 'content';
|
|
@@ -913,6 +934,51 @@ export interface RootLook {
|
|
|
913
934
|
* is in Chrome. Without it, `fontFamily`.
|
|
914
935
|
*/
|
|
915
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);
|
|
916
982
|
}
|
|
917
983
|
|
|
918
984
|
export function initialStyle(look: RootLook, scale = 1): ComputedStyle {
|
|
@@ -968,7 +1034,7 @@ export function initialStyle(look: RootLook, scale = 1): ComputedStyle {
|
|
|
968
1034
|
captionSide: 'top',
|
|
969
1035
|
emptyCells: 'show',
|
|
970
1036
|
tableTextAlignSet: false,
|
|
971
|
-
quotes:
|
|
1037
|
+
quotes: 'auto',
|
|
972
1038
|
custom: null,
|
|
973
1039
|
|
|
974
1040
|
display: 'inline',
|
|
@@ -1114,7 +1180,7 @@ export function initialStyle(look: RootLook, scale = 1): ComputedStyle {
|
|
|
1114
1180
|
flexDirection: 'row',
|
|
1115
1181
|
flexWrap: 'nowrap',
|
|
1116
1182
|
justifyContent: 'normal',
|
|
1117
|
-
alignItems: '
|
|
1183
|
+
alignItems: 'normal',
|
|
1118
1184
|
alignSelf: 'auto',
|
|
1119
1185
|
alignContent: 'stretch',
|
|
1120
1186
|
flexGrow: 0,
|
|
@@ -1314,6 +1380,11 @@ export function firstLineParent(
|
|
|
1314
1380
|
// --- applying a declaration -------------------------------------------------
|
|
1315
1381
|
|
|
1316
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
|
+
|
|
1317
1388
|
const COLOR_PROPS: Record<string, keyof ComputedStyle> = {
|
|
1318
1389
|
color: 'color',
|
|
1319
1390
|
'background-color': 'backgroundColor',
|
|
@@ -1438,6 +1509,11 @@ export function applyDeclaration(
|
|
|
1438
1509
|
if (/!\s*important$/i.test(value)) {
|
|
1439
1510
|
value = value.replace(/!\s*important$/i, '').trim();
|
|
1440
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
|
+
}
|
|
1441
1517
|
// the branch of each `light-dark()` this element's scheme picks, before
|
|
1442
1518
|
// anything reads the value: it stands wherever a colour can, in a
|
|
1443
1519
|
// shorthand, a shadow, a gradient or a mix, and most often arrives
|
|
@@ -2089,7 +2165,7 @@ export function applyDeclaration(
|
|
|
2089
2165
|
width = w;
|
|
2090
2166
|
continue;
|
|
2091
2167
|
}
|
|
2092
|
-
const c =
|
|
2168
|
+
const c = outlineColorOf(part, ctx);
|
|
2093
2169
|
if (c === null) return;
|
|
2094
2170
|
color = c;
|
|
2095
2171
|
}
|
|
@@ -2110,8 +2186,7 @@ export function applyDeclaration(
|
|
|
2110
2186
|
}
|
|
2111
2187
|
case 'outline-color': {
|
|
2112
2188
|
// `invert` is the text's colour where a browser cannot invert
|
|
2113
|
-
const
|
|
2114
|
-
const c = v === 'invert' ? 'currentColor' : parseColor(value);
|
|
2189
|
+
const c = outlineColorOf(value, ctx);
|
|
2115
2190
|
if (c !== null) style.outlineColor = c;
|
|
2116
2191
|
return;
|
|
2117
2192
|
}
|
|
@@ -2906,13 +2981,17 @@ export function applyDeclaration(
|
|
|
2906
2981
|
return;
|
|
2907
2982
|
}
|
|
2908
2983
|
case 'align-items': {
|
|
2909
|
-
const v =
|
|
2984
|
+
const v =
|
|
2985
|
+
value.trim().toLowerCase() === 'normal'
|
|
2986
|
+
? 'normal'
|
|
2987
|
+
: alignKeyword(value);
|
|
2910
2988
|
if (v) style.alignItems = v as ComputedStyle['alignItems'];
|
|
2911
2989
|
return;
|
|
2912
2990
|
}
|
|
2913
2991
|
case 'align-self': {
|
|
2914
|
-
|
|
2915
|
-
|
|
2992
|
+
const keyword = value.trim().toLowerCase();
|
|
2993
|
+
if (keyword === 'auto' || keyword === 'normal') {
|
|
2994
|
+
style.alignSelf = keyword;
|
|
2916
2995
|
return;
|
|
2917
2996
|
}
|
|
2918
2997
|
const v = alignKeyword(value);
|
|
@@ -2921,11 +3000,15 @@ export function applyDeclaration(
|
|
|
2921
3000
|
}
|
|
2922
3001
|
case 'align-content': {
|
|
2923
3002
|
// `normal` is `stretch` in a flex and a grid container, the only
|
|
2924
|
-
// 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);
|
|
2925
3006
|
const v =
|
|
2926
3007
|
value.trim().toLowerCase() === 'normal'
|
|
2927
3008
|
? 'stretch'
|
|
2928
|
-
:
|
|
3009
|
+
: plain === 'start' || plain === 'end'
|
|
3010
|
+
? plain
|
|
3011
|
+
: alignKeyword(value);
|
|
2929
3012
|
if (v) style.alignContent = v as ComputedStyle['alignContent'];
|
|
2930
3013
|
return;
|
|
2931
3014
|
}
|
|
@@ -3373,6 +3456,28 @@ export function settleContentVisibility(style: ComputedStyle): void {
|
|
|
3373
3456
|
}
|
|
3374
3457
|
}
|
|
3375
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
|
+
|
|
3376
3481
|
export function settleOverflow(style: ComputedStyle): void {
|
|
3377
3482
|
const x = style.overflowX;
|
|
3378
3483
|
const y = style.overflowY;
|
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
|
}
|
package/src/html/css/values.ts
CHANGED
|
@@ -127,6 +127,11 @@ export interface UnitContext {
|
|
|
127
127
|
rlh?: () => number;
|
|
128
128
|
/** Every property at its initial value, which `initial` sets one to. */
|
|
129
129
|
initial?: ComputedStyle;
|
|
130
|
+
/** The palette's focus ring, which `-webkit-focus-ring-color` is. */
|
|
131
|
+
focusRing?: string;
|
|
132
|
+
/** Each system colour as the `light-dark()` it is, the palette's in the
|
|
133
|
+
* palette's scheme (`systemColorTable`); Chrome's where it is missing. */
|
|
134
|
+
systemColors?: Map<string, string>;
|
|
130
135
|
}
|
|
131
136
|
|
|
132
137
|
/** A CSS number (CSS Syntax 3 4.3.12): a sign, digits with at most one
|
|
@@ -348,9 +353,10 @@ export function fourSides<T>(parts: T[]): [T, T, T, T] {
|
|
|
348
353
|
* element's own ink, everything else passed through. */
|
|
349
354
|
export function inkColor(color: string, current: string): string {
|
|
350
355
|
if (color === 'currentColor') return current;
|
|
351
|
-
// a `color-mix()` with `currentColor` in it waits
|
|
352
|
-
// every other functional colour was read when it was
|
|
353
|
-
|
|
356
|
+
// a `color-mix()` or a relative colour with `currentColor` in it waits
|
|
357
|
+
// for the colour too; every other functional colour was read when it was
|
|
358
|
+
// parsed, to `#rrggbb` or `rgba()` with no letters in it
|
|
359
|
+
if (color.charCodeAt(color.length - 1) === 41 && HAS_CURRENT.test(color)) {
|
|
354
360
|
return functionalColor(color.replace(CURRENT, current)) ?? current;
|
|
355
361
|
}
|
|
356
362
|
return color;
|
|
@@ -358,6 +364,7 @@ export function inkColor(color: string, current: string): string {
|
|
|
358
364
|
|
|
359
365
|
const CURRENT = /currentcolor/gi;
|
|
360
366
|
const HAS_CURRENT = /currentcolor/i;
|
|
367
|
+
const RELATIVE_COLOR = /^[a-z]+\(\s*from\s/i;
|
|
361
368
|
|
|
362
369
|
/** Whether a colour paints anything at all. Two string tests rather than a
|
|
363
370
|
* parse, for the reason at the top of the file. */
|
|
@@ -410,12 +417,19 @@ export function parseColor(value: string): string | null {
|
|
|
410
417
|
// five or seven, and a typo'd `#ff000` took the application down
|
|
411
418
|
if (v.startsWith('#')) return HEX_COLOR.test(v) ? v : null;
|
|
412
419
|
if (/^(?:rgba?|hsla?|hwb|lab|lch|oklab|oklch|color|color-mix)\(/i.test(v)) {
|
|
413
|
-
// a mix with `currentColor` in it is kept to be
|
|
414
|
-
// (`inkColor`), once it is known to be one
|
|
415
|
-
if (HAS_CURRENT.test(v)
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
420
|
+
// a mix or a relative colour with `currentColor` in it is kept to be
|
|
421
|
+
// worked out where it is used (`inkColor`), once it is known to be one
|
|
422
|
+
if (HAS_CURRENT.test(v)) {
|
|
423
|
+
if (/^color-mix\(/i.test(v)) {
|
|
424
|
+
return functionalColor(v.replace(CURRENT, '#000')) === null
|
|
425
|
+
? null
|
|
426
|
+
: `color-mix(${v.slice(v.indexOf('(') + 1)}`;
|
|
427
|
+
}
|
|
428
|
+
if (RELATIVE_COLOR.test(v)) {
|
|
429
|
+
return functionalColor(v.replace(CURRENT, '#000')) === null
|
|
430
|
+
? null
|
|
431
|
+
: `${v.slice(0, v.indexOf('(')).toLowerCase()}${v.slice(v.indexOf('('))}`;
|
|
432
|
+
}
|
|
419
433
|
}
|
|
420
434
|
return functionalColor(v);
|
|
421
435
|
}
|
package/src/html/index.ts
CHANGED
|
@@ -238,6 +238,13 @@ function deriveLook(
|
|
|
238
238
|
// `medium` and the face it reads pages in
|
|
239
239
|
controlFontSize: Number(theme.fontSize ?? 14),
|
|
240
240
|
controlFontFamily: String(theme.fontFamily ?? 'sans-serif'),
|
|
241
|
+
...(typeof theme.focusRing === 'string' && { focusRing: theme.focusRing }),
|
|
242
|
+
...(typeof theme.focusRingWidth === 'number' && {
|
|
243
|
+
focusRingWidth: theme.focusRingWidth,
|
|
244
|
+
}),
|
|
245
|
+
...(typeof theme.focusRingOffset === 'number' && {
|
|
246
|
+
focusRingOffset: theme.focusRingOffset,
|
|
247
|
+
}),
|
|
241
248
|
};
|
|
242
249
|
}
|
|
243
250
|
|