源码快照 @ 2026-09-01 (source-only, 剔除非源码素材)
Publish Claude Code Binaries / Stage and publish binaries (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-arm64 (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build linux-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build windows-amd64 (push) Canceled after 0s
Sync Tutti Agent Skills / sync (push) Canceled after 0s
Windows Agent Adapters / Test Windows agent adapters (push) Canceled after 0s
Windows Daemon Adapters / Test Windows daemon adapters (push) Canceled after 0s
Publish Tutti App Runtime / Publish runtime catalog (push) Canceled after 0s
Publish Claude Code Binaries / Stage and publish binaries (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-arm64 (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build linux-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build windows-amd64 (push) Canceled after 0s
Sync Tutti Agent Skills / sync (push) Canceled after 0s
Windows Agent Adapters / Test Windows agent adapters (push) Canceled after 0s
Windows Daemon Adapters / Test Windows daemon adapters (push) Canceled after 0s
Publish Tutti App Runtime / Publish runtime catalog (push) Canceled after 0s
This commit is contained in:
@@ -0,0 +1,104 @@
|
||||
import { useCallback, useRef, useState } from "react";
|
||||
import { findMessageLocatorScrollParent } from "./AgentMessageLocatorRail";
|
||||
|
||||
interface TurnDisclosureScrollAnchor {
|
||||
release: () => void;
|
||||
}
|
||||
|
||||
class TurnDisclosureMotionController {
|
||||
private readonly movingTurnKeys = new Set<string>();
|
||||
private scrollAnchor: TurnDisclosureScrollAnchor | null = null;
|
||||
|
||||
setTurnMotionActive(
|
||||
turnKey: string,
|
||||
active: boolean,
|
||||
anchorElement?: HTMLElement | null
|
||||
): boolean {
|
||||
if (active) {
|
||||
this.movingTurnKeys.add(turnKey);
|
||||
if (anchorElement) {
|
||||
this.scrollAnchor?.release();
|
||||
this.scrollAnchor = lockDisclosureRowPosition(anchorElement);
|
||||
}
|
||||
} else {
|
||||
this.movingTurnKeys.delete(turnKey);
|
||||
if (this.movingTurnKeys.size === 0) {
|
||||
this.scrollAnchor?.release();
|
||||
this.scrollAnchor = null;
|
||||
}
|
||||
}
|
||||
return this.movingTurnKeys.size > 0;
|
||||
}
|
||||
}
|
||||
|
||||
function lockDisclosureRowPosition(
|
||||
anchorElement: HTMLElement
|
||||
): TurnDisclosureScrollAnchor | null {
|
||||
const scrollParent = findMessageLocatorScrollParent(anchorElement);
|
||||
if (!scrollParent) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const anchorTop = anchorElement.getBoundingClientRect().top;
|
||||
const previousOverflowAnchor =
|
||||
scrollParent.style.getPropertyValue("overflow-anchor");
|
||||
const previousOverflowAnchorPriority =
|
||||
scrollParent.style.getPropertyPriority("overflow-anchor");
|
||||
scrollParent.style.setProperty("overflow-anchor", "none");
|
||||
|
||||
const preserveAnchorTop = (): void => {
|
||||
if (!anchorElement.isConnected) {
|
||||
return;
|
||||
}
|
||||
const topDelta = anchorElement.getBoundingClientRect().top - anchorTop;
|
||||
if (Math.abs(topDelta) < 0.5) {
|
||||
return;
|
||||
}
|
||||
scrollParent.scrollTop += topDelta;
|
||||
};
|
||||
scrollParent.addEventListener("scroll", preserveAnchorTop);
|
||||
|
||||
return {
|
||||
release: () => {
|
||||
preserveAnchorTop();
|
||||
scrollParent.removeEventListener("scroll", preserveAnchorTop);
|
||||
if (previousOverflowAnchor) {
|
||||
scrollParent.style.setProperty(
|
||||
"overflow-anchor",
|
||||
previousOverflowAnchor,
|
||||
previousOverflowAnchorPriority
|
||||
);
|
||||
} else {
|
||||
scrollParent.style.removeProperty("overflow-anchor");
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function useTurnDisclosureMotion(): readonly [
|
||||
active: boolean,
|
||||
setTurnMotionActive: (
|
||||
turnKey: string,
|
||||
active: boolean,
|
||||
anchorElement?: HTMLElement | null
|
||||
) => void
|
||||
] {
|
||||
const motionControllerRef = useRef<TurnDisclosureMotionController | null>(
|
||||
null
|
||||
);
|
||||
if (!motionControllerRef.current) {
|
||||
motionControllerRef.current = new TurnDisclosureMotionController();
|
||||
}
|
||||
const motionController = motionControllerRef.current;
|
||||
const [active, setActive] = useState(false);
|
||||
const setTurnMotionActive = useCallback(
|
||||
(turnKey: string, moving: boolean, anchorElement?: HTMLElement | null) => {
|
||||
setActive(
|
||||
motionController.setTurnMotionActive(turnKey, moving, anchorElement)
|
||||
);
|
||||
},
|
||||
[motionController]
|
||||
);
|
||||
|
||||
return [active, setTurnMotionActive];
|
||||
}
|
||||
Reference in New Issue
Block a user