@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/color.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import * as ntk from 'react-x11/ntk';
|
|
2
|
+
import { parseMathNumber } from './calc.js';
|
|
2
3
|
import { closingParen, topLevelComma } from './vars.js';
|
|
3
4
|
|
|
4
5
|
// CSS Color 4's functional colours: read here, and written back in the one
|
|
@@ -151,9 +152,7 @@ function readRgbaFunction(value: string): Rgba | null {
|
|
|
151
152
|
// `rgb(0, 128, 0` as a sheet's last words is green
|
|
152
153
|
if (body.endsWith(')')) body = body.slice(0, -1);
|
|
153
154
|
if (name === 'color-mix') return mix(body);
|
|
154
|
-
|
|
155
|
-
// and a declaration this cannot read is dropped
|
|
156
|
-
if (body.includes('(') || body.includes(')')) return null;
|
|
155
|
+
if (RELATIVE.test(body)) return relative(name, body);
|
|
157
156
|
|
|
158
157
|
let channels: Component[];
|
|
159
158
|
let alpha: Component | null = null;
|
|
@@ -161,13 +160,13 @@ function readRgbaFunction(value: string): Rgba | null {
|
|
|
161
160
|
if (body.includes(',')) {
|
|
162
161
|
// the legacy comma form, which only these four have
|
|
163
162
|
if (!LEGACY.has(name)) return null;
|
|
164
|
-
const parts = body
|
|
163
|
+
const parts = topLevelParts(body, ',');
|
|
165
164
|
if (parts.length !== 3 && parts.length !== 4) return null;
|
|
166
165
|
const read: Component[] = [];
|
|
167
166
|
for (const part of parts) {
|
|
168
167
|
const token = part.trim();
|
|
169
|
-
if (!token ||
|
|
170
|
-
const c =
|
|
168
|
+
if (!token || topLevelParts(token, ' ').length > 1) return null;
|
|
169
|
+
const c = channel(token);
|
|
171
170
|
if (!c || c.unit === 'none') return null;
|
|
172
171
|
read.push(c);
|
|
173
172
|
}
|
|
@@ -182,22 +181,22 @@ function readRgbaFunction(value: string): Rgba | null {
|
|
|
182
181
|
return null;
|
|
183
182
|
}
|
|
184
183
|
} else {
|
|
185
|
-
const slash = body
|
|
184
|
+
const slash = topLevelParts(body, '/');
|
|
186
185
|
if (slash.length > 2) return null;
|
|
187
|
-
const tokens = slash[0]
|
|
186
|
+
const tokens = words(slash[0]);
|
|
188
187
|
if (name === 'color') space = (tokens.shift() ?? '').toLowerCase();
|
|
189
188
|
if (tokens.length !== 3) return null;
|
|
190
189
|
const read: Component[] = [];
|
|
191
190
|
for (const token of tokens) {
|
|
192
|
-
const c =
|
|
191
|
+
const c = channel(token);
|
|
193
192
|
if (!c) return null;
|
|
194
193
|
read.push(c);
|
|
195
194
|
}
|
|
196
195
|
channels = read;
|
|
197
196
|
if (slash.length === 2) {
|
|
198
|
-
const token = slash[1]
|
|
199
|
-
if (
|
|
200
|
-
alpha =
|
|
197
|
+
const token = words(slash[1]);
|
|
198
|
+
if (token.length !== 1) return null;
|
|
199
|
+
alpha = channel(token[0]);
|
|
201
200
|
if (!alpha) return null;
|
|
202
201
|
}
|
|
203
202
|
}
|
|
@@ -215,6 +214,158 @@ function readRgbaFunction(value: string): Rgba | null {
|
|
|
215
214
|
|
|
216
215
|
const LEGACY = new Set(['rgb', 'rgba', 'hsl', 'hsla']);
|
|
217
216
|
|
|
217
|
+
/** A space-separated list's items, a function's arguments kept whole. */
|
|
218
|
+
function words(text: string): string[] {
|
|
219
|
+
return topLevelParts(text.trim(), ' ').filter(Boolean);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/** A channel: a number, a percentage, an angle or `none`, or a math
|
|
223
|
+
* function standing for a number — `rgb(calc(255 / 2) 0 0)`. */
|
|
224
|
+
function channel(token: string): Component | null {
|
|
225
|
+
if (!token.includes('(')) return component(token);
|
|
226
|
+
const n = parseMathNumber(token);
|
|
227
|
+
return n === null ? null : { value: n, unit: '' };
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// --- relative colours (CSS Color 5, 4) -------------------------------------
|
|
231
|
+
|
|
232
|
+
const RELATIVE = /^\s*from\s/i;
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* A relative colour, `rgb(from <colour> r g b / alpha)` and its kind in
|
|
236
|
+
* each function: the origin colour taken into the function's space, and
|
|
237
|
+
* each channel a number, a keyword that is one of the origin's channels,
|
|
238
|
+
* or a calculation with them in it. The keywords are numbers in the
|
|
239
|
+
* function's own range (`r` from 0 to 255, `s` and `l` from 0 to 100, an
|
|
240
|
+
* angle's degrees), and an alpha left out is the origin's, not 1. The
|
|
241
|
+
* origin is any colour but `currentColor`, which `parseColor` keeps the
|
|
242
|
+
* colour to be resolved where it is used.
|
|
243
|
+
*/
|
|
244
|
+
function relative(name: string, body: string): Rgba | null {
|
|
245
|
+
const slash = topLevelParts(body, '/');
|
|
246
|
+
if (slash.length > 2) return null;
|
|
247
|
+
const tokens = words(slash[0]);
|
|
248
|
+
if (tokens.length < 2 || tokens[0].toLowerCase() !== 'from') return null;
|
|
249
|
+
const origin = readRgba(tokens[1]);
|
|
250
|
+
if (!origin) return null;
|
|
251
|
+
const rest = tokens.slice(2);
|
|
252
|
+
const space = name === 'color' ? (rest.shift() ?? '').toLowerCase() : '';
|
|
253
|
+
if (rest.length !== 3) return null;
|
|
254
|
+
const coords = originIn(name, space, origin.rgb);
|
|
255
|
+
const keys = CHANNEL_NAMES[name] ?? (XYZ_SPACES.has(space) ? 'xyz' : 'rgb');
|
|
256
|
+
if (!coords) return null;
|
|
257
|
+
const known = new Map<string, number>([['alpha', origin.a]]);
|
|
258
|
+
for (let i = 0; i < 3; i += 1) {
|
|
259
|
+
// a hue a grey has none of is `none`, which is 0
|
|
260
|
+
known.set(keys[i], Number.isNaN(coords[i]) ? 0 : coords[i]);
|
|
261
|
+
}
|
|
262
|
+
const channels: Component[] = [];
|
|
263
|
+
for (const token of rest) {
|
|
264
|
+
const c = relativeChannel(token, known);
|
|
265
|
+
if (!c) return null;
|
|
266
|
+
channels.push(c);
|
|
267
|
+
}
|
|
268
|
+
let a = origin.a;
|
|
269
|
+
if (slash.length === 2) {
|
|
270
|
+
const token = words(slash[1]);
|
|
271
|
+
if (token.length !== 1) return null;
|
|
272
|
+
const alpha = relativeChannel(token[0], known);
|
|
273
|
+
if (!alpha || alpha.unit === 'deg' || alpha.unit === 'grad') return null;
|
|
274
|
+
if (alpha.unit === 'rad' || alpha.unit === 'turn') return null;
|
|
275
|
+
a = clamp01(alpha.unit === '%' ? alpha.value / 100 : alpha.value);
|
|
276
|
+
}
|
|
277
|
+
const rgb = toSrgb(name, space, channels);
|
|
278
|
+
return rgb ? { rgb, a } : null;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/** Each function's channel keywords, in order. */
|
|
282
|
+
const CHANNEL_NAMES: Record<string, string> = {
|
|
283
|
+
rgb: 'rgb',
|
|
284
|
+
rgba: 'rgb',
|
|
285
|
+
hsl: 'hsl',
|
|
286
|
+
hsla: 'hsl',
|
|
287
|
+
hwb: 'hwb',
|
|
288
|
+
lab: 'lab',
|
|
289
|
+
lch: 'lch',
|
|
290
|
+
oklab: 'lab',
|
|
291
|
+
oklch: 'lch',
|
|
292
|
+
};
|
|
293
|
+
|
|
294
|
+
const XYZ_SPACES = new Set(['xyz', 'xyz-d50', 'xyz-d65']);
|
|
295
|
+
|
|
296
|
+
/** A channel of a relative colour: a keyword is the origin's channel, and
|
|
297
|
+
* a calculation is worked out with the keywords' values put in. */
|
|
298
|
+
function relativeChannel(
|
|
299
|
+
token: string,
|
|
300
|
+
known: Map<string, number>,
|
|
301
|
+
): Component | null {
|
|
302
|
+
const value = known.get(token.toLowerCase());
|
|
303
|
+
if (value !== undefined) return { value, unit: '' };
|
|
304
|
+
if (!token.includes('(')) return component(token);
|
|
305
|
+
let unknown = false;
|
|
306
|
+
const text = token.replace(KEYWORD, (word) => {
|
|
307
|
+
const v = known.get(word.toLowerCase());
|
|
308
|
+
// a function's name, `pi`, `e`, `infinity`: the calculation's own
|
|
309
|
+
if (v === undefined) {
|
|
310
|
+
if (!/^(?:pi|e|-?infinity|nan)$/i.test(word)) unknown = true;
|
|
311
|
+
return word;
|
|
312
|
+
}
|
|
313
|
+
return `(${v})`;
|
|
314
|
+
});
|
|
315
|
+
if (unknown) return null;
|
|
316
|
+
return channel(text);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/** A word in a calculation that is not a function's name. */
|
|
320
|
+
const KEYWORD = /(?<![\w.-])-?[a-z][\w-]*(?![\w(-])/gi;
|
|
321
|
+
|
|
322
|
+
/** The origin colour's channels in a relative colour's function, in the
|
|
323
|
+
* ranges its keywords take. */
|
|
324
|
+
function originIn(name: string, space: string, rgb: Triple): Triple | null {
|
|
325
|
+
switch (name) {
|
|
326
|
+
case 'rgb':
|
|
327
|
+
case 'rgba':
|
|
328
|
+
return scaled(rgb, 255);
|
|
329
|
+
case 'hsl':
|
|
330
|
+
case 'hsla':
|
|
331
|
+
return srgbToHsl(rgb);
|
|
332
|
+
case 'hwb':
|
|
333
|
+
case 'lab':
|
|
334
|
+
case 'lch':
|
|
335
|
+
case 'oklab':
|
|
336
|
+
case 'oklch':
|
|
337
|
+
return toSpace(name, rgb);
|
|
338
|
+
case 'color':
|
|
339
|
+
return toSpace(space, rgb) ?? toPredefined(space, rgb);
|
|
340
|
+
default:
|
|
341
|
+
return null;
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/** Gamma-encoded sRGB in one of `color()`'s RGB spaces, as `predefined`
|
|
346
|
+
* reads them back. */
|
|
347
|
+
function toPredefined(space: string, rgb: Triple): Triple | null {
|
|
348
|
+
const xyz = multiply(LINEAR_SRGB_TO_XYZ, linearize(rgb));
|
|
349
|
+
switch (space) {
|
|
350
|
+
case 'display-p3':
|
|
351
|
+
return multiply(XYZ_TO_P3, xyz).map(srgbEncode) as Triple;
|
|
352
|
+
case 'display-p3-linear':
|
|
353
|
+
return multiply(XYZ_TO_P3, xyz);
|
|
354
|
+
case 'a98-rgb':
|
|
355
|
+
return multiply(XYZ_TO_A98, xyz).map(
|
|
356
|
+
(x) => Math.sign(x) * Math.abs(x) ** (256 / 563),
|
|
357
|
+
) as Triple;
|
|
358
|
+
case 'prophoto-rgb':
|
|
359
|
+
return multiply(XYZ_D50_TO_PROPHOTO, multiply(D65_TO_D50, xyz)).map(
|
|
360
|
+
prophotoEncode,
|
|
361
|
+
) as Triple;
|
|
362
|
+
case 'rec2020':
|
|
363
|
+
return multiply(XYZ_TO_REC2020, xyz).map(rec2020Encode) as Triple;
|
|
364
|
+
default:
|
|
365
|
+
return null;
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
218
369
|
/** A channel as a number, or null where its unit is not one it takes:
|
|
219
370
|
* `pct` is what 100% means for it. */
|
|
220
371
|
function amount(c: Component, pct: number): number | null {
|
|
@@ -674,12 +825,14 @@ function srgbLinear(x: number): number {
|
|
|
674
825
|
}
|
|
675
826
|
|
|
676
827
|
function gamma(rgb: Triple): Triple {
|
|
677
|
-
return rgb.map(
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
828
|
+
return rgb.map(srgbEncode) as Triple;
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
function srgbEncode(x: number): number {
|
|
832
|
+
const abs = Math.abs(x);
|
|
833
|
+
return abs > 0.0031308
|
|
834
|
+
? Math.sign(x) * (1.055 * abs ** (1 / 2.4) - 0.055)
|
|
835
|
+
: 12.92 * x;
|
|
683
836
|
}
|
|
684
837
|
|
|
685
838
|
function prophotoLinear(x: number): number {
|
|
@@ -687,6 +840,20 @@ function prophotoLinear(x: number): number {
|
|
|
687
840
|
return abs <= 16 / 512 ? x / 16 : Math.sign(x) * abs ** 1.8;
|
|
688
841
|
}
|
|
689
842
|
|
|
843
|
+
function prophotoEncode(x: number): number {
|
|
844
|
+
const abs = Math.abs(x);
|
|
845
|
+
return abs < 1 / 512 ? x * 16 : Math.sign(x) * abs ** (1 / 1.8);
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
function rec2020Encode(x: number): number {
|
|
849
|
+
const alpha = 1.09929682680944;
|
|
850
|
+
const beta = 0.018053968510807;
|
|
851
|
+
const abs = Math.abs(x);
|
|
852
|
+
return abs < beta
|
|
853
|
+
? x * 4.5
|
|
854
|
+
: Math.sign(x) * (alpha * abs ** 0.45 - (alpha - 1));
|
|
855
|
+
}
|
|
856
|
+
|
|
690
857
|
function rec2020Linear(x: number): number {
|
|
691
858
|
const alpha = 1.09929682680944;
|
|
692
859
|
const beta = 0.018053968510807;
|
|
@@ -755,6 +922,33 @@ const REC2020_TO_XYZ: readonly Triple[] = [
|
|
|
755
922
|
[0, 19567812 / 697040785, 295819943 / 278816314],
|
|
756
923
|
];
|
|
757
924
|
|
|
925
|
+
// The way back from XYZ to each RGB space, which a relative colour's origin
|
|
926
|
+
// is taken into.
|
|
927
|
+
|
|
928
|
+
const XYZ_TO_P3: readonly Triple[] = [
|
|
929
|
+
[446124 / 178915, -333277 / 357830, -72051 / 178915],
|
|
930
|
+
[-14852 / 17905, 63121 / 35810, 423 / 17905],
|
|
931
|
+
[11844 / 330415, -50337 / 660830, 316169 / 330415],
|
|
932
|
+
];
|
|
933
|
+
|
|
934
|
+
const XYZ_TO_A98: readonly Triple[] = [
|
|
935
|
+
[1829569 / 896150, -506331 / 896150, -308931 / 896150],
|
|
936
|
+
[-851781 / 878810, 1648619 / 878810, 36519 / 878810],
|
|
937
|
+
[16779 / 1248040, -147721 / 1248040, 1266979 / 1248040],
|
|
938
|
+
];
|
|
939
|
+
|
|
940
|
+
const XYZ_D50_TO_PROPHOTO: readonly Triple[] = [
|
|
941
|
+
[1.3457868816471583, -0.25557208737979464, -0.05110186497554526],
|
|
942
|
+
[-0.5446307051249019, 1.5082477428451468, 0.02052744743642139],
|
|
943
|
+
[0, 0, 1.2119675456389452],
|
|
944
|
+
];
|
|
945
|
+
|
|
946
|
+
const XYZ_TO_REC2020: readonly Triple[] = [
|
|
947
|
+
[30757411 / 17917100, -6372589 / 17917100, -4539589 / 17917100],
|
|
948
|
+
[-19765991 / 29648200, 47925759 / 29648200, 467509 / 29648200],
|
|
949
|
+
[792561 / 44930125, -1921689 / 44930125, 42328811 / 44930125],
|
|
950
|
+
];
|
|
951
|
+
|
|
758
952
|
// --- colour schemes ---------------------------------------------------------
|
|
759
953
|
|
|
760
954
|
/** Whether a value has a `light-dark()` in it. */
|
|
@@ -856,6 +1050,139 @@ export const SCHEME_COLORS = {
|
|
|
856
1050
|
dark: { canvas: '#121212', text: '#ffffff', link: '#9e9eff' },
|
|
857
1051
|
} as const;
|
|
858
1052
|
|
|
1053
|
+
// --- system colours (CSS Color 4, 6.2) -------------------------------------
|
|
1054
|
+
|
|
1055
|
+
/**
|
|
1056
|
+
* The system colours, in light and in dark, as Chrome has them where the
|
|
1057
|
+
* platform says nothing (Blink's `LayoutTheme::DefaultSystemColor`): the
|
|
1058
|
+
* colours of a browser's own page and controls, which a page names to
|
|
1059
|
+
* look like one and which follow its colour scheme.
|
|
1060
|
+
*/
|
|
1061
|
+
const SYSTEM_COLORS: Record<string, readonly [string, string]> = {
|
|
1062
|
+
accentcolor: ['#0075ff', '#0075ff'],
|
|
1063
|
+
accentcolortext: ['#ffffff', '#ffffff'],
|
|
1064
|
+
activetext: ['#ff0000', '#ff0000'],
|
|
1065
|
+
buttonborder: ['#767676', '#6b6b6b'],
|
|
1066
|
+
buttonface: ['#efefef', '#6b6b6b'],
|
|
1067
|
+
buttontext: ['#000000', '#ffffff'],
|
|
1068
|
+
canvas: ['#ffffff', '#121212'],
|
|
1069
|
+
canvastext: ['#000000', '#ffffff'],
|
|
1070
|
+
field: ['#ffffff', '#3b3b3b'],
|
|
1071
|
+
fieldtext: ['#000000', '#ffffff'],
|
|
1072
|
+
graytext: ['#808080', '#808080'],
|
|
1073
|
+
highlight: ['#b3d7ff', '#99c8ff'],
|
|
1074
|
+
highlighttext: ['#000000', '#000000'],
|
|
1075
|
+
linktext: ['#0000ee', '#9e9eff'],
|
|
1076
|
+
mark: ['#ffff00', '#ffff00'],
|
|
1077
|
+
marktext: ['#000000', '#000000'],
|
|
1078
|
+
selecteditem: ['#b3d7ff', '#99c8ff'],
|
|
1079
|
+
selecteditemtext: ['#000000', '#3b3b3b'],
|
|
1080
|
+
visitedtext: ['#551a8b', '#d0adf0'],
|
|
1081
|
+
};
|
|
1082
|
+
|
|
1083
|
+
/** The deprecated system colours, each the same as one of the rest
|
|
1084
|
+
* (CSS Color 4, 6.3). */
|
|
1085
|
+
const DEPRECATED_COLORS: Record<string, string> = {
|
|
1086
|
+
activeborder: 'buttonborder',
|
|
1087
|
+
inactiveborder: 'buttonborder',
|
|
1088
|
+
threeddarkshadow: 'buttonborder',
|
|
1089
|
+
threedhighlight: 'buttonborder',
|
|
1090
|
+
threedlightshadow: 'buttonborder',
|
|
1091
|
+
threedshadow: 'buttonborder',
|
|
1092
|
+
windowframe: 'buttonborder',
|
|
1093
|
+
buttonhighlight: 'buttonface',
|
|
1094
|
+
buttonshadow: 'buttonface',
|
|
1095
|
+
threedface: 'buttonface',
|
|
1096
|
+
activecaption: 'canvas',
|
|
1097
|
+
appworkspace: 'canvas',
|
|
1098
|
+
background: 'canvas',
|
|
1099
|
+
inactivecaption: 'canvas',
|
|
1100
|
+
infobackground: 'canvas',
|
|
1101
|
+
menu: 'canvas',
|
|
1102
|
+
scrollbar: 'canvas',
|
|
1103
|
+
window: 'canvas',
|
|
1104
|
+
captiontext: 'canvastext',
|
|
1105
|
+
infotext: 'canvastext',
|
|
1106
|
+
menutext: 'canvastext',
|
|
1107
|
+
windowtext: 'canvastext',
|
|
1108
|
+
inactivecaptiontext: 'graytext',
|
|
1109
|
+
};
|
|
1110
|
+
|
|
1111
|
+
/** The palette's colours a system colour is, in the palette's scheme. */
|
|
1112
|
+
export interface PaletteColors {
|
|
1113
|
+
color: string;
|
|
1114
|
+
background: string;
|
|
1115
|
+
linkColor: string;
|
|
1116
|
+
surface: string;
|
|
1117
|
+
borderColor: string;
|
|
1118
|
+
mutedColor: string;
|
|
1119
|
+
colorScheme: 'light' | 'dark';
|
|
1120
|
+
}
|
|
1121
|
+
|
|
1122
|
+
/** Which of the palette's colours each system colour is: the window's
|
|
1123
|
+
* ground and ink, its links and accent, its controls'. */
|
|
1124
|
+
const FROM_PALETTE: Record<string, keyof PaletteColors> = {
|
|
1125
|
+
accentcolor: 'linkColor',
|
|
1126
|
+
buttonborder: 'borderColor',
|
|
1127
|
+
buttonface: 'surface',
|
|
1128
|
+
buttontext: 'color',
|
|
1129
|
+
canvas: 'background',
|
|
1130
|
+
canvastext: 'color',
|
|
1131
|
+
field: 'background',
|
|
1132
|
+
fieldtext: 'color',
|
|
1133
|
+
graytext: 'mutedColor',
|
|
1134
|
+
linktext: 'linkColor',
|
|
1135
|
+
};
|
|
1136
|
+
|
|
1137
|
+
/**
|
|
1138
|
+
* Each system colour's name, lower-cased, and the `light-dark()` it is.
|
|
1139
|
+
* The palette is this renderer's platform, so in the palette's scheme the
|
|
1140
|
+
* colours it has are its own: `Canvas` is the window's ground and
|
|
1141
|
+
* `CanvasText` its text, as an unstyled document is drawn. In the other
|
|
1142
|
+
* scheme, and in an SVG image, which has no palette, they are Chrome's.
|
|
1143
|
+
*/
|
|
1144
|
+
export function systemColorTable(
|
|
1145
|
+
palette: PaletteColors | null,
|
|
1146
|
+
): Map<string, string> {
|
|
1147
|
+
const table = new Map<string, string>();
|
|
1148
|
+
const own = palette?.colorScheme === 'dark' ? 1 : 0;
|
|
1149
|
+
const pairOf = (name: string): string => {
|
|
1150
|
+
const pair = [...SYSTEM_COLORS[name]];
|
|
1151
|
+
const token = palette && FROM_PALETTE[name];
|
|
1152
|
+
if (token) pair[own] = palette[token] as string;
|
|
1153
|
+
return `light-dark(${pair[0]}, ${pair[1]})`;
|
|
1154
|
+
};
|
|
1155
|
+
for (const name of Object.keys(SYSTEM_COLORS)) table.set(name, pairOf(name));
|
|
1156
|
+
for (const [name, same] of Object.entries(DEPRECATED_COLORS)) {
|
|
1157
|
+
table.set(name, table.get(same)!);
|
|
1158
|
+
}
|
|
1159
|
+
return table;
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1162
|
+
/** Chrome's own, for a cascade with no palette. */
|
|
1163
|
+
let CHROME_TABLE: Map<string, string> | null = null;
|
|
1164
|
+
|
|
1165
|
+
/** Whether a value names a system colour: a word of its own, not a part
|
|
1166
|
+
* of a name, a path or a string. Written out, as a pattern built from the
|
|
1167
|
+
* tables would be a call at load. */
|
|
1168
|
+
export const SYSTEM_COLOR =
|
|
1169
|
+
/(?<![\w.#/'"-])(?:inactivecaptiontext|threedlightshadow|selecteditemtext|threeddarkshadow|accentcolortext|threedhighlight|buttonhighlight|inactivecaption|inactiveborder|infobackground|highlighttext|activecaption|buttonborder|selecteditem|activeborder|threedshadow|buttonshadow|appworkspace|accentcolor|visitedtext|windowframe|captiontext|activetext|buttonface|buttontext|canvastext|threedface|background|windowtext|fieldtext|highlight|scrollbar|graytext|linktext|marktext|infotext|menutext|canvas|window|field|mark|menu)(?![\w.(/'"-])/i;
|
|
1170
|
+
const SYSTEM_COLOR_AT =
|
|
1171
|
+
/(?<![\w.#/'"-])(?:inactivecaptiontext|threedlightshadow|selecteditemtext|threeddarkshadow|accentcolortext|threedhighlight|buttonhighlight|inactivecaption|inactiveborder|infobackground|highlighttext|activecaption|buttonborder|selecteditem|activeborder|threedshadow|buttonshadow|appworkspace|accentcolor|visitedtext|windowframe|captiontext|activetext|buttonface|buttontext|canvastext|threedface|background|windowtext|fieldtext|highlight|scrollbar|graytext|linktext|marktext|infotext|menutext|canvas|window|field|mark|menu)(?![\w.(/'"-])/gi;
|
|
1172
|
+
|
|
1173
|
+
/** A value with each system colour in it replaced by the `light-dark()` it
|
|
1174
|
+
* is, which the element's scheme then picks a branch of. */
|
|
1175
|
+
export function systemColors(
|
|
1176
|
+
value: string,
|
|
1177
|
+
table: Map<string, string> | undefined,
|
|
1178
|
+
): string {
|
|
1179
|
+
const colors = table ?? (CHROME_TABLE ??= systemColorTable(null));
|
|
1180
|
+
return value.replace(
|
|
1181
|
+
SYSTEM_COLOR_AT,
|
|
1182
|
+
(word) => colors.get(word.toLowerCase()) ?? word,
|
|
1183
|
+
);
|
|
1184
|
+
}
|
|
1185
|
+
|
|
859
1186
|
const SCHEME_IDENT = /^-?(?:[a-z_]|--)[\w-]*$/;
|
|
860
1187
|
/** What a scheme's name cannot be: the keywords the grammar has, and the
|
|
861
1188
|
* CSS-wide ones, which reach here only in a list. */
|
package/src/html/css/content.ts
CHANGED
|
@@ -35,10 +35,167 @@ export interface CounterChange {
|
|
|
35
35
|
counted?: true;
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
/** English's quotation marks, outer pair first: `quotes`
|
|
39
|
-
*
|
|
38
|
+
/** English's quotation marks, outer pair first: what `quotes: auto` is for
|
|
39
|
+
* a language with none of its own, and for none. */
|
|
40
40
|
export const DEFAULT_QUOTES: readonly string[] = ['“', '”', '‘', '’'];
|
|
41
41
|
|
|
42
|
+
/**
|
|
43
|
+
* Each language's quotation marks, outer pair then inner, where they are
|
|
44
|
+
* not its parent's — `fr-ch`'s where they are not `fr`'s, `fr`'s where
|
|
45
|
+
* they are not English's — from CLDR's delimiters, which Chrome reads
|
|
46
|
+
* through ICU. What `quotes: auto` makes of the element's language (CSS
|
|
47
|
+
* Content 3, 2.1): `<q>` in a French page is «un ‹deux› trois».
|
|
48
|
+
*/
|
|
49
|
+
const QUOTES_BY_LANGUAGE: Record<string, string> = {
|
|
50
|
+
agq: '„”‚’',
|
|
51
|
+
am: '«»‹›',
|
|
52
|
+
ar: '”“’‘',
|
|
53
|
+
ast: '«»“”',
|
|
54
|
+
'az-arab': '«»‹›',
|
|
55
|
+
'az-cyrl': '«»‹›',
|
|
56
|
+
bas: '«»„“',
|
|
57
|
+
be: '«»„“',
|
|
58
|
+
bg: '„“„“',
|
|
59
|
+
blo: '«»“”',
|
|
60
|
+
bm: '«»“”',
|
|
61
|
+
'bm-nkoo': '“”‘’',
|
|
62
|
+
br: '«»“”',
|
|
63
|
+
bs: '„”‘’',
|
|
64
|
+
'bs-cyrl': '„“‚‘',
|
|
65
|
+
bua: '«»„“',
|
|
66
|
+
ca: '«»“”',
|
|
67
|
+
cs: '„“‚‘',
|
|
68
|
+
cv: '«»“”',
|
|
69
|
+
de: '„“‚‘',
|
|
70
|
+
dsb: '„“‚‘',
|
|
71
|
+
dua: '«»‘’',
|
|
72
|
+
dyo: '«»“”',
|
|
73
|
+
el: '«»“”',
|
|
74
|
+
'el-polyton': '«»‘’',
|
|
75
|
+
eo: '“”«»',
|
|
76
|
+
'es-us': '«»“”',
|
|
77
|
+
et: '„“‚‘',
|
|
78
|
+
eu: '«»“”',
|
|
79
|
+
ewo: '«»“”',
|
|
80
|
+
fa: '«»‹›',
|
|
81
|
+
ff: '„”‚’',
|
|
82
|
+
'ff-adlm': '“”‘’',
|
|
83
|
+
fi: '””’’',
|
|
84
|
+
fr: '«»«»',
|
|
85
|
+
'fr-ca': '«»”“',
|
|
86
|
+
'fr-ch': '«»‹›',
|
|
87
|
+
fur: '‘’“”',
|
|
88
|
+
gsw: '«»‹›',
|
|
89
|
+
he: '””’’',
|
|
90
|
+
hr: '„“‚‘',
|
|
91
|
+
hsb: '„“‚‘',
|
|
92
|
+
ht: '«»«»',
|
|
93
|
+
hu: '„”»«',
|
|
94
|
+
hy: '«»«»',
|
|
95
|
+
ia: '‘’“”',
|
|
96
|
+
ie: '«»“”',
|
|
97
|
+
is: '„“‚‘',
|
|
98
|
+
it: '«»“”',
|
|
99
|
+
ja: '「」『』',
|
|
100
|
+
jgo: '«»‹›',
|
|
101
|
+
ka: '„“«»',
|
|
102
|
+
kab: '«»“”',
|
|
103
|
+
kk: '«»“”',
|
|
104
|
+
'kk-arab': '»«›‹',
|
|
105
|
+
kkj: '«»‹›',
|
|
106
|
+
ksf: '«»‘’',
|
|
107
|
+
ksh: '„“‚‘',
|
|
108
|
+
ky: '«»„“',
|
|
109
|
+
lag: '””’’',
|
|
110
|
+
lb: '„“‚‘',
|
|
111
|
+
lij: '«»“”',
|
|
112
|
+
lld: '”“’‘',
|
|
113
|
+
lt: '„“„“',
|
|
114
|
+
luy: '„“‚‘',
|
|
115
|
+
mg: '«»“”',
|
|
116
|
+
mk: '„“‚‘',
|
|
117
|
+
'ms-arab': '”“’‘',
|
|
118
|
+
mua: '«»“”',
|
|
119
|
+
mzn: '«»‹›',
|
|
120
|
+
nb: '«»‘’',
|
|
121
|
+
nds: '„“‚‘',
|
|
122
|
+
nl: '‘’‘’',
|
|
123
|
+
nmg: '„”«»',
|
|
124
|
+
nn: '«»‘’',
|
|
125
|
+
nnh: '«»“”',
|
|
126
|
+
no: '«»‘’',
|
|
127
|
+
oc: '«»«»',
|
|
128
|
+
os: '«»„“',
|
|
129
|
+
pl: '„”«»',
|
|
130
|
+
pms: '«»“”',
|
|
131
|
+
prg: '„“„“',
|
|
132
|
+
'pt-ao': '«»“”',
|
|
133
|
+
'pt-ch': '«»“”',
|
|
134
|
+
'pt-cv': '«»“”',
|
|
135
|
+
'pt-gq': '«»“”',
|
|
136
|
+
'pt-gw': '«»“”',
|
|
137
|
+
'pt-lu': '«»“”',
|
|
138
|
+
'pt-mo': '«»“”',
|
|
139
|
+
'pt-mz': '«»“”',
|
|
140
|
+
'pt-pt': '«»“”',
|
|
141
|
+
'pt-st': '«»“”',
|
|
142
|
+
'pt-tl': '«»“”',
|
|
143
|
+
rm: '«»‹›',
|
|
144
|
+
rn: '””’’',
|
|
145
|
+
ro: '„”«»',
|
|
146
|
+
ru: '«»„“',
|
|
147
|
+
rw: '«»‘’',
|
|
148
|
+
sah: '«»„“',
|
|
149
|
+
sc: '«»“”',
|
|
150
|
+
sdh: '«»‹›',
|
|
151
|
+
se: '””’’',
|
|
152
|
+
sg: '«»“”',
|
|
153
|
+
sgs: '„“„“',
|
|
154
|
+
shi: '«»„”',
|
|
155
|
+
sk: '„“‚‘',
|
|
156
|
+
sl: '„“‚‘',
|
|
157
|
+
sn: '””’’',
|
|
158
|
+
sr: '„”’’',
|
|
159
|
+
st: '“’“”',
|
|
160
|
+
sv: '””’’',
|
|
161
|
+
syr: '”“’‘',
|
|
162
|
+
szl: '„”»«',
|
|
163
|
+
ti: '«»“”',
|
|
164
|
+
'ti-er': '‘’“”',
|
|
165
|
+
tk: '“”“”',
|
|
166
|
+
tn: '‘’“”',
|
|
167
|
+
tyv: '«»„“',
|
|
168
|
+
ug: '»«›‹',
|
|
169
|
+
uk: '«»„“',
|
|
170
|
+
ur: '”“’‘',
|
|
171
|
+
uz: '“”’‘',
|
|
172
|
+
'uz-arab': '“”‘’',
|
|
173
|
+
'uz-cyrl': '“”‘’',
|
|
174
|
+
wae: '«»‹›',
|
|
175
|
+
yav: '«»«»',
|
|
176
|
+
yi: '””’’',
|
|
177
|
+
yue: '「」『』',
|
|
178
|
+
zgh: '«»„”',
|
|
179
|
+
'zh-hk': '「」『』',
|
|
180
|
+
'zh-hant': '「」『』',
|
|
181
|
+
'zh-mo': '「」『』',
|
|
182
|
+
'zh-tw': '「」『』',
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
/** The marks `quotes: auto` gives an element of a language: its tag's, or
|
|
186
|
+
* the nearest of the tags it narrows, or English's. */
|
|
187
|
+
export function languageQuotes(lang: string): readonly string[] {
|
|
188
|
+
let tag = lang.trim().toLowerCase().replace(/_/g, '-');
|
|
189
|
+
while (tag) {
|
|
190
|
+
if (Object.hasOwn(QUOTES_BY_LANGUAGE, tag)) {
|
|
191
|
+
return [...QUOTES_BY_LANGUAGE[tag]];
|
|
192
|
+
}
|
|
193
|
+
const cut = tag.lastIndexOf('-');
|
|
194
|
+
tag = cut < 0 ? '' : tag.slice(0, cut);
|
|
195
|
+
}
|
|
196
|
+
return DEFAULT_QUOTES;
|
|
197
|
+
}
|
|
198
|
+
|
|
42
199
|
export type Token =
|
|
43
200
|
| { kind: 'string'; text: string }
|
|
44
201
|
| { kind: 'url'; url: string }
|
|
@@ -227,17 +384,14 @@ export function parseListStyleType(value: string): string | null {
|
|
|
227
384
|
return lower === 'none' ? 'none' : counterStyleName(token.name);
|
|
228
385
|
}
|
|
229
386
|
|
|
230
|
-
/** `quotes`: `none`, or pairs of strings, outermost first. Null
|
|
231
|
-
* value cannot be read — an odd number of strings among them. */
|
|
232
|
-
export function parseQuotes(value: string): string[] | 'none' | null {
|
|
387
|
+
/** `quotes`: `none`, `auto`, or pairs of strings, outermost first. Null
|
|
388
|
+
* when the value cannot be read — an odd number of strings among them. */
|
|
389
|
+
export function parseQuotes(value: string): string[] | 'none' | 'auto' | null {
|
|
233
390
|
const tokens = tokenize(value);
|
|
234
391
|
if (!tokens?.length) return null;
|
|
235
|
-
if (
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
tokens[0].name.toLowerCase() === 'none'
|
|
239
|
-
) {
|
|
240
|
-
return 'none';
|
|
392
|
+
if (tokens.length === 1 && tokens[0].kind === 'ident') {
|
|
393
|
+
const name = tokens[0].name.toLowerCase();
|
|
394
|
+
return name === 'none' || name === 'auto' ? name : null;
|
|
241
395
|
}
|
|
242
396
|
if (tokens.length % 2 !== 0) return null;
|
|
243
397
|
const out: string[] = [];
|
|
@@ -251,13 +405,17 @@ export function parseQuotes(value: string): string[] | 'none' | null {
|
|
|
251
405
|
/** The mark an `open-quote` (side 0) or `close-quote` (side 1) at a nesting
|
|
252
406
|
* depth writes: the pair for that depth, or the innermost pair past it. */
|
|
253
407
|
export function quoteAt(
|
|
254
|
-
quotes: readonly string[] | 'none',
|
|
408
|
+
quotes: readonly string[] | 'none' | 'auto',
|
|
255
409
|
depth: number,
|
|
256
410
|
side: 0 | 1,
|
|
411
|
+
/** The element's language, for `auto`. */
|
|
412
|
+
lang = '',
|
|
257
413
|
): string {
|
|
258
|
-
if (quotes === 'none'
|
|
259
|
-
const
|
|
260
|
-
|
|
414
|
+
if (quotes === 'none') return '';
|
|
415
|
+
const marks = quotes === 'auto' ? languageQuotes(lang) : quotes;
|
|
416
|
+
if (marks.length < 2) return '';
|
|
417
|
+
const pair = Math.min(depth, marks.length / 2 - 1);
|
|
418
|
+
return marks[pair * 2 + side];
|
|
261
419
|
}
|
|
262
420
|
|
|
263
421
|
// --- counter styles -----------------------------------------------------------
|