新增 libs/shared 内部包,作为跨 IIFE 库的单一真相源: - ThemeName 类型(原 codemirror/themes.ts + xterm/themes.ts 各一份) - THEME_CHANGE_EVENT 常量(原 yggdrasil-core 导出 + codemirror/xterm 各硬编码同名 string literal,靠注释维系一致) - prefersReducedMotion 函数(原 lightbox + yggdrasil-core 各一份) codemirror-editor / xterm-terminal / lightbox / yggdrasil-core 四个库 改为 workspace 依赖 @yggdrasil/shared,Vite 构建时 inline 进各自 IIFE, 不破坏 IIFE 隔离。消除「改一处忘改另一处导致静默失效」的风险。 附:image_reader_limits 已在上一提交共享;本提交是 libs 侧的对称改动。
62 lines
2.3 KiB
TypeScript
62 lines
2.3 KiB
TypeScript
import { THEME_CHANGE_EVENT } from '@yggdrasil/shared';
|
||
import { TerminalInstance, XtermOptions } from './terminal';
|
||
import type { ThemeName } from './themes';
|
||
|
||
/**
|
||
* 主题切换事件由 @yggdrasil/shared 统一定义。
|
||
*
|
||
* yggdrasil-core 在 VT 回调内(NEW 快照捕获前)同步 dispatch 此事件,
|
||
* 让 xterm 同步 setTheme——否则圆形展开扫过终端区域时 OLD/NEW 快照同色
|
||
* (背景由 .xterm-scrollable-element 的 inline background-color 注入,不随
|
||
* .dark 翻转),看不到变化,动画结束后才瞬切。
|
||
*/
|
||
|
||
/**
|
||
* 模块入口:暴露对象字面量 { create } 作为默认导出。
|
||
* IIFE 产物挂在 window.XtermTerminal 上,由 Rust 侧用 Reflect::get 取
|
||
* (对象字面量,不能用 wasm-bindgen 的 extern fn——那会被编成函数调用而失败)。
|
||
*/
|
||
const XtermTerminal = {
|
||
_instances: new Map<string, TerminalInstance>(),
|
||
|
||
create(containerId: string, options: XtermOptions = new XtermOptions()): TerminalInstance | null {
|
||
const container = document.getElementById(containerId);
|
||
if (!container) return null;
|
||
|
||
// 销毁同 id 的旧实例
|
||
const existing = this._instances.get(containerId);
|
||
if (existing) {
|
||
existing.destroy();
|
||
this._instances.delete(containerId);
|
||
}
|
||
|
||
const instance = new TerminalInstance(container, options);
|
||
this._instances.set(containerId, instance);
|
||
return instance;
|
||
},
|
||
};
|
||
|
||
/**
|
||
* 订阅主题切换事件:VT 回调内同步 dispatch 时,遍历所有存活实例调 setTheme。
|
||
*
|
||
* 必须在模块加载时注册一次(IIFE 顶层),确保任何时刻 dispatch 都能命中。
|
||
* 单实例异常用 try/catch 隔离,避免一个实例失败中断其他实例换肤。
|
||
* 与 Dioxus use_effect 驱动的 set_theme 幂等共存(setTheme 相同主题是 no-op)。
|
||
*/
|
||
if (typeof window !== 'undefined') {
|
||
window.addEventListener(THEME_CHANGE_EVENT, (event) => {
|
||
const detail = (event as CustomEvent).detail as { isDark: boolean } | undefined;
|
||
if (!detail) return;
|
||
const theme: ThemeName = detail.isDark ? 'dark' : 'light';
|
||
XtermTerminal._instances.forEach((instance) => {
|
||
try {
|
||
instance.setTheme(theme);
|
||
} catch (e) {
|
||
console.error('[XtermTerminal] setTheme failed during theme change:', e);
|
||
}
|
||
});
|
||
});
|
||
}
|
||
|
||
export default XtermTerminal;
|