dsh-fonttune 0.1.6 → 0.2.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/CHANGELOG.en.md +29 -0
- package/CHANGELOG.md +29 -0
- package/README.en.md +23 -14
- package/README.md +22 -13
- package/lib/client.js +2855 -525
- package/lib/index.js +143 -27
- package/lib/shared.cjs +1361 -265
- package/package.json +4 -4
package/lib/shared.cjs
CHANGED
|
@@ -10,6 +10,14 @@
|
|
|
10
10
|
* configuration produces, and the sanitizing that keeps user-typed font names
|
|
11
11
|
* from breaking out of the injected stylesheet.
|
|
12
12
|
*
|
|
13
|
+
* 0.2.0 shape: the axes are grouped into three SURFACES — interface (the UI
|
|
14
|
+
* chrome), dialog (the conversation markdown) and code. Interface and dialog
|
|
15
|
+
* each carry a family, a size offset, a weight and a line-height ratio; code
|
|
16
|
+
* carries a family, a size offset, a weight, an additive line-height offset
|
|
17
|
+
* and the ligature/feature controls. A dialog field left at its neutral value
|
|
18
|
+
* (empty stack, 0, 100) FOLLOWS the interface value. `perTheme` enables a
|
|
19
|
+
* second value set for the dark theme, stored as a sparse override map.
|
|
20
|
+
*
|
|
13
21
|
* @module dsh-fonttune/shared
|
|
14
22
|
*/
|
|
15
23
|
"use strict";
|
|
@@ -17,24 +25,90 @@
|
|
|
17
25
|
/** Settings namespace registered by the host half. */
|
|
18
26
|
var NAMESPACE = "dsh-fonttune";
|
|
19
27
|
|
|
20
|
-
|
|
28
|
+
/* ------------------------------------------------------------------ *
|
|
29
|
+
* durable field names
|
|
30
|
+
* ------------------------------------------------------------------ */
|
|
31
|
+
|
|
32
|
+
/** Field carrying the interface CSS font-family stack (empty = leave DSH). */
|
|
21
33
|
var SANS_FIELD = "sans";
|
|
22
34
|
|
|
23
|
-
/** Field carrying the
|
|
35
|
+
/** Field carrying the dialog stack; empty = follow the interface. */
|
|
36
|
+
var STACK_DIALOG_FIELD = "stackDialog";
|
|
37
|
+
|
|
38
|
+
/** Field carrying the code CSS font-family stack (empty = leave DSH). */
|
|
24
39
|
var MONO_FIELD = "mono";
|
|
25
40
|
|
|
26
|
-
/** Field carrying the
|
|
41
|
+
/** Field carrying the interface font-size offset in px (0 = leave DSH alone). */
|
|
27
42
|
var SIZE_FIELD = "sizeOffset";
|
|
28
43
|
|
|
44
|
+
/** Field carrying the dialog font-size offset in px (0 = follow the interface). */
|
|
45
|
+
var SIZE_DIALOG_FIELD = "sizeOffsetDialog";
|
|
46
|
+
|
|
29
47
|
/** Field carrying the code font-size offset in px (0 = leave DSH alone). */
|
|
30
48
|
var CODE_SIZE_FIELD = "sizeOffsetCode";
|
|
31
49
|
|
|
32
|
-
/** Field carrying the
|
|
50
|
+
/** Field carrying the interface font weight (0 = leave DSH alone). */
|
|
33
51
|
var WEIGHT_FIELD = "weight";
|
|
34
52
|
|
|
53
|
+
/** Field carrying the dialog font weight (0 = follow the interface). */
|
|
54
|
+
var WEIGHT_DIALOG_FIELD = "weightDialog";
|
|
55
|
+
|
|
35
56
|
/** Field carrying the code font weight (0 = leave DSH alone). */
|
|
36
57
|
var CODE_WEIGHT_FIELD = "weightCode";
|
|
37
58
|
|
|
59
|
+
/**
|
|
60
|
+
* Field carrying the interface line-height ratio in percent (100 = untouched).
|
|
61
|
+
* A ratio rather than a px offset: interface line heights form a design ladder
|
|
62
|
+
* (14/18/20/22/24/26/28/32), and a ratio keeps every step proportional.
|
|
63
|
+
*/
|
|
64
|
+
var LINE_HEIGHT_FIELD = "lineHeight";
|
|
65
|
+
|
|
66
|
+
/** Field carrying the dialog line-height ratio in percent (100 = follow). */
|
|
67
|
+
var LINE_HEIGHT_DIALOG_FIELD = "lineHeightDialog";
|
|
68
|
+
|
|
69
|
+
/** Field carrying the code line-height offset in px (0 = leave DSH alone). */
|
|
70
|
+
var CODE_LINE_HEIGHT_FIELD = "lineHeightCode";
|
|
71
|
+
|
|
72
|
+
/** Ligature mode: 0 = DSH default, 1 = forced on, 2 = forced off. */
|
|
73
|
+
var LIGATURES_FIELD = "codeLigatures";
|
|
74
|
+
|
|
75
|
+
/** Advanced `font-feature-settings` value for code (sanitized; empty = none). */
|
|
76
|
+
var FEATURES_FIELD = "codeFeatures";
|
|
77
|
+
|
|
78
|
+
/** Disable synthetic (faux) italic — the "tilted CJK" rendering. */
|
|
79
|
+
var NO_SYNTHETIC_ITALIC_FIELD = "noSyntheticItalic";
|
|
80
|
+
|
|
81
|
+
/** Disable synthetic (faux) bold, for faces without a real bold. */
|
|
82
|
+
var NO_SYNTHETIC_BOLD_FIELD = "noSyntheticBold";
|
|
83
|
+
|
|
84
|
+
/** Whether the dark theme keeps its own value set (sparse overrides). */
|
|
85
|
+
var PER_THEME_FIELD = "perTheme";
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* JSON string of the dark-theme sparse overrides: a map of axis field to
|
|
89
|
+
* value, applied over the single set while the dark theme is active.
|
|
90
|
+
*/
|
|
91
|
+
var DARK_VALUES_FIELD = "darkValues";
|
|
92
|
+
|
|
93
|
+
/** JSON string of saved presets: [{name, values, savedAt}]. */
|
|
94
|
+
var PRESETS_FIELD = "presets";
|
|
95
|
+
|
|
96
|
+
/** Name of the preset the card's edits auto-save into. */
|
|
97
|
+
var ACTIVE_PRESET_FIELD = "activePreset";
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Whether the INTERFACE follows the conversation. The conversation owns every
|
|
101
|
+
* axis; the interface only has a family and a weight to give (DSH exposes no
|
|
102
|
+
* interface size or line-height hook), so on — the default — the conversation's
|
|
103
|
+
* family and weight win and the interface's own values stay as the fallback for
|
|
104
|
+
* axes the conversation does not set.
|
|
105
|
+
*/
|
|
106
|
+
var UI_FOLLOWS_FIELD = "uiFollowsDialog";
|
|
107
|
+
|
|
108
|
+
/* ------------------------------------------------------------------ *
|
|
109
|
+
* ranges and constants
|
|
110
|
+
* ------------------------------------------------------------------ */
|
|
111
|
+
|
|
38
112
|
/** Allowed font-size offset range. The upper bound stays under a 2x scale. */
|
|
39
113
|
var SIZE_MIN = -3;
|
|
40
114
|
var SIZE_MAX = 6;
|
|
@@ -44,10 +118,23 @@ var WEIGHT_MIN = 300;
|
|
|
44
118
|
var WEIGHT_MAX = 600;
|
|
45
119
|
var WEIGHT_UNSET = 0;
|
|
46
120
|
|
|
121
|
+
/** Allowed line-height ratio range, in percent (100 = untouched). */
|
|
122
|
+
var LINE_HEIGHT_MIN = 100;
|
|
123
|
+
var LINE_HEIGHT_MAX = 160;
|
|
124
|
+
|
|
125
|
+
/** Allowed code line-height offset range, in px. */
|
|
126
|
+
var CODE_LINE_HEIGHT_MIN = -4;
|
|
127
|
+
var CODE_LINE_HEIGHT_MAX = 8;
|
|
128
|
+
|
|
129
|
+
/** Ligature modes. */
|
|
130
|
+
var LIGATURES_DEFAULT = 0;
|
|
131
|
+
var LIGATURES_ON = 1;
|
|
132
|
+
var LIGATURES_OFF = 2;
|
|
133
|
+
|
|
47
134
|
/** Marker on both injected style tags, used for scoping every rule we write. */
|
|
48
135
|
var MARKER = "dfp";
|
|
49
136
|
|
|
50
|
-
/** `data-plugin-css` value of the tag applying
|
|
137
|
+
/** `data-plugin-css` value of the tag applying the configuration. */
|
|
51
138
|
var STYLE_TAG = "dsh-fonttune";
|
|
52
139
|
|
|
53
140
|
/** `data-plugin-css` value of the card's own chrome stylesheet. */
|
|
@@ -56,13 +143,52 @@ var CARD_STYLE_TAG = "dsh-fonttune-card";
|
|
|
56
143
|
/** Composition defaults: every axis dormant, so installing changes nothing. */
|
|
57
144
|
var DEFAULTS = {
|
|
58
145
|
sans: "",
|
|
146
|
+
stackDialog: "",
|
|
59
147
|
mono: "",
|
|
60
148
|
sizeOffset: 0,
|
|
149
|
+
sizeOffsetDialog: 0,
|
|
61
150
|
sizeOffsetCode: 0,
|
|
62
151
|
weight: WEIGHT_UNSET,
|
|
152
|
+
weightDialog: WEIGHT_UNSET,
|
|
63
153
|
weightCode: WEIGHT_UNSET,
|
|
154
|
+
lineHeight: LINE_HEIGHT_MIN,
|
|
155
|
+
lineHeightDialog: LINE_HEIGHT_MIN,
|
|
156
|
+
lineHeightCode: 0,
|
|
157
|
+
codeLigatures: LIGATURES_DEFAULT,
|
|
158
|
+
codeFeatures: "",
|
|
159
|
+
noSyntheticItalic: false,
|
|
160
|
+
noSyntheticBold: false,
|
|
161
|
+
perTheme: false,
|
|
162
|
+
darkValues: "{}",
|
|
163
|
+
presets: "[]",
|
|
164
|
+
activePreset: "",
|
|
165
|
+
uiFollowsDialog: true,
|
|
64
166
|
};
|
|
65
167
|
|
|
168
|
+
/**
|
|
169
|
+
* The axis fields a preset snapshots: everything the card edits, excluding
|
|
170
|
+
* the storage fields themselves.
|
|
171
|
+
*/
|
|
172
|
+
var VALUE_FIELDS = [
|
|
173
|
+
SANS_FIELD,
|
|
174
|
+
STACK_DIALOG_FIELD,
|
|
175
|
+
MONO_FIELD,
|
|
176
|
+
SIZE_FIELD,
|
|
177
|
+
SIZE_DIALOG_FIELD,
|
|
178
|
+
CODE_SIZE_FIELD,
|
|
179
|
+
WEIGHT_FIELD,
|
|
180
|
+
WEIGHT_DIALOG_FIELD,
|
|
181
|
+
CODE_WEIGHT_FIELD,
|
|
182
|
+
LINE_HEIGHT_FIELD,
|
|
183
|
+
LINE_HEIGHT_DIALOG_FIELD,
|
|
184
|
+
CODE_LINE_HEIGHT_FIELD,
|
|
185
|
+
LIGATURES_FIELD,
|
|
186
|
+
FEATURES_FIELD,
|
|
187
|
+
NO_SYNTHETIC_ITALIC_FIELD,
|
|
188
|
+
NO_SYNTHETIC_BOLD_FIELD,
|
|
189
|
+
UI_FOLLOWS_FIELD,
|
|
190
|
+
];
|
|
191
|
+
|
|
66
192
|
/**
|
|
67
193
|
* Longest accepted font stack, in characters (mirrored by the host schema).
|
|
68
194
|
*/
|
|
@@ -71,6 +197,19 @@ var MAX_STACK_LENGTH = 200;
|
|
|
71
197
|
/** Longest accepted single family name, in characters. */
|
|
72
198
|
var MAX_FAMILY_LENGTH = 64;
|
|
73
199
|
|
|
200
|
+
/** Longest accepted `font-feature-settings` value, in characters. */
|
|
201
|
+
var MAX_FEATURES_LENGTH = 120;
|
|
202
|
+
|
|
203
|
+
/** Most presets one configuration may hold. */
|
|
204
|
+
var MAX_PRESETS = 20;
|
|
205
|
+
|
|
206
|
+
/** Longest preset name, in characters. */
|
|
207
|
+
var MAX_PRESET_NAME = 32;
|
|
208
|
+
|
|
209
|
+
/* ------------------------------------------------------------------ *
|
|
210
|
+
* sanitizing
|
|
211
|
+
* ------------------------------------------------------------------ */
|
|
212
|
+
|
|
74
213
|
/**
|
|
75
214
|
* What may survive in a family name.
|
|
76
215
|
*
|
|
@@ -141,28 +280,93 @@ var GENERIC_FAMILIES = [
|
|
|
141
280
|
"blinkmacsystemfont",
|
|
142
281
|
];
|
|
143
282
|
|
|
283
|
+
/**
|
|
284
|
+
* Sanitize an advanced `font-feature-settings` value.
|
|
285
|
+
*
|
|
286
|
+
* Feature tags are letters and digits; entries may carry on/off/number and are
|
|
287
|
+
* comma separated. The allow-list (letters, digits, quotes, spaces, commas)
|
|
288
|
+
* keeps the value from closing the declaration or opening another one, and a
|
|
289
|
+
* coarse shape test rejects anything that is not a tag-entry list.
|
|
290
|
+
* @param {unknown} value - candidate features value.
|
|
291
|
+
* @returns {string} the sanitized value, or "".
|
|
292
|
+
*/
|
|
293
|
+
function sanitizeFeatures(value) {
|
|
294
|
+
if (typeof value !== "string") return "";
|
|
295
|
+
var text = value.replace(FEATURES_UNSAFE, "").slice(0, MAX_FEATURES_LENGTH).trim();
|
|
296
|
+
if (text === "") return "";
|
|
297
|
+
if (!/^(?:"?[a-zA-Z0-9]{1,8}"?(?: (?:on|off|-?\d+))?(?:, ?)?)+$/.test(text)) return "";
|
|
298
|
+
return text;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/** Characters a `font-feature-settings` value cannot contain. */
|
|
302
|
+
var FEATURES_UNSAFE = /[^a-zA-Z0-9"' ,]/g;
|
|
303
|
+
|
|
304
|
+
/* ------------------------------------------------------------------ *
|
|
305
|
+
* configuration normalization
|
|
306
|
+
* ------------------------------------------------------------------ */
|
|
307
|
+
|
|
308
|
+
/** Clamp/validate one field by name (shared by config and value sets). */
|
|
309
|
+
function normalizeField(field, value) {
|
|
310
|
+
switch (field) {
|
|
311
|
+
case SANS_FIELD:
|
|
312
|
+
case STACK_DIALOG_FIELD:
|
|
313
|
+
case MONO_FIELD:
|
|
314
|
+
return sanitize(value).slice(0, MAX_STACK_LENGTH);
|
|
315
|
+
case SIZE_FIELD:
|
|
316
|
+
case SIZE_DIALOG_FIELD:
|
|
317
|
+
case CODE_SIZE_FIELD:
|
|
318
|
+
return clampOffset(value);
|
|
319
|
+
case WEIGHT_FIELD:
|
|
320
|
+
case WEIGHT_DIALOG_FIELD:
|
|
321
|
+
case CODE_WEIGHT_FIELD:
|
|
322
|
+
return clampWeight(value);
|
|
323
|
+
case LINE_HEIGHT_FIELD:
|
|
324
|
+
case LINE_HEIGHT_DIALOG_FIELD:
|
|
325
|
+
return clampRatio(value);
|
|
326
|
+
case CODE_LINE_HEIGHT_FIELD:
|
|
327
|
+
return clampLineOffset(value);
|
|
328
|
+
case LIGATURES_FIELD:
|
|
329
|
+
return clampLigatures(value);
|
|
330
|
+
case FEATURES_FIELD:
|
|
331
|
+
return sanitizeFeatures(value);
|
|
332
|
+
case NO_SYNTHETIC_ITALIC_FIELD:
|
|
333
|
+
case NO_SYNTHETIC_BOLD_FIELD:
|
|
334
|
+
return toBool(value);
|
|
335
|
+
case UI_FOLLOWS_FIELD:
|
|
336
|
+
// Absent means the default: the interface follows the conversation.
|
|
337
|
+
return value === undefined || value === null || value === "" ? true : toBool(value);
|
|
338
|
+
default:
|
|
339
|
+
return undefined;
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
144
343
|
/**
|
|
145
344
|
* Normalize a configuration-shaped object coming from the settings document,
|
|
146
|
-
* a config layer, or a test fixture.
|
|
345
|
+
* a config layer, or a test fixture. Storage fields (`darkValues`,
|
|
346
|
+
* `presets`) are decoded and validated here too, so every consumer sees
|
|
347
|
+
* plain data.
|
|
147
348
|
* @param {unknown} value - candidate configuration.
|
|
148
|
-
* @returns {
|
|
349
|
+
* @returns {Record<string, string|number|boolean|object|array>} the normalized config.
|
|
149
350
|
*/
|
|
150
351
|
function normalizeConfig(value) {
|
|
151
352
|
var source = value !== null && typeof value === "object" ? value : {};
|
|
152
|
-
var size = clampOffset(source[SIZE_FIELD]);
|
|
153
|
-
var codeSize = clampOffset(source[CODE_SIZE_FIELD]);
|
|
154
|
-
var weight = clampWeight(source[WEIGHT_FIELD]);
|
|
155
|
-
var codeWeight = clampWeight(source[CODE_WEIGHT_FIELD]);
|
|
156
353
|
var config = {};
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
config[
|
|
162
|
-
config[
|
|
354
|
+
for (var index = 0; index < VALUE_FIELDS.length; index += 1) {
|
|
355
|
+
var field = VALUE_FIELDS[index];
|
|
356
|
+
config[field] = normalizeField(field, source[field]);
|
|
357
|
+
}
|
|
358
|
+
config[PER_THEME_FIELD] = toBool(source[PER_THEME_FIELD]);
|
|
359
|
+
config[DARK_VALUES_FIELD] = normalizeDarkValues(source[DARK_VALUES_FIELD]);
|
|
360
|
+
config[PRESETS_FIELD] = normalizePresets(source[PRESETS_FIELD]);
|
|
361
|
+
config[ACTIVE_PRESET_FIELD] = sanitize(source[ACTIVE_PRESET_FIELD]).slice(0, MAX_PRESET_NAME);
|
|
163
362
|
return config;
|
|
164
363
|
}
|
|
165
364
|
|
|
365
|
+
/** Coerce a settings value to a strict boolean; anything unusable is false. */
|
|
366
|
+
function toBool(value) {
|
|
367
|
+
return value === true || value === 1 || value === "1" || value === "true";
|
|
368
|
+
}
|
|
369
|
+
|
|
166
370
|
/**
|
|
167
371
|
* Clamp one size offset to the allowed range, with 0 for anything unusable.
|
|
168
372
|
* @param {unknown} value - candidate offset in px.
|
|
@@ -187,6 +391,109 @@ function clampWeight(value) {
|
|
|
187
391
|
return Math.min(WEIGHT_MAX, Math.max(WEIGHT_MIN, weight));
|
|
188
392
|
}
|
|
189
393
|
|
|
394
|
+
/**
|
|
395
|
+
* Clamp one line-height ratio to the allowed percent range.
|
|
396
|
+
* @param {unknown} value - candidate ratio in percent.
|
|
397
|
+
* @returns {number} the integer ratio (100 when unusable).
|
|
398
|
+
*/
|
|
399
|
+
function clampRatio(value) {
|
|
400
|
+
var ratio = Math.round(Number(value));
|
|
401
|
+
if (!isFinite(ratio)) return LINE_HEIGHT_MIN;
|
|
402
|
+
return Math.min(LINE_HEIGHT_MAX, Math.max(LINE_HEIGHT_MIN, ratio));
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
/**
|
|
406
|
+
* Clamp one code line-height offset to the allowed px range.
|
|
407
|
+
* @param {unknown} value - candidate offset in px.
|
|
408
|
+
* @returns {number} the integer offset (0 when unusable).
|
|
409
|
+
*/
|
|
410
|
+
function clampLineOffset(value) {
|
|
411
|
+
var offset = Math.round(Number(value));
|
|
412
|
+
if (!isFinite(offset) || offset === 0) return 0;
|
|
413
|
+
return Math.min(CODE_LINE_HEIGHT_MAX, Math.max(CODE_LINE_HEIGHT_MIN, offset));
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
/**
|
|
417
|
+
* Clamp one ligature mode to the three known values.
|
|
418
|
+
* @param {unknown} value - candidate mode.
|
|
419
|
+
* @returns {number} 0, 1 or 2.
|
|
420
|
+
*/
|
|
421
|
+
function clampLigatures(value) {
|
|
422
|
+
var mode = Math.round(Number(value));
|
|
423
|
+
if (!isFinite(mode) || mode <= 0) return LIGATURES_DEFAULT;
|
|
424
|
+
return mode >= LIGATURES_OFF ? LIGATURES_OFF : LIGATURES_ON;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
/**
|
|
428
|
+
* Validate and normalize one sparse axis-value map (dark overrides, presets).
|
|
429
|
+
* Only known axis fields survive, each clamped.
|
|
430
|
+
* @param {unknown} values - candidate map.
|
|
431
|
+
* @returns {Record<string, string|number|boolean>} the safe subset.
|
|
432
|
+
*/
|
|
433
|
+
function normalizeValueSet(values) {
|
|
434
|
+
var out = {};
|
|
435
|
+
if (values === null || typeof values !== "object" || Array.isArray(values)) return out;
|
|
436
|
+
for (var index = 0; index < VALUE_FIELDS.length; index += 1) {
|
|
437
|
+
var field = VALUE_FIELDS[index];
|
|
438
|
+
if (!Object.prototype.hasOwnProperty.call(values, field)) continue;
|
|
439
|
+
out[field] = normalizeField(field, values[field]);
|
|
440
|
+
}
|
|
441
|
+
return out;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
/**
|
|
445
|
+
* Parse and validate the durable `darkValues` JSON string.
|
|
446
|
+
* @param {unknown} value - candidate JSON text or object.
|
|
447
|
+
* @returns {Record<string, string|number|boolean>} the sparse override map.
|
|
448
|
+
*/
|
|
449
|
+
function normalizeDarkValues(value) {
|
|
450
|
+
if (typeof value === "string") {
|
|
451
|
+
if (value.trim() === "") return {};
|
|
452
|
+
try {
|
|
453
|
+
value = JSON.parse(value);
|
|
454
|
+
} catch (error) {
|
|
455
|
+
return {};
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
return normalizeValueSet(value);
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
/**
|
|
462
|
+
* Parse and validate the durable `presets` JSON string.
|
|
463
|
+
* @param {unknown} value - candidate JSON text or array.
|
|
464
|
+
* @returns {{name: string, values: object, savedAt: number}[]}
|
|
465
|
+
*/
|
|
466
|
+
function normalizePresets(value) {
|
|
467
|
+
var list = value;
|
|
468
|
+
if (typeof value === "string") {
|
|
469
|
+
if (value.trim() === "") return [];
|
|
470
|
+
try {
|
|
471
|
+
list = JSON.parse(value);
|
|
472
|
+
} catch (error) {
|
|
473
|
+
return [];
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
if (!Array.isArray(list)) return [];
|
|
477
|
+
var out = [];
|
|
478
|
+
for (var index = 0; index < list.length && out.length < MAX_PRESETS; index += 1) {
|
|
479
|
+
var entry = list[index];
|
|
480
|
+
if (entry === null || typeof entry !== "object") continue;
|
|
481
|
+
var name = sanitize(entry.name).slice(0, MAX_PRESET_NAME);
|
|
482
|
+
if (name === "") continue;
|
|
483
|
+
var stamp = Math.round(Number(entry.savedAt));
|
|
484
|
+
out.push({
|
|
485
|
+
name: name,
|
|
486
|
+
values: normalizeValueSet(entry.values),
|
|
487
|
+
savedAt: isFinite(stamp) && stamp > 0 ? stamp : 0,
|
|
488
|
+
});
|
|
489
|
+
}
|
|
490
|
+
return out;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
/* ------------------------------------------------------------------ *
|
|
494
|
+
* stacks
|
|
495
|
+
* ------------------------------------------------------------------ */
|
|
496
|
+
|
|
190
497
|
/**
|
|
191
498
|
* Format a family list as a CSS font-family value.
|
|
192
499
|
* @param {readonly string[]} families - family names in precedence order.
|
|
@@ -360,6 +667,10 @@ function removeStackEntry(families, family) {
|
|
|
360
667
|
return next;
|
|
361
668
|
}
|
|
362
669
|
|
|
670
|
+
/* ------------------------------------------------------------------ *
|
|
671
|
+
* scaling helpers
|
|
672
|
+
* ------------------------------------------------------------------ */
|
|
673
|
+
|
|
363
674
|
/**
|
|
364
675
|
* Compute the uniform scale a pixel offset produces over DSH's 16px base.
|
|
365
676
|
*
|
|
@@ -380,152 +691,16 @@ function scaleFor(sizeOffset, base) {
|
|
|
380
691
|
}
|
|
381
692
|
|
|
382
693
|
/**
|
|
383
|
-
*
|
|
384
|
-
* @param {
|
|
385
|
-
* @
|
|
386
|
-
|
|
387
|
-
function isDormant(config) {
|
|
388
|
-
return (
|
|
389
|
-
config[SANS_FIELD] === "" &&
|
|
390
|
-
config[MONO_FIELD] === "" &&
|
|
391
|
-
config[SIZE_FIELD] === 0 &&
|
|
392
|
-
config[CODE_SIZE_FIELD] === 0 &&
|
|
393
|
-
config[WEIGHT_FIELD] === WEIGHT_UNSET &&
|
|
394
|
-
config[CODE_WEIGHT_FIELD] === WEIGHT_UNSET
|
|
395
|
-
);
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
/**
|
|
399
|
-
* Build the stylesheet one configuration applies.
|
|
400
|
-
*
|
|
401
|
-
* Families are declared at their SOURCE: DSH's design tokens chain to two
|
|
402
|
-
* variables (`--dsw-font-family` for every sans token, `--ds-font-family-code`
|
|
403
|
-
* for every code token — verified against the shipped theme), so overriding
|
|
404
|
-
* those variables is what reaches the conversation markdown and the sidebar,
|
|
405
|
-
* whose elements declare `font-family: var(--dsw-font-family)` themselves and
|
|
406
|
-
* would never inherit a plain `body` rule. The explicit `body` / `pre,code`
|
|
407
|
-
* rules stay as a second path for elements that hardcode a family.
|
|
408
|
-
*
|
|
409
|
-
* The size offsets rewrite DSH's own design tokens rather than every element:
|
|
410
|
-
* a token whose name ends in `-font-size` or `-line-height` is re-declared as
|
|
411
|
-
* itself multiplied by one scale factor. `baseTokens` supplies the untouched
|
|
412
|
-
* values (read from the live document, falling back to the embedded map), so
|
|
413
|
-
* the ratio composes with DSH's own font-size slider instead of replacing it.
|
|
414
|
-
*
|
|
415
|
-
* Body and code are two independent axes. Body sizes hang off the content-size
|
|
416
|
-
* chain (`--dsh-content-font-size` and the `--dsw-font-*` steps), while every
|
|
417
|
-
* markdown code token is a bare literal (`--dsw-font-markdown-code-block:
|
|
418
|
-
* 11px/19px …`) that the content chain never touches — so the code axis is the
|
|
419
|
-
* only thing that moves it, and the body axis deliberately leaves those tokens
|
|
420
|
-
* alone. Code sizing is consumed through the `font` SHORTHAND tokens (that is
|
|
421
|
-
* what the shipped stylesheets use), which is why those are re-declared too,
|
|
422
|
-
* not just their `-font-size` / `-line-height` parts.
|
|
423
|
-
*
|
|
424
|
-
* Weight has no token chain to ride: no shipped rule reads a `-font-weight`
|
|
425
|
-
* token (measured — zero `font-weight: var(--dsw-font-…)` in rc.2), the theme
|
|
426
|
-
* writes literal weights on its own class rules, and that is exactly why the
|
|
427
|
-
* body axis has to be a blanket `!important` rule. The code axis is therefore a
|
|
428
|
-
* selector-scoped `!important` rule placed after it, which wins on specificity
|
|
429
|
-
* (`[class*=…]`) or on source order (`pre, …` ties with `body *`).
|
|
430
|
-
*
|
|
431
|
-
* @param {{sans: string, mono: string, sizeOffset: number, sizeOffsetCode: number, weight: number, weightCode: number}} config - normalized config.
|
|
432
|
-
* @param {Record<string, string>} [baseTokens] - token name to untouched value.
|
|
433
|
-
* @returns {string} declarations for one `<style>` element ("" when dormant).
|
|
694
|
+
* Round to a fixed number of decimals so generated CSS stays readable.
|
|
695
|
+
* @param {number} value - the number.
|
|
696
|
+
* @param {number} digits - decimals to keep.
|
|
697
|
+
* @returns {number} the rounded number.
|
|
434
698
|
*/
|
|
435
|
-
function
|
|
436
|
-
var
|
|
437
|
-
|
|
438
|
-
var mono = formatStack(parseStack(normalized[MONO_FIELD]));
|
|
439
|
-
var offset = normalized[SIZE_FIELD];
|
|
440
|
-
var codeOffset = normalized[CODE_SIZE_FIELD];
|
|
441
|
-
var weight = normalized[WEIGHT_FIELD];
|
|
442
|
-
var codeWeight = normalized[CODE_WEIGHT_FIELD];
|
|
443
|
-
if (isDormant(normalized)) return "";
|
|
444
|
-
var declarations = [];
|
|
445
|
-
|
|
446
|
-
if (sans !== "") {
|
|
447
|
-
declarations.push(":root,body{--dsw-font-family:" + sans + " !important}");
|
|
448
|
-
declarations.push("body{font-family:" + sans + " !important}");
|
|
449
|
-
}
|
|
450
|
-
if (mono !== "") {
|
|
451
|
-
// The theme's code tokens chain to `--ds-font-family-code`; the other name
|
|
452
|
-
// is what dsh-ui-font historically wrote and costs nothing to cover.
|
|
453
|
-
declarations.push(
|
|
454
|
-
":root,body{--dsw-font-mono:" + mono + " !important;--ds-font-family-code:" + mono + " !important}"
|
|
455
|
-
);
|
|
456
|
-
// Written after the body rule: equal specificity on a code element means
|
|
457
|
-
// the later declaration wins, so code keeps its own family inside the UI.
|
|
458
|
-
declarations.push(
|
|
459
|
-
"pre,code,kbd,samp,var,tt,textarea,.cm-editor .cm-content{font-family:" +
|
|
460
|
-
mono +
|
|
461
|
-
" !important}"
|
|
462
|
-
);
|
|
463
|
-
}
|
|
464
|
-
if (offset !== 0) {
|
|
465
|
-
var scaled = scaleTokens(bodyTokenNames(baseTokens), baseTokens, round(scaleFor(offset), 6));
|
|
466
|
-
if (scaled.length > 0) {
|
|
467
|
-
declarations.push(bodyAndDescendants(scaled.join(";")));
|
|
468
|
-
}
|
|
469
|
-
}
|
|
470
|
-
if (codeOffset !== 0) {
|
|
471
|
-
var scaledCode = scaleCodeTokens(baseTokens, round(scaleFor(codeOffset), 6));
|
|
472
|
-
if (scaledCode.length > 0) {
|
|
473
|
-
declarations.push(bodyAndDescendants(scaledCode.join(";")));
|
|
474
|
-
}
|
|
475
|
-
}
|
|
476
|
-
if (weight !== WEIGHT_UNSET) {
|
|
477
|
-
// Written as the user chose it: a variable font honors every integer, and
|
|
478
|
-
// a static one rounds to its own nearest step by itself.
|
|
479
|
-
declarations.push(bodyAndDescendants("font-weight:" + weight + " !important"));
|
|
480
|
-
}
|
|
481
|
-
if (codeWeight !== WEIGHT_UNSET) {
|
|
482
|
-
declarations.push(CODE_SELECTOR + "{font-weight:" + codeWeight + " !important}");
|
|
483
|
-
} else if (weight !== WEIGHT_UNSET) {
|
|
484
|
-
// The body axis is a blanket `body, body *` rule, so code would inherit the
|
|
485
|
-
// body weight unless it is explicitly taken back out. `normal` is what DSH
|
|
486
|
-
// itself computes there (the code `font` shorthands carry no weight), so an
|
|
487
|
-
// unset code weight means "code stays exactly as DSH shipped it".
|
|
488
|
-
declarations.push(CODE_SELECTOR + "{font-weight:normal !important}");
|
|
489
|
-
}
|
|
490
|
-
return declarations.join("\n");
|
|
699
|
+
function round(value, digits) {
|
|
700
|
+
var factor = Math.pow(10, digits);
|
|
701
|
+
return Math.round(value * factor) / factor;
|
|
491
702
|
}
|
|
492
703
|
|
|
493
|
-
/**
|
|
494
|
-
* What counts as code text for the weight axis.
|
|
495
|
-
*
|
|
496
|
-
* There is no token to hook here (see `buildFontCss`), so the code axis has to
|
|
497
|
-
* name the elements. Two layers, both measured against rc.2:
|
|
498
|
-
*
|
|
499
|
-
* 1. The tags DSH's own stylesheets size with a code token: markdown `pre` /
|
|
500
|
-
* inline `code`, the tool card's `:where(pre)` block, form controls, and the
|
|
501
|
-
* CodeMirror editor. Their descendants are listed too, because the blanket
|
|
502
|
-
* body rule matches EVERY element — a `<span>` inside a highlighted block
|
|
503
|
-
* would otherwise be pulled back to the body weight.
|
|
504
|
-
* 2. Containers that only a class name identifies: the terminal and tool-code
|
|
505
|
-
* bodies are plain `div`s (`…_terminal`, `…_codeBody`, `md-code-block`).
|
|
506
|
-
*
|
|
507
|
-
* The `i` flag keeps a casing change in a future DSH from silently dropping the
|
|
508
|
-
* rule, and an unmatched token/class simply means that surface follows the body
|
|
509
|
-
* axis — the same degradation as an unknown token name on the size axes.
|
|
510
|
-
*/
|
|
511
|
-
var CODE_SELECTOR = [
|
|
512
|
-
"pre",
|
|
513
|
-
"code",
|
|
514
|
-
"kbd",
|
|
515
|
-
"samp",
|
|
516
|
-
"var",
|
|
517
|
-
"tt",
|
|
518
|
-
"textarea",
|
|
519
|
-
".cm-editor",
|
|
520
|
-
".dfp-previewCode",
|
|
521
|
-
'[class*="code" i]',
|
|
522
|
-
'[class*="terminal" i]',
|
|
523
|
-
]
|
|
524
|
-
.map(function (selector) {
|
|
525
|
-
return selector + "," + selector + " *";
|
|
526
|
-
})
|
|
527
|
-
.join(",");
|
|
528
|
-
|
|
529
704
|
/**
|
|
530
705
|
* Values a ratio may multiply: a number with a unit, a bare number, or an
|
|
531
706
|
* expression. A keyword (`unset`, `inherit`) or an unrecognized shape is left
|
|
@@ -543,17 +718,17 @@ function isScalableValue(value) {
|
|
|
543
718
|
}
|
|
544
719
|
|
|
545
720
|
/**
|
|
546
|
-
* Scale one set of tokens by one ratio.
|
|
721
|
+
* Scale one set of SIZE tokens by one ratio.
|
|
547
722
|
*
|
|
548
723
|
* A base that itself references another token via var() derives from it:
|
|
549
724
|
* scaling the var target already scales this one, so re-scaling here would
|
|
550
725
|
* compound (markdown-base = var(--dsh-content-font-size) would take the ratio
|
|
551
726
|
* twice). DSH's chain does the work instead.
|
|
552
727
|
*
|
|
553
|
-
* `!important` is required: the theme writes `--dsh-content-font-size` INLINE
|
|
554
|
-
* body, and an inline declaration outranks a plain stylesheet one —
|
|
555
|
-
* flag body itself would keep DSH's own size while every
|
|
556
|
-
* splitting the page in two.
|
|
728
|
+
* `!important` is required: the theme writes `--dsh-content-font-size` INLINE
|
|
729
|
+
* on body, and an inline declaration outranks a plain stylesheet one —
|
|
730
|
+
* without the flag body itself would keep DSH's own size while every
|
|
731
|
+
* descendant scales, splitting the page in two.
|
|
557
732
|
*
|
|
558
733
|
* @param {string[]} names - token names to re-declare.
|
|
559
734
|
* @param {Record<string, string>} baseTokens - token name to untouched value.
|
|
@@ -574,66 +749,143 @@ function scaleTokens(names, baseTokens, scale) {
|
|
|
574
749
|
}
|
|
575
750
|
|
|
576
751
|
/**
|
|
577
|
-
*
|
|
578
|
-
*
|
|
579
|
-
* Two shapes exist and both must be covered, because the shipped stylesheets
|
|
580
|
-
* consume the shorthand (`font: var(--dsw-font-markdown-code-block-small)`)
|
|
581
|
-
* while the split tokens exist for anything that asks for a single part:
|
|
582
|
-
* `11px/19px <family>` and a bare `11px`.
|
|
752
|
+
* Scale one set of LINE-HEIGHT tokens by one factor.
|
|
583
753
|
*
|
|
754
|
+
* Unlike sizes, a line height never chains through another line height in
|
|
755
|
+
* DSH's theme (heights are literals or `calc(Npx + size-delta)`), so var()
|
|
756
|
+
* values are wrapped rather than skipped — the wrap keeps the var chain
|
|
757
|
+
* resolving at use time and cannot compound.
|
|
758
|
+
* @param {string[]} names - token names to re-declare.
|
|
584
759
|
* @param {Record<string, string>} baseTokens - token name to untouched value.
|
|
585
|
-
* @param {number}
|
|
760
|
+
* @param {number} factor - the ratio to apply.
|
|
586
761
|
* @returns {string[]} declarations.
|
|
587
762
|
*/
|
|
588
|
-
function
|
|
589
|
-
var names = codeTokenNames(baseTokens);
|
|
763
|
+
function scaleLineTokens(names, baseTokens, factor) {
|
|
590
764
|
var out = [];
|
|
591
765
|
for (var index = 0; index < names.length; index += 1) {
|
|
592
766
|
var name = names[index];
|
|
593
767
|
var base = baseTokens[name];
|
|
594
768
|
if (typeof base !== "string" || base === "") continue;
|
|
595
|
-
var shorthand = scaleFontShorthand(base, scale);
|
|
596
|
-
if (shorthand !== null) {
|
|
597
|
-
out.push(name + ":" + shorthand + " !important");
|
|
598
|
-
continue;
|
|
599
|
-
}
|
|
600
|
-
// A part token that derives from another token follows that token's own
|
|
601
|
-
// scaling; re-declaring it here would compound the ratio.
|
|
602
|
-
if (base.indexOf("var(") !== -1) continue;
|
|
603
769
|
if (!isScalableValue(base)) continue;
|
|
604
|
-
out.push(name + ":calc((" + base + ") * " +
|
|
770
|
+
out.push(name + ":calc((" + base + ") * " + factor + ") !important");
|
|
605
771
|
}
|
|
606
772
|
return out;
|
|
607
773
|
}
|
|
608
774
|
|
|
609
775
|
/**
|
|
610
|
-
*
|
|
611
|
-
*
|
|
612
|
-
* @param {string}
|
|
613
|
-
* @param {number}
|
|
614
|
-
* @returns {string
|
|
776
|
+
* Shift one set of tokens by an additive px amount.
|
|
777
|
+
* @param {string[]} names - token names to re-declare.
|
|
778
|
+
* @param {Record<string, string>} baseTokens - token name to untouched value.
|
|
779
|
+
* @param {number} offset - the px amount to add.
|
|
780
|
+
* @returns {string[]} declarations.
|
|
615
781
|
*/
|
|
616
|
-
function
|
|
617
|
-
var
|
|
782
|
+
function shiftTokens(names, baseTokens, offset) {
|
|
783
|
+
var out = [];
|
|
784
|
+
for (var index = 0; index < names.length; index += 1) {
|
|
785
|
+
var name = names[index];
|
|
786
|
+
var base = baseTokens[name];
|
|
787
|
+
if (typeof base !== "string" || base === "") continue;
|
|
788
|
+
if (!isScalableValue(base)) continue;
|
|
789
|
+
out.push(name + ":calc((" + base + ") + " + offset + "px) !important");
|
|
790
|
+
}
|
|
791
|
+
return out;
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
/* ------------------------------------------------------------------ *
|
|
795
|
+
* `font` shorthand parsing
|
|
796
|
+
* ------------------------------------------------------------------ */
|
|
797
|
+
|
|
798
|
+
/**
|
|
799
|
+
* The family reference every DSH `font` shorthand ends with. Markdown tokens
|
|
800
|
+
* chain to the sans variable, code tokens to the code variable.
|
|
801
|
+
*/
|
|
802
|
+
var FAMILY_REFS = ["var(--dsw-font-family)", "var(--ds-font-family-code)"];
|
|
803
|
+
|
|
804
|
+
/**
|
|
805
|
+
* Parse one `font` shorthand into its parts.
|
|
806
|
+
*
|
|
807
|
+
* DSH's shorthands come in four shapes, all ending in a family var():
|
|
808
|
+
* `11px/19px var(--ds-font-family-code)` (code),
|
|
809
|
+
* `var(--dsh-content-font-size,14px) / calc(24px + var(--dsh-content-font-delta)) var(--dsw-font-family)` (base),
|
|
810
|
+
* `700 calc(21px + var(...)) / calc(30px + var(...)) var(--dsw-font-family)` (h1),
|
|
811
|
+
* `italic 600 var(...) / calc(...) var(--dsw-font-family)` (strong-italic).
|
|
812
|
+
*
|
|
813
|
+
* Because the size expression may itself contain `var(...)`, the split keys
|
|
814
|
+
* off the family reference (found from the right) and the `/`, never off
|
|
815
|
+
* whitespace inside `calc()`.
|
|
816
|
+
* @param {string} value - a `font` shorthand value.
|
|
817
|
+
* @returns {{lead: string, size: string, height: string, family: string}|null} the parts, or null when the shape differs.
|
|
818
|
+
*/
|
|
819
|
+
function parseShorthand(value) {
|
|
820
|
+
if (typeof value !== "string") return null;
|
|
821
|
+
var familyAt = -1;
|
|
822
|
+
for (var index = 0; index < FAMILY_REFS.length; index += 1) {
|
|
823
|
+
var at = value.lastIndexOf(FAMILY_REFS[index]);
|
|
824
|
+
if (at > familyAt) familyAt = at;
|
|
825
|
+
}
|
|
826
|
+
if (familyAt <= 0) return null;
|
|
827
|
+
var family = value.slice(familyAt).trim();
|
|
828
|
+
if (family === "") return null;
|
|
829
|
+
var head = value.slice(0, familyAt).trim();
|
|
830
|
+
var slash = head.lastIndexOf("/");
|
|
618
831
|
if (slash <= 0) return null;
|
|
619
|
-
var
|
|
620
|
-
var
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
var
|
|
624
|
-
var
|
|
625
|
-
if (size === "" || height === "" || family === "") return null;
|
|
832
|
+
var sizeChunk = head.slice(0, slash).trim();
|
|
833
|
+
var height = head.slice(slash + 1).trim();
|
|
834
|
+
if (sizeChunk === "" || height === "") return null;
|
|
835
|
+
var chunks = splitTopLevel(sizeChunk);
|
|
836
|
+
var size = chunks.pop();
|
|
837
|
+
var lead = chunks.join(" ");
|
|
626
838
|
if (!isScalableValue(size) || !isScalableValue(height)) return null;
|
|
627
|
-
return
|
|
628
|
-
"calc((" + size + ") * " + scale + ")/calc((" + height + ") * " + scale + ") " + family
|
|
629
|
-
);
|
|
839
|
+
return { lead: lead, size: size, height: height, family: family };
|
|
630
840
|
}
|
|
631
841
|
|
|
632
842
|
/**
|
|
633
|
-
*
|
|
634
|
-
*
|
|
635
|
-
*
|
|
636
|
-
*
|
|
843
|
+
* Split a value on whitespace that sits OUTSIDE any parentheses, so a leading
|
|
844
|
+
* `700 calc(21px + var(…))` yields `["700", "calc(21px + var(...))"]`.
|
|
845
|
+
* @param {string} value - the chunk to split.
|
|
846
|
+
* @returns {string[]} the top-level chunks.
|
|
847
|
+
*/
|
|
848
|
+
function splitTopLevel(value) {
|
|
849
|
+
var out = [];
|
|
850
|
+
var current = "";
|
|
851
|
+
var depth = 0;
|
|
852
|
+
for (var index = 0; index < value.length; index += 1) {
|
|
853
|
+
var char = value.charAt(index);
|
|
854
|
+
if (char === "(") depth += 1;
|
|
855
|
+
else if (char === ")") depth -= 1;
|
|
856
|
+
if (depth === 0 && /\s/.test(char)) {
|
|
857
|
+
if (current !== "") {
|
|
858
|
+
out.push(current);
|
|
859
|
+
current = "";
|
|
860
|
+
}
|
|
861
|
+
continue;
|
|
862
|
+
}
|
|
863
|
+
current += char;
|
|
864
|
+
}
|
|
865
|
+
if (current !== "") out.push(current);
|
|
866
|
+
return out;
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
/**
|
|
870
|
+
* Rebuild one `font` shorthand from parsed parts.
|
|
871
|
+
* @param {{lead: string, size: string, height: string, family: string}} parts - parsed parts.
|
|
872
|
+
* @returns {string} the shorthand value (without `!important`).
|
|
873
|
+
*/
|
|
874
|
+
function rebuildShorthand(parts) {
|
|
875
|
+
var out = "";
|
|
876
|
+
if (parts.lead !== "") out += parts.lead + " ";
|
|
877
|
+
return out + parts.size + " / " + parts.height + " " + parts.family;
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
/* ------------------------------------------------------------------ *
|
|
881
|
+
* token classification
|
|
882
|
+
* ------------------------------------------------------------------ */
|
|
883
|
+
|
|
884
|
+
/**
|
|
885
|
+
* Match DSH's typography PART tokens: the design system's sizes plus the
|
|
886
|
+
* content size the theme plugin writes on `body` (whose secondary variant
|
|
887
|
+
* carries a suffix of its own). Line heights scale with sizes so a token's
|
|
888
|
+
* shorthand never disagrees with its parts.
|
|
637
889
|
*
|
|
638
890
|
* Deliberately a shape test rather than an enumeration: DSH generates these
|
|
639
891
|
* tokens at runtime, so whatever a future release names them, a token that
|
|
@@ -641,6 +893,26 @@ function scaleFontShorthand(value, scale) {
|
|
|
641
893
|
*/
|
|
642
894
|
var TOKEN_PATTERN = /^--(?:dsw-font-[a-z0-9-]*|dsh-content-font)-(?:font-size|line-height)(?:-secondary)?$|^--dsh-content-font-(?:size|line-height)(?:-secondary)?$/;
|
|
643
895
|
|
|
896
|
+
/**
|
|
897
|
+
* The content-size DELTA tokens the theme derives on `body`; the dialog size
|
|
898
|
+
* offset shifts these additively, which is what moves every delta-derived
|
|
899
|
+
* markdown size by exactly the offset.
|
|
900
|
+
*/
|
|
901
|
+
var DELTA_PATTERN = /^--dsh-content-font-delta(?:-secondary)?$/;
|
|
902
|
+
|
|
903
|
+
/** The whole official content-size chain (size / line height / delta). */
|
|
904
|
+
var CONTENT_PATTERN = /^--dsh-content-font-(?:size|line-height|delta)(?:-secondary)?$/;
|
|
905
|
+
|
|
906
|
+
/**
|
|
907
|
+
* The PART-token suffixes DSH appends for single-axis consumers.
|
|
908
|
+
*/
|
|
909
|
+
var PART_SUFFIXES = ["-font-size", "-line-height", "-font-family", "-font-weight", "-font-style"];
|
|
910
|
+
|
|
911
|
+
/**
|
|
912
|
+
* The bare family variables, which are NOT `font` shorthands.
|
|
913
|
+
*/
|
|
914
|
+
var FAMILY_VARS = { "--dsw-font-family": true, "--dsw-font-mono": true, "--ds-font-family-code": true };
|
|
915
|
+
|
|
644
916
|
/**
|
|
645
917
|
* A `body, body *` selector list, kept in one place because every global
|
|
646
918
|
* declaration needs the same reach (DSH writes some tokens on descendants).
|
|
@@ -652,7 +924,23 @@ function bodyAndDescendants(declarations) {
|
|
|
652
924
|
}
|
|
653
925
|
|
|
654
926
|
/**
|
|
655
|
-
* Whether one
|
|
927
|
+
* Whether one name belongs to the official CONTENT-size chain the conversation
|
|
928
|
+
* owns: `--dsh-content-font-size` / `-line-height` (the official "对话字号"
|
|
929
|
+
* setting writes the former) and the delta tokens derived from it.
|
|
930
|
+
*
|
|
931
|
+
* The interface axis must never scale these. Doing so was the real reason an
|
|
932
|
+
* interface size change moved the conversation: the markdown ladder consumes
|
|
933
|
+
* this chain directly, so scaling it is a conversation edit no matter which
|
|
934
|
+
* slider caused it.
|
|
935
|
+
* @param {string} name - custom property name including the leading dashes.
|
|
936
|
+
* @returns {boolean}
|
|
937
|
+
*/
|
|
938
|
+
function isContentToken(name) {
|
|
939
|
+
return typeof name === "string" && CONTENT_PATTERN.test(name);
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
/**
|
|
943
|
+
* Whether one custom-property name is a PART token this plugin scales.
|
|
656
944
|
* @param {string} name - custom property name including the leading dashes.
|
|
657
945
|
* @returns {boolean} true when the token carries a size or a line height.
|
|
658
946
|
*/
|
|
@@ -661,13 +949,21 @@ function isFontToken(name) {
|
|
|
661
949
|
}
|
|
662
950
|
|
|
663
951
|
/**
|
|
664
|
-
*
|
|
952
|
+
* Whether one name is a content-size delta token.
|
|
953
|
+
* @param {string} name - custom property name including the leading dashes.
|
|
954
|
+
* @returns {boolean}
|
|
955
|
+
*/
|
|
956
|
+
function isDeltaToken(name) {
|
|
957
|
+
return typeof name === "string" && DELTA_PATTERN.test(name);
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
/**
|
|
961
|
+
* Whether one name is a markdown CODE token: its own sizing axis.
|
|
665
962
|
*
|
|
666
963
|
* The theme declares these as literals the content-size chain never reaches
|
|
667
964
|
* (`--dsw-font-markdown-code:12px/19px …`, `-code-block:11px/19px …`,
|
|
668
965
|
* `-code-block-small:11px/16px …`), and the shipped stylesheets consume them
|
|
669
|
-
* through the `font` shorthand.
|
|
670
|
-
* to the code offset and to nothing else.
|
|
966
|
+
* through the `font` shorthand.
|
|
671
967
|
* @param {string} name - custom property name including the leading dashes.
|
|
672
968
|
* @returns {boolean} true when the token sizes code.
|
|
673
969
|
*/
|
|
@@ -676,23 +972,63 @@ function isCodeToken(name) {
|
|
|
676
972
|
}
|
|
677
973
|
|
|
678
974
|
/**
|
|
679
|
-
* Whether a token is
|
|
975
|
+
* Whether one name is a markdown (dialog) token that is NOT code: the token
|
|
976
|
+
* family the dialog axes own.
|
|
977
|
+
* @param {string} name - custom property name including the leading dashes.
|
|
978
|
+
* @returns {boolean}
|
|
979
|
+
*/
|
|
980
|
+
function isMarkdownToken(name) {
|
|
981
|
+
return typeof name === "string" && name.indexOf("--dsw-font-markdown-") === 0 && !isCodeToken(name);
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
/**
|
|
985
|
+
* Whether one NAME looks like a `font` shorthand token (a bare
|
|
986
|
+
* `--dsw-font-…` name that is not a PART token or a family variable).
|
|
680
987
|
* @param {string} name - custom property name including the leading dashes.
|
|
681
|
-
* @returns {boolean}
|
|
988
|
+
* @returns {boolean}
|
|
989
|
+
*/
|
|
990
|
+
function isShorthandName(name) {
|
|
991
|
+
if (typeof name !== "string" || name.indexOf("--dsw-font-") !== 0) return false;
|
|
992
|
+
if (FAMILY_VARS[name] === true) return false;
|
|
993
|
+
for (var index = 0; index < PART_SUFFIXES.length; index += 1) {
|
|
994
|
+
var suffix = PART_SUFFIXES[index];
|
|
995
|
+
if (name.length > suffix.length && name.slice(-suffix.length) === suffix) return false;
|
|
996
|
+
}
|
|
997
|
+
return true;
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
/**
|
|
1001
|
+
* Whether one token (name + value) is a `font` shorthand this plugin may
|
|
1002
|
+
* rewrite: a bare `--dsw-font-…` name whose value parses as a shorthand.
|
|
1003
|
+
* @param {string} name - custom property name.
|
|
1004
|
+
* @param {string} value - the token's untouched value.
|
|
1005
|
+
* @returns {boolean}
|
|
1006
|
+
*/
|
|
1007
|
+
function isShorthandToken(name, value) {
|
|
1008
|
+
return isShorthandName(name) && parseShorthand(value) !== null;
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
/**
|
|
1012
|
+
* Whether a token is one this plugin reads and re-declares at all.
|
|
1013
|
+
* @param {string} name - custom property name including the leading dashes.
|
|
1014
|
+
* @returns {boolean} true for part tokens, code tokens, shorthand names and
|
|
1015
|
+
* the content-size deltas.
|
|
682
1016
|
*/
|
|
683
1017
|
function isScaledToken(name) {
|
|
684
|
-
return isFontToken(name) || isCodeToken(name);
|
|
1018
|
+
return isFontToken(name) || isCodeToken(name) || isShorthandName(name) || isDeltaToken(name);
|
|
685
1019
|
}
|
|
686
1020
|
|
|
687
1021
|
/**
|
|
688
|
-
* Ordered token names present in a base map, sizes before line heights
|
|
689
|
-
* reader of the stylesheet sees each size next to its height.
|
|
1022
|
+
* Ordered PART-token names present in a base map, sizes before line heights
|
|
1023
|
+
* so a reader of the stylesheet sees each size next to its height.
|
|
690
1024
|
* @param {Record<string, string>} [baseTokens] - token name to value.
|
|
691
|
-
* @returns {string[]} the names
|
|
1025
|
+
* @returns {string[]} the names.
|
|
692
1026
|
*/
|
|
693
1027
|
function tokenNames(baseTokens) {
|
|
694
1028
|
if (baseTokens === null || typeof baseTokens !== "object") return [];
|
|
695
|
-
var names = Object.keys(baseTokens).filter(
|
|
1029
|
+
var names = Object.keys(baseTokens).filter(function (name) {
|
|
1030
|
+
return isFontToken(name) || isCodeToken(name) || isDeltaToken(name);
|
|
1031
|
+
});
|
|
696
1032
|
names.sort(function (left, right) {
|
|
697
1033
|
if (left.length !== right.length) return left.length - right.length;
|
|
698
1034
|
return left < right ? -1 : left > right ? 1 : 0;
|
|
@@ -701,102 +1037,795 @@ function tokenNames(baseTokens) {
|
|
|
701
1037
|
}
|
|
702
1038
|
|
|
703
1039
|
/**
|
|
704
|
-
* The
|
|
705
|
-
*
|
|
1040
|
+
* The interface PART size tokens: everything scalable except the code and
|
|
1041
|
+
* markdown families and the delta tokens (which the dialog offset shifts
|
|
1042
|
+
* additively, never multiplicatively).
|
|
706
1043
|
* @param {Record<string, string>} [baseTokens] - token name to value.
|
|
707
|
-
* @returns {string[]} the names the
|
|
1044
|
+
* @returns {string[]} the names the interface offset re-declares.
|
|
708
1045
|
*/
|
|
709
1046
|
function bodyTokenNames(baseTokens) {
|
|
710
1047
|
return tokenNames(baseTokens).filter(function (name) {
|
|
711
|
-
return
|
|
1048
|
+
return (
|
|
1049
|
+
!isCodeToken(name) &&
|
|
1050
|
+
!isMarkdownToken(name) &&
|
|
1051
|
+
!isDeltaToken(name) &&
|
|
1052
|
+
!isContentToken(name)
|
|
1053
|
+
);
|
|
1054
|
+
});
|
|
1055
|
+
}
|
|
1056
|
+
|
|
1057
|
+
/**
|
|
1058
|
+
* The interface line-height PART tokens (non-code, non-markdown).
|
|
1059
|
+
* @param {Record<string, string>} [baseTokens] - token name to value.
|
|
1060
|
+
* @returns {string[]}
|
|
1061
|
+
*/
|
|
1062
|
+
function uiLineTokenNames(baseTokens) {
|
|
1063
|
+
return bodyTokenNames(baseTokens).filter(function (name) {
|
|
1064
|
+
return name.indexOf("-line-height") >= 0;
|
|
712
1065
|
});
|
|
713
1066
|
}
|
|
714
1067
|
|
|
715
1068
|
/**
|
|
716
|
-
* The
|
|
717
|
-
*
|
|
1069
|
+
* The markdown (dialog) PART tokens: sizes and line heights of the markdown
|
|
1070
|
+
* family, excluding code.
|
|
718
1071
|
* @param {Record<string, string>} [baseTokens] - token name to value.
|
|
719
|
-
* @returns {string[]}
|
|
1072
|
+
* @returns {string[]}
|
|
1073
|
+
*/
|
|
1074
|
+
function markdownTokenNames(baseTokens) {
|
|
1075
|
+
return tokenNames(baseTokens).filter(isMarkdownToken);
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
/**
|
|
1079
|
+
* The markdown content-size DELTA tokens present in a base map.
|
|
1080
|
+
* @param {Record<string, string>} [baseTokens] - token name to value.
|
|
1081
|
+
* @returns {string[]}
|
|
1082
|
+
*/
|
|
1083
|
+
function deltaTokenNames(baseTokens) {
|
|
1084
|
+
if (baseTokens === null || typeof baseTokens !== "object") return [];
|
|
1085
|
+
return Object.keys(baseTokens).filter(isDeltaToken);
|
|
1086
|
+
}
|
|
1087
|
+
|
|
1088
|
+
/**
|
|
1089
|
+
* The code PART tokens: the `font` shorthands' size/height parts, when the
|
|
1090
|
+
* live stylesheets declare them. The shorthands themselves are handled by
|
|
1091
|
+
* `codeShorthandTokens`, so they are excluded here to avoid double
|
|
1092
|
+
* declarations.
|
|
1093
|
+
* @param {Record<string, string>} [baseTokens] - token name to value.
|
|
1094
|
+
* @returns {string[]}
|
|
720
1095
|
*/
|
|
721
1096
|
function codeTokenNames(baseTokens) {
|
|
722
|
-
return tokenNames(baseTokens).filter(
|
|
1097
|
+
return tokenNames(baseTokens).filter(function (name) {
|
|
1098
|
+
return isCodeToken(name) && !isShorthandName(name);
|
|
1099
|
+
});
|
|
1100
|
+
}
|
|
1101
|
+
|
|
1102
|
+
/**
|
|
1103
|
+
* Every `font` shorthand token in a base map that parses, sorted by name
|
|
1104
|
+
* length (families before variants, mirroring the part-token order).
|
|
1105
|
+
* @param {Record<string, string>} [baseTokens] - token name to value.
|
|
1106
|
+
* @returns {{name: string, parts: object}[]}
|
|
1107
|
+
*/
|
|
1108
|
+
function shorthandTokens(baseTokens) {
|
|
1109
|
+
if (baseTokens === null || typeof baseTokens !== "object") return [];
|
|
1110
|
+
var out = [];
|
|
1111
|
+
var names = Object.keys(baseTokens).filter(isShorthandName);
|
|
1112
|
+
names.sort(function (left, right) {
|
|
1113
|
+
if (left.length !== right.length) return left.length - right.length;
|
|
1114
|
+
return left < right ? -1 : left > right ? 1 : 0;
|
|
1115
|
+
});
|
|
1116
|
+
for (var index = 0; index < names.length; index += 1) {
|
|
1117
|
+
var parts = parseShorthand(baseTokens[names[index]]);
|
|
1118
|
+
if (parts !== null) out.push({ name: names[index], parts: parts });
|
|
1119
|
+
}
|
|
1120
|
+
return out;
|
|
1121
|
+
}
|
|
1122
|
+
|
|
1123
|
+
/**
|
|
1124
|
+
* The markdown (dialog) `font` shorthand tokens, excluding code.
|
|
1125
|
+
* @param {Record<string, string>} [baseTokens] - token name to value.
|
|
1126
|
+
* @returns {{name: string, parts: object}[]}
|
|
1127
|
+
*/
|
|
1128
|
+
function markdownShorthandTokens(baseTokens) {
|
|
1129
|
+
return shorthandTokens(baseTokens).filter(function (entry) {
|
|
1130
|
+
return isMarkdownToken(entry.name);
|
|
1131
|
+
});
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1134
|
+
/**
|
|
1135
|
+
* The interface (non-markdown, non-code) `font` shorthand tokens.
|
|
1136
|
+
* @param {Record<string, string>} [baseTokens] - token name to value.
|
|
1137
|
+
* @returns {{name: string, parts: object}[]}
|
|
1138
|
+
*/
|
|
1139
|
+
function uiShorthandTokens(baseTokens) {
|
|
1140
|
+
return shorthandTokens(baseTokens).filter(function (entry) {
|
|
1141
|
+
return !isMarkdownToken(entry.name) && !isCodeToken(entry.name);
|
|
1142
|
+
});
|
|
1143
|
+
}
|
|
1144
|
+
|
|
1145
|
+
/**
|
|
1146
|
+
* The code `font` shorthand tokens.
|
|
1147
|
+
* @param {Record<string, string>} [baseTokens] - token name to value.
|
|
1148
|
+
* @returns {{name: string, parts: object}[]}
|
|
1149
|
+
*/
|
|
1150
|
+
function codeShorthandTokens(baseTokens) {
|
|
1151
|
+
return shorthandTokens(baseTokens).filter(function (entry) {
|
|
1152
|
+
return isCodeToken(entry.name);
|
|
1153
|
+
});
|
|
1154
|
+
}
|
|
1155
|
+
|
|
1156
|
+
/* ------------------------------------------------------------------ *
|
|
1157
|
+
* selectors
|
|
1158
|
+
* ------------------------------------------------------------------ */
|
|
1159
|
+
|
|
1160
|
+
/**
|
|
1161
|
+
* What counts as code text for the code axes.
|
|
1162
|
+
*
|
|
1163
|
+
* There is no token to hook here (no shipped rule reads a `-font-weight`
|
|
1164
|
+
* token — measured, zero `font-weight: var(--dsw-font-…)` in rc.2), so the
|
|
1165
|
+
* code axes have to name the elements. Two layers, both measured against
|
|
1166
|
+
* rc.2:
|
|
1167
|
+
*
|
|
1168
|
+
* 1. The tags DSH's own stylesheets size with a code token: markdown `pre` /
|
|
1169
|
+
* inline `code`, the tool card's `:where(pre)` block, form controls, and
|
|
1170
|
+
* CodeMirror-like editors. Their descendants are listed too, because the
|
|
1171
|
+
* blanket body rule matches EVERY element — a `<span>` inside a
|
|
1172
|
+
* highlighted block would otherwise be pulled back to the body weight.
|
|
1173
|
+
* 2. Containers that only a class name identifies: the terminal and tool-code
|
|
1174
|
+
* bodies are plain `div`s (`…_terminal`, `…_codeBody`, `md-code-block`).
|
|
1175
|
+
*
|
|
1176
|
+
* The `i` flag keeps a casing change in a future DSH from silently dropping
|
|
1177
|
+
* the rule, and an unmatched token/class simply means that surface follows
|
|
1178
|
+
* the interface axis — the same degradation as an unknown token name.
|
|
1179
|
+
*/
|
|
1180
|
+
var CODE_SELECTOR = [
|
|
1181
|
+
"pre",
|
|
1182
|
+
"code",
|
|
1183
|
+
"kbd",
|
|
1184
|
+
"samp",
|
|
1185
|
+
"var",
|
|
1186
|
+
"tt",
|
|
1187
|
+
"textarea",
|
|
1188
|
+
".cm-editor",
|
|
1189
|
+
".dfp-previewCode",
|
|
1190
|
+
'[class*="code" i]',
|
|
1191
|
+
'[class*="terminal" i]',
|
|
1192
|
+
]
|
|
1193
|
+
.map(function (selector) {
|
|
1194
|
+
return selector + "," + selector + " *";
|
|
1195
|
+
})
|
|
1196
|
+
.join(",");
|
|
1197
|
+
|
|
1198
|
+
/**
|
|
1199
|
+
* Everything a code surface must be excluded from a DIALOG-scoped rule:
|
|
1200
|
+
* the code elements themselves AND their descendants (a highlight `<span>`
|
|
1201
|
+
* inside a `<pre>` is not a `pre`, so the exclusion has to reach through
|
|
1202
|
+
* ancestry with complex `:not()` arguments — supported by the Chromium line
|
|
1203
|
+
* DSH targets).
|
|
1204
|
+
*/
|
|
1205
|
+
var CODE_EXCLUDES = [
|
|
1206
|
+
"pre",
|
|
1207
|
+
"pre *",
|
|
1208
|
+
"code",
|
|
1209
|
+
"code *",
|
|
1210
|
+
"kbd",
|
|
1211
|
+
"kbd *",
|
|
1212
|
+
"samp",
|
|
1213
|
+
"samp *",
|
|
1214
|
+
"var",
|
|
1215
|
+
"var *",
|
|
1216
|
+
"tt",
|
|
1217
|
+
"tt *",
|
|
1218
|
+
"textarea",
|
|
1219
|
+
"textarea *",
|
|
1220
|
+
'[class*="code" i]',
|
|
1221
|
+
'[class*="code" i] *',
|
|
1222
|
+
'[class*="terminal" i]',
|
|
1223
|
+
'[class*="terminal" i] *',
|
|
1224
|
+
]
|
|
1225
|
+
.map(function (selector) {
|
|
1226
|
+
return ":not(" + selector + ")";
|
|
1227
|
+
})
|
|
1228
|
+
.join("");
|
|
1229
|
+
|
|
1230
|
+
/**
|
|
1231
|
+
* The dialog surface: DSH renders the conversation markdown in a CSS-module
|
|
1232
|
+
* container whose class hashes per release (`_markdown_<hash>_…`). A
|
|
1233
|
+
* class-substring match degrades safely — no match means the dialog follows
|
|
1234
|
+
* the interface axis, the same behavior as an unknown token name.
|
|
1235
|
+
*/
|
|
1236
|
+
var MARKDOWN_SELECTOR =
|
|
1237
|
+
'[class*="_markdown_" i]' +
|
|
1238
|
+
CODE_EXCLUDES +
|
|
1239
|
+
',[class*="_markdown_" i] *' +
|
|
1240
|
+
CODE_EXCLUDES;
|
|
1241
|
+
|
|
1242
|
+
/** The dark-theme attribute DSH's ThemePresenter writes on body. */
|
|
1243
|
+
var DARK_ATTR = "body[data-ds-dark-theme]";
|
|
1244
|
+
|
|
1245
|
+
/**
|
|
1246
|
+
* Prefix every selector in a comma list, for the per-theme rules.
|
|
1247
|
+
* @param {string} selectorList - a comma-separated selector list.
|
|
1248
|
+
* @param {string} prefix - the prefix to prepend to each selector.
|
|
1249
|
+
* @returns {string} the prefixed list.
|
|
1250
|
+
*/
|
|
1251
|
+
function prefixSelector(selectorList, prefix) {
|
|
1252
|
+
var parts = selectorList.split(",");
|
|
1253
|
+
var out = [];
|
|
1254
|
+
for (var index = 0; index < parts.length; index += 1) {
|
|
1255
|
+
out.push(prefix + " " + parts[index]);
|
|
1256
|
+
}
|
|
1257
|
+
return out.join(",");
|
|
1258
|
+
}
|
|
1259
|
+
|
|
1260
|
+
/* ------------------------------------------------------------------ *
|
|
1261
|
+
* per-theme resolution
|
|
1262
|
+
* ------------------------------------------------------------------ */
|
|
1263
|
+
|
|
1264
|
+
/**
|
|
1265
|
+
* Expand the FOLLOW semantics over one normalized configuration: the interface
|
|
1266
|
+
* borrows the conversation's family (and an unset conversation family falls back
|
|
1267
|
+
* to the interface's own). The interface's size, line-height and weight axes are
|
|
1268
|
+
* retired and render nothing, so no other axis follows.
|
|
1269
|
+
* @param {Record<string, unknown>} raw - a normalized configuration.
|
|
1270
|
+
* @returns {Record<string, unknown>} the set with the follow rule applied.
|
|
1271
|
+
*/
|
|
1272
|
+
function expandFollow(raw) {
|
|
1273
|
+
var out = {};
|
|
1274
|
+
for (var field in raw) {
|
|
1275
|
+
if (Object.prototype.hasOwnProperty.call(raw, field)) out[field] = raw[field];
|
|
1276
|
+
}
|
|
1277
|
+
// The conversation inherits the interface's FAMILY for as long as it does not
|
|
1278
|
+
// set one itself. That is the whole of the old "follow" behaviour that is
|
|
1279
|
+
// left: the interface's size, line height and weight axes are retired (DSH
|
|
1280
|
+
// exposes no interface hook for the first two, and a body-wide weight rule
|
|
1281
|
+
// cannot help but reach the conversation), so the conversation's own values
|
|
1282
|
+
// are the only ones that render for them.
|
|
1283
|
+
out[STACK_DIALOG_FIELD] = raw[STACK_DIALOG_FIELD] !== "" ? raw[STACK_DIALOG_FIELD] : raw[SANS_FIELD];
|
|
1284
|
+
out[WEIGHT_DIALOG_FIELD] = raw[WEIGHT_DIALOG_FIELD];
|
|
1285
|
+
// The interface follows the conversation: the conversation's family wins
|
|
1286
|
+
// whenever the conversation sets one, and the interface's own family stands as
|
|
1287
|
+
// the fallback otherwise.
|
|
1288
|
+
if (raw[UI_FOLLOWS_FIELD] !== false) {
|
|
1289
|
+
out[SANS_FIELD] = out[STACK_DIALOG_FIELD];
|
|
1290
|
+
}
|
|
1291
|
+
return out;
|
|
1292
|
+
}
|
|
1293
|
+
|
|
1294
|
+
/**
|
|
1295
|
+
* Resolve the effective light and dark axis sets of one configuration.
|
|
1296
|
+
*
|
|
1297
|
+
* With `perTheme` off the dark set IS the light set; with it on, the sparse
|
|
1298
|
+
* `darkValues` map is applied over the single set first and follow-expansion
|
|
1299
|
+
* runs on the result, so a dark dialog value still tracks the dark interface
|
|
1300
|
+
* value when the interface itself is overridden per theme.
|
|
1301
|
+
* @param {unknown} config - candidate configuration.
|
|
1302
|
+
* @returns {{light: object, dark: object}} the two axis sets.
|
|
1303
|
+
*/
|
|
1304
|
+
function resolveAxes(config) {
|
|
1305
|
+
var raw = normalizeConfig(config);
|
|
1306
|
+
var light = expandFollow(raw);
|
|
1307
|
+
if (raw[PER_THEME_FIELD] !== true) return { light: light, dark: light };
|
|
1308
|
+
// The dark overrides are applied to the RAW values and only then expanded: an
|
|
1309
|
+
// already-expanded set has the follow-borrowed values baked in, so expanding
|
|
1310
|
+
// it again would let a borrowed value shadow the dark override.
|
|
1311
|
+
var merged = {};
|
|
1312
|
+
for (var field in raw) {
|
|
1313
|
+
if (Object.prototype.hasOwnProperty.call(raw, field)) merged[field] = raw[field];
|
|
1314
|
+
}
|
|
1315
|
+
var overrides = raw[DARK_VALUES_FIELD];
|
|
1316
|
+
for (var key in overrides) {
|
|
1317
|
+
if (Object.prototype.hasOwnProperty.call(overrides, key)) merged[key] = overrides[key];
|
|
1318
|
+
}
|
|
1319
|
+
return { light: light, dark: expandFollow(merged) };
|
|
723
1320
|
}
|
|
724
1321
|
|
|
1322
|
+
/* ------------------------------------------------------------------ *
|
|
1323
|
+
* the stylesheet builder
|
|
1324
|
+
* ------------------------------------------------------------------ */
|
|
1325
|
+
|
|
1326
|
+
/**
|
|
1327
|
+
* Whether a normalized configuration asks for any change at all.
|
|
1328
|
+
* @param {unknown} config - candidate configuration.
|
|
1329
|
+
* @returns {boolean} true when nothing should be injected.
|
|
1330
|
+
*/
|
|
1331
|
+
function isDormant(config) {
|
|
1332
|
+
var axis = config !== null && typeof config === "object" ? config : {};
|
|
1333
|
+
if (axis[PER_THEME_FIELD] === true) {
|
|
1334
|
+
var dark = axis[DARK_VALUES_FIELD];
|
|
1335
|
+
if (dark !== null && typeof dark === "object") {
|
|
1336
|
+
for (var field in dark) {
|
|
1337
|
+
if (Object.prototype.hasOwnProperty.call(dark, field)) return false;
|
|
1338
|
+
}
|
|
1339
|
+
}
|
|
1340
|
+
}
|
|
1341
|
+
return (
|
|
1342
|
+
// The retired interface axes (size, line height, weight) are deliberately
|
|
1343
|
+
// absent: a configuration that only sets them renders nothing, so it is
|
|
1344
|
+
// dormant like an empty one.
|
|
1345
|
+
sanitize(axis[SANS_FIELD]) === "" &&
|
|
1346
|
+
sanitize(axis[STACK_DIALOG_FIELD]) === "" &&
|
|
1347
|
+
sanitize(axis[MONO_FIELD]) === "" &&
|
|
1348
|
+
clampOffset(axis[SIZE_DIALOG_FIELD]) === 0 &&
|
|
1349
|
+
clampOffset(axis[CODE_SIZE_FIELD]) === 0 &&
|
|
1350
|
+
clampWeight(axis[WEIGHT_DIALOG_FIELD]) === WEIGHT_UNSET &&
|
|
1351
|
+
clampWeight(axis[CODE_WEIGHT_FIELD]) === WEIGHT_UNSET &&
|
|
1352
|
+
clampRatio(axis[LINE_HEIGHT_DIALOG_FIELD]) === LINE_HEIGHT_MIN &&
|
|
1353
|
+
clampLineOffset(axis[CODE_LINE_HEIGHT_FIELD]) === 0 &&
|
|
1354
|
+
clampLigatures(axis[LIGATURES_FIELD]) === LIGATURES_DEFAULT &&
|
|
1355
|
+
sanitizeFeatures(axis[FEATURES_FIELD]) === "" &&
|
|
1356
|
+
toBool(axis[NO_SYNTHETIC_ITALIC_FIELD]) === false &&
|
|
1357
|
+
toBool(axis[NO_SYNTHETIC_BOLD_FIELD]) === false
|
|
1358
|
+
);
|
|
1359
|
+
}
|
|
1360
|
+
|
|
1361
|
+
/**
|
|
1362
|
+
* Build the stylesheet one THEME VALUE SET applies.
|
|
1363
|
+
*
|
|
1364
|
+
* The dialog axes reach the conversation markdown through a scope rule keyed
|
|
1365
|
+
* on the markdown container (class-substring match) rather than through the
|
|
1366
|
+
* design tokens: the tokens' `font` shorthands inline their parts, so a
|
|
1367
|
+
* token-level dialog override would need a full shorthand rebuild, while a
|
|
1368
|
+
* scoped `!important` rule wins over every non-important declaration the
|
|
1369
|
+
* theme ships and degrades to "follows the interface" when nothing matches.
|
|
1370
|
+
* Code surfaces are excluded from the dialog scope by construction (see
|
|
1371
|
+
* `CODE_EXCLUDES`), so inline code inside a serif conversation stays mono.
|
|
1372
|
+
*
|
|
1373
|
+
* @param {Record<string, unknown>} axis - an expanded (follow-applied) value set.
|
|
1374
|
+
* @param {Record<string, string>} baseTokens - token name to untouched value.
|
|
1375
|
+
* @param {boolean} isDark - prefix every rule with the dark-theme attribute.
|
|
1376
|
+
* @returns {string} declarations for one `<style>` element.
|
|
1377
|
+
*/
|
|
1378
|
+
function buildAxisCss(axis, baseTokens, isDark) {
|
|
1379
|
+
var sans = formatStack(parseStack(axis[SANS_FIELD]));
|
|
1380
|
+
var mono = formatStack(parseStack(axis[MONO_FIELD]));
|
|
1381
|
+
var declarations = [];
|
|
1382
|
+
|
|
1383
|
+
function blanket(declarations) {
|
|
1384
|
+
var selector = isDark ? DARK_ATTR + "," + DARK_ATTR + " *" : "body,body *";
|
|
1385
|
+
return selector + "{" + declarations + "}";
|
|
1386
|
+
}
|
|
1387
|
+
function codeRule(declarations) {
|
|
1388
|
+
var selector = isDark ? prefixSelector(CODE_SELECTOR, DARK_ATTR) : CODE_SELECTOR;
|
|
1389
|
+
return selector + "{" + declarations + "}";
|
|
1390
|
+
}
|
|
1391
|
+
function dialogRule(declarations) {
|
|
1392
|
+
var selector = isDark ? prefixSelector(MARKDOWN_SELECTOR, DARK_ATTR) : MARKDOWN_SELECTOR;
|
|
1393
|
+
return selector + "{" + declarations + "}";
|
|
1394
|
+
}
|
|
1395
|
+
/**
|
|
1396
|
+
* Re-assert the code family inside the dialog: scoped under the markdown
|
|
1397
|
+
* container, after the dialog rule and above it in specificity, so a
|
|
1398
|
+
* serif conversation keeps monospaced code.
|
|
1399
|
+
*/
|
|
1400
|
+
function pushScopedCodeFamily() {
|
|
1401
|
+
var scopedCode = [];
|
|
1402
|
+
var surfaces = CODE_SELECTOR.split(",");
|
|
1403
|
+
for (var cIndex = 0; cIndex < surfaces.length; cIndex += 1) {
|
|
1404
|
+
scopedCode.push('[class*="_markdown_" i] ' + surfaces[cIndex]);
|
|
1405
|
+
}
|
|
1406
|
+
declarations.push(scopedCode.join(",") + "{font-family:" + mono + " !important}");
|
|
1407
|
+
}
|
|
1408
|
+
|
|
1409
|
+
// ---- families ----
|
|
1410
|
+
if (sans !== "") {
|
|
1411
|
+
declarations.push(
|
|
1412
|
+
(isDark ? DARK_ATTR : ":root,body") + "{--dsw-font-family:" + sans + " !important}"
|
|
1413
|
+
);
|
|
1414
|
+
declarations.push((isDark ? DARK_ATTR : "body") + "{font-family:" + sans + " !important}");
|
|
1415
|
+
}
|
|
1416
|
+
if (mono !== "") {
|
|
1417
|
+
// The theme's code tokens chain to `--ds-font-family-code`; the other name
|
|
1418
|
+
// is what dsh-ui-font historically wrote and costs nothing to cover.
|
|
1419
|
+
declarations.push(
|
|
1420
|
+
(isDark ? DARK_ATTR : ":root,body") +
|
|
1421
|
+
"{--dsw-font-mono:" +
|
|
1422
|
+
mono +
|
|
1423
|
+
" !important;--ds-font-family-code:" +
|
|
1424
|
+
mono +
|
|
1425
|
+
" !important}"
|
|
1426
|
+
);
|
|
1427
|
+
// Written after the interface family rule: equal specificity on a code
|
|
1428
|
+
// element means the later declaration wins, so code keeps its own family.
|
|
1429
|
+
declarations.push(codeRule("font-family:" + mono + " !important"));
|
|
1430
|
+
}
|
|
1431
|
+
|
|
1432
|
+
// ---- interface size / line height: RETIRED, and deliberately so ----
|
|
1433
|
+
// Verified against DSH 0.1.5-rc.2: the official settings expose exactly one
|
|
1434
|
+
// size hook, the CONVERSATION font size (`--dsh-content-font-size`). The
|
|
1435
|
+
// interface ladder tokens (`--dsw-font-xs-13`, `--dsw-font-s-14`, …) are
|
|
1436
|
+
// consumed only by conversation-area widgets (tool cards, trajectory,
|
|
1437
|
+
// attachment chips — 6 client-ui packages), while the settings sheet, the
|
|
1438
|
+
// left workspace/sidebar and the right sidebar declare their text sizes as
|
|
1439
|
+
// literals inside their own CSS modules. There is therefore no interface
|
|
1440
|
+
// size or line-height hook: scaling the ladder from these fields moved
|
|
1441
|
+
// nothing a user could recognise as "interface", and scaling the content
|
|
1442
|
+
// chain from them moved the conversation — the exact cross-talk this release
|
|
1443
|
+
// removes. The durable fields stay in the schema (old documents keep
|
|
1444
|
+
// parsing, presets keep round-tripping) but inject nothing at all.
|
|
1445
|
+
//
|
|
1446
|
+
// The interface axis that DOES work is kept above: the family (a page-wide
|
|
1447
|
+
// `font-family` on body plus the theme variable) and the weight (a
|
|
1448
|
+
// `body, body *` rule).
|
|
1449
|
+
|
|
1450
|
+
// ---- interface weight: RETIRED ----
|
|
1451
|
+
// A weight is only expressible as a `body, body *` rule, which cannot help
|
|
1452
|
+
// but reach the conversation's markdown (narrowing it off the markdown
|
|
1453
|
+
// subtree is possible, but it would then also strip the theme's own heading
|
|
1454
|
+
// weights). The conversation owns the weight axis instead: its rule is scoped
|
|
1455
|
+
// to the markdown subtree, so it moves the conversation and nothing else.
|
|
1456
|
+
// The durable field stays in the schema (old documents and presets keep
|
|
1457
|
+
// parsing) but injects nothing.
|
|
1458
|
+
|
|
1459
|
+
// ---- dialog family (scoped; skipped when it equals the interface) ----
|
|
1460
|
+
var dialog = formatStack(parseStack(axis[STACK_DIALOG_FIELD]));
|
|
1461
|
+
if (dialog !== "" && dialog !== sans) {
|
|
1462
|
+
declarations.push(dialogRule("font-family:" + dialog + " !important"));
|
|
1463
|
+
if (mono !== "") {
|
|
1464
|
+
// Keep the code family inside the dialog.
|
|
1465
|
+
pushScopedCodeFamily();
|
|
1466
|
+
}
|
|
1467
|
+
}
|
|
1468
|
+
|
|
1469
|
+
// ---- dialog weight (scoped; skipped when it equals the interface) ----
|
|
1470
|
+
var dialogWeight = axis[WEIGHT_DIALOG_FIELD];
|
|
1471
|
+
if (dialogWeight !== WEIGHT_UNSET) {
|
|
1472
|
+
declarations.push(dialogRule("font-weight:" + dialogWeight + " !important"));
|
|
1473
|
+
}
|
|
1474
|
+
|
|
1475
|
+
// ---- dialog size offset (additive, on the official content-size source) ----
|
|
1476
|
+
// Every markdown size derives from the official content-size chain, and that
|
|
1477
|
+
// chain is COMPUTED ON BODY: custom properties resolve where they are
|
|
1478
|
+
// declared, so overriding the size deeper down would leave the derived tokens
|
|
1479
|
+
// (secondary size, both deltas, delta-based heading sizes, heights) at their
|
|
1480
|
+
// body-computed values. The offset is therefore added once, at the source —
|
|
1481
|
+
// `--dsh-content-font-size` becomes "official base + offset" — and the whole
|
|
1482
|
+
// ladder follows by derivation, with nothing counted twice. The interface
|
|
1483
|
+
// axis never touches this chain (see `bodyTokenNames`), so the conversation
|
|
1484
|
+
// moves only when this axis says so, and a dialog that FOLLOWS the interface
|
|
1485
|
+
// follows it through this value.
|
|
1486
|
+
var dialogOffsetValue = axis[SIZE_DIALOG_FIELD];
|
|
1487
|
+
if (dialogOffsetValue !== 0) {
|
|
1488
|
+
var contentSources = tokenNames(baseTokens).filter(function (name) {
|
|
1489
|
+
if (!isContentToken(name)) return false;
|
|
1490
|
+
var base = baseTokens[name];
|
|
1491
|
+
// The source token only: the derived ones carry var()/calc() and would
|
|
1492
|
+
// add the offset a second time if they were shifted as well.
|
|
1493
|
+
return (
|
|
1494
|
+
typeof base === "string" &&
|
|
1495
|
+
base !== "" &&
|
|
1496
|
+
base.indexOf("var(") === -1 &&
|
|
1497
|
+
base.indexOf("calc(") === -1
|
|
1498
|
+
);
|
|
1499
|
+
});
|
|
1500
|
+
var contentShift = shiftTokens(contentSources, baseTokens, dialogOffsetValue);
|
|
1501
|
+
if (contentShift.length > 0) declarations.push(blanket(contentShift.join(";")));
|
|
1502
|
+
// The `small` series is the only markdown family with literal sizes: it
|
|
1503
|
+
// does not derive from the chain, so its parts and shorthands shift here.
|
|
1504
|
+
var literals = markdownTokenNames(baseTokens).filter(function (name) {
|
|
1505
|
+
var base = baseTokens[name];
|
|
1506
|
+
return name.indexOf("-font-size") >= 0 && typeof base === "string" && base.indexOf("var(") === -1;
|
|
1507
|
+
});
|
|
1508
|
+
var literalShift = shiftTokens(literals, baseTokens, dialogOffsetValue);
|
|
1509
|
+
if (literalShift.length > 0) declarations.push(blanket(literalShift.join(";")));
|
|
1510
|
+
// The shorthands whose size is a literal (the small series) must ride the
|
|
1511
|
+
// shift too; the delta-derived shorthands flow through the chain above.
|
|
1512
|
+
var sizeShifted = [];
|
|
1513
|
+
var markdownEntries = shorthandTokens(baseTokens).filter(function (entry) {
|
|
1514
|
+
return isMarkdownToken(entry.name) && entry.parts.size.indexOf("var(") === -1;
|
|
1515
|
+
});
|
|
1516
|
+
for (var mIndex = 0; mIndex < markdownEntries.length; mIndex += 1) {
|
|
1517
|
+
var mEntry = markdownEntries[mIndex];
|
|
1518
|
+
var mSize =
|
|
1519
|
+
"calc((" + mEntry.parts.size + ") + " + dialogOffsetValue + "px)";
|
|
1520
|
+
sizeShifted.push(
|
|
1521
|
+
mEntry.name + ":" + rebuildShorthand({ lead: mEntry.parts.lead, size: mSize, height: mEntry.parts.height, family: mEntry.parts.family }) + " !important"
|
|
1522
|
+
);
|
|
1523
|
+
}
|
|
1524
|
+
if (sizeShifted.length > 0) declarations.push(blanket(sizeShifted.join(";")));
|
|
1525
|
+
}
|
|
1526
|
+
|
|
1527
|
+
// ---- dialog line height (ratio, through the shorthand heights) ----
|
|
1528
|
+
var dialogLine = axis[LINE_HEIGHT_DIALOG_FIELD];
|
|
1529
|
+
if (dialogLine !== LINE_HEIGHT_MIN) {
|
|
1530
|
+
var dialogFactor = round(dialogLine / LINE_HEIGHT_MIN, 6);
|
|
1531
|
+
var markdownLines = markdownTokenNames(baseTokens).filter(function (name) {
|
|
1532
|
+
return name.indexOf("-line-height") >= 0;
|
|
1533
|
+
});
|
|
1534
|
+
var dialogLineDecls = scaleLineTokens(markdownLines, baseTokens, dialogFactor);
|
|
1535
|
+
if (dialogLineDecls.length > 0) declarations.push(blanket(dialogLineDecls.join(";")));
|
|
1536
|
+
var dialogShorthandList = markdownShorthandTokens(baseTokens);
|
|
1537
|
+
var dialogShorthandShifted = [];
|
|
1538
|
+
for (var dIndex = 0; dIndex < dialogShorthandList.length; dIndex += 1) {
|
|
1539
|
+
var dEntry = dialogShorthandList[dIndex];
|
|
1540
|
+
dialogShorthandShifted.push(
|
|
1541
|
+
dEntry.name +
|
|
1542
|
+
":" +
|
|
1543
|
+
rebuildShorthand({
|
|
1544
|
+
lead: dEntry.parts.lead,
|
|
1545
|
+
size: dEntry.parts.size,
|
|
1546
|
+
height: "calc((" + dEntry.parts.height + ") * " + dialogFactor + ")",
|
|
1547
|
+
family: dEntry.parts.family,
|
|
1548
|
+
}) +
|
|
1549
|
+
" !important"
|
|
1550
|
+
);
|
|
1551
|
+
}
|
|
1552
|
+
if (dialogShorthandShifted.length > 0) {
|
|
1553
|
+
declarations.push(blanket(dialogShorthandShifted.join(";")));
|
|
1554
|
+
}
|
|
1555
|
+
}
|
|
1556
|
+
|
|
1557
|
+
// ---- code axes ----
|
|
1558
|
+
var codeSize = axis[CODE_SIZE_FIELD];
|
|
1559
|
+
var codeLine = axis[CODE_LINE_HEIGHT_FIELD];
|
|
1560
|
+
if (codeSize !== 0 || codeLine !== 0) {
|
|
1561
|
+
var codeScaled = scaleCodeTokens(
|
|
1562
|
+
baseTokens,
|
|
1563
|
+
round(scaleFor(codeSize), 6),
|
|
1564
|
+
codeLine
|
|
1565
|
+
);
|
|
1566
|
+
if (codeScaled.length > 0) declarations.push(blanket(codeScaled.join(";")));
|
|
1567
|
+
}
|
|
1568
|
+
var codeWeight = axis[CODE_WEIGHT_FIELD];
|
|
1569
|
+
if (codeWeight !== WEIGHT_UNSET) {
|
|
1570
|
+
declarations.push(codeRule("font-weight:" + codeWeight + " !important"));
|
|
1571
|
+
}
|
|
1572
|
+
var ligatures = axis[LIGATURES_FIELD];
|
|
1573
|
+
if (ligatures === LIGATURES_ON) {
|
|
1574
|
+
declarations.push(codeRule("font-variant-ligatures:contextual !important"));
|
|
1575
|
+
} else if (ligatures === LIGATURES_OFF) {
|
|
1576
|
+
declarations.push(codeRule("font-variant-ligatures:none !important"));
|
|
1577
|
+
}
|
|
1578
|
+
var features = axis[FEATURES_FIELD];
|
|
1579
|
+
if (features !== "") {
|
|
1580
|
+
declarations.push(codeRule("font-feature-settings:" + features + " !important"));
|
|
1581
|
+
}
|
|
1582
|
+
|
|
1583
|
+
// ---- synthesis (faux italic / faux bold) ----
|
|
1584
|
+
var noItalic = axis[NO_SYNTHETIC_ITALIC_FIELD] === true;
|
|
1585
|
+
var noBold = axis[NO_SYNTHETIC_BOLD_FIELD] === true;
|
|
1586
|
+
if (noItalic && noBold) {
|
|
1587
|
+
declarations.push(blanket("font-synthesis:none !important"));
|
|
1588
|
+
} else {
|
|
1589
|
+
if (noItalic) declarations.push(blanket("font-synthesis-style:none !important"));
|
|
1590
|
+
if (noBold) declarations.push(blanket("font-synthesis-weight:none !important"));
|
|
1591
|
+
}
|
|
1592
|
+
|
|
1593
|
+
return declarations.join("\n");
|
|
1594
|
+
}
|
|
1595
|
+
|
|
1596
|
+
/**
|
|
1597
|
+
* Re-declare the markdown code tokens at their own ratio and line offset.
|
|
1598
|
+
*
|
|
1599
|
+
* Two shapes exist and both must be covered, because the shipped stylesheets
|
|
1600
|
+
* consume the shorthand (`font: var(--dsw-font-markdown-code-block-small)`)
|
|
1601
|
+
* while the split tokens exist for anything that asks for a single part:
|
|
1602
|
+
* `11px/19px <family>` and a bare `11px`. The shorthand's height rides BOTH
|
|
1603
|
+
* the ratio and the additive offset; the size rides the ratio only.
|
|
1604
|
+
*
|
|
1605
|
+
* @param {Record<string, string>} baseTokens - token name to untouched value.
|
|
1606
|
+
* @param {number} scale - the size ratio to apply.
|
|
1607
|
+
* @param {number} [lineOffset=0] - additive px on the height.
|
|
1608
|
+
* @returns {string[]} declarations.
|
|
1609
|
+
*/
|
|
1610
|
+
function scaleCodeTokens(baseTokens, scale, lineOffset) {
|
|
1611
|
+
var shift = typeof lineOffset === "number" && lineOffset !== 0 ? lineOffset : 0;
|
|
1612
|
+
// The code axis covers every `--dsw-font-markdown-code*` token exactly once:
|
|
1613
|
+
// a value that parses as a shorthand rides the shorthand path, everything
|
|
1614
|
+
// else the part path.
|
|
1615
|
+
var entries = [];
|
|
1616
|
+
var seen = {};
|
|
1617
|
+
var all = tokenNames(baseTokens).filter(isCodeToken);
|
|
1618
|
+
for (var aIndex = 0; aIndex < all.length; aIndex += 1) {
|
|
1619
|
+
seen[all[aIndex]] = true;
|
|
1620
|
+
entries.push({ name: all[aIndex], value: baseTokens[all[aIndex]] });
|
|
1621
|
+
}
|
|
1622
|
+
var shorthands = shorthandTokens(baseTokens);
|
|
1623
|
+
for (var sIndex = 0; sIndex < shorthands.length; sIndex += 1) {
|
|
1624
|
+
var entry = shorthands[sIndex];
|
|
1625
|
+
if (!isCodeToken(entry.name) || seen[entry.name] === true) continue;
|
|
1626
|
+
seen[entry.name] = true;
|
|
1627
|
+
entries.push({ name: entry.name, value: baseTokens[entry.name] });
|
|
1628
|
+
}
|
|
1629
|
+
var out = [];
|
|
1630
|
+
for (var index = 0; index < entries.length; index += 1) {
|
|
1631
|
+
var name = entries[index].name;
|
|
1632
|
+
var base = entries[index].value;
|
|
1633
|
+
if (typeof base !== "string" || base === "") continue;
|
|
1634
|
+
var parsed = parseShorthand(base);
|
|
1635
|
+
if (parsed !== null) {
|
|
1636
|
+
var size = scale === 1 ? parsed.size : "calc((" + parsed.size + ") * " + scale + ")";
|
|
1637
|
+
var height = "calc((" + parsed.height + ") * " + scale + ")";
|
|
1638
|
+
if (shift !== 0) height = "calc(" + height + " + " + shift + "px)";
|
|
1639
|
+
out.push(
|
|
1640
|
+
name +
|
|
1641
|
+
":" +
|
|
1642
|
+
rebuildShorthand({ lead: parsed.lead, size: size, height: height, family: parsed.family }) +
|
|
1643
|
+
" !important"
|
|
1644
|
+
);
|
|
1645
|
+
continue;
|
|
1646
|
+
}
|
|
1647
|
+
// A part token that derives from another token follows that token's own
|
|
1648
|
+
// scaling; re-declaring it here would compound the ratio — except the
|
|
1649
|
+
// line-height parts, which must additionally carry the offset.
|
|
1650
|
+
if (base.indexOf("var(") !== -1 && !(name.indexOf("-line-height") >= 0 && shift !== 0)) continue;
|
|
1651
|
+
if (!isScalableValue(base)) continue;
|
|
1652
|
+
if (name.indexOf("-line-height") >= 0 && shift !== 0) {
|
|
1653
|
+
out.push(
|
|
1654
|
+
name +
|
|
1655
|
+
":calc(((" + base + ") * " + scale + ") + " + shift + "px) !important"
|
|
1656
|
+
);
|
|
1657
|
+
} else {
|
|
1658
|
+
out.push(name + ":calc((" + base + ") * " + scale + ") !important");
|
|
1659
|
+
}
|
|
1660
|
+
}
|
|
1661
|
+
return out;
|
|
1662
|
+
}
|
|
1663
|
+
|
|
1664
|
+
/* ------------------------------------------------------------------ *
|
|
1665
|
+
* entry point
|
|
1666
|
+
* ------------------------------------------------------------------ */
|
|
1667
|
+
|
|
1668
|
+
/**
|
|
1669
|
+
* Build the stylesheet one configuration applies.
|
|
1670
|
+
*
|
|
1671
|
+
* The configuration resolves into a light set and — when `perTheme` is on — a
|
|
1672
|
+
* dark set; the light rules are emitted unprefixed and the dark rules carry
|
|
1673
|
+
* the dark-theme attribute, so the dark set wins by specificity exactly while
|
|
1674
|
+
* that theme is active. When `perTheme` is off, or the dark set equals the
|
|
1675
|
+
* light set, no prefixed rules are emitted at all.
|
|
1676
|
+
*
|
|
1677
|
+
* @param {unknown} config - normalized or raw configuration.
|
|
1678
|
+
* @param {Record<string, string>} [baseTokens] - token name to untouched value.
|
|
1679
|
+
* @returns {string} declarations for one `<style>` element ("" when dormant).
|
|
1680
|
+
*/
|
|
1681
|
+
function buildFontCss(config, baseTokens) {
|
|
1682
|
+
var tokens = baseTokens === undefined || baseTokens === null ? FALLBACK_TOKENS : baseTokens;
|
|
1683
|
+
var sets = resolveAxes(config);
|
|
1684
|
+
if (isDormant(sets.light) && isDormant(sets.dark)) return "";
|
|
1685
|
+
var light = buildAxisCss(sets.light, tokens, false);
|
|
1686
|
+
if (sameValueSet(sets.light, sets.dark)) return light;
|
|
1687
|
+
var dark = buildAxisCss(sets.dark, tokens, true);
|
|
1688
|
+
if (dark === "") return light;
|
|
1689
|
+
return light + "\n" + dark;
|
|
1690
|
+
}
|
|
1691
|
+
|
|
1692
|
+
/**
|
|
1693
|
+
* Whether two axis sets produce the same declarations, compared over the
|
|
1694
|
+
* visual fields only (the storage fields — presets, the dark map itself —
|
|
1695
|
+
* never render).
|
|
1696
|
+
* @param {Record<string, unknown>} left - an expanded axis set.
|
|
1697
|
+
* @param {Record<string, unknown>} right - another expanded axis set.
|
|
1698
|
+
* @returns {boolean}
|
|
1699
|
+
*/
|
|
1700
|
+
function sameValueSet(left, right) {
|
|
1701
|
+
for (var index = 0; index < VALUE_FIELDS.length; index += 1) {
|
|
1702
|
+
var field = VALUE_FIELDS[index];
|
|
1703
|
+
if (left[field] !== right[field]) return false;
|
|
1704
|
+
}
|
|
1705
|
+
return true;
|
|
1706
|
+
}
|
|
1707
|
+
|
|
1708
|
+
/* ------------------------------------------------------------------ *
|
|
1709
|
+
* embedded fallback map
|
|
1710
|
+
* ------------------------------------------------------------------ */
|
|
1711
|
+
|
|
725
1712
|
/**
|
|
726
1713
|
* Embedded fallback for the tokens the theme plugin defines at runtime.
|
|
727
1714
|
*
|
|
728
1715
|
* The live document is always preferred; this map only covers the window
|
|
729
1716
|
* before those declarations exist (and any token a future release renames
|
|
730
|
-
* away from the pattern). Values
|
|
731
|
-
* DSH 0.1.5-rc.2.
|
|
1717
|
+
* away from the pattern). Values are read byte for byte from the theme's own
|
|
1718
|
+
* stylesheet in DSH 0.1.5-rc.2.
|
|
732
1719
|
*/
|
|
733
1720
|
var FALLBACK_TOKENS = {
|
|
1721
|
+
// interface ladder shorthands
|
|
1722
|
+
"--dsw-font-xxxs-11": "11px/14px var(--dsw-font-family)",
|
|
1723
|
+
"--dsw-font-xxxs-strong-11": "500 11px/14px var(--dsw-font-family)",
|
|
1724
|
+
"--dsw-font-xxs-12": "12px/18px var(--dsw-font-family)",
|
|
1725
|
+
"--dsw-font-xxs-strong-12": "500 12px/18px var(--dsw-font-family)",
|
|
1726
|
+
"--dsw-font-xs-13": "13px/20px var(--dsw-font-family)",
|
|
1727
|
+
"--dsw-font-xs-strong-13": "500 13px/20px var(--dsw-font-family)",
|
|
1728
|
+
"--dsw-font-s-14": "14px/22px var(--dsw-font-family)",
|
|
1729
|
+
"--dsw-font-s-strong-14": "500 14px/22px var(--dsw-font-family)",
|
|
1730
|
+
"--dsw-font-base-16": "16px/24px var(--dsw-font-family)",
|
|
1731
|
+
"--dsw-font-base-strong-16": "500 16px/24px var(--dsw-font-family)",
|
|
1732
|
+
"--dsw-font-m-18": "500 16px/28px var(--dsw-font-family)",
|
|
1733
|
+
"--dsw-font-l-20": "500 20px/28px var(--dsw-font-family)",
|
|
1734
|
+
"--dsw-font-xl-24": "600 24px/32px var(--dsw-font-family)",
|
|
1735
|
+
// interface ladder parts
|
|
734
1736
|
"--dsw-font-xxxs-11-font-size": "11px",
|
|
735
|
-
"--dsw-font-xxxs-11-line-height": "
|
|
1737
|
+
"--dsw-font-xxxs-11-line-height": "14px",
|
|
736
1738
|
"--dsw-font-xxxs-strong-11-font-size": "11px",
|
|
737
|
-
"--dsw-font-xxxs-strong-11-line-height": "
|
|
1739
|
+
"--dsw-font-xxxs-strong-11-line-height": "14px",
|
|
738
1740
|
"--dsw-font-xxs-12-font-size": "12px",
|
|
739
|
-
"--dsw-font-xxs-12-line-height": "
|
|
1741
|
+
"--dsw-font-xxs-12-line-height": "18px",
|
|
740
1742
|
"--dsw-font-xxs-strong-12-font-size": "12px",
|
|
741
|
-
"--dsw-font-xxs-strong-12-line-height": "
|
|
1743
|
+
"--dsw-font-xxs-strong-12-line-height": "18px",
|
|
742
1744
|
"--dsw-font-xs-13-font-size": "13px",
|
|
743
|
-
"--dsw-font-xs-13-line-height": "
|
|
1745
|
+
"--dsw-font-xs-13-line-height": "20px",
|
|
744
1746
|
"--dsw-font-xs-strong-13-font-size": "13px",
|
|
745
|
-
"--dsw-font-xs-strong-13-line-height": "
|
|
1747
|
+
"--dsw-font-xs-strong-13-line-height": "20px",
|
|
746
1748
|
"--dsw-font-s-14-font-size": "14px",
|
|
747
|
-
"--dsw-font-s-14-line-height": "
|
|
1749
|
+
"--dsw-font-s-14-line-height": "22px",
|
|
748
1750
|
"--dsw-font-s-strong-14-font-size": "14px",
|
|
749
|
-
"--dsw-font-s-strong-14-line-height": "
|
|
1751
|
+
"--dsw-font-s-strong-14-line-height": "22px",
|
|
750
1752
|
"--dsw-font-base-16-font-size": "16px",
|
|
751
|
-
"--dsw-font-base-16-line-height": "
|
|
1753
|
+
"--dsw-font-base-16-line-height": "24px",
|
|
752
1754
|
"--dsw-font-base-strong-16-font-size": "16px",
|
|
753
|
-
"--dsw-font-base-strong-16-line-height": "
|
|
754
|
-
"--dsw-font-m-18-font-size": "
|
|
1755
|
+
"--dsw-font-base-strong-16-line-height": "24px",
|
|
1756
|
+
"--dsw-font-m-18-font-size": "16px",
|
|
755
1757
|
"--dsw-font-m-18-line-height": "28px",
|
|
756
1758
|
"--dsw-font-l-20-font-size": "20px",
|
|
757
|
-
"--dsw-font-l-20-line-height": "
|
|
1759
|
+
"--dsw-font-l-20-line-height": "28px",
|
|
758
1760
|
"--dsw-font-xl-24-font-size": "24px",
|
|
759
|
-
"--dsw-font-xl-24-line-height": "
|
|
760
|
-
|
|
761
|
-
"--dsw-font-markdown-
|
|
762
|
-
"--dsw-font-markdown-
|
|
763
|
-
"--dsw-font-markdown-
|
|
764
|
-
"--dsw-font-markdown-
|
|
765
|
-
"--dsw-font-markdown-
|
|
766
|
-
"--dsw-font-markdown-
|
|
767
|
-
"--dsw-font-markdown-
|
|
768
|
-
"--dsw-font-markdown-
|
|
769
|
-
"--dsw-font-markdown-
|
|
770
|
-
"--dsw-font-markdown-
|
|
771
|
-
"--dsw-font-markdown-
|
|
772
|
-
|
|
773
|
-
|
|
1761
|
+
"--dsw-font-xl-24-line-height": "32px",
|
|
1762
|
+
// markdown (dialog) shorthands
|
|
1763
|
+
"--dsw-font-markdown-h1": "700 calc(21px + var(--dsh-content-font-delta)) / calc(30px + var(--dsh-content-font-delta)) var(--dsw-font-family)",
|
|
1764
|
+
"--dsw-font-markdown-h2": "700 calc(19px + var(--dsh-content-font-delta)) / calc(28px + var(--dsh-content-font-delta)) var(--dsw-font-family)",
|
|
1765
|
+
"--dsw-font-markdown-h3": "700 calc(18px + var(--dsh-content-font-delta)) / calc(26px + var(--dsh-content-font-delta)) var(--dsw-font-family)",
|
|
1766
|
+
"--dsw-font-markdown-h4": "600 var(--dsh-content-font-size,14px) / calc(24px + var(--dsh-content-font-delta)) var(--dsw-font-family)",
|
|
1767
|
+
"--dsw-font-markdown-base": "var(--dsh-content-font-size,14px) / calc(24px + var(--dsh-content-font-delta)) var(--dsw-font-family)",
|
|
1768
|
+
"--dsw-font-markdown-base-strong": "600 var(--dsh-content-font-size,14px) / calc(24px + var(--dsh-content-font-delta)) var(--dsw-font-family)",
|
|
1769
|
+
"--dsw-font-markdown-base-italic": "italic var(--dsh-content-font-size,14px) / calc(24px + var(--dsh-content-font-delta)) var(--dsw-font-family)",
|
|
1770
|
+
"--dsw-font-markdown-base-strong-italic": "italic 600 var(--dsh-content-font-size,14px) / calc(24px + var(--dsh-content-font-delta)) var(--dsw-font-family)",
|
|
1771
|
+
"--dsw-font-markdown-table": "var(--dsh-content-font-size-secondary,13px)/calc(22px + var(--dsh-content-font-delta-secondary,0px)) var(--dsw-font-family)",
|
|
1772
|
+
"--dsw-font-markdown-table-head": "500 var(--dsh-content-font-size-secondary,13px)/calc(22px + var(--dsh-content-font-delta-secondary,0px)) var(--dsw-font-family)",
|
|
1773
|
+
"--dsw-font-markdown-small": "12px/20px var(--dsw-font-family)",
|
|
1774
|
+
"--dsw-font-markdown-small-strong": "600 12px/20px var(--dsw-font-family)",
|
|
1775
|
+
"--dsw-font-markdown-small-italic": "italic 12px/20px var(--dsw-font-family)",
|
|
1776
|
+
"--dsw-font-markdown-small-strong-italic": "italic 600 12px/20px var(--dsw-font-family)",
|
|
1777
|
+
// markdown small-series literals (the additive dialog offset rewrites these)
|
|
1778
|
+
"--dsw-font-markdown-small-font-size": "12px",
|
|
1779
|
+
"--dsw-font-markdown-small-line-height": "20px",
|
|
1780
|
+
"--dsw-font-markdown-small-strong-font-size": "12px",
|
|
1781
|
+
"--dsw-font-markdown-small-strong-line-height": "20px",
|
|
1782
|
+
"--dsw-font-markdown-small-italic-font-size": "12px",
|
|
1783
|
+
"--dsw-font-markdown-small-italic-line-height": "20px",
|
|
1784
|
+
"--dsw-font-markdown-small-strong-italic-font-size": "12px",
|
|
1785
|
+
"--dsw-font-markdown-small-strong-italic-line-height": "20px",
|
|
1786
|
+
// markdown part tokens (the dialog line-height ratio scales their heights;
|
|
1787
|
+
// their var-derived sizes ride the delta tokens, never the ratio)
|
|
1788
|
+
"--dsw-font-markdown-base-font-size": "var(--dsh-content-font-size,14px)",
|
|
1789
|
+
"--dsw-font-markdown-base-line-height": "calc(24px + var(--dsh-content-font-delta))",
|
|
1790
|
+
"--dsw-font-markdown-base-strong-font-size": "var(--dsh-content-font-size,14px)",
|
|
1791
|
+
"--dsw-font-markdown-base-strong-line-height": "calc(24px + var(--dsh-content-font-delta))",
|
|
1792
|
+
"--dsw-font-markdown-base-italic-font-size": "var(--dsh-content-font-size,14px)",
|
|
1793
|
+
"--dsw-font-markdown-base-italic-line-height": "calc(24px + var(--dsh-content-font-delta))",
|
|
1794
|
+
"--dsw-font-markdown-base-strong-italic-font-size": "var(--dsh-content-font-size,14px)",
|
|
1795
|
+
"--dsw-font-markdown-base-strong-italic-line-height": "calc(24px + var(--dsh-content-font-delta))",
|
|
1796
|
+
"--dsw-font-markdown-h1-font-size": "calc(21px + var(--dsh-content-font-delta))",
|
|
1797
|
+
"--dsw-font-markdown-h1-line-height": "calc(30px + var(--dsh-content-font-delta))",
|
|
1798
|
+
"--dsw-font-markdown-h2-font-size": "calc(19px + var(--dsh-content-font-delta))",
|
|
1799
|
+
"--dsw-font-markdown-h2-line-height": "calc(28px + var(--dsh-content-font-delta))",
|
|
1800
|
+
"--dsw-font-markdown-h3-font-size": "calc(18px + var(--dsh-content-font-delta))",
|
|
1801
|
+
"--dsw-font-markdown-h3-line-height": "calc(26px + var(--dsh-content-font-delta))",
|
|
1802
|
+
"--dsw-font-markdown-h4-font-size": "var(--dsh-content-font-size,14px)",
|
|
1803
|
+
"--dsw-font-markdown-h4-line-height": "calc(24px + var(--dsh-content-font-delta))",
|
|
1804
|
+
"--dsw-font-markdown-table-font-size": "var(--dsh-content-font-size-secondary,13px)",
|
|
1805
|
+
"--dsw-font-markdown-table-line-height": "calc(22px + var(--dsh-content-font-delta-secondary,0px))",
|
|
1806
|
+
"--dsw-font-markdown-table-head-font-size": "var(--dsh-content-font-size-secondary,13px)",
|
|
1807
|
+
"--dsw-font-markdown-table-head-line-height": "calc(22px + var(--dsh-content-font-delta-secondary,0px))",
|
|
1808
|
+
// markdown delta tokens (the dialog offset shifts these additively)
|
|
1809
|
+
"--dsh-content-font-delta": "calc(var(--dsh-content-font-size,14px) - 14px)",
|
|
1810
|
+
"--dsh-content-font-delta-secondary": "calc(var(--dsh-content-font-size-secondary) - 13px)",
|
|
1811
|
+
// the code family: shorthand + parts, at rc.2's values
|
|
774
1812
|
"--dsw-font-markdown-code": "12px/19px var(--ds-font-family-code)",
|
|
775
1813
|
"--dsw-font-markdown-code-font-size": "12px",
|
|
776
1814
|
"--dsw-font-markdown-code-line-height": "19px",
|
|
777
1815
|
"--dsw-font-markdown-code-block": "11px/19px var(--ds-font-family-code)",
|
|
778
|
-
"--dsw-font-markdown-code-block": "11px/19px var(--ds-font-family-code)",
|
|
779
1816
|
"--dsw-font-markdown-code-block-font-size": "11px",
|
|
780
1817
|
"--dsw-font-markdown-code-block-line-height": "19px",
|
|
781
1818
|
"--dsw-font-markdown-code-block-small": "11px/16px var(--ds-font-family-code)",
|
|
782
1819
|
"--dsw-font-markdown-code-block-small-font-size": "11px",
|
|
783
1820
|
"--dsw-font-markdown-code-block-small-line-height": "16px",
|
|
784
|
-
|
|
785
|
-
"--dsw-font-markdown-table-line-height": "22px",
|
|
1821
|
+
// content size (the interface chain's anchor, written inline by the theme)
|
|
786
1822
|
"--dsh-content-font-size": "14px",
|
|
787
1823
|
"--dsh-content-font-size-secondary": "13px",
|
|
788
1824
|
};
|
|
789
1825
|
|
|
790
|
-
|
|
791
|
-
*
|
|
792
|
-
*
|
|
793
|
-
* @param {number} digits - decimals to keep.
|
|
794
|
-
* @returns {number} the rounded number.
|
|
795
|
-
*/
|
|
796
|
-
function round(value, digits) {
|
|
797
|
-
var factor = Math.pow(10, digits);
|
|
798
|
-
return Math.round(value * factor) / factor;
|
|
799
|
-
}
|
|
1826
|
+
/* ------------------------------------------------------------------ *
|
|
1827
|
+
* curated family lists
|
|
1828
|
+
* ------------------------------------------------------------------ */
|
|
800
1829
|
|
|
801
1830
|
/**
|
|
802
1831
|
* Curated families offered when the browser cannot enumerate local fonts —
|
|
@@ -862,44 +1891,111 @@ var PRESETS = {
|
|
|
862
1891
|
generic: ["system-ui", "sans-serif", "serif"],
|
|
863
1892
|
};
|
|
864
1893
|
|
|
1894
|
+
/**
|
|
1895
|
+
* Legacy single-purpose helper kept for compatibility: scale the size and the
|
|
1896
|
+
* height inside one simple `size/height <family>` shorthand.
|
|
1897
|
+
* @param {string} value - a value shaped like `11px/19px var(--ds-font-family-code)`.
|
|
1898
|
+
* @param {number} scale - the ratio to apply.
|
|
1899
|
+
* @returns {string|null} the scaled shorthand, or null when the shape differs.
|
|
1900
|
+
*/
|
|
1901
|
+
function scaleFontShorthand(value, scale) {
|
|
1902
|
+
var parts = parseShorthand(value);
|
|
1903
|
+
if (parts === null || parts.lead !== "") return null;
|
|
1904
|
+
return (
|
|
1905
|
+
"calc((" + parts.size + ") * " + scale + ")/calc((" + parts.height + ") * " + scale + ") " + parts.family
|
|
1906
|
+
);
|
|
1907
|
+
}
|
|
1908
|
+
|
|
865
1909
|
var shared = {
|
|
866
1910
|
NAMESPACE: NAMESPACE,
|
|
867
1911
|
SANS_FIELD: SANS_FIELD,
|
|
1912
|
+
STACK_DIALOG_FIELD: STACK_DIALOG_FIELD,
|
|
868
1913
|
MONO_FIELD: MONO_FIELD,
|
|
869
1914
|
SIZE_FIELD: SIZE_FIELD,
|
|
1915
|
+
SIZE_DIALOG_FIELD: SIZE_DIALOG_FIELD,
|
|
870
1916
|
CODE_SIZE_FIELD: CODE_SIZE_FIELD,
|
|
871
1917
|
WEIGHT_FIELD: WEIGHT_FIELD,
|
|
1918
|
+
WEIGHT_DIALOG_FIELD: WEIGHT_DIALOG_FIELD,
|
|
872
1919
|
CODE_WEIGHT_FIELD: CODE_WEIGHT_FIELD,
|
|
1920
|
+
LINE_HEIGHT_FIELD: LINE_HEIGHT_FIELD,
|
|
1921
|
+
LINE_HEIGHT_DIALOG_FIELD: LINE_HEIGHT_DIALOG_FIELD,
|
|
1922
|
+
CODE_LINE_HEIGHT_FIELD: CODE_LINE_HEIGHT_FIELD,
|
|
1923
|
+
LIGATURES_FIELD: LIGATURES_FIELD,
|
|
1924
|
+
FEATURES_FIELD: FEATURES_FIELD,
|
|
1925
|
+
NO_SYNTHETIC_ITALIC_FIELD: NO_SYNTHETIC_ITALIC_FIELD,
|
|
1926
|
+
NO_SYNTHETIC_BOLD_FIELD: NO_SYNTHETIC_BOLD_FIELD,
|
|
1927
|
+
PER_THEME_FIELD: PER_THEME_FIELD,
|
|
1928
|
+
DARK_VALUES_FIELD: DARK_VALUES_FIELD,
|
|
1929
|
+
PRESETS_FIELD: PRESETS_FIELD,
|
|
1930
|
+
ACTIVE_PRESET_FIELD: ACTIVE_PRESET_FIELD,
|
|
1931
|
+
UI_FOLLOWS_FIELD: UI_FOLLOWS_FIELD,
|
|
1932
|
+
VALUE_FIELDS: VALUE_FIELDS,
|
|
873
1933
|
SIZE_MIN: SIZE_MIN,
|
|
874
1934
|
SIZE_MAX: SIZE_MAX,
|
|
875
1935
|
WEIGHT_MIN: WEIGHT_MIN,
|
|
876
1936
|
WEIGHT_MAX: WEIGHT_MAX,
|
|
877
1937
|
WEIGHT_UNSET: WEIGHT_UNSET,
|
|
1938
|
+
LINE_HEIGHT_MIN: LINE_HEIGHT_MIN,
|
|
1939
|
+
LINE_HEIGHT_MAX: LINE_HEIGHT_MAX,
|
|
1940
|
+
CODE_LINE_HEIGHT_MIN: CODE_LINE_HEIGHT_MIN,
|
|
1941
|
+
CODE_LINE_HEIGHT_MAX: CODE_LINE_HEIGHT_MAX,
|
|
1942
|
+
LIGATURES_DEFAULT: LIGATURES_DEFAULT,
|
|
1943
|
+
LIGATURES_ON: LIGATURES_ON,
|
|
1944
|
+
LIGATURES_OFF: LIGATURES_OFF,
|
|
878
1945
|
MARKER: MARKER,
|
|
879
1946
|
STYLE_TAG: STYLE_TAG,
|
|
880
1947
|
CARD_STYLE_TAG: CARD_STYLE_TAG,
|
|
881
1948
|
DEFAULTS: DEFAULTS,
|
|
882
1949
|
MAX_STACK_LENGTH: MAX_STACK_LENGTH,
|
|
883
1950
|
MAX_FAMILY_LENGTH: MAX_FAMILY_LENGTH,
|
|
1951
|
+
MAX_FEATURES_LENGTH: MAX_FEATURES_LENGTH,
|
|
1952
|
+
MAX_PRESETS: MAX_PRESETS,
|
|
1953
|
+
MAX_PRESET_NAME: MAX_PRESET_NAME,
|
|
884
1954
|
FALLBACK_TOKENS: FALLBACK_TOKENS,
|
|
885
1955
|
PRESETS: PRESETS,
|
|
1956
|
+
MARKDOWN_SELECTOR: MARKDOWN_SELECTOR,
|
|
1957
|
+
DARK_ATTR: DARK_ATTR,
|
|
886
1958
|
sanitize: sanitize,
|
|
887
1959
|
sanitizeFamily: sanitizeFamily,
|
|
1960
|
+
sanitizeFeatures: sanitizeFeatures,
|
|
888
1961
|
quoteFamily: quoteFamily,
|
|
889
1962
|
normalizeConfig: normalizeConfig,
|
|
1963
|
+
normalizeDarkValues: normalizeDarkValues,
|
|
1964
|
+
normalizePresets: normalizePresets,
|
|
1965
|
+
normalizeValueSet: normalizeValueSet,
|
|
890
1966
|
clampOffset: clampOffset,
|
|
891
1967
|
clampWeight: clampWeight,
|
|
1968
|
+
clampRatio: clampRatio,
|
|
1969
|
+
clampLineOffset: clampLineOffset,
|
|
1970
|
+
clampLigatures: clampLigatures,
|
|
892
1971
|
CODE_SELECTOR: CODE_SELECTOR,
|
|
893
1972
|
formatStack: formatStack,
|
|
894
1973
|
parseStack: parseStack,
|
|
1974
|
+
parseShorthand: parseShorthand,
|
|
1975
|
+
rebuildShorthand: rebuildShorthand,
|
|
895
1976
|
scaleFor: scaleFor,
|
|
896
1977
|
isDormant: isDormant,
|
|
1978
|
+
buildAxisCss: buildAxisCss,
|
|
897
1979
|
buildFontCss: buildFontCss,
|
|
1980
|
+
resolveAxes: resolveAxes,
|
|
1981
|
+
expandFollow: expandFollow,
|
|
898
1982
|
isFontToken: isFontToken,
|
|
899
1983
|
isCodeToken: isCodeToken,
|
|
1984
|
+
isMarkdownToken: isMarkdownToken,
|
|
1985
|
+
isDeltaToken: isDeltaToken,
|
|
1986
|
+
isShorthandName: isShorthandName,
|
|
1987
|
+
isShorthandToken: isShorthandToken,
|
|
900
1988
|
isScaledToken: isScaledToken,
|
|
1989
|
+
tokenNames: tokenNames,
|
|
901
1990
|
codeTokenNames: codeTokenNames,
|
|
1991
|
+
markdownTokenNames: markdownTokenNames,
|
|
902
1992
|
bodyTokenNames: bodyTokenNames,
|
|
1993
|
+
uiLineTokenNames: uiLineTokenNames,
|
|
1994
|
+
deltaTokenNames: deltaTokenNames,
|
|
1995
|
+
shorthandTokens: shorthandTokens,
|
|
1996
|
+
markdownShorthandTokens: markdownShorthandTokens,
|
|
1997
|
+
uiShorthandTokens: uiShorthandTokens,
|
|
1998
|
+
codeShorthandTokens: codeShorthandTokens,
|
|
903
1999
|
scaleFontShorthand: scaleFontShorthand,
|
|
904
2000
|
isGenericFamilyName: isGenericFamilyName,
|
|
905
2001
|
isCJKFamilyName: isCJKFamilyName,
|