MathField
math-fieldMathLive 驱动的公式输入框:所见即所得地敲出 LaTeX,可注入公式输入框与作答卡,另给 CAS 等价判分
用法
基础用法
受控的 LaTeX 值(不带 $)。首帧是骨架,mathlive 在客户端动态加载。
\frac{a}{b}+\sqrt{c}
<MathField value={latex} onChange={setLatex} aria-label="公式" />注入 MathTextarea
传组件本身给 visualEditor,MathTextarea 多出「可视化输入」页签,确认后仍按 $…$ 插到光标处。
预览(与题目展示用同一套排版)
已知 ,求 。
预览里出现红色源码,说明这段公式 KaTeX 解析不了,请检查命令拼写。
<MathTextarea multiline value={value} onChange={setValue} visualEditor={MathField} />填空题公式键盘与三档判分
QuestionAnswer 的 blankInput 设为 math;提交时 gradeObjective 依次走字面、归一、createCasComparator。
const equivalent = await createCasComparator();
<QuestionAnswer question={q} value={v} onChange={setV} blankInput="math" mathField={MathField}
onSubmit={(a) => gradeObjective(q, a, { normalize: true, equivalent })} />虚拟键盘策略
manual 只由切换钮弹出;off 不挂键盘(策略 manual 且隐藏切换钮),适合桌面端录题。
\frac{a}{b}+\sqrt{c}
<MathField value={latex} onChange={setLatex} virtualKeyboard="off" />禁用与只读
已提交的作答传 disabled;展示参考答案传 readOnly。
\frac{a}{b}+\sqrt{c}
\int_0^1 x\,dx
<MathField value={latex} onChange={setLatex} disabled />
<MathField value={latex} onChange={setLatex} readOnly />何时用
学生在填空题里要敲 $\frac{5}{6}$、老师录题时不会写 \sqrt{} 这类场合:用户面对的是一个像计算器一样的公式框,敲出来的东西已经是 LaTeX,你拿到的 value 直接能进 Formula 排版、进 gradeObjective 判分。只是要在一段文字里插公式,用 MathTextarea 并把本件传给它的 visualEditor,它负责套 $…$ 与插到光标处;本件自己不产出 $。
只展示不编辑用 Formula。
安装
mathlive 是可选 peer,装了才能用本件:
pnpm add mathlive要用 createCasComparator 再装 @cortex-js/compute-engine(它是 mathlive 钉死的依赖,通常已经在 node_modules 里,显式装一次让打包器的解析不依赖 hoist):
pnpm add mathlive @cortex-js/compute-engine字体由你引入一次(Next 放根 layout,Vite 放 main.tsx);缺字体只是回退成系统字体,不是白屏:
import "mathlive/fonts.css";peer 下界 mathlive >=0.110.0、@cortex-js/compute-engine >=0.58.0:只承诺测过的版本。MathLive 0.9x 到 0.10x 之间 menuItems 与虚拟键盘策略的语义都改过,往下放宽等于拿没测过的组合做承诺。
导入
import { MathField, createCasComparator } from "@hulianui/ui/math-field"住独立子路径而不是 @hulianui/ui/math:MathLive 本体加 Compute Engine 是几百 KB 的懒加载 chunk,只有真要可视化输入 / CAS 判分的页面才为它们买单;@hulianui/ui/math 自身零 MathLive。
用法
const [latex, setLatex] = useState("\\frac{a}{b}");
<MathField value={latex} onChange={setLatex} aria-label="公式" />注入 MathTextarea(多出「可视化输入」页签,确认后按 $…$ 插到光标处):
<MathTextarea multiline value={stem} onChange={setStem} visualEditor={MathField} />
<QuestionEditor value={question} onChange={setQuestion} visualEditor={MathField} />注入 QuestionAnswer 的填空,并接三档判分:
const equivalent = await createCasComparator(); // 页面挂载时做一次即可
<QuestionAnswer
question={q}
value={v}
onChange={setV}
blankInput="math"
mathField={MathField}
onSubmit={(answer) => gradeObjective(q, answer, { normalize: true, equivalent })}
/>Props
MathFieldProps 继承 `MathFieldLikeProps`,前六行就是那份契约。
| 名称 | 类型 | 默认 | 说明 |
|---|---|---|---|
| value | string | - | LaTeX,不带 $ |
| onChange | (latex: string) => void | - | 每次击键回传 |
| onSubmit | (latex: string) => void | - | 回车。MathTextarea 把它接到「插入到光标处」 |
| disabled | boolean | false | 锁定(已提交 / 提交中) |
| aria-label | string | - | 无障碍名,透传给 <math-field> |
| className | string | - | 外层容器 |
| virtualKeyboard | "auto" | "manual" | "off" | "auto" | 虚拟键盘策略:auto 触屏聚焦时弹出、manual 只由切换钮弹出、off 不挂键盘(策略 manual 且隐藏切换钮) |
| keyboardLayouts | readonly unknown[] | - | 透传给 window.mathVirtualKeyboard.layouts。键盘是页面级单例,后挂载的覆盖先挂载的 |
| readOnly | boolean | false | 只读:能选中复制,不能改 |
| placeholder | string | - | 空值时的占位 |
Events
| 事件 | 参数 | 时机 |
|---|---|---|
| onChange | latex: string | 用户每次击键(MathLive 的 input 事件) |
| onSubmit | latex: string | 回车(Shift+Enter 不触发) |
createCasComparator
function createCasComparator(): Promise<(a: string, b: string) => boolean>用 Compute Engine 判两个 LaTeX 是否数学等价:\frac{1}{2} 与 0.5、2x+1 与 1+2x 都为真。返回值是同步比较器,直接喂 gradeObjective 的 equivalent(第 3 档,只在字面与归一都不等时才调)。两侧的 $…$ / $$…$$ / \(…\) 会先剥掉(stripMathDelimiters),解析失败、空串、任何异常一律 false:判分宁可漏判不可误判。
它是 async 的:Compute Engine 不随 mathlive 打包,第一次调用才 import(),之后同一个引擎实例复用。没装 @cortex-js/compute-engine 时抛 ComputeEngineUnavailableError,消息里带安装命令。
服务端才是判分 SSOT(见 Formula 文档里 gradeObjective 一节):这里给的是即时反馈与录题自测,正式成绩以服务端为准。
SSR 与加载
组件有三态,data-slot="math-field" 上的 data-status 分别是 loading / ready / unavailable:
- loading:服务端与客户端首帧都只渲染一个同尺寸
Skeleton,两边 HTML 一致,没有 hydration mismatch。 - ready:
mathlive在useEffect里import()成功后,用document.createElement("math-field")挂真元素并做受控同步。 - unavailable:没装 mathlive、或解析到的是 MathLive 的 SSR 构建(没有
MathfieldElement),渲染一条带安装命令的Alert,不抛错,静态导出不会因此整页失败。开发期warnOnce一次。
Next App Router 直接用,组件已是 client 组件,不需要 next/dynamic。Vite 可选 optimizeDeps.include: ["mathlive", "@cortex-js/compute-engine"],避免第一次打开时中途重优化。
虚拟键盘
MathLive 的虚拟键盘是页面级单例(window.mathVirtualKeyboard),同页多个 MathField 共用同一块键盘;keyboardLayouts 给了就写进这个单例,后挂载的覆盖先挂载的。桌面端录题一般 virtualKeyboard="off",学生端触屏作答用默认 auto。
主题
MathLive 通过 CSS 变量取色,本件把它们钉到瑚琏 token,亮暗随主题切换:
| MathLive 变量 | 瑚琏 token |
|---|---|
--caret-color | --color-primary |
--selection-background-color | --color-primary 18% |
--selection-color / --latex-color / --highlight-text | --color-foreground |
--contains-highlight-background-color | --color-primary 10% |
--placeholder-color / --smart-fence-color | --color-muted-foreground |
--correct-color / --incorrect-color | --color-success / --color-danger |
外框与 Input 同一套边框 / 焦点环;MathLive 内置右键菜单已关掉(menuItems = [])。
国际化
文案只有加载中占位的无障碍名与缺依赖提示两组,从 ConfigProvider 的 components.mathField 取,SSOT 在 math-field.locale.ts(MATH_FIELD_LOCALE_ZH / MATH_FIELD_LOCALE_EN)。
禁忌 / 坑
- 值是不带 `$` 的 LaTeX。要插进题干走 MathTextarea 的
visualEditor(它负责套$);直接把value拼进题干会得到没有定界符的裸记号。 - 注入的是组件不是元素:
mathField={MathField}、visualEditor={MathField},不是<MathField />。 - jsdom 里 mathlive 解析到 SSR 构建,组件会显示安装提示:消费方单测里
vi.mock("mathlive")一个只实现getValue/setValue的假元素,或只断言首帧骨架。 - MathLive 的 `setValue` 要求元素已挂载,本件内部先
appendChild再写值;自己直接操作<math-field>时同理。
相关
- MathTextarea ——
MathFieldLikeProps契约与visualEditor注入点 - QuestionAnswer ——
blankInput="math"+mathField - QuestionEditor ——
visualEditor透传给每个 MathTextarea - Formula —— 排版与
@hulianui/ui/math的题目域纯函数(gradeObjective) - 消费指南 · 数学题件 —— 三条入口各买什么体积、SSR、判分 SSOT
Playground
\frac{a}{b}+\sqrt{c}
<MathField placeholder="输入公式" aria-label="公式" value={latex} onChange={setLatex} />