@lobehub/ui 5.39.2 → 5.40.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/es/Highlighter/SyntaxHighlighter/StreamRenderer.mjs +34 -9
- package/es/Highlighter/SyntaxHighlighter/StreamRenderer.mjs.map +1 -1
- package/es/Highlighter/SyntaxHighlighter/style.mjs +6 -1
- package/es/Highlighter/SyntaxHighlighter/style.mjs.map +1 -1
- package/es/Highlighter/SyntaxHighlighter/tokenFade.mjs +64 -0
- package/es/Highlighter/SyntaxHighlighter/tokenFade.mjs.map +1 -0
- package/es/base-ui/Input/style.d.mts +1 -1
- package/package.json +1 -1
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { useStreamHighlight } from "../../hooks/useStreamHighlight.mjs";
|
|
3
|
-
import {
|
|
3
|
+
import { createTokenFadeStore, markTokenBirths, resolveTokenFadeStyle } from "./tokenFade.mjs";
|
|
4
|
+
import { memo, useRef } from "react";
|
|
4
5
|
import { jsx } from "react/jsx-runtime";
|
|
5
6
|
import { cx } from "antd-style";
|
|
6
7
|
import { getTokenStyleObject } from "@shikijs/core";
|
|
@@ -20,13 +21,18 @@ const getTokenInlineStyle = (token) => {
|
|
|
20
21
|
whiteSpace: "pre"
|
|
21
22
|
};
|
|
22
23
|
};
|
|
23
|
-
const TokenSpan = memo(({ token }) => {
|
|
24
|
+
const TokenSpan = memo(({ fadeStyle, token }) => {
|
|
25
|
+
const style = fadeStyle ? {
|
|
26
|
+
...getTokenInlineStyle(token),
|
|
27
|
+
...fadeStyle
|
|
28
|
+
} : getTokenInlineStyle(token);
|
|
24
29
|
return /* @__PURE__ */ jsx("span", {
|
|
25
|
-
|
|
30
|
+
className: fadeStyle ? "stream-char" : void 0,
|
|
31
|
+
style,
|
|
26
32
|
children: token.content
|
|
27
|
-
}
|
|
28
|
-
}, (prev, next) => prev.token === next.token);
|
|
29
|
-
const TokenLine = memo(({ line }) => {
|
|
33
|
+
});
|
|
34
|
+
}, (prev, next) => prev.token === next.token && prev.fadeStyle === next.fadeStyle);
|
|
35
|
+
const TokenLine = memo(({ fade, line, now, start }) => {
|
|
30
36
|
if (!line.length) return /* @__PURE__ */ jsx("span", {
|
|
31
37
|
className: "line",
|
|
32
38
|
children: /* @__PURE__ */ jsx("span", {
|
|
@@ -34,11 +40,19 @@ const TokenLine = memo(({ line }) => {
|
|
|
34
40
|
children: "\xA0"
|
|
35
41
|
})
|
|
36
42
|
});
|
|
43
|
+
let offset = start;
|
|
37
44
|
return /* @__PURE__ */ jsx("span", {
|
|
38
45
|
className: "line",
|
|
39
|
-
children: line.map((token
|
|
46
|
+
children: line.map((token) => {
|
|
47
|
+
const tokenOffset = offset;
|
|
48
|
+
offset += token.content.length;
|
|
49
|
+
return /* @__PURE__ */ jsx(TokenSpan, {
|
|
50
|
+
fadeStyle: resolveTokenFadeStyle(fade, tokenOffset, now),
|
|
51
|
+
token
|
|
52
|
+
}, tokenOffset);
|
|
53
|
+
})
|
|
40
54
|
});
|
|
41
|
-
}, (prev, next) => prev.line === next.line);
|
|
55
|
+
}, (prev, next) => prev.line === next.line && prev.start === next.start);
|
|
42
56
|
const StreamRenderer = memo(({ children, className, enableTransformer, fallbackClassName, language, style, theme }) => {
|
|
43
57
|
const safeChildren = children ?? "";
|
|
44
58
|
const streaming = useStreamHighlight(safeChildren, {
|
|
@@ -49,6 +63,12 @@ const StreamRenderer = memo(({ children, className, enableTransformer, fallbackC
|
|
|
49
63
|
});
|
|
50
64
|
const lines = streaming?.lines;
|
|
51
65
|
const preStyle = streaming?.preStyle;
|
|
66
|
+
const fadeRef = useRef(createTokenFadeStore());
|
|
67
|
+
const previousTextRef = useRef("");
|
|
68
|
+
if (!safeChildren.startsWith(previousTextRef.current)) fadeRef.current = createTokenFadeStore();
|
|
69
|
+
previousTextRef.current = safeChildren;
|
|
70
|
+
const now = performance.now();
|
|
71
|
+
const lineStarts = lines ? markTokenBirths(fadeRef.current, lines, now) : [];
|
|
52
72
|
if (!lines || lines.length === 0) return /* @__PURE__ */ jsx("div", {
|
|
53
73
|
className: fallbackClassName,
|
|
54
74
|
dir: "ltr",
|
|
@@ -69,7 +89,12 @@ const StreamRenderer = memo(({ children, className, enableTransformer, fallbackC
|
|
|
69
89
|
flexDirection: "column",
|
|
70
90
|
whiteSpace: "pre"
|
|
71
91
|
},
|
|
72
|
-
children: lines.map((line, index) => /* @__PURE__ */ jsx(TokenLine, {
|
|
92
|
+
children: lines.map((line, index) => /* @__PURE__ */ jsx(TokenLine, {
|
|
93
|
+
fade: fadeRef.current,
|
|
94
|
+
line,
|
|
95
|
+
now,
|
|
96
|
+
start: lineStarts[index]
|
|
97
|
+
}, `line-${index}`))
|
|
73
98
|
})
|
|
74
99
|
})
|
|
75
100
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"StreamRenderer.mjs","names":[],"sources":["../../../src/Highlighter/SyntaxHighlighter/StreamRenderer.tsx"],"sourcesContent":["'use client';\n\nimport { getTokenStyleObject } from '@shikijs/core';\nimport { cx } from 'antd-style';\nimport type { CSSProperties } from 'react';\nimport { memo } from 'react';\nimport type { BuiltinTheme, ThemedToken } from 'shiki';\n\nimport { useStreamHighlight } from '@/hooks/useStreamHighlight';\n\ninterface StreamRendererProps {\n children: string;\n className?: string;\n enableTransformer?: boolean;\n fallbackClassName?: string;\n language: string;\n style?: CSSProperties;\n theme?: BuiltinTheme;\n}\n\nconst normalizeStyleKeys = (style: Record<string, string | number>): CSSProperties => {\n const normalized: CSSProperties = {};\n Object.entries(style).forEach(([key, value]) => {\n const normalizedKey = key.replaceAll(/-([a-z])/g, (_, char) => char.toUpperCase());\n (normalized as Record<string, string | number>)[normalizedKey] = value;\n });\n return normalized;\n};\n\nconst getTokenInlineStyle = (token: ThemedToken): CSSProperties => {\n const rawStyle = token.htmlStyle || getTokenStyleObject(token);\n const baseStyle = normalizeStyleKeys(rawStyle);\n return { ...baseStyle, whiteSpace: 'pre' };\n};\n\nconst TokenSpan = memo(\n ({ token }: { token: ThemedToken }) => {\n return (\n <span
|
|
1
|
+
{"version":3,"file":"StreamRenderer.mjs","names":[],"sources":["../../../src/Highlighter/SyntaxHighlighter/StreamRenderer.tsx"],"sourcesContent":["'use client';\n\nimport { getTokenStyleObject } from '@shikijs/core';\nimport { cx } from 'antd-style';\nimport type { CSSProperties } from 'react';\nimport { memo, useRef } from 'react';\nimport type { BuiltinTheme, ThemedToken } from 'shiki';\n\nimport { useStreamHighlight } from '@/hooks/useStreamHighlight';\n\nimport {\n createTokenFadeStore,\n markTokenBirths,\n resolveTokenFadeStyle,\n type TokenFadeStore,\n} from './tokenFade';\n\ninterface StreamRendererProps {\n children: string;\n className?: string;\n enableTransformer?: boolean;\n fallbackClassName?: string;\n language: string;\n style?: CSSProperties;\n theme?: BuiltinTheme;\n}\n\nconst normalizeStyleKeys = (style: Record<string, string | number>): CSSProperties => {\n const normalized: CSSProperties = {};\n Object.entries(style).forEach(([key, value]) => {\n const normalizedKey = key.replaceAll(/-([a-z])/g, (_, char) => char.toUpperCase());\n (normalized as Record<string, string | number>)[normalizedKey] = value;\n });\n return normalized;\n};\n\nconst getTokenInlineStyle = (token: ThemedToken): CSSProperties => {\n const rawStyle = token.htmlStyle || getTokenStyleObject(token);\n const baseStyle = normalizeStyleKeys(rawStyle);\n return { ...baseStyle, whiteSpace: 'pre' };\n};\n\nconst TokenSpan = memo(\n ({ fadeStyle, token }: { fadeStyle?: CSSProperties | null; token: ThemedToken }) => {\n const style = fadeStyle\n ? { ...getTokenInlineStyle(token), ...fadeStyle }\n : getTokenInlineStyle(token);\n return (\n <span className={fadeStyle ? 'stream-char' : undefined} style={style}>\n {token.content}\n </span>\n );\n },\n (prev, next) => prev.token === next.token && prev.fadeStyle === next.fadeStyle,\n);\n\nconst TokenLine = memo(\n ({\n fade,\n line,\n now,\n start,\n }: {\n fade: TokenFadeStore;\n line: ThemedToken[];\n now: number;\n start: number;\n }) => {\n if (!line.length) {\n return (\n <span className=\"line\">\n <span style={{ whiteSpace: 'pre' }}>{'\\u00A0'}</span>\n </span>\n );\n }\n\n let offset = start;\n return (\n <span className=\"line\">\n {line.map((token) => {\n const tokenOffset = offset;\n offset += token.content.length;\n return (\n <TokenSpan\n fadeStyle={resolveTokenFadeStyle(fade, tokenOffset, now)}\n key={tokenOffset}\n token={token}\n />\n );\n })}\n </span>\n );\n },\n (prev, next) => prev.line === next.line && prev.start === next.start,\n);\n\nconst StreamRenderer = memo<StreamRendererProps>(\n ({ children, className, enableTransformer, fallbackClassName, language, style, theme }) => {\n // Safely handle empty or invalid children\n const safeChildren = children ?? '';\n\n const streaming = useStreamHighlight(safeChildren, {\n enableTransformer,\n language,\n streaming: true,\n theme,\n });\n\n const lines = streaming?.lines;\n const preStyle = streaming?.preStyle;\n\n const fadeRef = useRef<TokenFadeStore>(createTokenFadeStore());\n const previousTextRef = useRef('');\n if (!safeChildren.startsWith(previousTextRef.current)) {\n fadeRef.current = createTokenFadeStore();\n }\n previousTextRef.current = safeChildren;\n const now = performance.now();\n const lineStarts = lines ? markTokenBirths(fadeRef.current, lines, now) : [];\n\n if (!lines || lines.length === 0) {\n return (\n <div className={fallbackClassName} dir=\"ltr\" style={style}>\n <pre>\n <code>{safeChildren}</code>\n </pre>\n </div>\n );\n }\n\n return (\n <div className={className} dir=\"ltr\" style={style}>\n <pre className={cx('shiki', theme)} style={preStyle} tabIndex={0}>\n <code style={{ display: 'flex', flexDirection: 'column', whiteSpace: 'pre' }}>\n {lines.map((line, index) => (\n <TokenLine\n fade={fadeRef.current}\n key={`line-${index}`}\n line={line}\n now={now}\n start={lineStarts[index]}\n />\n ))}\n </code>\n </pre>\n </div>\n );\n },\n);\n\nStreamRenderer.displayName = 'StreamRenderer';\n\nexport default StreamRenderer;\n"],"mappings":";;;;;;;;AA2BA,MAAM,sBAAsB,UAA0D;CACpF,MAAM,aAA4B,CAAC;CACnC,OAAO,QAAQ,KAAK,CAAC,CAAC,SAAS,CAAC,KAAK,WAAW;EAC9C,MAAM,gBAAgB,IAAI,WAAW,cAAc,GAAG,SAAS,KAAK,YAAY,CAAC;EACjF,WAAgD,iBAAiB;CACnE,CAAC;CACD,OAAO;AACT;AAEA,MAAM,uBAAuB,UAAsC;CACjE,MAAM,WAAW,MAAM,aAAa,oBAAoB,KAAK;CAE7D,OAAO;EAAE,GADS,mBAAmB,QACjB;EAAG,YAAY;CAAM;AAC3C;AAEA,MAAM,YAAY,MACf,EAAE,WAAW,YAAsE;CAClF,MAAM,QAAQ,YACV;EAAE,GAAG,oBAAoB,KAAK;EAAG,GAAG;CAAU,IAC9C,oBAAoB,KAAK;CAC7B,OACE,oBAAC,QAAD;EAAM,WAAW,YAAY,gBAAgB,KAAA;EAAkB;EAC5D,UAAA,MAAM;CACH,CAAA;AAEV,IACC,MAAM,SAAS,KAAK,UAAU,KAAK,SAAS,KAAK,cAAc,KAAK,SACvE;AAEA,MAAM,YAAY,MACf,EACC,MACA,MACA,KACA,YAMI;CACJ,IAAI,CAAC,KAAK,QACR,OACE,oBAAC,QAAD;EAAM,WAAU;EACd,UAAA,oBAAC,QAAD;GAAM,OAAO,EAAE,YAAY,MAAM;GAAI,UAAA;EAAe,CAAA;CAChD,CAAA;CAIV,IAAI,SAAS;CACb,OACE,oBAAC,QAAD;EAAM,WAAU;EACb,UAAA,KAAK,KAAK,UAAU;GACnB,MAAM,cAAc;GACpB,UAAU,MAAM,QAAQ;GACxB,OACE,oBAAC,WAAD;IACE,WAAW,sBAAsB,MAAM,aAAa,GAAG;IAEhD;GACR,GAFM,WAEN;EAEL,CAAC;CACG,CAAA;AAEV,IACC,MAAM,SAAS,KAAK,SAAS,KAAK,QAAQ,KAAK,UAAU,KAAK,KACjE;AAEA,MAAM,iBAAiB,MACpB,EAAE,UAAU,WAAW,mBAAmB,mBAAmB,UAAU,OAAO,YAAY;CAEzF,MAAM,eAAe,YAAY;CAEjC,MAAM,YAAY,mBAAmB,cAAc;EACjD;EACA;EACA,WAAW;EACX;CACF,CAAC;CAED,MAAM,QAAQ,WAAW;CACzB,MAAM,WAAW,WAAW;CAE5B,MAAM,UAAU,OAAuB,qBAAqB,CAAC;CAC7D,MAAM,kBAAkB,OAAO,EAAE;CACjC,IAAI,CAAC,aAAa,WAAW,gBAAgB,OAAO,GAClD,QAAQ,UAAU,qBAAqB;CAEzC,gBAAgB,UAAU;CAC1B,MAAM,MAAM,YAAY,IAAI;CAC5B,MAAM,aAAa,QAAQ,gBAAgB,QAAQ,SAAS,OAAO,GAAG,IAAI,CAAC;CAE3E,IAAI,CAAC,SAAS,MAAM,WAAW,GAC7B,OACE,oBAAC,OAAD;EAAK,WAAW;EAAmB,KAAI;EAAa;EAClD,UAAA,oBAAC,OAAD,EAAA,UACE,oBAAC,QAAD,EAAA,UAAO,aAAmB,CAAA,EACvB,CAAA;CACF,CAAA;CAIT,OACE,oBAAC,OAAD;EAAgB;EAAW,KAAI;EAAa;EAC1C,UAAA,oBAAC,OAAD;GAAK,WAAW,GAAG,SAAS,KAAK;GAAG,OAAO;GAAU,UAAU;GAC7D,UAAA,oBAAC,QAAD;IAAM,OAAO;KAAE,SAAS;KAAQ,eAAe;KAAU,YAAY;IAAM;IACxE,UAAA,MAAM,KAAK,MAAM,UAChB,oBAAC,WAAD;KACE,MAAM,QAAQ;KAER;KACD;KACL,OAAO,WAAW;IACnB,GAJM,QAAQ,OAId,CACF;GACG,CAAA;EACH,CAAA;CACF,CAAA;AAET,CACF;AAEA,eAAe,cAAc"}
|
|
@@ -1,18 +1,23 @@
|
|
|
1
1
|
import { fadeIn } from "../../styles/animations.mjs";
|
|
2
2
|
import { createStaticStyles, cx } from "antd-style";
|
|
3
3
|
import { cva } from "class-variance-authority";
|
|
4
|
+
import { STREAM_FADE_DURATION } from "@lobehub/streamdown";
|
|
4
5
|
//#region src/Highlighter/SyntaxHighlighter/style.ts
|
|
5
6
|
const styles = createStaticStyles(({ css, cssVar }) => {
|
|
6
7
|
return {
|
|
7
8
|
animated: css`
|
|
8
9
|
.animate-fade-in,
|
|
9
10
|
.katex-html > .katex-base,
|
|
10
|
-
span.line > span,
|
|
11
11
|
code:not(:has(span.line)) {
|
|
12
12
|
opacity: 1;
|
|
13
13
|
animation: ${fadeIn} 1s ease-in-out;
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
+
span.line > span.stream-char {
|
|
17
|
+
opacity: 0;
|
|
18
|
+
animation: ${fadeIn} ${STREAM_FADE_DURATION}ms cubic-bezier(0.33, 0, 0.67, 1) forwards;
|
|
19
|
+
}
|
|
20
|
+
|
|
16
21
|
/* 只对 .katex-base 级别的 span 应用流式动画,不要穿透到内部 */
|
|
17
22
|
.katex-display .katex-html span {
|
|
18
23
|
mask: none !important;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"style.mjs","names":[],"sources":["../../../src/Highlighter/SyntaxHighlighter/style.ts"],"sourcesContent":["import { createStaticStyles, cx } from 'antd-style';\nimport { cva } from 'class-variance-authority';\n\nimport { fadeIn } from '@/styles/animations';\n\nexport const styles = createStaticStyles(({ css, cssVar }) => {\n return {\n animated: css`\n .animate-fade-in,\n .katex-html > .katex-base,\n
|
|
1
|
+
{"version":3,"file":"style.mjs","names":[],"sources":["../../../src/Highlighter/SyntaxHighlighter/style.ts"],"sourcesContent":["import { STREAM_FADE_DURATION } from '@lobehub/streamdown';\nimport { createStaticStyles, cx } from 'antd-style';\nimport { cva } from 'class-variance-authority';\n\nimport { fadeIn } from '@/styles/animations';\n\nexport const styles = createStaticStyles(({ css, cssVar }) => {\n return {\n animated: css`\n .animate-fade-in,\n .katex-html > .katex-base,\n code:not(:has(span.line)) {\n opacity: 1;\n animation: ${fadeIn} 1s ease-in-out;\n }\n\n span.line > span.stream-char {\n opacity: 0;\n animation: ${fadeIn} ${STREAM_FADE_DURATION}ms cubic-bezier(0.33, 0, 0.67, 1) forwards;\n }\n\n /* 只对 .katex-base 级别的 span 应用流式动画,不要穿透到内部 */\n .katex-display .katex-html span {\n mask: none !important;\n animation: none !important;\n }\n `,\n\n noBackground: css`\n pre {\n background: transparent !important;\n }\n `,\n\n noPadding: css`\n pre {\n padding: 0;\n }\n `,\n\n padding: css`\n pre {\n padding: 16px;\n }\n `,\n root: css`\n direction: ltr;\n margin: 0;\n padding: 0;\n text-align: start;\n\n pre {\n overflow-x: auto;\n margin: 0;\n }\n `,\n shiki: cx(\n 'ant-highlighter-highlighter-shiki',\n css`\n pre {\n user-select: none;\n\n code {\n display: flex;\n flex-direction: column;\n gap: 4px;\n\n .line {\n user-select: text;\n\n display: block;\n\n width: calc(100% + 32px);\n margin-block: 0;\n margin-inline: -16px;\n padding-block: 0;\n padding-inline: 16px;\n }\n }\n\n &.has-focused {\n .line:not(.focused) {\n opacity: 0.5;\n }\n }\n\n .highlighted {\n background: ${cssVar.colorFillTertiary};\n\n &.warning {\n background: ${cssVar.colorWarningBg};\n }\n\n &.error {\n background: ${cssVar.colorErrorBg};\n }\n }\n\n .highlighted-word {\n padding-block: 0.1em;\n padding-inline: 0.2em;\n border: 1px solid ${cssVar.colorBorderSecondary};\n border-radius: ${cssVar.borderRadius};\n\n background: ${cssVar.colorFillTertiary};\n }\n\n .diff {\n &.remove {\n background: ${cssVar.colorErrorBg};\n\n &::before {\n content: '-';\n\n position: absolute;\n inset-inline-start: 4px;\n\n display: inline-block;\n\n color: ${cssVar.colorErrorText};\n }\n }\n\n &.add {\n background: ${cssVar.colorSuccessBg};\n\n &::before {\n content: '+';\n\n position: absolute;\n inset-inline-start: 4px;\n\n display: inline-block;\n\n color: ${cssVar.colorSuccessText};\n }\n }\n }\n }\n `,\n ),\n unshiki: css`\n color: ${cssVar.colorTextDescription};\n `,\n };\n});\n\nexport const variants = cva(styles.root, {\n defaultVariants: {\n animated: false,\n shiki: true,\n showBackground: false,\n variant: 'borderless',\n },\n\n variants: {\n shiki: {\n false: styles.unshiki,\n true: styles.shiki,\n },\n showBackground: {\n false: styles.noBackground,\n true: null,\n },\n animated: {\n true: styles.animated,\n false: null,\n },\n variant: {\n filled: styles.padding,\n outlined: styles.padding,\n borderless: styles.noPadding,\n },\n },\n});\n"],"mappings":";;;;;AAMA,MAAa,SAAS,oBAAoB,EAAE,KAAK,aAAa;CAC5D,OAAO;EACL,UAAU,GAAG;;;;;qBAKI,OAAO;;;;;qBAKP,OAAO,GAAG,qBAAqB;;;;;;;;;EAUhD,cAAc,GAAG;;;;;EAMjB,WAAW,GAAG;;;;;EAMd,SAAS,GAAG;;;;;EAKZ,MAAM,GAAG;;;;;;;;;;;EAWT,OAAO,GACL,qCACA,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;0BA6BiB,OAAO,kBAAkB;;;4BAGvB,OAAO,eAAe;;;;4BAItB,OAAO,aAAa;;;;;;;gCAOhB,OAAO,qBAAqB;6BAC/B,OAAO,aAAa;;0BAEvB,OAAO,kBAAkB;;;;;4BAKvB,OAAO,aAAa;;;;;;;;;;yBAUvB,OAAO,eAAe;;;;;4BAKnB,OAAO,eAAe;;;;;;;;;;yBAUzB,OAAO,iBAAiB;;;;;OAM7C;EACA,SAAS,GAAG;eACD,OAAO,qBAAqB;;CAEzC;AACF,CAAC;AAED,MAAa,WAAW,IAAI,OAAO,MAAM;CACvC,iBAAiB;EACf,UAAU;EACV,OAAO;EACP,gBAAgB;EAChB,SAAS;CACX;CAEA,UAAU;EACR,OAAO;GACL,OAAO,OAAO;GACd,MAAM,OAAO;EACf;EACA,gBAAgB;GACd,OAAO,OAAO;GACd,MAAM;EACR;EACA,UAAU;GACR,MAAM,OAAO;GACb,OAAO;EACT;EACA,SAAS;GACP,QAAQ,OAAO;GACf,UAAU,OAAO;GACjB,YAAY,OAAO;EACrB;CACF;AACF,CAAC"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { STREAM_FADE_DURATION } from "@lobehub/streamdown";
|
|
2
|
+
//#region src/Highlighter/SyntaxHighlighter/tokenFade.ts
|
|
3
|
+
const MIN_GAP_MS = 16;
|
|
4
|
+
const MAX_GAP_MS = 160;
|
|
5
|
+
const MIN_CHAR_PACE_MS = 2;
|
|
6
|
+
const MAX_CHAR_PACE_MS = 18;
|
|
7
|
+
const createTokenFadeStore = () => ({
|
|
8
|
+
births: [],
|
|
9
|
+
lastCommitTs: 0,
|
|
10
|
+
styles: /* @__PURE__ */ new Map()
|
|
11
|
+
});
|
|
12
|
+
const clamp = (value, min, max) => Math.min(max, Math.max(min, value));
|
|
13
|
+
const RETOKENIZED_LINES = 2;
|
|
14
|
+
const markTokenBirths = (store, lines, now) => {
|
|
15
|
+
const lineStarts = [];
|
|
16
|
+
const tokens = [];
|
|
17
|
+
let offset = 0;
|
|
18
|
+
for (const [lineIndex, line] of lines.entries()) {
|
|
19
|
+
if (lineIndex > 0) offset += 1;
|
|
20
|
+
lineStarts.push(offset);
|
|
21
|
+
if (lineIndex >= lines.length - RETOKENIZED_LINES) for (const token of line) {
|
|
22
|
+
tokens.push({
|
|
23
|
+
length: token.content.length,
|
|
24
|
+
offset
|
|
25
|
+
});
|
|
26
|
+
offset += token.content.length;
|
|
27
|
+
}
|
|
28
|
+
else for (const token of line) offset += token.content.length;
|
|
29
|
+
}
|
|
30
|
+
const { births } = store;
|
|
31
|
+
const newChars = offset - births.length;
|
|
32
|
+
if (newChars > 0) {
|
|
33
|
+
const gapMs = store.lastCommitTs === 0 ? MIN_GAP_MS : clamp(now - store.lastCommitTs, MIN_GAP_MS, MAX_GAP_MS);
|
|
34
|
+
const pace = clamp(gapMs / newChars, MIN_CHAR_PACE_MS, MAX_CHAR_PACE_MS);
|
|
35
|
+
const cap = now + gapMs + STREAM_FADE_DURATION;
|
|
36
|
+
for (let i = births.length; i < offset; i++) {
|
|
37
|
+
const chained = i > 0 ? births[i - 1] + pace : now;
|
|
38
|
+
births.push(Math.min(cap, Math.max(chained, now)));
|
|
39
|
+
}
|
|
40
|
+
store.lastCommitTs = now;
|
|
41
|
+
}
|
|
42
|
+
for (const token of tokens) {
|
|
43
|
+
let birth = births[token.offset];
|
|
44
|
+
for (let i = token.offset + 1; i < token.offset + token.length; i++) if (births[i] < birth) birth = births[i];
|
|
45
|
+
for (let i = token.offset; i < token.offset + token.length; i++) births[i] = birth;
|
|
46
|
+
}
|
|
47
|
+
return lineStarts;
|
|
48
|
+
};
|
|
49
|
+
const resolveTokenFadeStyle = (store, offset, now) => {
|
|
50
|
+
const birth = store.births[offset];
|
|
51
|
+
const cached = store.styles.get(offset);
|
|
52
|
+
if (cached && cached.birth === birth) return cached.style;
|
|
53
|
+
const elapsed = now - birth;
|
|
54
|
+
const style = elapsed >= STREAM_FADE_DURATION ? null : { animationDelay: `${-elapsed}ms` };
|
|
55
|
+
store.styles.set(offset, {
|
|
56
|
+
birth,
|
|
57
|
+
style
|
|
58
|
+
});
|
|
59
|
+
return style;
|
|
60
|
+
};
|
|
61
|
+
//#endregion
|
|
62
|
+
export { createTokenFadeStore, markTokenBirths, resolveTokenFadeStyle };
|
|
63
|
+
|
|
64
|
+
//# sourceMappingURL=tokenFade.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tokenFade.mjs","names":[],"sources":["../../../src/Highlighter/SyntaxHighlighter/tokenFade.ts"],"sourcesContent":["import { STREAM_FADE_DURATION } from '@lobehub/streamdown';\nimport type { CSSProperties } from 'react';\nimport type { ThemedToken } from 'shiki';\n\nconst MIN_GAP_MS = 16;\nconst MAX_GAP_MS = 160;\nconst MIN_CHAR_PACE_MS = 2;\nconst MAX_CHAR_PACE_MS = 18;\n\nexport interface TokenFadeStore {\n /**\n * Birth timestamp per source char. A char's birth is the birth of the\n * token it is displayed in, and it only ever moves earlier: shiki\n * re-splits the last line as it completes (`'rea` becomes `'`, `rea`,\n * `'`) and merges tokens the other way, and a char that was already on\n * screen at some opacity must never drop below it in the new token.\n */\n births: number[];\n lastCommitTs: number;\n styles: Map<number, { birth: number; style: CSSProperties | null }>;\n}\n\nexport const createTokenFadeStore = (): TokenFadeStore => ({\n births: [],\n lastCommitTs: 0,\n styles: new Map(),\n});\n\nconst clamp = (value: number, min: number, max: number) => Math.min(max, Math.max(min, value));\n\nconst RETOKENIZED_LINES = 2;\n\nexport const markTokenBirths = (\n store: TokenFadeStore,\n lines: ThemedToken[][],\n now: number,\n): number[] => {\n const lineStarts: number[] = [];\n const tokens: { length: number; offset: number }[] = [];\n let offset = 0;\n\n for (const [lineIndex, line] of lines.entries()) {\n if (lineIndex > 0) offset += 1;\n lineStarts.push(offset);\n if (lineIndex >= lines.length - RETOKENIZED_LINES) {\n for (const token of line) {\n tokens.push({ length: token.content.length, offset });\n offset += token.content.length;\n }\n } else {\n for (const token of line) offset += token.content.length;\n }\n }\n\n const { births } = store;\n const newChars = offset - births.length;\n if (newChars > 0) {\n const gapMs =\n store.lastCommitTs === 0\n ? MIN_GAP_MS\n : clamp(now - store.lastCommitTs, MIN_GAP_MS, MAX_GAP_MS);\n const pace = clamp(gapMs / newChars, MIN_CHAR_PACE_MS, MAX_CHAR_PACE_MS);\n const cap = now + gapMs + STREAM_FADE_DURATION;\n for (let i = births.length; i < offset; i++) {\n const chained = i > 0 ? births[i - 1] + pace : now;\n births.push(Math.min(cap, Math.max(chained, now)));\n }\n store.lastCommitTs = now;\n }\n\n for (const token of tokens) {\n let birth = births[token.offset];\n for (let i = token.offset + 1; i < token.offset + token.length; i++) {\n if (births[i] < birth) birth = births[i];\n }\n for (let i = token.offset; i < token.offset + token.length; i++) births[i] = birth;\n }\n\n return lineStarts;\n};\n\nexport const resolveTokenFadeStyle = (\n store: TokenFadeStore,\n offset: number,\n now: number,\n): CSSProperties | null => {\n const birth = store.births[offset];\n const cached = store.styles.get(offset);\n if (cached && cached.birth === birth) return cached.style;\n\n const elapsed = now - birth;\n const style = elapsed >= STREAM_FADE_DURATION ? null : { animationDelay: `${-elapsed}ms` };\n store.styles.set(offset, { birth, style });\n return style;\n};\n"],"mappings":";;AAIA,MAAM,aAAa;AACnB,MAAM,aAAa;AACnB,MAAM,mBAAmB;AACzB,MAAM,mBAAmB;AAezB,MAAa,8BAA8C;CACzD,QAAQ,CAAC;CACT,cAAc;CACd,wBAAQ,IAAI,IAAI;AAClB;AAEA,MAAM,SAAS,OAAe,KAAa,QAAgB,KAAK,IAAI,KAAK,KAAK,IAAI,KAAK,KAAK,CAAC;AAE7F,MAAM,oBAAoB;AAE1B,MAAa,mBACX,OACA,OACA,QACa;CACb,MAAM,aAAuB,CAAC;CAC9B,MAAM,SAA+C,CAAC;CACtD,IAAI,SAAS;CAEb,KAAK,MAAM,CAAC,WAAW,SAAS,MAAM,QAAQ,GAAG;EAC/C,IAAI,YAAY,GAAG,UAAU;EAC7B,WAAW,KAAK,MAAM;EACtB,IAAI,aAAa,MAAM,SAAS,mBAC9B,KAAK,MAAM,SAAS,MAAM;GACxB,OAAO,KAAK;IAAE,QAAQ,MAAM,QAAQ;IAAQ;GAAO,CAAC;GACpD,UAAU,MAAM,QAAQ;EAC1B;OAEA,KAAK,MAAM,SAAS,MAAM,UAAU,MAAM,QAAQ;CAEtD;CAEA,MAAM,EAAE,WAAW;CACnB,MAAM,WAAW,SAAS,OAAO;CACjC,IAAI,WAAW,GAAG;EAChB,MAAM,QACJ,MAAM,iBAAiB,IACnB,aACA,MAAM,MAAM,MAAM,cAAc,YAAY,UAAU;EAC5D,MAAM,OAAO,MAAM,QAAQ,UAAU,kBAAkB,gBAAgB;EACvE,MAAM,MAAM,MAAM,QAAQ;EAC1B,KAAK,IAAI,IAAI,OAAO,QAAQ,IAAI,QAAQ,KAAK;GAC3C,MAAM,UAAU,IAAI,IAAI,OAAO,IAAI,KAAK,OAAO;GAC/C,OAAO,KAAK,KAAK,IAAI,KAAK,KAAK,IAAI,SAAS,GAAG,CAAC,CAAC;EACnD;EACA,MAAM,eAAe;CACvB;CAEA,KAAK,MAAM,SAAS,QAAQ;EAC1B,IAAI,QAAQ,OAAO,MAAM;EACzB,KAAK,IAAI,IAAI,MAAM,SAAS,GAAG,IAAI,MAAM,SAAS,MAAM,QAAQ,KAC9D,IAAI,OAAO,KAAK,OAAO,QAAQ,OAAO;EAExC,KAAK,IAAI,IAAI,MAAM,QAAQ,IAAI,MAAM,SAAS,MAAM,QAAQ,KAAK,OAAO,KAAK;CAC/E;CAEA,OAAO;AACT;AAEA,MAAa,yBACX,OACA,QACA,QACyB;CACzB,MAAM,QAAQ,MAAM,OAAO;CAC3B,MAAM,SAAS,MAAM,OAAO,IAAI,MAAM;CACtC,IAAI,UAAU,OAAO,UAAU,OAAO,OAAO,OAAO;CAEpD,MAAM,UAAU,MAAM;CACtB,MAAM,QAAQ,WAAW,uBAAuB,OAAO,EAAE,gBAAgB,GAAG,CAAC,QAAQ,IAAI;CACzF,MAAM,OAAO,IAAI,QAAQ;EAAE;EAAO;CAAM,CAAC;CACzC,OAAO;AACT"}
|
|
@@ -24,7 +24,7 @@ declare const styles: {
|
|
|
24
24
|
declare const rootVariants: (props?: ({
|
|
25
25
|
shadow?: boolean | null | undefined;
|
|
26
26
|
size?: "large" | "middle" | "small" | null | undefined;
|
|
27
|
-
variant?: "
|
|
27
|
+
variant?: "borderless" | "filled" | "outlined" | null | undefined;
|
|
28
28
|
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
|
|
29
29
|
//#endregion
|
|
30
30
|
export { rootVariants, styles };
|