mirror of
https://github.com/getpaseo/paseo.git
synced 2026-10-25 10:42:45 +00:00
* fix(app): preserve chat scroll across workspace switches Hidden retained panels can emit geometry-driven scroll events while collapsed, which previously looked like user intent and disabled output following. Keep the retained DOM mounted, suspend its inactive scroll work, and only detach from the bottom after explicit input. * fix(app): classify chat scroll intent at input A held pointer is not evidence that a geometry-driven scroll was user initiated. Detach output following directly from upward wheel, key, touch, or mouse-drag input, and keep ordinary scroll events responsible only for metrics and bottom reattachment. * fix(app): keep editing keys out of scroll intent Question inputs render inside the chat viewport, so their caret-navigation keys bubble through the same keyboard boundary. Treat keys from editable descendants as editing intent while preserving viewport-level keyboard scrolling. * fix(app): exclude zoom gestures from scroll intent Ctrl-wheel and trackpad pinch events can carry a negative wheel delta without scrolling chat content. Keep those browser zoom gestures from disabling output following. * fix(app): require pointer-driven chat movement Mouse movement alone is not viewport scroll intent. Correlate actual upward scroll with short-lived content-drag evidence or a scrollbar-gutter gesture, while excluding embedded editors and retaining geometry-only following. * fix(app): recognize overlay chat scrollbars * fix(app): scope scroll intent to chat viewport * fix(app): rearm chat history at scroll start * fix(app): track chat autoscroll intent * fix(app): separate pointer scroll gestures
70 lines
1.8 KiB
TypeScript
70 lines
1.8 KiB
TypeScript
import React, { createContext, memo, type ReactNode, useContext } from "react";
|
|
import { StyleSheet, View, type StyleProp, type ViewStyle } from "react-native";
|
|
|
|
const RetainedPanelActiveContext = createContext(true);
|
|
|
|
export function useRetainedPanelActive(): boolean {
|
|
return useContext(RetainedPanelActiveContext);
|
|
}
|
|
|
|
interface RetainedPanelProps {
|
|
active: boolean;
|
|
children: ReactNode;
|
|
style?: StyleProp<ViewStyle>;
|
|
testID?: string;
|
|
}
|
|
|
|
interface RetainedPanelActivityProps {
|
|
active: boolean;
|
|
children: ReactNode;
|
|
}
|
|
|
|
export function RetainedPanelActivity({ active, children }: RetainedPanelActivityProps) {
|
|
const parentActive = useRetainedPanelActive();
|
|
return (
|
|
<RetainedPanelActiveContext value={parentActive && active}>
|
|
{children}
|
|
</RetainedPanelActiveContext>
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Keeps expensive panel state mounted without letting an inactive panel render
|
|
* on screen. The stable, non-collapsible native root is intentional: retained
|
|
* panels must not detach or reparent their native descendants when visibility
|
|
* changes.
|
|
*/
|
|
export const RetainedPanel = memo(function RetainedPanel({
|
|
active,
|
|
children,
|
|
style,
|
|
testID,
|
|
}: RetainedPanelProps) {
|
|
const visibleStyle = StyleSheet.compose<ViewStyle, ViewStyle, ViewStyle>(styles.root, style);
|
|
const panelStyle = active
|
|
? visibleStyle
|
|
: StyleSheet.compose<ViewStyle, ViewStyle, ViewStyle>(visibleStyle, styles.hidden);
|
|
|
|
return (
|
|
<RetainedPanelActivity active={active}>
|
|
<View
|
|
collapsable={false}
|
|
pointerEvents={active ? "auto" : "none"}
|
|
style={panelStyle}
|
|
testID={testID}
|
|
>
|
|
{children}
|
|
</View>
|
|
</RetainedPanelActivity>
|
|
);
|
|
});
|
|
|
|
const styles = StyleSheet.create({
|
|
root: {
|
|
flex: 1,
|
|
},
|
|
hidden: {
|
|
display: "none",
|
|
},
|
|
});
|