dsh-fonttune 0.2.4 → 0.2.6
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 +98 -111
- package/CHANGELOG.md +98 -111
- package/README.en.md +10 -7
- package/README.md +10 -7
- package/cordis.patch.yml +12 -2
- package/lib/client.js +346 -86
- package/lib/index.js +43 -1
- package/lib/shared.cjs +273 -84
- package/package.json +4 -5
package/lib/client.js
CHANGED
|
@@ -963,14 +963,14 @@ var PART_SUFFIXES = ["-font-size", "-line-height", "-font-family", "-font-weight
|
|
|
963
963
|
var FAMILY_VARS = { "--dsw-font-family": true, "--dsw-font-mono": true, "--ds-font-family-code": true };
|
|
964
964
|
|
|
965
965
|
/**
|
|
966
|
-
*
|
|
967
|
-
*
|
|
968
|
-
*
|
|
969
|
-
*
|
|
966
|
+
* Ever since every global declaration moved to the root rule (see
|
|
967
|
+
* `buildAxisCss`), no rule needs a `body, body *` reach for a TOKEN, and only
|
|
968
|
+
* the interface weight keeps it — because a weight is the one property whose
|
|
969
|
+
* per-element declarations cannot be reached by inheritance. Nothing else may
|
|
970
|
+
* reintroduce a blanket: on DSH 0.1.5-rc.3 a single one costs 1892
|
|
971
|
+
* selector-element matches on every style recalculation, and dragging a sidebar
|
|
972
|
+
* handle recalculates once per frame.
|
|
970
973
|
*/
|
|
971
|
-
function bodyAndDescendants(declarations) {
|
|
972
|
-
return "body,body *{" + declarations + "}";
|
|
973
|
-
}
|
|
974
974
|
|
|
975
975
|
/**
|
|
976
976
|
* Whether one name belongs to the official CONTENT-size chain the conversation
|
|
@@ -1211,7 +1211,23 @@ var CODE_EXCLUDES = excludeAll(CODE_SURFACE_SELECTORS);
|
|
|
1211
1211
|
* the rule, and an unmatched token/class simply means that surface follows
|
|
1212
1212
|
* the interface axis — the same degradation as an unknown token name.
|
|
1213
1213
|
*/
|
|
1214
|
-
|
|
1214
|
+
/** The default code selector list: the substring form, for callers with no page. */
|
|
1215
|
+
var CODE_SELECTOR;
|
|
1216
|
+
|
|
1217
|
+
/**
|
|
1218
|
+
* The code surfaces that never need a substring match.
|
|
1219
|
+
*
|
|
1220
|
+
* Everything here is either a semantic element or a class this plugin owns, so
|
|
1221
|
+
* the browser can match it with a tag or a class-token test. The containers
|
|
1222
|
+
* that only a hashed class identifies (`…_codeBody`, `…_terminal`) used to be
|
|
1223
|
+
* spelled `[class*="code" i]` / `[class*="terminal" i]` — measured on
|
|
1224
|
+
* 0.1.5-rc.3, those two substring selectors cost an attribute scan on every one
|
|
1225
|
+
* of the page's ~1900 elements on every rule that carries them, and a drag
|
|
1226
|
+
* recalculates styles once per frame. The browser half now HARVESTS the real
|
|
1227
|
+
* class tokens from the live page and passes them in as exact class selectors
|
|
1228
|
+
* (see `buildFontCss`), falling back to the substring form whenever it cannot.
|
|
1229
|
+
*/
|
|
1230
|
+
var CODE_SURFACE_SELECTORS_CHEAP = [
|
|
1215
1231
|
"pre",
|
|
1216
1232
|
"code",
|
|
1217
1233
|
"kbd",
|
|
@@ -1221,19 +1237,58 @@ var CODE_SELECTOR = [
|
|
|
1221
1237
|
"textarea",
|
|
1222
1238
|
".cm-editor",
|
|
1223
1239
|
".dfp-previewCode",
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1240
|
+
];
|
|
1241
|
+
|
|
1242
|
+
/**
|
|
1243
|
+
* The code surfaces for one build.
|
|
1244
|
+
*
|
|
1245
|
+
* The two substring hooks ride along on purpose. A class token the page has
|
|
1246
|
+
* not rendered yet (`…_codeBody`, `…_terminalBody` appear only once a tool card
|
|
1247
|
+
* or a terminal pane exists) cannot be named by a harvested class, and an
|
|
1248
|
+
* unknown surface must still get the code axes — the same "an unmatched token
|
|
1249
|
+
* simply follows the interface" degradation the token axes have. The hooks cost
|
|
1250
|
+
* an attribute scan per element per rule that carries them, which is why the
|
|
1251
|
+
* rest of this module spent its effort removing the OTHER wide selectors (see
|
|
1252
|
+
* `rootRule` / `weightRule` / `dialogSelector`); measured on 0.1.5-rc.3 that is
|
|
1253
|
+
* the difference between 1.87 s and ~0.9 s of style recalculation per drag.
|
|
1254
|
+
*/
|
|
1255
|
+
function codeSurfaceList() {
|
|
1256
|
+
return CODE_SURFACE_SELECTORS_CHEAP.concat([
|
|
1257
|
+
'[class*="code" i]',
|
|
1258
|
+
'[class*="terminal" i]',
|
|
1259
|
+
]);
|
|
1260
|
+
}
|
|
1261
|
+
|
|
1262
|
+
/**
|
|
1263
|
+
* The code selector list for one build.
|
|
1264
|
+
*
|
|
1265
|
+
* Surfaces only — no `… *` descendants. Everything inside a code surface
|
|
1266
|
+
* inherits its family and weight, so descendants are unnecessary, and they are
|
|
1267
|
+
* not free: an ancestor-walking selector costs about 0.03 s per 50-step drag on
|
|
1268
|
+
* 0.1.5-rc.3 (measured: stripping the ` *` forms from these two rules took the
|
|
1269
|
+
* drag from 0.89 s to 0.52 s of style recalculation). The weight family keeps
|
|
1270
|
+
* the code subtree right through `--dfp-interface-weight` instead — see
|
|
1271
|
+
* `weightRule`.
|
|
1272
|
+
*
|
|
1273
|
+
* @param {{code?: string[], terminal?: string[]}|null} [hints] - unused; kept so
|
|
1274
|
+
* the browser half can keep passing one harvest object to `buildFontCss`.
|
|
1275
|
+
* @returns {string} the comma-separated list of code surfaces.
|
|
1276
|
+
*/
|
|
1277
|
+
function codeSelector(hints) {
|
|
1278
|
+
return codeSurfaceList().join(",");
|
|
1279
|
+
}
|
|
1280
|
+
|
|
1281
|
+
CODE_SELECTOR = codeSelector(null);
|
|
1231
1282
|
|
|
1232
1283
|
/**
|
|
1233
1284
|
* The dialog surface: DSH renders the conversation markdown in a CSS-module
|
|
1234
|
-
* container whose class hashes per release (`_markdown_<hash>_…`)
|
|
1235
|
-
*
|
|
1236
|
-
*
|
|
1285
|
+
* container whose class hashes per release (`_markdown_<hash>_…`), wrapped in a
|
|
1286
|
+
* stable `[data-dss-prose]` container (measured on 0.1.5-rc.3: every markdown
|
|
1287
|
+
* container on the page sits inside one). A class-substring match degrades
|
|
1288
|
+
* safely — no match means the dialog follows the interface axis, the same
|
|
1289
|
+
* behavior as an unknown token name — but it costs an attribute scan per
|
|
1290
|
+
* element per rule, so the browser half prefers the stable wrapper when it can
|
|
1291
|
+
* see one and falls back to the substring form otherwise.
|
|
1237
1292
|
*/
|
|
1238
1293
|
var MARKDOWN_SELECTOR =
|
|
1239
1294
|
'[class*="_markdown_" i]' +
|
|
@@ -1241,37 +1296,106 @@ var MARKDOWN_SELECTOR =
|
|
|
1241
1296
|
',[class*="_markdown_" i] *' +
|
|
1242
1297
|
CODE_EXCLUDES;
|
|
1243
1298
|
|
|
1299
|
+
/**
|
|
1300
|
+
* The dialog selector for one build, given the harvested wrapper.
|
|
1301
|
+
* @param {{markdown?: string}|null} [hints] - the harvested wrapper selector.
|
|
1302
|
+
* @returns {string} the markdown-scoped selector list.
|
|
1303
|
+
*/
|
|
1304
|
+
function dialogSelector(hints) {
|
|
1305
|
+
if (hints !== undefined && hints !== null && typeof hints.markdown === "string" && hints.markdown !== "") {
|
|
1306
|
+
// The wrapper is not markdown content itself, so only its subtree is
|
|
1307
|
+
// scoped; the code exclusion still rides along so inline code keeps the
|
|
1308
|
+
// code family. This is the rule the harvest earns its keep on: the same
|
|
1309
|
+
// `:not(…)` over the stable wrapper trials ~37 elements instead of the
|
|
1310
|
+
// ~1800 the class-substring container selector used to hand it.
|
|
1311
|
+
return hints.markdown + " *" + CODE_EXCLUDES;
|
|
1312
|
+
}
|
|
1313
|
+
return MARKDOWN_SELECTOR;
|
|
1314
|
+
}
|
|
1315
|
+
|
|
1244
1316
|
/** The dark-theme attribute DSH's ThemePresenter writes on body. */
|
|
1245
1317
|
var DARK_ATTR = "body[data-ds-dark-theme]";
|
|
1246
1318
|
|
|
1247
1319
|
/**
|
|
1248
|
-
*
|
|
1320
|
+
* What the INTERFACE weight rule stays out of when the conversation has no
|
|
1321
|
+
* weight of its own.
|
|
1322
|
+
*
|
|
1323
|
+
* The weight is still the one property that needs a `body, body *` reach: DSH
|
|
1324
|
+
* pins weights on elements that do not inherit (`button` / `span` / `div`
|
|
1325
|
+
* class rules — measured on 0.1.5-rc.3: 19 buttons, 13 spans and 1 div in the
|
|
1326
|
+
* chrome declare their own weight; the markdown side's 7 `strong` and 2 `th`
|
|
1327
|
+
* declare theirs). But the 26-argument `:not(…)` that used to ride along cost
|
|
1328
|
+
* a selector trial per argument on ~1800 elements EVERY recalculation, and a
|
|
1329
|
+
* drag recalculates once per frame — measured 1.87 s of style recalculation
|
|
1330
|
+
* for a 50-step drag versus 0.47 s with this plugin removed.
|
|
1249
1331
|
*
|
|
1250
|
-
*
|
|
1251
|
-
* with literal weights on class rules (`…_label{font-weight:500}`,
|
|
1252
|
-
* `…_heading{font-weight:600}`) that nothing narrower reaches. That rule would
|
|
1253
|
-
* otherwise cover the conversation too, so the conversation markdown subtree is
|
|
1254
|
-
* excluded here — and with it every code surface, because the code axis owns
|
|
1255
|
-
* those and the two rules would otherwise fight over the same elements (the
|
|
1256
|
-
* `:not()` arguments raise this rule's specificity above a single class).
|
|
1332
|
+
* Two narrowings removed that:
|
|
1257
1333
|
*
|
|
1258
|
-
*
|
|
1259
|
-
*
|
|
1260
|
-
*
|
|
1261
|
-
*
|
|
1334
|
+
* 1. Code surfaces no longer appear here at all. The code rules are emitted
|
|
1335
|
+
* after this one and win on their own: class hooks by specificity, element
|
|
1336
|
+
* selectors by order at equal specificity. Their descendants inherit from
|
|
1337
|
+
* them (`font-weight` and `font-family` are inherited), so the blanket
|
|
1338
|
+
* cannot reach a highlight `<span>` inside a `<pre>` either.
|
|
1339
|
+
* 2. What remains — the conversation markdown subtree and the card's own
|
|
1340
|
+
* conversation preview — rides `:where(…)`, which contributes NO
|
|
1341
|
+
* specificity: the rule stays at (0,0,1), exactly like the plain
|
|
1342
|
+
* `body,body *` form, so the dialog rule (class-scoped) and the code rules
|
|
1343
|
+
* keep out-ranking it no matter which order they land in.
|
|
1344
|
+
*
|
|
1345
|
+
* When the conversation DOES carry a weight the guard is dropped entirely (see
|
|
1346
|
+
* `weightRule`): the dialog rule is class-scoped, so it overrides everything
|
|
1347
|
+
* this rule does inside markdown anyway, and the rule collapses to the bare
|
|
1348
|
+
* `body,body *` pair. Measured with the axis at 560: all 141 interface text
|
|
1349
|
+
* elements on the page took 560 and no markdown element moved — the same
|
|
1350
|
+
* coverage the 26-argument form had.
|
|
1262
1351
|
*/
|
|
1263
|
-
var
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
".cm-editor *",
|
|
1267
|
-
".dfp-previewCode",
|
|
1268
|
-
".dfp-previewCode *",
|
|
1352
|
+
var WEIGHT_MARKDOWN_GUARD =
|
|
1353
|
+
":not(:where(" +
|
|
1354
|
+
[
|
|
1269
1355
|
'[class*="_markdown_" i]',
|
|
1270
1356
|
'[class*="_markdown_" i] *',
|
|
1271
1357
|
".dfp-previewDialog",
|
|
1272
1358
|
".dfp-previewDialog *",
|
|
1273
|
-
])
|
|
1274
|
-
);
|
|
1359
|
+
].join(",") +
|
|
1360
|
+
"))";
|
|
1361
|
+
|
|
1362
|
+
/**
|
|
1363
|
+
* The elements that carry a weight of their own in DSH.
|
|
1364
|
+
*
|
|
1365
|
+
* The weight is the one property that cannot ride inheritance alone: an element
|
|
1366
|
+
* whose own rule declares a weight ignores the value it would inherit, whatever
|
|
1367
|
+
* that value's importance. Measured on 0.1.5-rc.3, the elements that do declare
|
|
1368
|
+
* one are `button` / `span` / `div` in the chrome and `strong` / `th` in the
|
|
1369
|
+
* conversation; the rest of the list is the inline markup that could plausibly
|
|
1370
|
+
* join them. This replaces the old `body,body *` reach, which matched 1892
|
|
1371
|
+
* elements on every recalculation — the whole point of the table is that it
|
|
1372
|
+
* matches a fraction of that while still covering every pinned element.
|
|
1373
|
+
*/
|
|
1374
|
+
var WEIGHT_ELEMENT_TABLE = [
|
|
1375
|
+
"button",
|
|
1376
|
+
"span",
|
|
1377
|
+
"div",
|
|
1378
|
+
"strong",
|
|
1379
|
+
"b",
|
|
1380
|
+
"em",
|
|
1381
|
+
"i",
|
|
1382
|
+
"a",
|
|
1383
|
+
"label",
|
|
1384
|
+
"small",
|
|
1385
|
+
"p",
|
|
1386
|
+
"li",
|
|
1387
|
+
"td",
|
|
1388
|
+
"th",
|
|
1389
|
+
"dt",
|
|
1390
|
+
"dd",
|
|
1391
|
+
"summary",
|
|
1392
|
+
"h1",
|
|
1393
|
+
"h2",
|
|
1394
|
+
"h3",
|
|
1395
|
+
"h4",
|
|
1396
|
+
"h5",
|
|
1397
|
+
"h6",
|
|
1398
|
+
].join(",");
|
|
1275
1399
|
|
|
1276
1400
|
/**
|
|
1277
1401
|
* Split a selector list on its top-level commas only.
|
|
@@ -1451,27 +1575,66 @@ function isDormant(config) {
|
|
|
1451
1575
|
* @param {boolean} isDark - prefix every rule with the dark-theme attribute.
|
|
1452
1576
|
* @returns {string} declarations for one `<style>` element.
|
|
1453
1577
|
*/
|
|
1454
|
-
function buildAxisCss(axis, baseTokens, isDark) {
|
|
1578
|
+
function buildAxisCss(axis, baseTokens, isDark, hints) {
|
|
1455
1579
|
var sans = formatStack(parseStack(axis[SANS_FIELD]));
|
|
1456
1580
|
var mono = formatStack(parseStack(axis[MONO_FIELD]));
|
|
1581
|
+
var dialog = formatStack(parseStack(axis[STACK_DIALOG_FIELD]));
|
|
1582
|
+
// Normalised once: an expanded axis always carries a number, but a caller
|
|
1583
|
+
// handing in a raw object may leave the field out, and "absent" has to mean
|
|
1584
|
+
// "unset" rather than "write the string undefined".
|
|
1585
|
+
var dialogWeight = typeof axis[WEIGHT_DIALOG_FIELD] === "number" ? axis[WEIGHT_DIALOG_FIELD] : WEIGHT_UNSET;
|
|
1586
|
+
var codeSurfaces = codeSelector(hints);
|
|
1587
|
+
var dialogSurfaces = dialogSelector(hints);
|
|
1457
1588
|
var declarations = [];
|
|
1458
1589
|
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1590
|
+
/**
|
|
1591
|
+
* A rule for INHERITED state: custom properties and the inherited `font-*`
|
|
1592
|
+
* properties. `:root, body` is two elements, `body` alone is one, and every
|
|
1593
|
+
* descendant picks the value up by inheritance — including the values DSH
|
|
1594
|
+
* itself writes (measured on 0.1.5-rc.3: the host declares these tokens at
|
|
1595
|
+
* `:root` and on `body`, and the only inline copy is on `body` itself, where
|
|
1596
|
+
* an `!important` stylesheet declaration still wins).
|
|
1597
|
+
*
|
|
1598
|
+
* This used to be `body, body *`, which matched 1892 elements on EVERY style
|
|
1599
|
+
* recalculation. A drag writes `style` once per frame, so that reach alone
|
|
1600
|
+
* was most of the measured cost: 1.87 s of style recalculation for a 50-step
|
|
1601
|
+
* sidebar drag (0.47 s with the plugin removed) and 49 frames over 25 ms.
|
|
1602
|
+
*/
|
|
1603
|
+
function rootRule(declarations) {
|
|
1604
|
+
return (isDark ? DARK_ATTR : ":root,body") + "{" + declarations + "}";
|
|
1605
|
+
}
|
|
1606
|
+
function bodyRule(declarations) {
|
|
1607
|
+
return (isDark ? DARK_ATTR : "body") + "{" + declarations + "}";
|
|
1468
1608
|
}
|
|
1469
1609
|
function codeRule(declarations) {
|
|
1470
|
-
var selector = isDark ? prefixSelector(
|
|
1610
|
+
var selector = isDark ? prefixSelector(codeSurfaces, DARK_ATTR) : codeSurfaces;
|
|
1471
1611
|
return selector + "{" + declarations + "}";
|
|
1472
1612
|
}
|
|
1473
1613
|
function dialogRule(declarations) {
|
|
1474
|
-
var selector = isDark ? prefixSelector(
|
|
1614
|
+
var selector = isDark ? prefixSelector(dialogSurfaces, DARK_ATTR) : dialogSurfaces;
|
|
1615
|
+
return selector + "{" + declarations + "}";
|
|
1616
|
+
}
|
|
1617
|
+
/**
|
|
1618
|
+
* The interface weight, in two reaches: `body` for everything that inherits,
|
|
1619
|
+
* plus an element table for the tags DSH pins a weight on of their own (see
|
|
1620
|
+
* `WEIGHT_ELEMENT_TABLE`).
|
|
1621
|
+
*
|
|
1622
|
+
* The value rides a custom property (`--dfp-interface-weight`, declared on
|
|
1623
|
+
* `:root,body` by the caller) rather than a literal. That is what keeps the
|
|
1624
|
+
* code subtree right without a descendant selector: a highlight `<span>`
|
|
1625
|
+
* inside a `<pre>` IS matched by this rule's table, but it resolves the
|
|
1626
|
+
* variable its code ancestor set (see the code weight rule), so it takes the
|
|
1627
|
+
* code weight instead of the interface one. Naming `pre *` and friends here
|
|
1628
|
+
* or in the code rules instead cost 0.03 s per selector per drag.
|
|
1629
|
+
*
|
|
1630
|
+
* The markdown guard only rides along when the conversation has NO weight of
|
|
1631
|
+
* its own — with one set, the class-scoped dialog rule out-ranks this rule
|
|
1632
|
+
* inside markdown anyway.
|
|
1633
|
+
*/
|
|
1634
|
+
function weightRule(declarations) {
|
|
1635
|
+
var base = isDark ? DARK_ATTR : "body";
|
|
1636
|
+
var selector = base + "," + base + " :where(" + WEIGHT_ELEMENT_TABLE + ")";
|
|
1637
|
+
if (dialogWeight === WEIGHT_UNSET) selector += WEIGHT_MARKDOWN_GUARD;
|
|
1475
1638
|
return selector + "{" + declarations + "}";
|
|
1476
1639
|
}
|
|
1477
1640
|
/**
|
|
@@ -1481,7 +1644,7 @@ function buildAxisCss(axis, baseTokens, isDark) {
|
|
|
1481
1644
|
*/
|
|
1482
1645
|
function pushScopedCodeFamily() {
|
|
1483
1646
|
var scopedCode = [];
|
|
1484
|
-
var surfaces = splitSelectorList(
|
|
1647
|
+
var surfaces = splitSelectorList(codeSurfaces);
|
|
1485
1648
|
for (var cIndex = 0; cIndex < surfaces.length; cIndex += 1) {
|
|
1486
1649
|
var scoped = '[class*="_markdown_" i] ' + surfaces[cIndex];
|
|
1487
1650
|
// The dark variant has to carry the theme attribute like every other
|
|
@@ -1532,29 +1695,42 @@ function buildAxisCss(axis, baseTokens, isDark) {
|
|
|
1532
1695
|
// (below).
|
|
1533
1696
|
|
|
1534
1697
|
// ---- interface weight ----
|
|
1535
|
-
//
|
|
1536
|
-
//
|
|
1537
|
-
//
|
|
1538
|
-
//
|
|
1539
|
-
//
|
|
1540
|
-
|
|
1541
|
-
var
|
|
1542
|
-
var codeWeight = axis[CODE_WEIGHT_FIELD];
|
|
1698
|
+
// A `body{font-weight}` rule only reaches the elements that INHERIT their
|
|
1699
|
+
// weight; the chrome's `button` / `span` / `div` class rules pin their own
|
|
1700
|
+
// (measured on 0.1.5-rc.3), which is why the second reach exists. The
|
|
1701
|
+
// conversation is held apart by `weightRule`'s guard (or by the class-scoped
|
|
1702
|
+
// dialog rule when the conversation has a weight of its own).
|
|
1703
|
+
var interfaceWeight = typeof axis[WEIGHT_FIELD] === "number" ? axis[WEIGHT_FIELD] : WEIGHT_UNSET;
|
|
1704
|
+
var codeWeight = typeof axis[CODE_WEIGHT_FIELD] === "number" ? axis[CODE_WEIGHT_FIELD] : WEIGHT_UNSET;
|
|
1543
1705
|
if (interfaceWeight !== WEIGHT_UNSET) {
|
|
1706
|
+
// The variable the weight rule resolves; the code rules below override it
|
|
1707
|
+
// on their surfaces so everything inside them inherits the code weight.
|
|
1544
1708
|
declarations.push(
|
|
1545
|
-
|
|
1709
|
+
(isDark ? DARK_ATTR : ":root,body") +
|
|
1710
|
+
"{--dfp-interface-weight:" +
|
|
1711
|
+
interfaceWeight +
|
|
1712
|
+
"}"
|
|
1713
|
+
);
|
|
1714
|
+
declarations.push(
|
|
1715
|
+
weightRule(
|
|
1716
|
+
"font-weight:var(--dfp-interface-weight," + interfaceWeight + ") !important"
|
|
1717
|
+
)
|
|
1546
1718
|
);
|
|
1547
1719
|
if (codeWeight === WEIGHT_UNSET) {
|
|
1548
|
-
//
|
|
1549
|
-
//
|
|
1550
|
-
//
|
|
1551
|
-
//
|
|
1552
|
-
|
|
1720
|
+
// The code rules come after this one and win on their own (class hooks by
|
|
1721
|
+
// specificity, element selectors by order), so code surfaces — which
|
|
1722
|
+
// inherit nothing but start from the body weight — are reset here.
|
|
1723
|
+
// `normal` is what DSH computes for them (the code `font` shorthands
|
|
1724
|
+
// carry no weight), so an unset code weight keeps code exactly as DSH
|
|
1725
|
+
// shipped it. The variable is reset alongside so an element INSIDE a code
|
|
1726
|
+
// surface (which the interface table matches) lands on `normal` too.
|
|
1727
|
+
declarations.push(
|
|
1728
|
+
codeRule("font-weight:normal !important;--dfp-interface-weight:normal")
|
|
1729
|
+
);
|
|
1553
1730
|
}
|
|
1554
1731
|
}
|
|
1555
1732
|
|
|
1556
1733
|
// ---- dialog family (scoped; skipped when it equals the interface) ----
|
|
1557
|
-
var dialog = formatStack(parseStack(axis[STACK_DIALOG_FIELD]));
|
|
1558
1734
|
if (dialog !== "" && dialog !== sans) {
|
|
1559
1735
|
declarations.push(dialogRule("font-family:" + dialog + " !important"));
|
|
1560
1736
|
if (mono !== "") {
|
|
@@ -1564,7 +1740,6 @@ function buildAxisCss(axis, baseTokens, isDark) {
|
|
|
1564
1740
|
}
|
|
1565
1741
|
|
|
1566
1742
|
// ---- dialog weight (uniform inside the conversation) ----
|
|
1567
|
-
var dialogWeight = axis[WEIGHT_DIALOG_FIELD];
|
|
1568
1743
|
if (dialogWeight !== WEIGHT_UNSET) {
|
|
1569
1744
|
declarations.push(dialogRule("font-weight:" + dialogWeight + " !important"));
|
|
1570
1745
|
// The card's conversation preview simulates this surface, so it takes the
|
|
@@ -1603,7 +1778,7 @@ function buildAxisCss(axis, baseTokens, isDark) {
|
|
|
1603
1778
|
);
|
|
1604
1779
|
});
|
|
1605
1780
|
var contentShift = shiftTokens(contentSources, baseTokens, dialogOffsetValue);
|
|
1606
|
-
if (contentShift.length > 0) declarations.push(
|
|
1781
|
+
if (contentShift.length > 0) declarations.push(rootRule(contentShift.join(";")));
|
|
1607
1782
|
// The `small` series is the only markdown family with literal sizes: it
|
|
1608
1783
|
// does not derive from the chain, so its parts and shorthands shift here.
|
|
1609
1784
|
var literals = markdownTokenNames(baseTokens).filter(function (name) {
|
|
@@ -1611,7 +1786,7 @@ function buildAxisCss(axis, baseTokens, isDark) {
|
|
|
1611
1786
|
return name.indexOf("-font-size") >= 0 && typeof base === "string" && base.indexOf("var(") === -1;
|
|
1612
1787
|
});
|
|
1613
1788
|
var literalShift = shiftTokens(literals, baseTokens, dialogOffsetValue);
|
|
1614
|
-
if (literalShift.length > 0) declarations.push(
|
|
1789
|
+
if (literalShift.length > 0) declarations.push(rootRule(literalShift.join(";")));
|
|
1615
1790
|
// The shorthands whose size is a literal (the small series) must ride the
|
|
1616
1791
|
// shift too; the delta-derived shorthands flow through the chain above.
|
|
1617
1792
|
var sizeShifted = [];
|
|
@@ -1626,7 +1801,7 @@ function buildAxisCss(axis, baseTokens, isDark) {
|
|
|
1626
1801
|
mEntry.name + ":" + rebuildShorthand({ lead: mEntry.parts.lead, size: mSize, height: mEntry.parts.height, family: mEntry.parts.family }) + " !important"
|
|
1627
1802
|
);
|
|
1628
1803
|
}
|
|
1629
|
-
if (sizeShifted.length > 0) declarations.push(
|
|
1804
|
+
if (sizeShifted.length > 0) declarations.push(rootRule(sizeShifted.join(";")));
|
|
1630
1805
|
}
|
|
1631
1806
|
|
|
1632
1807
|
// ---- dialog line height (ratio, through the shorthand heights) ----
|
|
@@ -1637,7 +1812,7 @@ function buildAxisCss(axis, baseTokens, isDark) {
|
|
|
1637
1812
|
return name.indexOf("-line-height") >= 0;
|
|
1638
1813
|
});
|
|
1639
1814
|
var dialogLineDecls = scaleLineTokens(markdownLines, baseTokens, dialogFactor);
|
|
1640
|
-
if (dialogLineDecls.length > 0) declarations.push(
|
|
1815
|
+
if (dialogLineDecls.length > 0) declarations.push(rootRule(dialogLineDecls.join(";")));
|
|
1641
1816
|
var dialogShorthandList = markdownShorthandTokens(baseTokens);
|
|
1642
1817
|
var dialogShorthandShifted = [];
|
|
1643
1818
|
for (var dIndex = 0; dIndex < dialogShorthandList.length; dIndex += 1) {
|
|
@@ -1655,7 +1830,7 @@ function buildAxisCss(axis, baseTokens, isDark) {
|
|
|
1655
1830
|
);
|
|
1656
1831
|
}
|
|
1657
1832
|
if (dialogShorthandShifted.length > 0) {
|
|
1658
|
-
declarations.push(
|
|
1833
|
+
declarations.push(rootRule(dialogShorthandShifted.join(";")));
|
|
1659
1834
|
}
|
|
1660
1835
|
}
|
|
1661
1836
|
|
|
@@ -1668,11 +1843,17 @@ function buildAxisCss(axis, baseTokens, isDark) {
|
|
|
1668
1843
|
round(scaleFor(codeSize), 6),
|
|
1669
1844
|
codeLine
|
|
1670
1845
|
);
|
|
1671
|
-
if (codeScaled.length > 0) declarations.push(
|
|
1846
|
+
if (codeScaled.length > 0) declarations.push(rootRule(codeScaled.join(";")));
|
|
1672
1847
|
}
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1848
|
+
if (codeWeight !== WEIGHT_UNSET) {
|
|
1849
|
+
// The variable rides along so the elements INSIDE a code surface — which the
|
|
1850
|
+
// interface weight rule's element table matches — resolve the code weight
|
|
1851
|
+
// instead of the interface one, without any `… *` selector.
|
|
1852
|
+
declarations.push(
|
|
1853
|
+
codeRule(
|
|
1854
|
+
"font-weight:" + codeWeight + " !important;--dfp-interface-weight:" + codeWeight
|
|
1855
|
+
)
|
|
1856
|
+
);
|
|
1676
1857
|
}
|
|
1677
1858
|
var ligatures = axis[LIGATURES_FIELD];
|
|
1678
1859
|
if (ligatures === LIGATURES_ON) {
|
|
@@ -1689,10 +1870,10 @@ function buildAxisCss(axis, baseTokens, isDark) {
|
|
|
1689
1870
|
var noItalic = axis[NO_SYNTHETIC_ITALIC_FIELD] === true;
|
|
1690
1871
|
var noBold = axis[NO_SYNTHETIC_BOLD_FIELD] === true;
|
|
1691
1872
|
if (noItalic && noBold) {
|
|
1692
|
-
declarations.push(
|
|
1873
|
+
declarations.push(bodyRule("font-synthesis:none !important"));
|
|
1693
1874
|
} else {
|
|
1694
|
-
if (noItalic) declarations.push(
|
|
1695
|
-
if (noBold) declarations.push(
|
|
1875
|
+
if (noItalic) declarations.push(bodyRule("font-synthesis-style:none !important"));
|
|
1876
|
+
if (noBold) declarations.push(bodyRule("font-synthesis-weight:none !important"));
|
|
1696
1877
|
}
|
|
1697
1878
|
|
|
1698
1879
|
return declarations.join("\n");
|
|
@@ -1781,15 +1962,19 @@ function scaleCodeTokens(baseTokens, scale, lineOffset) {
|
|
|
1781
1962
|
*
|
|
1782
1963
|
* @param {unknown} config - normalized or raw configuration.
|
|
1783
1964
|
* @param {Record<string, string>} [baseTokens] - token name to untouched value.
|
|
1965
|
+
* @param {{markdown?: string, code?: string[], terminal?: string[]}} [hints] - what
|
|
1966
|
+
* the page harvested: the stable markdown wrapper, and the real class tokens
|
|
1967
|
+
* behind the code/terminal hooks. Absent means "match by substring", which is
|
|
1968
|
+
* correct everywhere and costs an attribute scan per element per rule.
|
|
1784
1969
|
* @returns {string} declarations for one `<style>` element ("" when dormant).
|
|
1785
1970
|
*/
|
|
1786
|
-
function buildFontCss(config, baseTokens) {
|
|
1971
|
+
function buildFontCss(config, baseTokens, hints) {
|
|
1787
1972
|
var tokens = baseTokens === undefined || baseTokens === null ? FALLBACK_TOKENS : baseTokens;
|
|
1788
1973
|
var sets = resolveAxes(config);
|
|
1789
1974
|
if (isDormant(sets.light) && isDormant(sets.dark)) return "";
|
|
1790
|
-
var light = buildAxisCss(sets.light, tokens, false);
|
|
1975
|
+
var light = buildAxisCss(sets.light, tokens, false, hints);
|
|
1791
1976
|
if (sameValueSet(sets.light, sets.dark)) return light;
|
|
1792
|
-
var dark = buildAxisCss(sets.dark, tokens, true);
|
|
1977
|
+
var dark = buildAxisCss(sets.dark, tokens, true, hints);
|
|
1793
1978
|
if (dark === "") return light;
|
|
1794
1979
|
return light + "\n" + dark;
|
|
1795
1980
|
}
|
|
@@ -2183,9 +2368,13 @@ var shared = {
|
|
|
2183
2368
|
pickerGroups: pickerGroups,
|
|
2184
2369
|
catalogRefreshDue: catalogRefreshDue,
|
|
2185
2370
|
MARKDOWN_SELECTOR: MARKDOWN_SELECTOR,
|
|
2371
|
+
codeSelector: codeSelector,
|
|
2372
|
+
dialogSelector: dialogSelector,
|
|
2373
|
+
CODE_SURFACE_SELECTORS_CHEAP: CODE_SURFACE_SELECTORS_CHEAP,
|
|
2186
2374
|
CODE_SURFACE_SELECTORS: CODE_SURFACE_SELECTORS,
|
|
2187
2375
|
CODE_EXCLUDES: CODE_EXCLUDES,
|
|
2188
|
-
|
|
2376
|
+
WEIGHT_MARKDOWN_GUARD: WEIGHT_MARKDOWN_GUARD,
|
|
2377
|
+
WEIGHT_ELEMENT_TABLE: WEIGHT_ELEMENT_TABLE,
|
|
2189
2378
|
excludeAll: excludeAll,
|
|
2190
2379
|
splitSelectorList: splitSelectorList,
|
|
2191
2380
|
prefixSelector: prefixSelector,
|
|
@@ -3037,6 +3226,35 @@ function readBaseTokens() {
|
|
|
3037
3226
|
return out;
|
|
3038
3227
|
}
|
|
3039
3228
|
|
|
3229
|
+
/**
|
|
3230
|
+
* Find the cheap markdown selector for this page, so the stylesheet does not
|
|
3231
|
+
* have to match the conversation container by substring attribute.
|
|
3232
|
+
*
|
|
3233
|
+
* The substring form `[class*="_markdown_" i]` is what the dialog rule used to
|
|
3234
|
+
* be scoped by, and that rule's code exclusion is a `:not(...)` with ~30
|
|
3235
|
+
* arguments, so its scan is paid once per candidate element: naming the wrapper
|
|
3236
|
+
* takes that from ~1800 candidates per recalculation to ~37.
|
|
3237
|
+
*
|
|
3238
|
+
* The code hooks stay as substring selectors (see `codeSurfaceList()`): a class
|
|
3239
|
+
* the page has not rendered yet cannot be named, and an unknown surface must
|
|
3240
|
+
* still get the code axes.
|
|
3241
|
+
*
|
|
3242
|
+
* A page without the wrapper keeps the substring form — the builder's default —
|
|
3243
|
+
* and only loses the cheaper one.
|
|
3244
|
+
*
|
|
3245
|
+
* @returns {{markdown?: string}} the hints.
|
|
3246
|
+
*/
|
|
3247
|
+
function harvestScope() {
|
|
3248
|
+
var hints = {};
|
|
3249
|
+
if (typeof document === "undefined" || typeof document.querySelector !== "function") return hints;
|
|
3250
|
+
try {
|
|
3251
|
+
if (document.querySelector("[data-dss-prose]") !== null) hints.markdown = "[data-dss-prose]";
|
|
3252
|
+
} catch (error) {
|
|
3253
|
+
// No wrapper to name: the builder falls back to the substring selector.
|
|
3254
|
+
}
|
|
3255
|
+
return hints;
|
|
3256
|
+
}
|
|
3257
|
+
|
|
3040
3258
|
/**
|
|
3041
3259
|
* Create the writer that keeps one `<style>` element in sync.
|
|
3042
3260
|
*
|
|
@@ -3056,7 +3274,7 @@ function createStylesheet(tokens) {
|
|
|
3056
3274
|
var lastCss = null;
|
|
3057
3275
|
return function apply(config) {
|
|
3058
3276
|
if (typeof document === "undefined") return;
|
|
3059
|
-
var css = buildFontCss(config, tokens());
|
|
3277
|
+
var css = buildFontCss(config, tokens(), harvestScope());
|
|
3060
3278
|
if (css === lastCss && tag !== null && tag.isConnected) return;
|
|
3061
3279
|
lastCss = css;
|
|
3062
3280
|
if (tag === null || !tag.isConnected) {
|
|
@@ -4290,7 +4508,15 @@ function firstFamily(stack) {
|
|
|
4290
4508
|
function FontCardPage(props) {
|
|
4291
4509
|
var t = typeof props.t === "function" ? props.t : cardT || fallbackTranslate;
|
|
4292
4510
|
if (props.view === "summary") return t("card.description");
|
|
4293
|
-
|
|
4511
|
+
// Every other view is the card as the content of a section the reader has
|
|
4512
|
+
// already opened, so it starts open: the row/entry they clicked IS the
|
|
4513
|
+
// expansion gesture, and asking for a second click on the card's own header
|
|
4514
|
+
// was the one thing left to remove on the official plugin page.
|
|
4515
|
+
return h(FontCard, {
|
|
4516
|
+
scope: props.scope || cardScope || MEMORY_SCOPE,
|
|
4517
|
+
t: t,
|
|
4518
|
+
defaultOpen: props.view !== "row",
|
|
4519
|
+
});
|
|
4294
4520
|
}
|
|
4295
4521
|
|
|
4296
4522
|
/**
|
|
@@ -4315,7 +4541,14 @@ function FontCard(props) {
|
|
|
4315
4541
|
// values `apply` bound are the fallback.
|
|
4316
4542
|
var t = typeof props.t === "function" ? props.t : cardT || fallbackTranslate;
|
|
4317
4543
|
var scope = props.scope || cardScope || MEMORY_SCOPE;
|
|
4318
|
-
|
|
4544
|
+
// The official plugin page (0.1.7: `view: "page"`) and the rc line's
|
|
4545
|
+
// configuration cell (which passes no props at all) both render this card as
|
|
4546
|
+
// the whole body of a section the reader has ALREADY opened — the host's own
|
|
4547
|
+
// row is the expansion gesture there, so starting collapsed asked for a
|
|
4548
|
+
// second click. The card therefore starts open unless the seat says
|
|
4549
|
+
// otherwise (`defaultOpen: false`), and `FontCardPage` answers a one-line
|
|
4550
|
+
// summary request with its description instead of the card.
|
|
4551
|
+
var [open, setOpen] = useState(props.defaultOpen !== false);
|
|
4319
4552
|
var [view, setView] = useState(readViewMode);
|
|
4320
4553
|
var [expanded, setExpanded] = useState(null);
|
|
4321
4554
|
var [presetName, setPresetName] = useState("");
|
|
@@ -5703,6 +5936,33 @@ function apply(ctx) {
|
|
|
5703
5936
|
);
|
|
5704
5937
|
sync();
|
|
5705
5938
|
|
|
5939
|
+
// Which selectors the sheet can be narrowed to depends on what the page
|
|
5940
|
+
// contains, and the conversation renders AFTER this bundle activates: the
|
|
5941
|
+
// first harvest sees an empty shell and falls back to the wide attribute
|
|
5942
|
+
// hooks (correct, but an attribute scan per element per rule). Re-harvest
|
|
5943
|
+
// when the DOM grows. The applier returns untouched when the resulting CSS
|
|
5944
|
+
// is unchanged, so a redundant re-sync costs one string compare.
|
|
5945
|
+
ctx.effect(
|
|
5946
|
+
function () {
|
|
5947
|
+
if (typeof document === "undefined") return undefined;
|
|
5948
|
+
if (typeof MutationObserver !== "function" || !document.body) return undefined;
|
|
5949
|
+
var pending = null;
|
|
5950
|
+
var observer = new MutationObserver(function () {
|
|
5951
|
+
if (pending !== null) return;
|
|
5952
|
+
pending = globalThis.setTimeout(function () {
|
|
5953
|
+
pending = null;
|
|
5954
|
+
sync();
|
|
5955
|
+
}, 1000);
|
|
5956
|
+
});
|
|
5957
|
+
observer.observe(document.body, { childList: true, subtree: true });
|
|
5958
|
+
return function () {
|
|
5959
|
+
if (pending !== null) globalThis.clearTimeout(pending);
|
|
5960
|
+
observer.disconnect();
|
|
5961
|
+
};
|
|
5962
|
+
},
|
|
5963
|
+
"dsh-fonttune: scope re-harvest"
|
|
5964
|
+
);
|
|
5965
|
+
|
|
5706
5966
|
// DSH writes its tokens (and its content font size) after this bundle
|
|
5707
5967
|
// activates, and the host's own boot row only covers the first frame; the
|
|
5708
5968
|
// size axis therefore re-reads the live values and reapplies when they
|