fix: stop leaking CSS rules from dynamic UI styles (#1084) (#1103)

* fix: stop leaking CSS rules from dynamic position styles

* Fix floating surfaces on web

* Stop diff controls growing Unistyles rules

* Use inline style escape hatch in diff pane

* Centralize dynamic style escape hatches

* Restore startup workspace navigation

* Upgrade Unistyles for web cleanup fix

* Update review style test for inline styles

* Use redirect for startup workspace restore

* Update startup redirect test
This commit is contained in:
Mohamed Boudra
2026-05-20 16:55:00 +08:00
committed by GitHub
parent 905a0985f9
commit f933bf6b3a
18 changed files with 366 additions and 186 deletions
@@ -1,17 +1,20 @@
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import {
PanResponder,
View,
type GestureResponderEvent,
type LayoutChangeEvent,
type NativeScrollEvent,
type NativeSyntheticEvent,
type ViewStyle,
View,
} from "react-native";
import { StyleSheet, useUnistyles } from "react-native-unistyles";
import { isWeb as platformIsWeb } from "@/constants/platform";
import { inlineUnistylesStyle } from "@/styles/unistyles-inline-style";
import {
computeScrollOffsetFromDragDelta,
computeVerticalScrollbarGeometry,
} from "./web-desktop-scrollbar.math";
import { isWeb as platformIsWeb } from "@/constants/platform";
const METRICS_EPSILON = 0.5;
const HANDLE_WIDTH_IDLE = 6;
@@ -27,6 +30,15 @@ const HANDLE_WIDTH_TRANSITION_DURATION_MS = 240;
const HANDLE_SCROLL_VISIBILITY_MS = 1200;
const HANDLE_SCROLL_ACTIVE_MS = 110;
interface WebPointerStyle {
cursor?: "grab" | "grabbing";
touchAction?: "none";
userSelect?: "none";
transitionProperty?: string;
transitionDuration?: string;
transitionTimingFunction?: string;
}
interface PointerLikeEvent {
clientY?: number;
pageY?: number;
@@ -59,6 +71,13 @@ function areMetricsEqual(a: ScrollbarMetrics, b: ScrollbarMetrics): boolean {
);
}
interface WebDesktopScrollbarOverlayProps {
enabled: boolean;
metrics: ScrollbarMetrics;
onScrollToOffset: (offset: number) => void;
inverted?: boolean;
}
export function useWebDesktopScrollbarMetrics() {
const [metrics, setMetrics] = useState<ScrollbarMetrics>({
offset: 0,
@@ -115,13 +134,6 @@ export function useWebDesktopScrollbarMetrics() {
};
}
interface WebDesktopScrollbarOverlayProps {
enabled: boolean;
metrics: ScrollbarMetrics;
onScrollToOffset: (offset: number) => void;
inverted?: boolean;
}
export function WebDesktopScrollbarOverlay({
enabled,
metrics,
@@ -273,8 +285,12 @@ export function WebDesktopScrollbarOverlay({
onStartShouldSetPanResponder: () => true,
onMoveShouldSetPanResponder: () => true,
onPanResponderTerminationRequest: () => false,
onPanResponderGrant: () => {
onPanResponderGrant: (event: GestureResponderEvent) => {
const clientY = readClientY(event);
dragStartOffsetRef.current = normalizedOffsetRef.current;
if (clientY !== null) {
dragStartClientYRef.current = clientY;
}
setIsDragging(true);
},
onPanResponderMove: (_event, gestureState) => {
@@ -362,20 +378,19 @@ export function WebDesktopScrollbarOverlay({
const thumbRegionStyle = useMemo(
() => [
styles.thumbRegion,
{
top: 0,
inlineUnistylesStyle({
height: thumbRegionHeight,
transform: [{ translateY: thumbRegionOffset }],
},
}),
platformIsWeb &&
({
inlineUnistylesStyle({
cursor: handleCursor,
touchAction: "none",
userSelect: "none",
transitionProperty: "transform",
transitionDuration: `${handleTravelDurationMs}ms`,
transitionTimingFunction: "linear",
} as object),
} satisfies WebPointerStyle as unknown as ViewStyle),
],
[thumbRegionHeight, thumbRegionOffset, handleCursor, handleTravelDurationMs],
);
@@ -383,19 +398,19 @@ export function WebDesktopScrollbarOverlay({
const handleStyle = useMemo(
() => [
styles.handle,
{
inlineUnistylesStyle({
marginTop: handleInsetTop,
height: geometry.handleSize,
width: handleWidth,
backgroundColor: handleColor,
opacity: handleOpacity,
},
}),
platformIsWeb &&
({
inlineUnistylesStyle({
transitionProperty: "opacity, width, background-color",
transitionDuration: `${HANDLE_FADE_DURATION_MS}ms, ${HANDLE_WIDTH_TRANSITION_DURATION_MS}ms, ${HANDLE_FADE_DURATION_MS}ms`,
transitionTimingFunction: "ease-out, cubic-bezier(0.22, 0.75, 0.2, 1), ease-out",
} as object),
} satisfies WebPointerStyle as unknown as ViewStyle),
],
[handleInsetTop, geometry.handleSize, handleWidth, handleColor, handleOpacity],
);
@@ -413,8 +428,6 @@ export function WebDesktopScrollbarOverlay({
{...(platformIsWeb
? ({
onPointerDown: startWebDrag,
onPointerEnter: handleGrabHoverIn,
onPointerLeave: handleGrabHoverOut,
onMouseEnter: handleGrabHoverIn,
onMouseLeave: handleGrabHoverOut,
} as object)
@@ -446,5 +459,6 @@ const styles = StyleSheet.create(() => ({
position: "absolute",
right: -3,
width: HANDLE_GRAB_WIDTH,
top: 0,
},
}));