mirror of
https://github.com/getpaseo/paseo.git
synced 2026-07-29 12:01:31 +00:00
Fix sidebar selection and Unistyles warnings
Forward selection state into native draggable lists so virtualized workspace rows update when the active route changes. Keep floating caller styles array-shaped and strip Unistyles metadata from owned inline geometry to avoid runtime style warnings.
This commit is contained in:
@@ -88,6 +88,8 @@ Do not split a component into plain and Unistyles variants just to handle one hi
|
||||
|
||||
When a reusable component has a prop whose whole job is dynamic geometry, make that prop the seam. For example, `FloatingSurface.frameStyle` and `FloatingScrollView.style` own their own escape hatch so menu, tooltip, hover-card, and combobox callers can stay declarative instead of knowing about Unistyles internals.
|
||||
|
||||
Do not flatten a caller-provided style array and pass the flattened object back to a React Native component. Unistyles style entries carry `unistyles_*` metadata; flattening two entries produces one object with multiple metadata keys and triggers the runtime warning: "use array syntax instead of object syntax." Preserve caller styles as arrays, and only flatten the dynamic geometry value you explicitly own. If that owned value was flattened from a mixed style prop, strip `unistyles_*` metadata before sending it through `inlineUnistylesStyle`.
|
||||
|
||||
## Main Gotcha: `contentContainerStyle`
|
||||
|
||||
`ScrollView.contentContainerStyle` is the canonical trap. It looks like a style prop, but it is not the same prop that Unistyles' remapped native component registers by default. The upstream tutorial calls this out directly in its [ScrollView Background Issue](https://www.unistyl.es/v3/tutorial/settings-screen#scrollview-background-issue) section.
|
||||
|
||||
@@ -29,6 +29,7 @@ export function DraggableList<T>({
|
||||
useDragHandle: _useDragHandle = false,
|
||||
refreshing,
|
||||
onRefresh,
|
||||
extraData,
|
||||
simultaneousGestureRef,
|
||||
waitFor,
|
||||
onDragBegin: onDragBeginProp,
|
||||
@@ -116,6 +117,7 @@ export function DraggableList<T>({
|
||||
ListEmptyComponent={ListEmptyComponent}
|
||||
showsVerticalScrollIndicator={showsVerticalScrollIndicator}
|
||||
scrollEnabled={scrollEnabled}
|
||||
extraData={extraData}
|
||||
simultaneousHandlers={simultaneousHandlers}
|
||||
// Higher activation distance reduces accidental drag capture while nested
|
||||
// lists are inside a scroll container.
|
||||
|
||||
@@ -46,6 +46,8 @@ export interface DraggableListProps<T> {
|
||||
onRefresh?: () => void;
|
||||
/** Fill remaining space when content is smaller than container */
|
||||
contentContainerFlexGrow?: boolean;
|
||||
/** External row state that should invalidate virtualized native cells. */
|
||||
extraData?: unknown;
|
||||
/** Gesture ref for simultaneous handling with parent gestures (e.g., sidebar close) */
|
||||
simultaneousGestureRef?: MutableRefObject<GestureType | undefined>;
|
||||
/** Gesture ref(s) that the list should wait for before handling scroll */
|
||||
|
||||
@@ -135,6 +135,7 @@ export function DraggableList<T>({
|
||||
showsVerticalScrollIndicator = true,
|
||||
enableDesktopWebScrollbar = false,
|
||||
scrollEnabled = true,
|
||||
extraData: _extraData,
|
||||
useDragHandle = false,
|
||||
// simultaneousGestureRef is native-only, ignored on web
|
||||
onDragBegin,
|
||||
|
||||
@@ -220,6 +220,10 @@ function isProjectSelectedByRoute(input: {
|
||||
);
|
||||
}
|
||||
|
||||
function activeWorkspaceSelectionKey(selection: ActiveWorkspaceSelection | null): string {
|
||||
return selection ? `${selection.serverId}:${selection.workspaceId}` : "";
|
||||
}
|
||||
|
||||
function selectionForSelectedWorkspace(
|
||||
selected: boolean,
|
||||
workspace: SidebarWorkspaceEntry,
|
||||
@@ -2299,6 +2303,7 @@ function ProjectBlock({
|
||||
keyExtractor={workspaceKeyExtractor}
|
||||
renderItem={renderWorkspace}
|
||||
onDragEnd={handleWorkspaceDragEnd}
|
||||
extraData={activeWorkspaceSelectionKey(activeWorkspaceSelection)}
|
||||
scrollEnabled={false}
|
||||
useDragHandle
|
||||
nestable={useNestable}
|
||||
@@ -2315,18 +2320,6 @@ function ProjectBlock({
|
||||
type ProjectBlockProps = Parameters<typeof ProjectBlock>[0];
|
||||
|
||||
function areProjectBlockPropsEqual(previous: ProjectBlockProps, next: ProjectBlockProps): boolean {
|
||||
const previousActive = isProjectSelectedByRoute({
|
||||
selection: previous.activeWorkspaceSelection,
|
||||
project: previous.project,
|
||||
serverId: previous.serverId,
|
||||
enabled: previous.selectionEnabled,
|
||||
});
|
||||
const nextActive = isProjectSelectedByRoute({
|
||||
selection: next.activeWorkspaceSelection,
|
||||
project: next.project,
|
||||
serverId: next.serverId,
|
||||
enabled: next.selectionEnabled,
|
||||
});
|
||||
return (
|
||||
previous.project === next.project &&
|
||||
previous.collapsed === next.collapsed &&
|
||||
@@ -2346,7 +2339,35 @@ function areProjectBlockPropsEqual(previous: ProjectBlockProps, next: ProjectBlo
|
||||
previous.dragHandleProps === next.dragHandleProps &&
|
||||
previous.useNestable === next.useNestable &&
|
||||
previous.creatingWorkspaceIds === next.creatingWorkspaceIds &&
|
||||
previousActive === nextActive
|
||||
areProjectBlockSelectionsEqual(previous, next)
|
||||
);
|
||||
}
|
||||
|
||||
function areProjectBlockSelectionsEqual(
|
||||
previous: ProjectBlockProps,
|
||||
next: ProjectBlockProps,
|
||||
): boolean {
|
||||
const previousActive = isProjectSelectedByRoute({
|
||||
selection: previous.activeWorkspaceSelection,
|
||||
project: previous.project,
|
||||
serverId: previous.serverId,
|
||||
enabled: previous.selectionEnabled,
|
||||
});
|
||||
const nextActive = isProjectSelectedByRoute({
|
||||
selection: next.activeWorkspaceSelection,
|
||||
project: next.project,
|
||||
serverId: next.serverId,
|
||||
enabled: next.selectionEnabled,
|
||||
});
|
||||
if (previousActive !== nextActive) {
|
||||
return false;
|
||||
}
|
||||
if (!previousActive) {
|
||||
return true;
|
||||
}
|
||||
return (
|
||||
activeWorkspaceSelectionKey(previous.activeWorkspaceSelection) ===
|
||||
activeWorkspaceSelectionKey(next.activeWorkspaceSelection)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2721,6 +2742,7 @@ function ProjectModeList({
|
||||
keyExtractor={projectKeyExtractor}
|
||||
renderItem={renderProject}
|
||||
onDragEnd={handleProjectDragEnd}
|
||||
extraData={activeWorkspaceSelectionKey(activeWorkspaceSelection)}
|
||||
scrollEnabled={false}
|
||||
useDragHandle
|
||||
nestable={platformIsNative}
|
||||
|
||||
@@ -21,9 +21,12 @@ export const FloatingSurface = forwardRef<View, FloatingSurfaceProps>(function F
|
||||
): ReactElement {
|
||||
const inlineFrameStyle = useMemo(() => {
|
||||
const flattened = StyleSheet.flatten(frameStyle);
|
||||
return flattened ? inlineUnistylesStyle(flattened) : undefined;
|
||||
return flattened ? inlineUnistylesStyle(stripUnistylesMetadata(flattened)) : undefined;
|
||||
}, [frameStyle]);
|
||||
const surfaceStyle = useMemo(() => [style, inlineFrameStyle], [inlineFrameStyle, style]);
|
||||
const surfaceStyle = useMemo(
|
||||
() => appendStyle(style, inlineFrameStyle),
|
||||
[inlineFrameStyle, style],
|
||||
);
|
||||
return <Animated.View {...props} ref={ref} style={surfaceStyle} />;
|
||||
});
|
||||
|
||||
@@ -46,7 +49,7 @@ export function FloatingScrollView({
|
||||
}: FloatingScrollViewProps): ReactElement {
|
||||
const inlineStyle = useMemo(() => {
|
||||
const flattened = StyleSheet.flatten(style);
|
||||
return flattened ? inlineUnistylesStyle(flattened) : undefined;
|
||||
return flattened ? inlineUnistylesStyle(stripUnistylesMetadata(flattened)) : undefined;
|
||||
}, [style]);
|
||||
|
||||
return (
|
||||
@@ -61,3 +64,26 @@ export function FloatingScrollView({
|
||||
</ScrollView>
|
||||
);
|
||||
}
|
||||
|
||||
function appendStyle(
|
||||
style: StyleProp<ViewStyle>,
|
||||
extraStyle: ViewStyle | undefined,
|
||||
): StyleProp<ViewStyle> {
|
||||
if (!extraStyle) {
|
||||
return style;
|
||||
}
|
||||
if (Array.isArray(style)) {
|
||||
return [...style, extraStyle];
|
||||
}
|
||||
return [style, extraStyle];
|
||||
}
|
||||
|
||||
function stripUnistylesMetadata(style: ViewStyle): ViewStyle {
|
||||
const cleanStyle: Record<string, unknown> = { ...style };
|
||||
for (const key of Object.keys(cleanStyle)) {
|
||||
if (key.startsWith("unistyles_")) {
|
||||
delete cleanStyle[key];
|
||||
}
|
||||
}
|
||||
return cleanStyle as ViewStyle;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user