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.
|
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`
|
## 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.
|
`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,
|
useDragHandle: _useDragHandle = false,
|
||||||
refreshing,
|
refreshing,
|
||||||
onRefresh,
|
onRefresh,
|
||||||
|
extraData,
|
||||||
simultaneousGestureRef,
|
simultaneousGestureRef,
|
||||||
waitFor,
|
waitFor,
|
||||||
onDragBegin: onDragBeginProp,
|
onDragBegin: onDragBeginProp,
|
||||||
@@ -116,6 +117,7 @@ export function DraggableList<T>({
|
|||||||
ListEmptyComponent={ListEmptyComponent}
|
ListEmptyComponent={ListEmptyComponent}
|
||||||
showsVerticalScrollIndicator={showsVerticalScrollIndicator}
|
showsVerticalScrollIndicator={showsVerticalScrollIndicator}
|
||||||
scrollEnabled={scrollEnabled}
|
scrollEnabled={scrollEnabled}
|
||||||
|
extraData={extraData}
|
||||||
simultaneousHandlers={simultaneousHandlers}
|
simultaneousHandlers={simultaneousHandlers}
|
||||||
// Higher activation distance reduces accidental drag capture while nested
|
// Higher activation distance reduces accidental drag capture while nested
|
||||||
// lists are inside a scroll container.
|
// lists are inside a scroll container.
|
||||||
|
|||||||
@@ -46,6 +46,8 @@ export interface DraggableListProps<T> {
|
|||||||
onRefresh?: () => void;
|
onRefresh?: () => void;
|
||||||
/** Fill remaining space when content is smaller than container */
|
/** Fill remaining space when content is smaller than container */
|
||||||
contentContainerFlexGrow?: boolean;
|
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) */
|
/** Gesture ref for simultaneous handling with parent gestures (e.g., sidebar close) */
|
||||||
simultaneousGestureRef?: MutableRefObject<GestureType | undefined>;
|
simultaneousGestureRef?: MutableRefObject<GestureType | undefined>;
|
||||||
/** Gesture ref(s) that the list should wait for before handling scroll */
|
/** Gesture ref(s) that the list should wait for before handling scroll */
|
||||||
|
|||||||
@@ -135,6 +135,7 @@ export function DraggableList<T>({
|
|||||||
showsVerticalScrollIndicator = true,
|
showsVerticalScrollIndicator = true,
|
||||||
enableDesktopWebScrollbar = false,
|
enableDesktopWebScrollbar = false,
|
||||||
scrollEnabled = true,
|
scrollEnabled = true,
|
||||||
|
extraData: _extraData,
|
||||||
useDragHandle = false,
|
useDragHandle = false,
|
||||||
// simultaneousGestureRef is native-only, ignored on web
|
// simultaneousGestureRef is native-only, ignored on web
|
||||||
onDragBegin,
|
onDragBegin,
|
||||||
|
|||||||
@@ -220,6 +220,10 @@ function isProjectSelectedByRoute(input: {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function activeWorkspaceSelectionKey(selection: ActiveWorkspaceSelection | null): string {
|
||||||
|
return selection ? `${selection.serverId}:${selection.workspaceId}` : "";
|
||||||
|
}
|
||||||
|
|
||||||
function selectionForSelectedWorkspace(
|
function selectionForSelectedWorkspace(
|
||||||
selected: boolean,
|
selected: boolean,
|
||||||
workspace: SidebarWorkspaceEntry,
|
workspace: SidebarWorkspaceEntry,
|
||||||
@@ -2299,6 +2303,7 @@ function ProjectBlock({
|
|||||||
keyExtractor={workspaceKeyExtractor}
|
keyExtractor={workspaceKeyExtractor}
|
||||||
renderItem={renderWorkspace}
|
renderItem={renderWorkspace}
|
||||||
onDragEnd={handleWorkspaceDragEnd}
|
onDragEnd={handleWorkspaceDragEnd}
|
||||||
|
extraData={activeWorkspaceSelectionKey(activeWorkspaceSelection)}
|
||||||
scrollEnabled={false}
|
scrollEnabled={false}
|
||||||
useDragHandle
|
useDragHandle
|
||||||
nestable={useNestable}
|
nestable={useNestable}
|
||||||
@@ -2315,18 +2320,6 @@ function ProjectBlock({
|
|||||||
type ProjectBlockProps = Parameters<typeof ProjectBlock>[0];
|
type ProjectBlockProps = Parameters<typeof ProjectBlock>[0];
|
||||||
|
|
||||||
function areProjectBlockPropsEqual(previous: ProjectBlockProps, next: ProjectBlockProps): boolean {
|
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 (
|
return (
|
||||||
previous.project === next.project &&
|
previous.project === next.project &&
|
||||||
previous.collapsed === next.collapsed &&
|
previous.collapsed === next.collapsed &&
|
||||||
@@ -2346,7 +2339,35 @@ function areProjectBlockPropsEqual(previous: ProjectBlockProps, next: ProjectBlo
|
|||||||
previous.dragHandleProps === next.dragHandleProps &&
|
previous.dragHandleProps === next.dragHandleProps &&
|
||||||
previous.useNestable === next.useNestable &&
|
previous.useNestable === next.useNestable &&
|
||||||
previous.creatingWorkspaceIds === next.creatingWorkspaceIds &&
|
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}
|
keyExtractor={projectKeyExtractor}
|
||||||
renderItem={renderProject}
|
renderItem={renderProject}
|
||||||
onDragEnd={handleProjectDragEnd}
|
onDragEnd={handleProjectDragEnd}
|
||||||
|
extraData={activeWorkspaceSelectionKey(activeWorkspaceSelection)}
|
||||||
scrollEnabled={false}
|
scrollEnabled={false}
|
||||||
useDragHandle
|
useDragHandle
|
||||||
nestable={platformIsNative}
|
nestable={platformIsNative}
|
||||||
|
|||||||
@@ -21,9 +21,12 @@ export const FloatingSurface = forwardRef<View, FloatingSurfaceProps>(function F
|
|||||||
): ReactElement {
|
): ReactElement {
|
||||||
const inlineFrameStyle = useMemo(() => {
|
const inlineFrameStyle = useMemo(() => {
|
||||||
const flattened = StyleSheet.flatten(frameStyle);
|
const flattened = StyleSheet.flatten(frameStyle);
|
||||||
return flattened ? inlineUnistylesStyle(flattened) : undefined;
|
return flattened ? inlineUnistylesStyle(stripUnistylesMetadata(flattened)) : undefined;
|
||||||
}, [frameStyle]);
|
}, [frameStyle]);
|
||||||
const surfaceStyle = useMemo(() => [style, inlineFrameStyle], [inlineFrameStyle, style]);
|
const surfaceStyle = useMemo(
|
||||||
|
() => appendStyle(style, inlineFrameStyle),
|
||||||
|
[inlineFrameStyle, style],
|
||||||
|
);
|
||||||
return <Animated.View {...props} ref={ref} style={surfaceStyle} />;
|
return <Animated.View {...props} ref={ref} style={surfaceStyle} />;
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -46,7 +49,7 @@ export function FloatingScrollView({
|
|||||||
}: FloatingScrollViewProps): ReactElement {
|
}: FloatingScrollViewProps): ReactElement {
|
||||||
const inlineStyle = useMemo(() => {
|
const inlineStyle = useMemo(() => {
|
||||||
const flattened = StyleSheet.flatten(style);
|
const flattened = StyleSheet.flatten(style);
|
||||||
return flattened ? inlineUnistylesStyle(flattened) : undefined;
|
return flattened ? inlineUnistylesStyle(stripUnistylesMetadata(flattened)) : undefined;
|
||||||
}, [style]);
|
}, [style]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -61,3 +64,26 @@ export function FloatingScrollView({
|
|||||||
</ScrollView>
|
</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