@wingleeio/mugen-markdown 0.3.0 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +255 -149
- package/dist/index.d.cts +74 -11
- package/dist/index.d.mts +74 -11
- package/dist/index.mjs +255 -150
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -227,120 +227,6 @@ function resolveTheme(theme) {
|
|
|
227
227
|
return resolved;
|
|
228
228
|
}
|
|
229
229
|
//#endregion
|
|
230
|
-
//#region src/inline.ts
|
|
231
|
-
/** A base format for body text at a given size/weight/colour. */
|
|
232
|
-
function baseFormat(theme, opts = {}) {
|
|
233
|
-
return {
|
|
234
|
-
family: theme.fontFamily,
|
|
235
|
-
monoFamily: theme.monoFamily,
|
|
236
|
-
size: opts.size ?? theme.fontSize,
|
|
237
|
-
weight: opts.weight ?? 400,
|
|
238
|
-
italic: false,
|
|
239
|
-
mono: false,
|
|
240
|
-
underline: false,
|
|
241
|
-
strike: false,
|
|
242
|
-
color: opts.color ?? (theme.color !== "inherit" ? theme.color : void 0)
|
|
243
|
-
};
|
|
244
|
-
}
|
|
245
|
-
/** Compose a measurable `Font` shorthand from a format. */
|
|
246
|
-
function composeFont(fmt) {
|
|
247
|
-
const family = fmt.mono ? fmt.monoFamily : fmt.family;
|
|
248
|
-
return `${fmt.italic ? "italic " : ""}${fmt.weight} ${fmt.size}px ${family}`;
|
|
249
|
-
}
|
|
250
|
-
function pushRun(out, text, fmt) {
|
|
251
|
-
if (text.length === 0) return;
|
|
252
|
-
const run = {
|
|
253
|
-
text,
|
|
254
|
-
font: composeFont(fmt)
|
|
255
|
-
};
|
|
256
|
-
if (fmt.color != null) run.color = fmt.color;
|
|
257
|
-
if (fmt.background != null) run.background = fmt.background;
|
|
258
|
-
const decoration = [fmt.underline ? "underline" : "", fmt.strike ? "line-through" : ""].filter(Boolean).join(" ");
|
|
259
|
-
if (decoration) run.decoration = decoration;
|
|
260
|
-
if (fmt.href != null) {
|
|
261
|
-
run.href = fmt.href;
|
|
262
|
-
run.as = "a";
|
|
263
|
-
} else if (fmt.mono) run.as = "code";
|
|
264
|
-
out.push(run);
|
|
265
|
-
}
|
|
266
|
-
/**
|
|
267
|
-
* Flatten phrasing content into styled runs. Recursive over the inline marks;
|
|
268
|
-
* the result feeds a single `<RichText>` so the whole paragraph wraps as one
|
|
269
|
-
* flow and measures exactly.
|
|
270
|
-
*/
|
|
271
|
-
function flattenInline(nodes, fmt, theme, out) {
|
|
272
|
-
for (const node of nodes) switch (node.type) {
|
|
273
|
-
case "text":
|
|
274
|
-
pushRun(out, node.value, fmt);
|
|
275
|
-
break;
|
|
276
|
-
case "strong":
|
|
277
|
-
flattenInline(node.children, {
|
|
278
|
-
...fmt,
|
|
279
|
-
weight: theme.strongWeight
|
|
280
|
-
}, theme, out);
|
|
281
|
-
break;
|
|
282
|
-
case "emphasis":
|
|
283
|
-
flattenInline(node.children, {
|
|
284
|
-
...fmt,
|
|
285
|
-
italic: theme.emphasisItalic ? true : fmt.italic
|
|
286
|
-
}, theme, out);
|
|
287
|
-
break;
|
|
288
|
-
case "delete":
|
|
289
|
-
flattenInline(node.children, {
|
|
290
|
-
...fmt,
|
|
291
|
-
strike: true
|
|
292
|
-
}, theme, out);
|
|
293
|
-
break;
|
|
294
|
-
case "inlineCode":
|
|
295
|
-
pushRun(out, node.value, {
|
|
296
|
-
...fmt,
|
|
297
|
-
mono: true,
|
|
298
|
-
size: Math.round(fmt.size * theme.inlineCode.sizeScale),
|
|
299
|
-
color: theme.inlineCode.color !== "inherit" ? theme.inlineCode.color : fmt.color,
|
|
300
|
-
background: theme.inlineCode.background
|
|
301
|
-
});
|
|
302
|
-
break;
|
|
303
|
-
case "link":
|
|
304
|
-
flattenInline(node.children, {
|
|
305
|
-
...fmt,
|
|
306
|
-
href: node.url,
|
|
307
|
-
color: theme.link.color,
|
|
308
|
-
underline: theme.link.underline ? true : fmt.underline
|
|
309
|
-
}, theme, out);
|
|
310
|
-
break;
|
|
311
|
-
case "linkReference":
|
|
312
|
-
flattenInline(node.children, fmt, theme, out);
|
|
313
|
-
break;
|
|
314
|
-
case "break":
|
|
315
|
-
out.push({
|
|
316
|
-
text: "",
|
|
317
|
-
break: true
|
|
318
|
-
});
|
|
319
|
-
break;
|
|
320
|
-
case "image":
|
|
321
|
-
if (node.alt) pushRun(out, node.alt, {
|
|
322
|
-
...fmt,
|
|
323
|
-
color: theme.image.color
|
|
324
|
-
});
|
|
325
|
-
break;
|
|
326
|
-
case "imageReference":
|
|
327
|
-
if (node.alt) pushRun(out, node.alt, {
|
|
328
|
-
...fmt,
|
|
329
|
-
color: theme.image.color
|
|
330
|
-
});
|
|
331
|
-
break;
|
|
332
|
-
case "footnoteReference":
|
|
333
|
-
pushRun(out, `[${node.label ?? node.identifier}]`, {
|
|
334
|
-
...fmt,
|
|
335
|
-
color: theme.link.color
|
|
336
|
-
});
|
|
337
|
-
break;
|
|
338
|
-
default:
|
|
339
|
-
if ("children" in node && Array.isArray(node.children)) flattenInline(node.children, fmt, theme, out);
|
|
340
|
-
break;
|
|
341
|
-
}
|
|
342
|
-
}
|
|
343
|
-
//#endregion
|
|
344
230
|
//#region src/primitives/rich-text.tsx
|
|
345
231
|
function resolveRunFont(run, fallback) {
|
|
346
232
|
const font = run.font ?? fallback;
|
|
@@ -348,6 +234,7 @@ function resolveRunFont(run, fallback) {
|
|
|
348
234
|
(0, _wingleeio_mugen.assertMeasurableFont)(font);
|
|
349
235
|
return font;
|
|
350
236
|
}
|
|
237
|
+
const BOX_PLACEHOLDER = "";
|
|
351
238
|
/** Split runs into hard-break-delimited segments, each a list of rich-inline items. */
|
|
352
239
|
function segmentItems(runs, fallback) {
|
|
353
240
|
const segments = [];
|
|
@@ -358,9 +245,19 @@ function segmentItems(runs, fallback) {
|
|
|
358
245
|
cur = [];
|
|
359
246
|
continue;
|
|
360
247
|
}
|
|
361
|
-
if (run.
|
|
248
|
+
if (run.advance != null) {
|
|
249
|
+
cur.push({
|
|
250
|
+
text: BOX_PLACEHOLDER,
|
|
251
|
+
font: resolveRunFont(run, fallback),
|
|
252
|
+
extraWidth: Math.max(0, run.advance),
|
|
253
|
+
break: "never"
|
|
254
|
+
});
|
|
255
|
+
continue;
|
|
256
|
+
}
|
|
257
|
+
const text = run.text ?? "";
|
|
258
|
+
if (text.length === 0) continue;
|
|
362
259
|
const item = {
|
|
363
|
-
text
|
|
260
|
+
text,
|
|
364
261
|
font: resolveRunFont(run, fallback)
|
|
365
262
|
};
|
|
366
263
|
if (run.letterSpacing != null) item.letterSpacing = run.letterSpacing;
|
|
@@ -425,6 +322,15 @@ function renderRichText(props) {
|
|
|
425
322
|
};
|
|
426
323
|
const children = props.runs.map((run, i) => {
|
|
427
324
|
if (run.break) return (0, react.createElement)("br", { key: i });
|
|
325
|
+
if (run.advance != null) return (0, react.createElement)("span", {
|
|
326
|
+
key: i,
|
|
327
|
+
style: {
|
|
328
|
+
display: "inline-block",
|
|
329
|
+
verticalAlign: "middle",
|
|
330
|
+
lineHeight: 0,
|
|
331
|
+
whiteSpace: "nowrap"
|
|
332
|
+
}
|
|
333
|
+
}, run.content);
|
|
428
334
|
const tag = run.as ?? (run.href != null ? "a" : "span");
|
|
429
335
|
const elementProps = {
|
|
430
336
|
key: i,
|
|
@@ -443,7 +349,7 @@ function renderRichText(props) {
|
|
|
443
349
|
if (run.title != null) elementProps.title = run.title;
|
|
444
350
|
if (run.onClick != null) elementProps.onClick = run.onClick;
|
|
445
351
|
if (run.className != null) elementProps.className = run.className;
|
|
446
|
-
return (0, react.createElement)(tag, elementProps, run.text);
|
|
352
|
+
return (0, react.createElement)(tag, elementProps, run.text ?? "");
|
|
447
353
|
});
|
|
448
354
|
return (0, react.createElement)("div", {
|
|
449
355
|
className: props.className,
|
|
@@ -471,12 +377,173 @@ const RichText = (0, _wingleeio_mugen.markPrimitive)(renderRichText, {
|
|
|
471
377
|
return max;
|
|
472
378
|
}
|
|
473
379
|
});
|
|
380
|
+
/**
|
|
381
|
+
* Measure a string's rendered advance in px for a given measurable font — the
|
|
382
|
+
* same ruler `RichText` measures with. Use it to size an inline box: a text
|
|
383
|
+
* "pill" reserves `measureInline(label, font) + horizontalPadding`.
|
|
384
|
+
*/
|
|
385
|
+
function measureInline(text, font) {
|
|
386
|
+
if (text.length === 0) return 0;
|
|
387
|
+
(0, _wingleeio_mugen.assertMeasurableFont)(font);
|
|
388
|
+
return (0, _chenglou_pretext_rich_inline.measureRichInlineStats)(prepareCached([{
|
|
389
|
+
text,
|
|
390
|
+
font
|
|
391
|
+
}]), 1e7).maxLineWidth;
|
|
392
|
+
}
|
|
474
393
|
/** Drop the rich-inline prepare cache (tests / memory pressure). */
|
|
475
394
|
function clearRichTextCache() {
|
|
476
395
|
prepCache.clear();
|
|
477
396
|
cacheEpoch = -1;
|
|
478
397
|
}
|
|
479
398
|
//#endregion
|
|
399
|
+
//#region src/inline.ts
|
|
400
|
+
/** A base format for body text at a given size/weight/colour. */
|
|
401
|
+
function baseFormat(theme, opts = {}) {
|
|
402
|
+
return {
|
|
403
|
+
family: theme.fontFamily,
|
|
404
|
+
monoFamily: theme.monoFamily,
|
|
405
|
+
size: opts.size ?? theme.fontSize,
|
|
406
|
+
weight: opts.weight ?? 400,
|
|
407
|
+
italic: false,
|
|
408
|
+
mono: false,
|
|
409
|
+
underline: false,
|
|
410
|
+
strike: false,
|
|
411
|
+
color: opts.color ?? (theme.color !== "inherit" ? theme.color : void 0)
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
/** Compose a measurable `Font` shorthand from a format. */
|
|
415
|
+
function composeFont(fmt) {
|
|
416
|
+
const family = fmt.mono ? fmt.monoFamily : fmt.family;
|
|
417
|
+
return `${fmt.italic ? "italic " : ""}${fmt.weight} ${fmt.size}px ${family}`;
|
|
418
|
+
}
|
|
419
|
+
function pushRun(out, text, fmt) {
|
|
420
|
+
if (text.length === 0) return;
|
|
421
|
+
const run = {
|
|
422
|
+
text,
|
|
423
|
+
font: composeFont(fmt)
|
|
424
|
+
};
|
|
425
|
+
if (fmt.color != null) run.color = fmt.color;
|
|
426
|
+
if (fmt.background != null) run.background = fmt.background;
|
|
427
|
+
const decoration = [fmt.underline ? "underline" : "", fmt.strike ? "line-through" : ""].filter(Boolean).join(" ");
|
|
428
|
+
if (decoration) run.decoration = decoration;
|
|
429
|
+
if (fmt.href != null) {
|
|
430
|
+
run.href = fmt.href;
|
|
431
|
+
run.as = "a";
|
|
432
|
+
} else if (fmt.mono) run.as = "code";
|
|
433
|
+
out.push(run);
|
|
434
|
+
}
|
|
435
|
+
/** Build the context handed to an inline override. */
|
|
436
|
+
function makeInlineCtx(fmt, theme, inline) {
|
|
437
|
+
return {
|
|
438
|
+
theme,
|
|
439
|
+
fmt,
|
|
440
|
+
font: (overrides) => composeFont(overrides ? {
|
|
441
|
+
...fmt,
|
|
442
|
+
...overrides
|
|
443
|
+
} : fmt),
|
|
444
|
+
measure: (text, font) => measureInline(text, font),
|
|
445
|
+
runs: (nodes, fmtOverrides) => {
|
|
446
|
+
const sub = [];
|
|
447
|
+
flattenInline(nodes, fmtOverrides ? {
|
|
448
|
+
...fmt,
|
|
449
|
+
...fmtOverrides
|
|
450
|
+
} : fmt, theme, sub, inline);
|
|
451
|
+
return sub;
|
|
452
|
+
}
|
|
453
|
+
};
|
|
454
|
+
}
|
|
455
|
+
/**
|
|
456
|
+
* Flatten phrasing content into styled runs. Recursive over the inline marks;
|
|
457
|
+
* the result feeds a single `<RichText>` so the whole paragraph wraps as one
|
|
458
|
+
* flow and measures exactly. An `inline` override map can replace how any node
|
|
459
|
+
* type flattens — returning its own runs (e.g. a measured inline box) or `null`
|
|
460
|
+
* to fall through to the default.
|
|
461
|
+
*/
|
|
462
|
+
function flattenInline(nodes, fmt, theme, out, inline) {
|
|
463
|
+
for (const node of nodes) {
|
|
464
|
+
if (inline != null) {
|
|
465
|
+
const override = inline[node.type];
|
|
466
|
+
if (override != null) {
|
|
467
|
+
const produced = override(node, makeInlineCtx(fmt, theme, inline));
|
|
468
|
+
if (produced != null) {
|
|
469
|
+
for (const run of produced) out.push(run);
|
|
470
|
+
continue;
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
switch (node.type) {
|
|
475
|
+
case "text":
|
|
476
|
+
pushRun(out, node.value, fmt);
|
|
477
|
+
break;
|
|
478
|
+
case "strong":
|
|
479
|
+
flattenInline(node.children, {
|
|
480
|
+
...fmt,
|
|
481
|
+
weight: theme.strongWeight
|
|
482
|
+
}, theme, out, inline);
|
|
483
|
+
break;
|
|
484
|
+
case "emphasis":
|
|
485
|
+
flattenInline(node.children, {
|
|
486
|
+
...fmt,
|
|
487
|
+
italic: theme.emphasisItalic ? true : fmt.italic
|
|
488
|
+
}, theme, out, inline);
|
|
489
|
+
break;
|
|
490
|
+
case "delete":
|
|
491
|
+
flattenInline(node.children, {
|
|
492
|
+
...fmt,
|
|
493
|
+
strike: true
|
|
494
|
+
}, theme, out, inline);
|
|
495
|
+
break;
|
|
496
|
+
case "inlineCode":
|
|
497
|
+
pushRun(out, node.value, {
|
|
498
|
+
...fmt,
|
|
499
|
+
mono: true,
|
|
500
|
+
size: Math.round(fmt.size * theme.inlineCode.sizeScale),
|
|
501
|
+
color: theme.inlineCode.color !== "inherit" ? theme.inlineCode.color : fmt.color,
|
|
502
|
+
background: theme.inlineCode.background
|
|
503
|
+
});
|
|
504
|
+
break;
|
|
505
|
+
case "link":
|
|
506
|
+
flattenInline(node.children, {
|
|
507
|
+
...fmt,
|
|
508
|
+
href: node.url,
|
|
509
|
+
color: theme.link.color,
|
|
510
|
+
underline: theme.link.underline ? true : fmt.underline
|
|
511
|
+
}, theme, out, inline);
|
|
512
|
+
break;
|
|
513
|
+
case "linkReference":
|
|
514
|
+
flattenInline(node.children, fmt, theme, out, inline);
|
|
515
|
+
break;
|
|
516
|
+
case "break":
|
|
517
|
+
out.push({
|
|
518
|
+
text: "",
|
|
519
|
+
break: true
|
|
520
|
+
});
|
|
521
|
+
break;
|
|
522
|
+
case "image":
|
|
523
|
+
if (node.alt) pushRun(out, node.alt, {
|
|
524
|
+
...fmt,
|
|
525
|
+
color: theme.image.color
|
|
526
|
+
});
|
|
527
|
+
break;
|
|
528
|
+
case "imageReference":
|
|
529
|
+
if (node.alt) pushRun(out, node.alt, {
|
|
530
|
+
...fmt,
|
|
531
|
+
color: theme.image.color
|
|
532
|
+
});
|
|
533
|
+
break;
|
|
534
|
+
case "footnoteReference":
|
|
535
|
+
pushRun(out, `[${node.label ?? node.identifier}]`, {
|
|
536
|
+
...fmt,
|
|
537
|
+
color: theme.link.color
|
|
538
|
+
});
|
|
539
|
+
break;
|
|
540
|
+
default:
|
|
541
|
+
if ("children" in node && Array.isArray(node.children)) flattenInline(node.children, fmt, theme, out, inline);
|
|
542
|
+
break;
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
//#endregion
|
|
480
547
|
//#region src/highlight/languages.ts
|
|
481
548
|
function words(s) {
|
|
482
549
|
return new Set(s.split(" "));
|
|
@@ -1930,7 +1997,7 @@ function createContext(theme, components) {
|
|
|
1930
1997
|
...base
|
|
1931
1998
|
};
|
|
1932
1999
|
const out = [];
|
|
1933
|
-
flattenInline(nodes, fmt, theme, out);
|
|
2000
|
+
flattenInline(nodes, fmt, theme, out, components.inline);
|
|
1934
2001
|
return out;
|
|
1935
2002
|
},
|
|
1936
2003
|
inlineText(nodes, opts = {}) {
|
|
@@ -1940,7 +2007,7 @@ function createContext(theme, components) {
|
|
|
1940
2007
|
...opts.color != null ? { color: opts.color } : null
|
|
1941
2008
|
});
|
|
1942
2009
|
const out = [];
|
|
1943
|
-
flattenInline(nodes, fmt, theme, out);
|
|
2010
|
+
flattenInline(nodes, fmt, theme, out, components.inline);
|
|
1944
2011
|
return (0, react.createElement)(RichText, {
|
|
1945
2012
|
runs: out,
|
|
1946
2013
|
font: composeFont(fmt),
|
|
@@ -1983,25 +2050,42 @@ const EMA_SEED_MS = 160;
|
|
|
1983
2050
|
const MIN_FADE_MS = 120;
|
|
1984
2051
|
const MAX_FADE_MS = 400;
|
|
1985
2052
|
const MAX_VEILS = 32;
|
|
1986
|
-
function commonPrefixLength(a, b) {
|
|
1987
|
-
const n = Math.min(a.length, b.length);
|
|
1988
|
-
let i = 0;
|
|
1989
|
-
while (i < n && a.charCodeAt(i) === b.charCodeAt(i)) i++;
|
|
1990
|
-
return i;
|
|
1991
|
-
}
|
|
1992
2053
|
function inChrome(node, container) {
|
|
1993
2054
|
for (let p = node.parentElement; p != null && p !== container; p = p.parentElement) if (p.tagName === "BUTTON") return true;
|
|
1994
2055
|
return false;
|
|
1995
2056
|
}
|
|
2057
|
+
/** Whether `el` (or an ancestor up to `container`) is interactive chrome. */
|
|
2058
|
+
function elementInChrome(el, container) {
|
|
2059
|
+
for (let p = el; p != null && p !== container; p = p.parentElement) if (p.tagName === "BUTTON") return true;
|
|
2060
|
+
return false;
|
|
2061
|
+
}
|
|
1996
2062
|
function contentTextFilter(container) {
|
|
1997
2063
|
return { acceptNode: (n) => inChrome(n, container) ? NodeFilter.FILTER_REJECT : NodeFilter.FILTER_ACCEPT };
|
|
1998
2064
|
}
|
|
1999
|
-
/** `container
|
|
2000
|
-
function
|
|
2065
|
+
/** Chrome-free text length of `container` — a full walk; used to seed/reconcile. */
|
|
2066
|
+
function chromeFreeLength(container) {
|
|
2001
2067
|
const walker = document.createTreeWalker(container, NodeFilter.SHOW_TEXT, contentTextFilter(container));
|
|
2002
|
-
let
|
|
2003
|
-
for (let
|
|
2004
|
-
return
|
|
2068
|
+
let n = 0;
|
|
2069
|
+
for (let t = walker.nextNode(); t != null; t = walker.nextNode()) n += t.data.length;
|
|
2070
|
+
return n;
|
|
2071
|
+
}
|
|
2072
|
+
/**
|
|
2073
|
+
* Chrome-free text length of one node's subtree — used to fold a single
|
|
2074
|
+
* added/removed node into the running length without touching the rest of the
|
|
2075
|
+
* DOM. Buttons inside the node are skipped; a button node itself counts zero.
|
|
2076
|
+
*/
|
|
2077
|
+
function subtreeTextLength(node) {
|
|
2078
|
+
if (node.nodeType === 3) return node.data.length;
|
|
2079
|
+
if (node.nodeType !== 1) return 0;
|
|
2080
|
+
const el = node;
|
|
2081
|
+
if (el.tagName === "BUTTON") return 0;
|
|
2082
|
+
let n = 0;
|
|
2083
|
+
const walker = document.createTreeWalker(el, NodeFilter.SHOW_TEXT, { acceptNode: (t) => {
|
|
2084
|
+
for (let p = t.parentElement; p != null && p !== el; p = p.parentElement) if (p.tagName === "BUTTON") return NodeFilter.FILTER_REJECT;
|
|
2085
|
+
return NodeFilter.FILTER_ACCEPT;
|
|
2086
|
+
} });
|
|
2087
|
+
for (let t = walker.nextNode(); t != null; t = walker.nextNode()) n += t.data.length;
|
|
2088
|
+
return n;
|
|
2005
2089
|
}
|
|
2006
2090
|
let scratch;
|
|
2007
2091
|
function scratchCtx() {
|
|
@@ -2043,14 +2127,20 @@ function effectiveBackground(el, cache) {
|
|
|
2043
2127
|
}
|
|
2044
2128
|
/**
|
|
2045
2129
|
* Paints the dissolving veil over a single content element's newly-arrived text.
|
|
2046
|
-
* Driven by a MutationObserver
|
|
2047
|
-
*
|
|
2130
|
+
* Driven by a MutationObserver that folds each change into a running length, so
|
|
2131
|
+
* a streaming tick costs O(delta) — never an O(n) walk of the whole content,
|
|
2132
|
+
* which is what made long streams lag. A rAF loop runs only while veils are
|
|
2133
|
+
* alive, then stops until the next mutation, so an idle (settled) block uses no
|
|
2134
|
+
* frames. Only the small, recent veil region is ever measured for geometry.
|
|
2048
2135
|
*/
|
|
2049
2136
|
var FadePainter = class {
|
|
2050
2137
|
content = null;
|
|
2051
2138
|
canvas = null;
|
|
2052
2139
|
ctx = null;
|
|
2053
|
-
|
|
2140
|
+
/** Chrome-free text length, tracked incrementally from mutation records. */
|
|
2141
|
+
length = 0;
|
|
2142
|
+
/** Net length change observed since the last frame folded it in. */
|
|
2143
|
+
pendingDelta = 0;
|
|
2054
2144
|
veils = [];
|
|
2055
2145
|
ema = EMA_SEED_MS;
|
|
2056
2146
|
lastAppend = 0;
|
|
@@ -2064,14 +2154,29 @@ var FadePainter = class {
|
|
|
2064
2154
|
this.content = content;
|
|
2065
2155
|
this.canvas = canvas;
|
|
2066
2156
|
this.ctx = ctx;
|
|
2067
|
-
this.
|
|
2068
|
-
this.mo = new MutationObserver(() => this.
|
|
2157
|
+
this.length = chromeFreeLength(content);
|
|
2158
|
+
this.mo = new MutationObserver((records) => this.onMutations(records));
|
|
2069
2159
|
this.mo.observe(content, {
|
|
2070
2160
|
subtree: true,
|
|
2071
2161
|
childList: true,
|
|
2072
|
-
characterData: true
|
|
2162
|
+
characterData: true,
|
|
2163
|
+
characterDataOldValue: true
|
|
2073
2164
|
});
|
|
2074
2165
|
}
|
|
2166
|
+
onMutations(records) {
|
|
2167
|
+
const content = this.content;
|
|
2168
|
+
if (content == null) return;
|
|
2169
|
+
for (const rec of records) if (rec.type === "characterData") {
|
|
2170
|
+
const node = rec.target;
|
|
2171
|
+
if (inChrome(node, content)) continue;
|
|
2172
|
+
this.pendingDelta += (node.nodeValue?.length ?? 0) - (rec.oldValue?.length ?? 0);
|
|
2173
|
+
} else if (rec.type === "childList") {
|
|
2174
|
+
if (rec.target instanceof Element && elementInChrome(rec.target, content)) continue;
|
|
2175
|
+
for (let i = 0; i < rec.addedNodes.length; i++) this.pendingDelta += subtreeTextLength(rec.addedNodes[i]);
|
|
2176
|
+
for (let i = 0; i < rec.removedNodes.length; i++) this.pendingDelta -= subtreeTextLength(rec.removedNodes[i]);
|
|
2177
|
+
}
|
|
2178
|
+
this.wake();
|
|
2179
|
+
}
|
|
2075
2180
|
destroy() {
|
|
2076
2181
|
this.mo?.disconnect();
|
|
2077
2182
|
this.mo = null;
|
|
@@ -2096,25 +2201,24 @@ var FadePainter = class {
|
|
|
2096
2201
|
return;
|
|
2097
2202
|
}
|
|
2098
2203
|
const now = performance.now();
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
...v,
|
|
2104
|
-
start: Math.min(v.start, prefix),
|
|
2105
|
-
end: Math.min(v.end, prefix)
|
|
2106
|
-
})).filter((v) => v.end > v.start);
|
|
2107
|
-
if (text.length > prefix) {
|
|
2204
|
+
if (this.pendingDelta !== 0) {
|
|
2205
|
+
const newLength = Math.max(0, this.length + this.pendingDelta);
|
|
2206
|
+
this.pendingDelta = 0;
|
|
2207
|
+
if (newLength > this.length) {
|
|
2108
2208
|
if (this.lastAppend > 0) this.ema = this.ema * .7 + Math.min(now - this.lastAppend, 1e3) * .3;
|
|
2109
2209
|
this.lastAppend = now;
|
|
2110
2210
|
this.veils.push({
|
|
2111
|
-
start:
|
|
2112
|
-
end:
|
|
2211
|
+
start: this.length,
|
|
2212
|
+
end: newLength,
|
|
2113
2213
|
t0: now
|
|
2114
2214
|
});
|
|
2115
2215
|
if (this.veils.length > MAX_VEILS) this.veils.splice(0, this.veils.length - MAX_VEILS);
|
|
2116
|
-
}
|
|
2117
|
-
|
|
2216
|
+
} else if (newLength < this.length) this.veils = this.veils.map((v) => ({
|
|
2217
|
+
...v,
|
|
2218
|
+
start: Math.min(v.start, newLength),
|
|
2219
|
+
end: Math.min(v.end, newLength)
|
|
2220
|
+
})).filter((v) => v.end > v.start);
|
|
2221
|
+
this.length = newLength;
|
|
2118
2222
|
}
|
|
2119
2223
|
const duration = Math.min(MAX_FADE_MS, Math.max(MIN_FADE_MS, this.ema * 3));
|
|
2120
2224
|
const boost = 1 + .3 * Math.max(0, this.veils.length - 2);
|
|
@@ -2174,6 +2278,7 @@ var FadePainter = class {
|
|
|
2174
2278
|
}
|
|
2175
2279
|
if (this.veils.length > 0) this.raf = requestAnimationFrame(this.frame);
|
|
2176
2280
|
else {
|
|
2281
|
+
this.length = chromeFreeLength(content);
|
|
2177
2282
|
this.running = false;
|
|
2178
2283
|
this.raf = 0;
|
|
2179
2284
|
}
|
|
@@ -2295,6 +2400,7 @@ Object.defineProperty(exports, "definePrimitive", {
|
|
|
2295
2400
|
}
|
|
2296
2401
|
});
|
|
2297
2402
|
exports.flattenInline = flattenInline;
|
|
2403
|
+
exports.measureInline = measureInline;
|
|
2298
2404
|
exports.parseMarkdown = parseMarkdown;
|
|
2299
2405
|
exports.profileFor = profileFor;
|
|
2300
2406
|
exports.registerLanguage = registerLanguage;
|
package/dist/index.d.cts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { CSSProperties, JSX, ReactElement, ReactNode } from "react";
|
|
2
2
|
import { ParserOptions } from "@incremark/core";
|
|
3
|
-
import { Blockquote, Blockquote as Blockquote$1, Code, Code as Code$1, Heading, Heading as Heading$1, Html, Html as Html$1, Image, Image as Image$1, Link, List, List as List$1, ListItem, Paragraph, Paragraph as Paragraph$1, PhrasingContent, PhrasingContent as PhrasingContent$1, Root, Root as Root$1, RootContent, RootContent as RootContent$1, Table, Table as Table$1, TableCell, TableRow, ThematicBreak, ThematicBreak as ThematicBreak$1 } from "mdast";
|
|
3
|
+
import { Blockquote, Blockquote as Blockquote$1, Code, Code as Code$1, Delete, Emphasis, Heading, Heading as Heading$1, Html, Html as Html$1, Image, Image as Image$1, InlineCode, Link, Link as Link$1, List, List as List$1, ListItem, Paragraph, Paragraph as Paragraph$1, PhrasingContent, PhrasingContent as PhrasingContent$1, Root, Root as Root$1, RootContent, RootContent as RootContent$1, Strong, Table, Table as Table$1, TableCell, TableRow, Text as Text$1, ThematicBreak, ThematicBreak as ThematicBreak$1 } from "mdast";
|
|
4
4
|
import { BoxProps, Font, Font as Font$1, HStack, HStackProps, MeasurableStyle, PrimitiveComponent, SafeClassName, Text, TextProps, VStack, VStackProps, definePrimitive } from "@wingleeio/mugen";
|
|
5
5
|
|
|
6
6
|
//#region src/parse.d.ts
|
|
@@ -175,8 +175,19 @@ declare function resolveTheme(theme?: DeepPartial<MarkdownTheme>): MarkdownTheme
|
|
|
175
175
|
* rich-inline layout) and the painted spans use the identical fonts.
|
|
176
176
|
*/
|
|
177
177
|
interface RichTextRun {
|
|
178
|
-
/** The run's text. Ignored when `break` is set. */
|
|
179
|
-
text
|
|
178
|
+
/** The run's text. Ignored when `break` or `advance` is set. */
|
|
179
|
+
text?: string;
|
|
180
|
+
/**
|
|
181
|
+
* Render this run as an **inline box** — the inline analogue of mugen's
|
|
182
|
+
* `Escape`: it reserves exactly `advance` px in the flow (via pretext's
|
|
183
|
+
* `extraWidth`) and paints {@link content}, whatever it is, without measuring
|
|
184
|
+
* its insides. The flow wraps it as one non-breaking atom. The caller owns the
|
|
185
|
+
* contract: `content` must render exactly `advance` px wide and no taller than
|
|
186
|
+
* the line (use the exported `measureInline` to size text-based boxes).
|
|
187
|
+
*/
|
|
188
|
+
advance?: number;
|
|
189
|
+
/** The painted content of an inline box (see {@link advance}). */
|
|
190
|
+
content?: ReactNode;
|
|
180
191
|
/** Measurable font for this run; falls back to the `<RichText font>` prop. */
|
|
181
192
|
font?: Font$1;
|
|
182
193
|
color?: string;
|
|
@@ -217,6 +228,12 @@ interface RichTextProps<C extends string = string> {
|
|
|
217
228
|
* performs over the rendered spans, so the analytic height matches the paint.
|
|
218
229
|
*/
|
|
219
230
|
declare const RichText: <C extends string = string>(props: RichTextProps<C>) => ReactElement;
|
|
231
|
+
/**
|
|
232
|
+
* Measure a string's rendered advance in px for a given measurable font — the
|
|
233
|
+
* same ruler `RichText` measures with. Use it to size an inline box: a text
|
|
234
|
+
* "pill" reserves `measureInline(label, font) + horizontalPadding`.
|
|
235
|
+
*/
|
|
236
|
+
declare function measureInline(text: string, font: Font$1): number;
|
|
220
237
|
/** Drop the rich-inline prepare cache (tests / memory pressure). */
|
|
221
238
|
declare function clearRichTextCache(): void;
|
|
222
239
|
//#endregion
|
|
@@ -252,9 +269,11 @@ declare function composeFont(fmt: InlineFormat): Font$1;
|
|
|
252
269
|
/**
|
|
253
270
|
* Flatten phrasing content into styled runs. Recursive over the inline marks;
|
|
254
271
|
* the result feeds a single `<RichText>` so the whole paragraph wraps as one
|
|
255
|
-
* flow and measures exactly.
|
|
272
|
+
* flow and measures exactly. An `inline` override map can replace how any node
|
|
273
|
+
* type flattens — returning its own runs (e.g. a measured inline box) or `null`
|
|
274
|
+
* to fall through to the default.
|
|
256
275
|
*/
|
|
257
|
-
declare function flattenInline(nodes: readonly PhrasingContent$1[], fmt: InlineFormat, theme: MarkdownTheme, out: RichTextRun[]): void;
|
|
276
|
+
declare function flattenInline(nodes: readonly PhrasingContent$1[], fmt: InlineFormat, theme: MarkdownTheme, out: RichTextRun[], inline?: InlineComponents): void;
|
|
258
277
|
//#endregion
|
|
259
278
|
//#region src/types.d.ts
|
|
260
279
|
/** Options for building a body/heading inline-text (`RichText`) element. */
|
|
@@ -315,10 +334,49 @@ interface MarkdownComponentProps<N> {
|
|
|
315
334
|
*/
|
|
316
335
|
type MarkdownComponent<N> = (props: MarkdownComponentProps<N>) => ReactNode;
|
|
317
336
|
/**
|
|
318
|
-
* The
|
|
319
|
-
*
|
|
320
|
-
*
|
|
321
|
-
|
|
337
|
+
* The context passed to an inline override. It carries the active inline format
|
|
338
|
+
* (so an override can match the surrounding type) and the helpers to build runs
|
|
339
|
+
* that stay exactly measurable.
|
|
340
|
+
*/
|
|
341
|
+
interface InlineRenderContext {
|
|
342
|
+
readonly theme: MarkdownTheme;
|
|
343
|
+
/** The composed inline format at this node (family, size, weight, colour…). */
|
|
344
|
+
readonly fmt: InlineFormat;
|
|
345
|
+
/** Compose a measurable `Font` from the current format plus overrides. */
|
|
346
|
+
font(overrides?: Partial<InlineFormat>): Font$1;
|
|
347
|
+
/**
|
|
348
|
+
* Measure a string's rendered advance in px for a font — to size an inline
|
|
349
|
+
* box. A text pill reserves `measure(label, font) + horizontalPadding`.
|
|
350
|
+
*/
|
|
351
|
+
measure(text: string, font: Font$1): number;
|
|
352
|
+
/** Default-flatten phrasing children into runs, to compose with your own. */
|
|
353
|
+
runs(nodes: readonly PhrasingContent$1[], fmtOverrides?: Partial<InlineFormat>): RichTextRun[];
|
|
354
|
+
}
|
|
355
|
+
/**
|
|
356
|
+
* An inline-node override: given the mdast node and the inline context, return
|
|
357
|
+
* the runs it should flatten to — styled text, an inline box (`{ advance,
|
|
358
|
+
* content }`), or a mix — or `null` to fall back to the default styling.
|
|
359
|
+
*/
|
|
360
|
+
type InlineComponent<N> = (node: N, ctx: InlineRenderContext) => RichTextRun[] | null;
|
|
361
|
+
/**
|
|
362
|
+
* Inline-node overrides, keyed by mdast inline type. The index signature admits
|
|
363
|
+
* custom inline tokens (from a remark plugin) beyond the named ones.
|
|
364
|
+
*/
|
|
365
|
+
interface InlineComponents {
|
|
366
|
+
text?: InlineComponent<Text$1>;
|
|
367
|
+
strong?: InlineComponent<Strong>;
|
|
368
|
+
emphasis?: InlineComponent<Emphasis>;
|
|
369
|
+
delete?: InlineComponent<Delete>;
|
|
370
|
+
inlineCode?: InlineComponent<InlineCode>;
|
|
371
|
+
link?: InlineComponent<Link$1>;
|
|
372
|
+
[type: string]: InlineComponent<any> | undefined;
|
|
373
|
+
}
|
|
374
|
+
/**
|
|
375
|
+
* The overridable components. Block-level marks are keyed by mdast node type and
|
|
376
|
+
* typed to the matching node. Inline marks (bold, italic, code, links) are
|
|
377
|
+
* styled through the {@link MarkdownTheme} by default, but `inline` lets you
|
|
378
|
+
* override how an inline node flattens into runs — including a measured inline
|
|
379
|
+
* box (`{ advance, content }`), the inline twin of mugen's `Escape`.
|
|
322
380
|
*/
|
|
323
381
|
interface MarkdownComponents {
|
|
324
382
|
paragraph?: MarkdownComponent<Paragraph$1>;
|
|
@@ -330,8 +388,13 @@ interface MarkdownComponents {
|
|
|
330
388
|
table?: MarkdownComponent<Table$1>;
|
|
331
389
|
image?: MarkdownComponent<Image$1>;
|
|
332
390
|
html?: MarkdownComponent<Html$1>;
|
|
391
|
+
/** Inline-node overrides (see {@link InlineComponents}). */
|
|
392
|
+
inline?: InlineComponents;
|
|
333
393
|
}
|
|
334
|
-
|
|
394
|
+
/** Block components resolve to defaults; `inline` stays optional. */
|
|
395
|
+
type ResolvedMarkdownComponents = Required<Omit<MarkdownComponents, 'inline'>> & {
|
|
396
|
+
inline?: InlineComponents;
|
|
397
|
+
};
|
|
335
398
|
/**
|
|
336
399
|
* Identity helper for authoring a typed component set with full inference and
|
|
337
400
|
* `node` narrowing per key:
|
|
@@ -556,4 +619,4 @@ declare function registerLanguage(names: string | readonly string[], p: Language
|
|
|
556
619
|
/** The profile for a fence language, or `null` when the language is unknown. */
|
|
557
620
|
declare function profileFor(lang: string | undefined): LanguageProfile | null;
|
|
558
621
|
//#endregion
|
|
559
|
-
export { type Blockquote, type BoxProps, type Code, CodeBlock, type CodeBlockHeader, type CodeBlockProps, type CodeTokenColors, type DeepPartial, FadeMarkdown, type Font, HStack, type HStackProps, type Heading, type Html, type Image, type InlineFormat, type InlineTextOptions, type LanguageProfile, type Link, type List, type ListItem, Markdown, type MarkdownComponent, type MarkdownComponentProps, type MarkdownComponents, type MarkdownParseOptions, type MarkdownProps, type MarkdownRenderContext, type MarkdownTheme, type Paragraph, type PhrasingContent, type PrimitiveComponent, type RenderMarkdownOptions, type ResolvedMarkdownComponents, RichText, type RichTextProps, type RichTextRun, type Root, type RootContent, type Table, TableBlock, type TableBlockProps, type TableCell, type TableRow, Text, type TextProps, type ThematicBreak, type TokenType, VStack, type VStackProps, baseFormat, clearParseCache, clearRichTextCache, composeFont, defaultComponents, defaultTheme, defaultTokenColors, defineMarkdownComponents, definePrimitive, flattenInline, parseMarkdown, profileFor, registerLanguage, renderMarkdown, resolveTheme };
|
|
622
|
+
export { type Blockquote, type BoxProps, type Code, CodeBlock, type CodeBlockHeader, type CodeBlockProps, type CodeTokenColors, type DeepPartial, FadeMarkdown, type Font, HStack, type HStackProps, type Heading, type Html, type Image, type InlineComponent, type InlineComponents, type InlineFormat, type InlineRenderContext, type InlineTextOptions, type LanguageProfile, type Link, type List, type ListItem, Markdown, type MarkdownComponent, type MarkdownComponentProps, type MarkdownComponents, type MarkdownParseOptions, type MarkdownProps, type MarkdownRenderContext, type MarkdownTheme, type Paragraph, type PhrasingContent, type PrimitiveComponent, type RenderMarkdownOptions, type ResolvedMarkdownComponents, RichText, type RichTextProps, type RichTextRun, type Root, type RootContent, type Table, TableBlock, type TableBlockProps, type TableCell, type TableRow, Text, type TextProps, type ThematicBreak, type TokenType, VStack, type VStackProps, baseFormat, clearParseCache, clearRichTextCache, composeFont, defaultComponents, defaultTheme, defaultTokenColors, defineMarkdownComponents, definePrimitive, flattenInline, measureInline, parseMarkdown, profileFor, registerLanguage, renderMarkdown, resolveTheme };
|
package/dist/index.d.mts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { CSSProperties, JSX, ReactElement, ReactNode } from "react";
|
|
2
2
|
import { BoxProps, Font, Font as Font$1, HStack, HStackProps, MeasurableStyle, PrimitiveComponent, SafeClassName, Text, TextProps, VStack, VStackProps, definePrimitive } from "@wingleeio/mugen";
|
|
3
3
|
import { ParserOptions } from "@incremark/core";
|
|
4
|
-
import { Blockquote, Blockquote as Blockquote$1, Code, Code as Code$1, Heading, Heading as Heading$1, Html, Html as Html$1, Image, Image as Image$1, Link, List, List as List$1, ListItem, Paragraph, Paragraph as Paragraph$1, PhrasingContent, PhrasingContent as PhrasingContent$1, Root, Root as Root$1, RootContent, RootContent as RootContent$1, Table, Table as Table$1, TableCell, TableRow, ThematicBreak, ThematicBreak as ThematicBreak$1 } from "mdast";
|
|
4
|
+
import { Blockquote, Blockquote as Blockquote$1, Code, Code as Code$1, Delete, Emphasis, Heading, Heading as Heading$1, Html, Html as Html$1, Image, Image as Image$1, InlineCode, Link, Link as Link$1, List, List as List$1, ListItem, Paragraph, Paragraph as Paragraph$1, PhrasingContent, PhrasingContent as PhrasingContent$1, Root, Root as Root$1, RootContent, RootContent as RootContent$1, Strong, Table, Table as Table$1, TableCell, TableRow, Text as Text$1, ThematicBreak, ThematicBreak as ThematicBreak$1 } from "mdast";
|
|
5
5
|
|
|
6
6
|
//#region src/parse.d.ts
|
|
7
7
|
/**
|
|
@@ -175,8 +175,19 @@ declare function resolveTheme(theme?: DeepPartial<MarkdownTheme>): MarkdownTheme
|
|
|
175
175
|
* rich-inline layout) and the painted spans use the identical fonts.
|
|
176
176
|
*/
|
|
177
177
|
interface RichTextRun {
|
|
178
|
-
/** The run's text. Ignored when `break` is set. */
|
|
179
|
-
text
|
|
178
|
+
/** The run's text. Ignored when `break` or `advance` is set. */
|
|
179
|
+
text?: string;
|
|
180
|
+
/**
|
|
181
|
+
* Render this run as an **inline box** — the inline analogue of mugen's
|
|
182
|
+
* `Escape`: it reserves exactly `advance` px in the flow (via pretext's
|
|
183
|
+
* `extraWidth`) and paints {@link content}, whatever it is, without measuring
|
|
184
|
+
* its insides. The flow wraps it as one non-breaking atom. The caller owns the
|
|
185
|
+
* contract: `content` must render exactly `advance` px wide and no taller than
|
|
186
|
+
* the line (use the exported `measureInline` to size text-based boxes).
|
|
187
|
+
*/
|
|
188
|
+
advance?: number;
|
|
189
|
+
/** The painted content of an inline box (see {@link advance}). */
|
|
190
|
+
content?: ReactNode;
|
|
180
191
|
/** Measurable font for this run; falls back to the `<RichText font>` prop. */
|
|
181
192
|
font?: Font$1;
|
|
182
193
|
color?: string;
|
|
@@ -217,6 +228,12 @@ interface RichTextProps<C extends string = string> {
|
|
|
217
228
|
* performs over the rendered spans, so the analytic height matches the paint.
|
|
218
229
|
*/
|
|
219
230
|
declare const RichText: <C extends string = string>(props: RichTextProps<C>) => ReactElement;
|
|
231
|
+
/**
|
|
232
|
+
* Measure a string's rendered advance in px for a given measurable font — the
|
|
233
|
+
* same ruler `RichText` measures with. Use it to size an inline box: a text
|
|
234
|
+
* "pill" reserves `measureInline(label, font) + horizontalPadding`.
|
|
235
|
+
*/
|
|
236
|
+
declare function measureInline(text: string, font: Font$1): number;
|
|
220
237
|
/** Drop the rich-inline prepare cache (tests / memory pressure). */
|
|
221
238
|
declare function clearRichTextCache(): void;
|
|
222
239
|
//#endregion
|
|
@@ -252,9 +269,11 @@ declare function composeFont(fmt: InlineFormat): Font$1;
|
|
|
252
269
|
/**
|
|
253
270
|
* Flatten phrasing content into styled runs. Recursive over the inline marks;
|
|
254
271
|
* the result feeds a single `<RichText>` so the whole paragraph wraps as one
|
|
255
|
-
* flow and measures exactly.
|
|
272
|
+
* flow and measures exactly. An `inline` override map can replace how any node
|
|
273
|
+
* type flattens — returning its own runs (e.g. a measured inline box) or `null`
|
|
274
|
+
* to fall through to the default.
|
|
256
275
|
*/
|
|
257
|
-
declare function flattenInline(nodes: readonly PhrasingContent$1[], fmt: InlineFormat, theme: MarkdownTheme, out: RichTextRun[]): void;
|
|
276
|
+
declare function flattenInline(nodes: readonly PhrasingContent$1[], fmt: InlineFormat, theme: MarkdownTheme, out: RichTextRun[], inline?: InlineComponents): void;
|
|
258
277
|
//#endregion
|
|
259
278
|
//#region src/types.d.ts
|
|
260
279
|
/** Options for building a body/heading inline-text (`RichText`) element. */
|
|
@@ -315,10 +334,49 @@ interface MarkdownComponentProps<N> {
|
|
|
315
334
|
*/
|
|
316
335
|
type MarkdownComponent<N> = (props: MarkdownComponentProps<N>) => ReactNode;
|
|
317
336
|
/**
|
|
318
|
-
* The
|
|
319
|
-
*
|
|
320
|
-
*
|
|
321
|
-
|
|
337
|
+
* The context passed to an inline override. It carries the active inline format
|
|
338
|
+
* (so an override can match the surrounding type) and the helpers to build runs
|
|
339
|
+
* that stay exactly measurable.
|
|
340
|
+
*/
|
|
341
|
+
interface InlineRenderContext {
|
|
342
|
+
readonly theme: MarkdownTheme;
|
|
343
|
+
/** The composed inline format at this node (family, size, weight, colour…). */
|
|
344
|
+
readonly fmt: InlineFormat;
|
|
345
|
+
/** Compose a measurable `Font` from the current format plus overrides. */
|
|
346
|
+
font(overrides?: Partial<InlineFormat>): Font$1;
|
|
347
|
+
/**
|
|
348
|
+
* Measure a string's rendered advance in px for a font — to size an inline
|
|
349
|
+
* box. A text pill reserves `measure(label, font) + horizontalPadding`.
|
|
350
|
+
*/
|
|
351
|
+
measure(text: string, font: Font$1): number;
|
|
352
|
+
/** Default-flatten phrasing children into runs, to compose with your own. */
|
|
353
|
+
runs(nodes: readonly PhrasingContent$1[], fmtOverrides?: Partial<InlineFormat>): RichTextRun[];
|
|
354
|
+
}
|
|
355
|
+
/**
|
|
356
|
+
* An inline-node override: given the mdast node and the inline context, return
|
|
357
|
+
* the runs it should flatten to — styled text, an inline box (`{ advance,
|
|
358
|
+
* content }`), or a mix — or `null` to fall back to the default styling.
|
|
359
|
+
*/
|
|
360
|
+
type InlineComponent<N> = (node: N, ctx: InlineRenderContext) => RichTextRun[] | null;
|
|
361
|
+
/**
|
|
362
|
+
* Inline-node overrides, keyed by mdast inline type. The index signature admits
|
|
363
|
+
* custom inline tokens (from a remark plugin) beyond the named ones.
|
|
364
|
+
*/
|
|
365
|
+
interface InlineComponents {
|
|
366
|
+
text?: InlineComponent<Text$1>;
|
|
367
|
+
strong?: InlineComponent<Strong>;
|
|
368
|
+
emphasis?: InlineComponent<Emphasis>;
|
|
369
|
+
delete?: InlineComponent<Delete>;
|
|
370
|
+
inlineCode?: InlineComponent<InlineCode>;
|
|
371
|
+
link?: InlineComponent<Link$1>;
|
|
372
|
+
[type: string]: InlineComponent<any> | undefined;
|
|
373
|
+
}
|
|
374
|
+
/**
|
|
375
|
+
* The overridable components. Block-level marks are keyed by mdast node type and
|
|
376
|
+
* typed to the matching node. Inline marks (bold, italic, code, links) are
|
|
377
|
+
* styled through the {@link MarkdownTheme} by default, but `inline` lets you
|
|
378
|
+
* override how an inline node flattens into runs — including a measured inline
|
|
379
|
+
* box (`{ advance, content }`), the inline twin of mugen's `Escape`.
|
|
322
380
|
*/
|
|
323
381
|
interface MarkdownComponents {
|
|
324
382
|
paragraph?: MarkdownComponent<Paragraph$1>;
|
|
@@ -330,8 +388,13 @@ interface MarkdownComponents {
|
|
|
330
388
|
table?: MarkdownComponent<Table$1>;
|
|
331
389
|
image?: MarkdownComponent<Image$1>;
|
|
332
390
|
html?: MarkdownComponent<Html$1>;
|
|
391
|
+
/** Inline-node overrides (see {@link InlineComponents}). */
|
|
392
|
+
inline?: InlineComponents;
|
|
333
393
|
}
|
|
334
|
-
|
|
394
|
+
/** Block components resolve to defaults; `inline` stays optional. */
|
|
395
|
+
type ResolvedMarkdownComponents = Required<Omit<MarkdownComponents, 'inline'>> & {
|
|
396
|
+
inline?: InlineComponents;
|
|
397
|
+
};
|
|
335
398
|
/**
|
|
336
399
|
* Identity helper for authoring a typed component set with full inference and
|
|
337
400
|
* `node` narrowing per key:
|
|
@@ -556,4 +619,4 @@ declare function registerLanguage(names: string | readonly string[], p: Language
|
|
|
556
619
|
/** The profile for a fence language, or `null` when the language is unknown. */
|
|
557
620
|
declare function profileFor(lang: string | undefined): LanguageProfile | null;
|
|
558
621
|
//#endregion
|
|
559
|
-
export { type Blockquote, type BoxProps, type Code, CodeBlock, type CodeBlockHeader, type CodeBlockProps, type CodeTokenColors, type DeepPartial, FadeMarkdown, type Font, HStack, type HStackProps, type Heading, type Html, type Image, type InlineFormat, type InlineTextOptions, type LanguageProfile, type Link, type List, type ListItem, Markdown, type MarkdownComponent, type MarkdownComponentProps, type MarkdownComponents, type MarkdownParseOptions, type MarkdownProps, type MarkdownRenderContext, type MarkdownTheme, type Paragraph, type PhrasingContent, type PrimitiveComponent, type RenderMarkdownOptions, type ResolvedMarkdownComponents, RichText, type RichTextProps, type RichTextRun, type Root, type RootContent, type Table, TableBlock, type TableBlockProps, type TableCell, type TableRow, Text, type TextProps, type ThematicBreak, type TokenType, VStack, type VStackProps, baseFormat, clearParseCache, clearRichTextCache, composeFont, defaultComponents, defaultTheme, defaultTokenColors, defineMarkdownComponents, definePrimitive, flattenInline, parseMarkdown, profileFor, registerLanguage, renderMarkdown, resolveTheme };
|
|
622
|
+
export { type Blockquote, type BoxProps, type Code, CodeBlock, type CodeBlockHeader, type CodeBlockProps, type CodeTokenColors, type DeepPartial, FadeMarkdown, type Font, HStack, type HStackProps, type Heading, type Html, type Image, type InlineComponent, type InlineComponents, type InlineFormat, type InlineRenderContext, type InlineTextOptions, type LanguageProfile, type Link, type List, type ListItem, Markdown, type MarkdownComponent, type MarkdownComponentProps, type MarkdownComponents, type MarkdownParseOptions, type MarkdownProps, type MarkdownRenderContext, type MarkdownTheme, type Paragraph, type PhrasingContent, type PrimitiveComponent, type RenderMarkdownOptions, type ResolvedMarkdownComponents, RichText, type RichTextProps, type RichTextRun, type Root, type RootContent, type Table, TableBlock, type TableBlockProps, type TableCell, type TableRow, Text, type TextProps, type ThematicBreak, type TokenType, VStack, type VStackProps, baseFormat, clearParseCache, clearRichTextCache, composeFont, defaultComponents, defaultTheme, defaultTokenColors, defineMarkdownComponents, definePrimitive, flattenInline, measureInline, parseMarkdown, profileFor, registerLanguage, renderMarkdown, resolveTheme };
|
package/dist/index.mjs
CHANGED
|
@@ -226,120 +226,6 @@ function resolveTheme(theme) {
|
|
|
226
226
|
return resolved;
|
|
227
227
|
}
|
|
228
228
|
//#endregion
|
|
229
|
-
//#region src/inline.ts
|
|
230
|
-
/** A base format for body text at a given size/weight/colour. */
|
|
231
|
-
function baseFormat(theme, opts = {}) {
|
|
232
|
-
return {
|
|
233
|
-
family: theme.fontFamily,
|
|
234
|
-
monoFamily: theme.monoFamily,
|
|
235
|
-
size: opts.size ?? theme.fontSize,
|
|
236
|
-
weight: opts.weight ?? 400,
|
|
237
|
-
italic: false,
|
|
238
|
-
mono: false,
|
|
239
|
-
underline: false,
|
|
240
|
-
strike: false,
|
|
241
|
-
color: opts.color ?? (theme.color !== "inherit" ? theme.color : void 0)
|
|
242
|
-
};
|
|
243
|
-
}
|
|
244
|
-
/** Compose a measurable `Font` shorthand from a format. */
|
|
245
|
-
function composeFont(fmt) {
|
|
246
|
-
const family = fmt.mono ? fmt.monoFamily : fmt.family;
|
|
247
|
-
return `${fmt.italic ? "italic " : ""}${fmt.weight} ${fmt.size}px ${family}`;
|
|
248
|
-
}
|
|
249
|
-
function pushRun(out, text, fmt) {
|
|
250
|
-
if (text.length === 0) return;
|
|
251
|
-
const run = {
|
|
252
|
-
text,
|
|
253
|
-
font: composeFont(fmt)
|
|
254
|
-
};
|
|
255
|
-
if (fmt.color != null) run.color = fmt.color;
|
|
256
|
-
if (fmt.background != null) run.background = fmt.background;
|
|
257
|
-
const decoration = [fmt.underline ? "underline" : "", fmt.strike ? "line-through" : ""].filter(Boolean).join(" ");
|
|
258
|
-
if (decoration) run.decoration = decoration;
|
|
259
|
-
if (fmt.href != null) {
|
|
260
|
-
run.href = fmt.href;
|
|
261
|
-
run.as = "a";
|
|
262
|
-
} else if (fmt.mono) run.as = "code";
|
|
263
|
-
out.push(run);
|
|
264
|
-
}
|
|
265
|
-
/**
|
|
266
|
-
* Flatten phrasing content into styled runs. Recursive over the inline marks;
|
|
267
|
-
* the result feeds a single `<RichText>` so the whole paragraph wraps as one
|
|
268
|
-
* flow and measures exactly.
|
|
269
|
-
*/
|
|
270
|
-
function flattenInline(nodes, fmt, theme, out) {
|
|
271
|
-
for (const node of nodes) switch (node.type) {
|
|
272
|
-
case "text":
|
|
273
|
-
pushRun(out, node.value, fmt);
|
|
274
|
-
break;
|
|
275
|
-
case "strong":
|
|
276
|
-
flattenInline(node.children, {
|
|
277
|
-
...fmt,
|
|
278
|
-
weight: theme.strongWeight
|
|
279
|
-
}, theme, out);
|
|
280
|
-
break;
|
|
281
|
-
case "emphasis":
|
|
282
|
-
flattenInline(node.children, {
|
|
283
|
-
...fmt,
|
|
284
|
-
italic: theme.emphasisItalic ? true : fmt.italic
|
|
285
|
-
}, theme, out);
|
|
286
|
-
break;
|
|
287
|
-
case "delete":
|
|
288
|
-
flattenInline(node.children, {
|
|
289
|
-
...fmt,
|
|
290
|
-
strike: true
|
|
291
|
-
}, theme, out);
|
|
292
|
-
break;
|
|
293
|
-
case "inlineCode":
|
|
294
|
-
pushRun(out, node.value, {
|
|
295
|
-
...fmt,
|
|
296
|
-
mono: true,
|
|
297
|
-
size: Math.round(fmt.size * theme.inlineCode.sizeScale),
|
|
298
|
-
color: theme.inlineCode.color !== "inherit" ? theme.inlineCode.color : fmt.color,
|
|
299
|
-
background: theme.inlineCode.background
|
|
300
|
-
});
|
|
301
|
-
break;
|
|
302
|
-
case "link":
|
|
303
|
-
flattenInline(node.children, {
|
|
304
|
-
...fmt,
|
|
305
|
-
href: node.url,
|
|
306
|
-
color: theme.link.color,
|
|
307
|
-
underline: theme.link.underline ? true : fmt.underline
|
|
308
|
-
}, theme, out);
|
|
309
|
-
break;
|
|
310
|
-
case "linkReference":
|
|
311
|
-
flattenInline(node.children, fmt, theme, out);
|
|
312
|
-
break;
|
|
313
|
-
case "break":
|
|
314
|
-
out.push({
|
|
315
|
-
text: "",
|
|
316
|
-
break: true
|
|
317
|
-
});
|
|
318
|
-
break;
|
|
319
|
-
case "image":
|
|
320
|
-
if (node.alt) pushRun(out, node.alt, {
|
|
321
|
-
...fmt,
|
|
322
|
-
color: theme.image.color
|
|
323
|
-
});
|
|
324
|
-
break;
|
|
325
|
-
case "imageReference":
|
|
326
|
-
if (node.alt) pushRun(out, node.alt, {
|
|
327
|
-
...fmt,
|
|
328
|
-
color: theme.image.color
|
|
329
|
-
});
|
|
330
|
-
break;
|
|
331
|
-
case "footnoteReference":
|
|
332
|
-
pushRun(out, `[${node.label ?? node.identifier}]`, {
|
|
333
|
-
...fmt,
|
|
334
|
-
color: theme.link.color
|
|
335
|
-
});
|
|
336
|
-
break;
|
|
337
|
-
default:
|
|
338
|
-
if ("children" in node && Array.isArray(node.children)) flattenInline(node.children, fmt, theme, out);
|
|
339
|
-
break;
|
|
340
|
-
}
|
|
341
|
-
}
|
|
342
|
-
//#endregion
|
|
343
229
|
//#region src/primitives/rich-text.tsx
|
|
344
230
|
function resolveRunFont(run, fallback) {
|
|
345
231
|
const font = run.font ?? fallback;
|
|
@@ -347,6 +233,7 @@ function resolveRunFont(run, fallback) {
|
|
|
347
233
|
assertMeasurableFont(font);
|
|
348
234
|
return font;
|
|
349
235
|
}
|
|
236
|
+
const BOX_PLACEHOLDER = "";
|
|
350
237
|
/** Split runs into hard-break-delimited segments, each a list of rich-inline items. */
|
|
351
238
|
function segmentItems(runs, fallback) {
|
|
352
239
|
const segments = [];
|
|
@@ -357,9 +244,19 @@ function segmentItems(runs, fallback) {
|
|
|
357
244
|
cur = [];
|
|
358
245
|
continue;
|
|
359
246
|
}
|
|
360
|
-
if (run.
|
|
247
|
+
if (run.advance != null) {
|
|
248
|
+
cur.push({
|
|
249
|
+
text: BOX_PLACEHOLDER,
|
|
250
|
+
font: resolveRunFont(run, fallback),
|
|
251
|
+
extraWidth: Math.max(0, run.advance),
|
|
252
|
+
break: "never"
|
|
253
|
+
});
|
|
254
|
+
continue;
|
|
255
|
+
}
|
|
256
|
+
const text = run.text ?? "";
|
|
257
|
+
if (text.length === 0) continue;
|
|
361
258
|
const item = {
|
|
362
|
-
text
|
|
259
|
+
text,
|
|
363
260
|
font: resolveRunFont(run, fallback)
|
|
364
261
|
};
|
|
365
262
|
if (run.letterSpacing != null) item.letterSpacing = run.letterSpacing;
|
|
@@ -424,6 +321,15 @@ function renderRichText(props) {
|
|
|
424
321
|
};
|
|
425
322
|
const children = props.runs.map((run, i) => {
|
|
426
323
|
if (run.break) return createElement("br", { key: i });
|
|
324
|
+
if (run.advance != null) return createElement("span", {
|
|
325
|
+
key: i,
|
|
326
|
+
style: {
|
|
327
|
+
display: "inline-block",
|
|
328
|
+
verticalAlign: "middle",
|
|
329
|
+
lineHeight: 0,
|
|
330
|
+
whiteSpace: "nowrap"
|
|
331
|
+
}
|
|
332
|
+
}, run.content);
|
|
427
333
|
const tag = run.as ?? (run.href != null ? "a" : "span");
|
|
428
334
|
const elementProps = {
|
|
429
335
|
key: i,
|
|
@@ -442,7 +348,7 @@ function renderRichText(props) {
|
|
|
442
348
|
if (run.title != null) elementProps.title = run.title;
|
|
443
349
|
if (run.onClick != null) elementProps.onClick = run.onClick;
|
|
444
350
|
if (run.className != null) elementProps.className = run.className;
|
|
445
|
-
return createElement(tag, elementProps, run.text);
|
|
351
|
+
return createElement(tag, elementProps, run.text ?? "");
|
|
446
352
|
});
|
|
447
353
|
return createElement("div", {
|
|
448
354
|
className: props.className,
|
|
@@ -470,12 +376,173 @@ const RichText = markPrimitive(renderRichText, {
|
|
|
470
376
|
return max;
|
|
471
377
|
}
|
|
472
378
|
});
|
|
379
|
+
/**
|
|
380
|
+
* Measure a string's rendered advance in px for a given measurable font — the
|
|
381
|
+
* same ruler `RichText` measures with. Use it to size an inline box: a text
|
|
382
|
+
* "pill" reserves `measureInline(label, font) + horizontalPadding`.
|
|
383
|
+
*/
|
|
384
|
+
function measureInline(text, font) {
|
|
385
|
+
if (text.length === 0) return 0;
|
|
386
|
+
assertMeasurableFont(font);
|
|
387
|
+
return measureRichInlineStats(prepareCached([{
|
|
388
|
+
text,
|
|
389
|
+
font
|
|
390
|
+
}]), 1e7).maxLineWidth;
|
|
391
|
+
}
|
|
473
392
|
/** Drop the rich-inline prepare cache (tests / memory pressure). */
|
|
474
393
|
function clearRichTextCache() {
|
|
475
394
|
prepCache.clear();
|
|
476
395
|
cacheEpoch = -1;
|
|
477
396
|
}
|
|
478
397
|
//#endregion
|
|
398
|
+
//#region src/inline.ts
|
|
399
|
+
/** A base format for body text at a given size/weight/colour. */
|
|
400
|
+
function baseFormat(theme, opts = {}) {
|
|
401
|
+
return {
|
|
402
|
+
family: theme.fontFamily,
|
|
403
|
+
monoFamily: theme.monoFamily,
|
|
404
|
+
size: opts.size ?? theme.fontSize,
|
|
405
|
+
weight: opts.weight ?? 400,
|
|
406
|
+
italic: false,
|
|
407
|
+
mono: false,
|
|
408
|
+
underline: false,
|
|
409
|
+
strike: false,
|
|
410
|
+
color: opts.color ?? (theme.color !== "inherit" ? theme.color : void 0)
|
|
411
|
+
};
|
|
412
|
+
}
|
|
413
|
+
/** Compose a measurable `Font` shorthand from a format. */
|
|
414
|
+
function composeFont(fmt) {
|
|
415
|
+
const family = fmt.mono ? fmt.monoFamily : fmt.family;
|
|
416
|
+
return `${fmt.italic ? "italic " : ""}${fmt.weight} ${fmt.size}px ${family}`;
|
|
417
|
+
}
|
|
418
|
+
function pushRun(out, text, fmt) {
|
|
419
|
+
if (text.length === 0) return;
|
|
420
|
+
const run = {
|
|
421
|
+
text,
|
|
422
|
+
font: composeFont(fmt)
|
|
423
|
+
};
|
|
424
|
+
if (fmt.color != null) run.color = fmt.color;
|
|
425
|
+
if (fmt.background != null) run.background = fmt.background;
|
|
426
|
+
const decoration = [fmt.underline ? "underline" : "", fmt.strike ? "line-through" : ""].filter(Boolean).join(" ");
|
|
427
|
+
if (decoration) run.decoration = decoration;
|
|
428
|
+
if (fmt.href != null) {
|
|
429
|
+
run.href = fmt.href;
|
|
430
|
+
run.as = "a";
|
|
431
|
+
} else if (fmt.mono) run.as = "code";
|
|
432
|
+
out.push(run);
|
|
433
|
+
}
|
|
434
|
+
/** Build the context handed to an inline override. */
|
|
435
|
+
function makeInlineCtx(fmt, theme, inline) {
|
|
436
|
+
return {
|
|
437
|
+
theme,
|
|
438
|
+
fmt,
|
|
439
|
+
font: (overrides) => composeFont(overrides ? {
|
|
440
|
+
...fmt,
|
|
441
|
+
...overrides
|
|
442
|
+
} : fmt),
|
|
443
|
+
measure: (text, font) => measureInline(text, font),
|
|
444
|
+
runs: (nodes, fmtOverrides) => {
|
|
445
|
+
const sub = [];
|
|
446
|
+
flattenInline(nodes, fmtOverrides ? {
|
|
447
|
+
...fmt,
|
|
448
|
+
...fmtOverrides
|
|
449
|
+
} : fmt, theme, sub, inline);
|
|
450
|
+
return sub;
|
|
451
|
+
}
|
|
452
|
+
};
|
|
453
|
+
}
|
|
454
|
+
/**
|
|
455
|
+
* Flatten phrasing content into styled runs. Recursive over the inline marks;
|
|
456
|
+
* the result feeds a single `<RichText>` so the whole paragraph wraps as one
|
|
457
|
+
* flow and measures exactly. An `inline` override map can replace how any node
|
|
458
|
+
* type flattens — returning its own runs (e.g. a measured inline box) or `null`
|
|
459
|
+
* to fall through to the default.
|
|
460
|
+
*/
|
|
461
|
+
function flattenInline(nodes, fmt, theme, out, inline) {
|
|
462
|
+
for (const node of nodes) {
|
|
463
|
+
if (inline != null) {
|
|
464
|
+
const override = inline[node.type];
|
|
465
|
+
if (override != null) {
|
|
466
|
+
const produced = override(node, makeInlineCtx(fmt, theme, inline));
|
|
467
|
+
if (produced != null) {
|
|
468
|
+
for (const run of produced) out.push(run);
|
|
469
|
+
continue;
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
switch (node.type) {
|
|
474
|
+
case "text":
|
|
475
|
+
pushRun(out, node.value, fmt);
|
|
476
|
+
break;
|
|
477
|
+
case "strong":
|
|
478
|
+
flattenInline(node.children, {
|
|
479
|
+
...fmt,
|
|
480
|
+
weight: theme.strongWeight
|
|
481
|
+
}, theme, out, inline);
|
|
482
|
+
break;
|
|
483
|
+
case "emphasis":
|
|
484
|
+
flattenInline(node.children, {
|
|
485
|
+
...fmt,
|
|
486
|
+
italic: theme.emphasisItalic ? true : fmt.italic
|
|
487
|
+
}, theme, out, inline);
|
|
488
|
+
break;
|
|
489
|
+
case "delete":
|
|
490
|
+
flattenInline(node.children, {
|
|
491
|
+
...fmt,
|
|
492
|
+
strike: true
|
|
493
|
+
}, theme, out, inline);
|
|
494
|
+
break;
|
|
495
|
+
case "inlineCode":
|
|
496
|
+
pushRun(out, node.value, {
|
|
497
|
+
...fmt,
|
|
498
|
+
mono: true,
|
|
499
|
+
size: Math.round(fmt.size * theme.inlineCode.sizeScale),
|
|
500
|
+
color: theme.inlineCode.color !== "inherit" ? theme.inlineCode.color : fmt.color,
|
|
501
|
+
background: theme.inlineCode.background
|
|
502
|
+
});
|
|
503
|
+
break;
|
|
504
|
+
case "link":
|
|
505
|
+
flattenInline(node.children, {
|
|
506
|
+
...fmt,
|
|
507
|
+
href: node.url,
|
|
508
|
+
color: theme.link.color,
|
|
509
|
+
underline: theme.link.underline ? true : fmt.underline
|
|
510
|
+
}, theme, out, inline);
|
|
511
|
+
break;
|
|
512
|
+
case "linkReference":
|
|
513
|
+
flattenInline(node.children, fmt, theme, out, inline);
|
|
514
|
+
break;
|
|
515
|
+
case "break":
|
|
516
|
+
out.push({
|
|
517
|
+
text: "",
|
|
518
|
+
break: true
|
|
519
|
+
});
|
|
520
|
+
break;
|
|
521
|
+
case "image":
|
|
522
|
+
if (node.alt) pushRun(out, node.alt, {
|
|
523
|
+
...fmt,
|
|
524
|
+
color: theme.image.color
|
|
525
|
+
});
|
|
526
|
+
break;
|
|
527
|
+
case "imageReference":
|
|
528
|
+
if (node.alt) pushRun(out, node.alt, {
|
|
529
|
+
...fmt,
|
|
530
|
+
color: theme.image.color
|
|
531
|
+
});
|
|
532
|
+
break;
|
|
533
|
+
case "footnoteReference":
|
|
534
|
+
pushRun(out, `[${node.label ?? node.identifier}]`, {
|
|
535
|
+
...fmt,
|
|
536
|
+
color: theme.link.color
|
|
537
|
+
});
|
|
538
|
+
break;
|
|
539
|
+
default:
|
|
540
|
+
if ("children" in node && Array.isArray(node.children)) flattenInline(node.children, fmt, theme, out, inline);
|
|
541
|
+
break;
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
//#endregion
|
|
479
546
|
//#region src/highlight/languages.ts
|
|
480
547
|
function words(s) {
|
|
481
548
|
return new Set(s.split(" "));
|
|
@@ -1929,7 +1996,7 @@ function createContext(theme, components) {
|
|
|
1929
1996
|
...base
|
|
1930
1997
|
};
|
|
1931
1998
|
const out = [];
|
|
1932
|
-
flattenInline(nodes, fmt, theme, out);
|
|
1999
|
+
flattenInline(nodes, fmt, theme, out, components.inline);
|
|
1933
2000
|
return out;
|
|
1934
2001
|
},
|
|
1935
2002
|
inlineText(nodes, opts = {}) {
|
|
@@ -1939,7 +2006,7 @@ function createContext(theme, components) {
|
|
|
1939
2006
|
...opts.color != null ? { color: opts.color } : null
|
|
1940
2007
|
});
|
|
1941
2008
|
const out = [];
|
|
1942
|
-
flattenInline(nodes, fmt, theme, out);
|
|
2009
|
+
flattenInline(nodes, fmt, theme, out, components.inline);
|
|
1943
2010
|
return createElement(RichText, {
|
|
1944
2011
|
runs: out,
|
|
1945
2012
|
font: composeFont(fmt),
|
|
@@ -1982,25 +2049,42 @@ const EMA_SEED_MS = 160;
|
|
|
1982
2049
|
const MIN_FADE_MS = 120;
|
|
1983
2050
|
const MAX_FADE_MS = 400;
|
|
1984
2051
|
const MAX_VEILS = 32;
|
|
1985
|
-
function commonPrefixLength(a, b) {
|
|
1986
|
-
const n = Math.min(a.length, b.length);
|
|
1987
|
-
let i = 0;
|
|
1988
|
-
while (i < n && a.charCodeAt(i) === b.charCodeAt(i)) i++;
|
|
1989
|
-
return i;
|
|
1990
|
-
}
|
|
1991
2052
|
function inChrome(node, container) {
|
|
1992
2053
|
for (let p = node.parentElement; p != null && p !== container; p = p.parentElement) if (p.tagName === "BUTTON") return true;
|
|
1993
2054
|
return false;
|
|
1994
2055
|
}
|
|
2056
|
+
/** Whether `el` (or an ancestor up to `container`) is interactive chrome. */
|
|
2057
|
+
function elementInChrome(el, container) {
|
|
2058
|
+
for (let p = el; p != null && p !== container; p = p.parentElement) if (p.tagName === "BUTTON") return true;
|
|
2059
|
+
return false;
|
|
2060
|
+
}
|
|
1995
2061
|
function contentTextFilter(container) {
|
|
1996
2062
|
return { acceptNode: (n) => inChrome(n, container) ? NodeFilter.FILTER_REJECT : NodeFilter.FILTER_ACCEPT };
|
|
1997
2063
|
}
|
|
1998
|
-
/** `container
|
|
1999
|
-
function
|
|
2064
|
+
/** Chrome-free text length of `container` — a full walk; used to seed/reconcile. */
|
|
2065
|
+
function chromeFreeLength(container) {
|
|
2000
2066
|
const walker = document.createTreeWalker(container, NodeFilter.SHOW_TEXT, contentTextFilter(container));
|
|
2001
|
-
let
|
|
2002
|
-
for (let
|
|
2003
|
-
return
|
|
2067
|
+
let n = 0;
|
|
2068
|
+
for (let t = walker.nextNode(); t != null; t = walker.nextNode()) n += t.data.length;
|
|
2069
|
+
return n;
|
|
2070
|
+
}
|
|
2071
|
+
/**
|
|
2072
|
+
* Chrome-free text length of one node's subtree — used to fold a single
|
|
2073
|
+
* added/removed node into the running length without touching the rest of the
|
|
2074
|
+
* DOM. Buttons inside the node are skipped; a button node itself counts zero.
|
|
2075
|
+
*/
|
|
2076
|
+
function subtreeTextLength(node) {
|
|
2077
|
+
if (node.nodeType === 3) return node.data.length;
|
|
2078
|
+
if (node.nodeType !== 1) return 0;
|
|
2079
|
+
const el = node;
|
|
2080
|
+
if (el.tagName === "BUTTON") return 0;
|
|
2081
|
+
let n = 0;
|
|
2082
|
+
const walker = document.createTreeWalker(el, NodeFilter.SHOW_TEXT, { acceptNode: (t) => {
|
|
2083
|
+
for (let p = t.parentElement; p != null && p !== el; p = p.parentElement) if (p.tagName === "BUTTON") return NodeFilter.FILTER_REJECT;
|
|
2084
|
+
return NodeFilter.FILTER_ACCEPT;
|
|
2085
|
+
} });
|
|
2086
|
+
for (let t = walker.nextNode(); t != null; t = walker.nextNode()) n += t.data.length;
|
|
2087
|
+
return n;
|
|
2004
2088
|
}
|
|
2005
2089
|
let scratch;
|
|
2006
2090
|
function scratchCtx() {
|
|
@@ -2042,14 +2126,20 @@ function effectiveBackground(el, cache) {
|
|
|
2042
2126
|
}
|
|
2043
2127
|
/**
|
|
2044
2128
|
* Paints the dissolving veil over a single content element's newly-arrived text.
|
|
2045
|
-
* Driven by a MutationObserver
|
|
2046
|
-
*
|
|
2129
|
+
* Driven by a MutationObserver that folds each change into a running length, so
|
|
2130
|
+
* a streaming tick costs O(delta) — never an O(n) walk of the whole content,
|
|
2131
|
+
* which is what made long streams lag. A rAF loop runs only while veils are
|
|
2132
|
+
* alive, then stops until the next mutation, so an idle (settled) block uses no
|
|
2133
|
+
* frames. Only the small, recent veil region is ever measured for geometry.
|
|
2047
2134
|
*/
|
|
2048
2135
|
var FadePainter = class {
|
|
2049
2136
|
content = null;
|
|
2050
2137
|
canvas = null;
|
|
2051
2138
|
ctx = null;
|
|
2052
|
-
|
|
2139
|
+
/** Chrome-free text length, tracked incrementally from mutation records. */
|
|
2140
|
+
length = 0;
|
|
2141
|
+
/** Net length change observed since the last frame folded it in. */
|
|
2142
|
+
pendingDelta = 0;
|
|
2053
2143
|
veils = [];
|
|
2054
2144
|
ema = EMA_SEED_MS;
|
|
2055
2145
|
lastAppend = 0;
|
|
@@ -2063,14 +2153,29 @@ var FadePainter = class {
|
|
|
2063
2153
|
this.content = content;
|
|
2064
2154
|
this.canvas = canvas;
|
|
2065
2155
|
this.ctx = ctx;
|
|
2066
|
-
this.
|
|
2067
|
-
this.mo = new MutationObserver(() => this.
|
|
2156
|
+
this.length = chromeFreeLength(content);
|
|
2157
|
+
this.mo = new MutationObserver((records) => this.onMutations(records));
|
|
2068
2158
|
this.mo.observe(content, {
|
|
2069
2159
|
subtree: true,
|
|
2070
2160
|
childList: true,
|
|
2071
|
-
characterData: true
|
|
2161
|
+
characterData: true,
|
|
2162
|
+
characterDataOldValue: true
|
|
2072
2163
|
});
|
|
2073
2164
|
}
|
|
2165
|
+
onMutations(records) {
|
|
2166
|
+
const content = this.content;
|
|
2167
|
+
if (content == null) return;
|
|
2168
|
+
for (const rec of records) if (rec.type === "characterData") {
|
|
2169
|
+
const node = rec.target;
|
|
2170
|
+
if (inChrome(node, content)) continue;
|
|
2171
|
+
this.pendingDelta += (node.nodeValue?.length ?? 0) - (rec.oldValue?.length ?? 0);
|
|
2172
|
+
} else if (rec.type === "childList") {
|
|
2173
|
+
if (rec.target instanceof Element && elementInChrome(rec.target, content)) continue;
|
|
2174
|
+
for (let i = 0; i < rec.addedNodes.length; i++) this.pendingDelta += subtreeTextLength(rec.addedNodes[i]);
|
|
2175
|
+
for (let i = 0; i < rec.removedNodes.length; i++) this.pendingDelta -= subtreeTextLength(rec.removedNodes[i]);
|
|
2176
|
+
}
|
|
2177
|
+
this.wake();
|
|
2178
|
+
}
|
|
2074
2179
|
destroy() {
|
|
2075
2180
|
this.mo?.disconnect();
|
|
2076
2181
|
this.mo = null;
|
|
@@ -2095,25 +2200,24 @@ var FadePainter = class {
|
|
|
2095
2200
|
return;
|
|
2096
2201
|
}
|
|
2097
2202
|
const now = performance.now();
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
...v,
|
|
2103
|
-
start: Math.min(v.start, prefix),
|
|
2104
|
-
end: Math.min(v.end, prefix)
|
|
2105
|
-
})).filter((v) => v.end > v.start);
|
|
2106
|
-
if (text.length > prefix) {
|
|
2203
|
+
if (this.pendingDelta !== 0) {
|
|
2204
|
+
const newLength = Math.max(0, this.length + this.pendingDelta);
|
|
2205
|
+
this.pendingDelta = 0;
|
|
2206
|
+
if (newLength > this.length) {
|
|
2107
2207
|
if (this.lastAppend > 0) this.ema = this.ema * .7 + Math.min(now - this.lastAppend, 1e3) * .3;
|
|
2108
2208
|
this.lastAppend = now;
|
|
2109
2209
|
this.veils.push({
|
|
2110
|
-
start:
|
|
2111
|
-
end:
|
|
2210
|
+
start: this.length,
|
|
2211
|
+
end: newLength,
|
|
2112
2212
|
t0: now
|
|
2113
2213
|
});
|
|
2114
2214
|
if (this.veils.length > MAX_VEILS) this.veils.splice(0, this.veils.length - MAX_VEILS);
|
|
2115
|
-
}
|
|
2116
|
-
|
|
2215
|
+
} else if (newLength < this.length) this.veils = this.veils.map((v) => ({
|
|
2216
|
+
...v,
|
|
2217
|
+
start: Math.min(v.start, newLength),
|
|
2218
|
+
end: Math.min(v.end, newLength)
|
|
2219
|
+
})).filter((v) => v.end > v.start);
|
|
2220
|
+
this.length = newLength;
|
|
2117
2221
|
}
|
|
2118
2222
|
const duration = Math.min(MAX_FADE_MS, Math.max(MIN_FADE_MS, this.ema * 3));
|
|
2119
2223
|
const boost = 1 + .3 * Math.max(0, this.veils.length - 2);
|
|
@@ -2173,6 +2277,7 @@ var FadePainter = class {
|
|
|
2173
2277
|
}
|
|
2174
2278
|
if (this.veils.length > 0) this.raf = requestAnimationFrame(this.frame);
|
|
2175
2279
|
else {
|
|
2280
|
+
this.length = chromeFreeLength(content);
|
|
2176
2281
|
this.running = false;
|
|
2177
2282
|
this.raf = 0;
|
|
2178
2283
|
}
|
|
@@ -2256,4 +2361,4 @@ function defineMarkdownComponents(components) {
|
|
|
2256
2361
|
return components;
|
|
2257
2362
|
}
|
|
2258
2363
|
//#endregion
|
|
2259
|
-
export { CodeBlock, FadeMarkdown, HStack, Markdown, RichText, TableBlock, Text, VStack, baseFormat, clearParseCache, clearRichTextCache, composeFont, defaultComponents, defaultTheme, defaultTokenColors, defineMarkdownComponents, definePrimitive, flattenInline, parseMarkdown, profileFor, registerLanguage, renderMarkdown, resolveTheme };
|
|
2364
|
+
export { CodeBlock, FadeMarkdown, HStack, Markdown, RichText, TableBlock, Text, VStack, baseFormat, clearParseCache, clearRichTextCache, composeFont, defaultComponents, defaultTheme, defaultTokenColors, defineMarkdownComponents, definePrimitive, flattenInline, measureInline, parseMarkdown, profileFor, registerLanguage, renderMarkdown, resolveTheme };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wingleeio/mugen-markdown",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.1",
|
|
4
4
|
"description": "Measurable markdown for mugen — incremark-parsed, rendered with mugen primitives so the virtualizer's tree walker computes exact row heights.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Wing Lee <contact@winglee.io>",
|