Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 49 additions & 36 deletions lib/components/group/Group.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
"use client";
import { useEffect, useMemo, useRef, useState } from "react";
import { useEffect, useMemo, useRef, type CSSProperties } from "react";
import { mountGroup } from "../../global/mountGroup";
import { eventEmitter, read } from "../../global/mutableState";
import { layoutsEqual } from "../../global/utils/layoutsEqual";
Expand All @@ -9,10 +9,8 @@ import { useIsomorphicLayoutEffect } from "../../hooks/useIsomorphicLayoutEffect
import { useMergedRefs } from "../../hooks/useMergedRefs";
import { useStableCallback } from "../../hooks/useStableCallback";
import { useStableObject } from "../../hooks/useStableObject";
import { POINTER_EVENTS_CSS_PROPERTY_NAME } from "../panel/constants";
import type { RegisteredPanel } from "../panel/types";
import type { RegisteredSeparator } from "../separator/types";
import { getPanelSizeCssPropertyName } from "./getPanelSizeCssPropertyName";
import { GroupContext } from "./GroupContext";
import { sortByElementOffset } from "./sortByElementOffset";
import type { GroupProps, Layout, RegisteredGroup } from "./types";
Expand Down Expand Up @@ -60,8 +58,6 @@ export function Group({

const elementRef = useRef<HTMLDivElement | null>(null);

const [dragActive, setDragActive] = useState(false);
const [layout, setLayout] = useState(defaultLayout ?? {});
const [panelOrSeparatorChangeSigil, forceUpdate] = useForceUpdate();

const inMemoryValuesRef = useRef<{
Expand All @@ -80,8 +76,43 @@ export function Group({

useGroupImperativeHandle(id, groupRef);

// TRICKY Don't read for state; it will always lag behind by one tick
const getPanelStyles = useStableCallback(
(groupId: string, panelId: string) => {
const { interactionState, mountedGroups } = read();

for (const group of mountedGroups.keys()) {
if (group.id === groupId) {
const match = mountedGroups.get(group);
if (match) {
let dragActive = false;
switch (interactionState.state) {
case "active": {
dragActive = interactionState.hitRegions.some(
(current) => current.group === group
);
break;
}
}

return {
flexGrow: match.layout[panelId] ?? 1,
pointerEvents: dragActive ? "none" : undefined
} satisfies CSSProperties;
}
}
}

// This is unexpected
return {
flexGrow: 1
} satisfies CSSProperties;
}
);

const context = useMemo(
() => ({
getPanelStyles,
id,
orientation,
registerPanel: (panel: RegisteredPanel) => {
Expand Down Expand Up @@ -119,7 +150,7 @@ export function Group({
};
}
}),
[id, forceUpdate, orientation]
[getPanelStyles, id, forceUpdate, orientation]
);

const stableProps = useStableObject({
Expand Down Expand Up @@ -163,29 +194,20 @@ export function Group({
const { defaultLayoutDeferred, derivedPanelConstraints, layout } = match;

if (!defaultLayoutDeferred && derivedPanelConstraints.length > 0) {
setLayout(layout);

onLayoutChangeStable?.(layout);

inMemoryValues.panels.forEach((panel) => {
panel.scheduleUpdate();
});
}
}

const removeInteractionStateChangeListener = eventEmitter.addListener(
"interactionStateChange",
(interactionState) => {
switch (interactionState.state) {
case "active": {
setDragActive(
interactionState.hitRegions.some(
(current) => current.group === group
)
);
break;
}
default: {
setDragActive(false);
break;
}
}
() => {
inMemoryValues.panels.forEach((panel) => {
panel.scheduleUpdate();
});
}
);

Expand All @@ -204,9 +226,11 @@ export function Group({
return;
}

setLayout(layout);

onLayoutChangeStable?.(layout);

inMemoryValues.panels.forEach((panel) => {
panel.scheduleUpdate();
});
}
}
);
Expand Down Expand Up @@ -237,16 +261,6 @@ export function Group({
}
});

// Panel layouts and Group dragging state are shared via CSS variables
const cssVariables: { [key: string]: number | string | undefined } = {
[POINTER_EVENTS_CSS_PROPERTY_NAME]: dragActive ? "none" : undefined
};
for (const panelId in layout) {
const propertyName = getPanelSizeCssPropertyName(id, panelId);
const flexGrow = layout[panelId];
cssVariables[propertyName] = flexGrow;
}

return (
<GroupContext.Provider value={context}>
<div
Expand All @@ -262,7 +276,6 @@ export function Group({
width: "100%",
overflow: "hidden",
...style,
...cssVariables,
display: "flex",
flexDirection: orientation === "horizontal" ? "row" : "column",
flexWrap: "nowrap"
Expand Down
6 changes: 0 additions & 6 deletions lib/components/group/getPanelSizeCssPropertyName.ts

This file was deleted.

1 change: 1 addition & 0 deletions lib/components/group/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ export type RegisteredGroup = {
};

export type GroupContextType = {
getPanelStyles: (groupId: string, panelId: string) => CSSProperties;
id: string;
orientation: Orientation;
registerPanel: (panel: RegisteredPanel) => () => void;
Expand Down
4 changes: 2 additions & 2 deletions lib/components/panel/Panel.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ describe("Panel", () => {
});

describe("memoization", () => {
test("Panels and their contents should not re-render on Group layout change", () => {
test("Panels contents should not re-render on Group layout change", () => {
const onGroupRender = vi.fn();
const onPanelRender = vi.fn();
const onPanelChildrenRender = vi.fn();
Expand Down Expand Up @@ -62,7 +62,7 @@ describe("Panel", () => {
});

expect(onGroupRender).toBeCalledTimes(1);
expect(onPanelRender).not.toBeCalled();
expect(onPanelRender).toBeCalled();
expect(onPanelChildrenRender).not.toBeCalled();
});
});
Expand Down
20 changes: 9 additions & 11 deletions lib/components/panel/Panel.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
"use client";

import type { Property } from "csstype";
import { useRef, type CSSProperties } from "react";
import { useForceUpdate } from "../../hooks/useForceUpdate";
import { useId } from "../../hooks/useId";
import { useIsomorphicLayoutEffect } from "../../hooks/useIsomorphicLayoutEffect";
import { useMergedRefs } from "../../hooks/useMergedRefs";
import { useStableCallback } from "../../hooks/useStableCallback";
import { getPanelSizeCssPropertyName } from "../group/getPanelSizeCssPropertyName";
import { useGroupContext } from "../group/useGroupContext";
import { POINTER_EVENTS_CSS_PROPERTY_NAME } from "./constants";
import type { PanelProps, PanelSize } from "./types";
import { usePanelImperativeHandle } from "./usePanelImperativeHandle";

Expand Down Expand Up @@ -60,7 +58,9 @@ export function Panel({

const mergedRef = useMergedRefs(elementRef, elementRefProp);

const { id: groupId, registerPanel } = useGroupContext();
const [, forceUpdate] = useForceUpdate();

const { getPanelStyles, id: groupId, registerPanel } = useGroupContext();

const hasOnResize = onResizeUnstable !== null;
const onResizeStable = useStableCallback(
Expand Down Expand Up @@ -92,13 +92,15 @@ export function Panel({
defaultSize,
maxSize,
minSize
}
},
scheduleUpdate: forceUpdate
});
}
}, [
collapsedSize,
collapsible,
defaultSize,
forceUpdate,
hasOnResize,
id,
idIsStable,
Expand All @@ -110,7 +112,7 @@ export function Panel({

usePanelImperativeHandle(id, panelRef);

const flexGrowVar = getPanelSizeCssPropertyName(groupId, id);
const panelStyles = getPanelStyles(groupId, id);

return (
<div
Expand All @@ -123,16 +125,12 @@ export function Panel({
...PROHIBITED_CSS_PROPERTIES,

flexBasis: 0,
flexGrow: `var(${flexGrowVar}, 1)`,
flexShrink: 1,

// Prevent Panel content from interfering with panel size
overflow: "hidden",

// Disable pointer events inside of a panel during resize
// This avoid edge cases like nested iframes
pointerEvents:
`var(${POINTER_EVENTS_CSS_PROPERTY_NAME})` as Property.PointerEvents
...panelStyles
}}
>
<div
Expand Down
1 change: 1 addition & 0 deletions lib/components/panel/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ export type RegisteredPanel = {
};
onResize: OnPanelResize | undefined;
panelConstraints: PanelConstraintProps;
scheduleUpdate: () => void;
};

/**
Expand Down
3 changes: 2 additions & 1 deletion lib/global/test/mockGroup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,8 @@ export function mockGroup(
prevSize: undefined
},
panelConstraints: constraints,
onResize: vi.fn()
onResize: vi.fn(),
scheduleUpdate: vi.fn()
};

mockPanels.add(panel);
Expand Down
Loading