fix(web): 基础控件 FloatingDock - 宿主没有 matchMedia 时不再渲染失败 (#563)

Original-Commit: 3da77b3931a675de06b02c1bba93f112d253041b
fix(web): 基础控件 FloatingDock - 宿主没有 matchMedia 时不再渲染失败

`window` 存在不代表 `matchMedia` 存在(SSR 垫片、嵌入式 webview、部分 DOM polyfill),
而 FloatingDock 在渲染期直接取 `window.matchMedia("(pointer: coarse)").matches`,
取不到就抛 TypeError,整个 dock(画布工具栏/素材托盘/缩放控件)渲染失败。
仓库里同一模式已有先例:canvas-workspace-tool-panel 用 typeof 守卫。

抽 coarsePointerQuery():两个都探测,取不到按"非触屏"处理,useEffect 也提前返回;
能取到时行为不变。新增源码级守卫用例(上游原态失败、补丁后通过)。
This commit is contained in:
AetherNo2332
2026-09-21 07:05:03 +08:00
committed by ddCat
parent 76e02734f1
commit 22d65d0192
2 changed files with 28 additions and 2 deletions

View File

@@ -65,6 +65,13 @@ const DOCK_METRICS: Record<NonNullable<FloatingDockProps["size"]>, DockMetrics>
compact: { base: 26, magnified: 32, icon: 13, iconMagnified: 16, distance: 84 },
};
// `window` 存在不代表 `matchMedia` 存在:测试与 renderToString 下它可能是 undefined,
// 渲染期直接取用会抛 TypeError。指针能力是纯增强,取不到就按"非触屏"处理。
function coarsePointerQuery(): MediaQueryList | undefined {
if (typeof window === "undefined" || typeof window.matchMedia !== "function") return undefined;
return window.matchMedia("(pointer: coarse)");
}
const TOUCH_DOCK_METRICS: Record<NonNullable<FloatingDockProps["size"]>, DockMetrics> = {
default: { base: 40, magnified: 40, icon: 18, iconMagnified: 18, distance: 0 },
compact: { base: 36, magnified: 36, icon: 16, iconMagnified: 16, distance: 0 },
@@ -73,12 +80,13 @@ const TOUCH_DOCK_METRICS: Record<NonNullable<FloatingDockProps["size"]>, DockMet
export const FloatingDock = forwardRef<HTMLDivElement, FloatingDockProps>(function FloatingDock({ items, size = "default", embedded = false, className, style, ariaLabel = "画布工具", showLabels = false }, forwardedRef) {
const mouseX = useMotionValue(Number.POSITIVE_INFINITY);
const reducedMotion = useReducedMotion();
const [coarsePointer, setCoarsePointer] = useState(() => typeof window !== "undefined" && window.matchMedia("(pointer: coarse)").matches);
const [coarsePointer, setCoarsePointer] = useState(() => coarsePointerQuery()?.matches ?? false);
// 窄屏下 dock 按钮总宽易超出可用宽度:此时允许横向滚动并禁用放大(放大依赖 overflow-visible,与滚动互斥)
const [narrow, setNarrow] = useState(() => (typeof window !== "undefined" ? window.innerWidth < 768 : false));
useEffect(() => {
const media = window.matchMedia("(pointer: coarse)");
const media = coarsePointerQuery();
if (!media) return;
const update = () => setCoarsePointer(media.matches);
update();
media.addEventListener("change", update);