Skip to content
Closed
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
11 changes: 6 additions & 5 deletions lib/components/group/Group.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -207,11 +207,12 @@ export function Group({
}

const group = getRegisteredGroup(id);
const groupState = getMountedGroupState(id);
if (group && groupState) {
updateMountedGroup(group, {
...groupState,
derivedPanelConstraints: calculatePanelConstraints(group)
if (group) {
updateMountedGroup({
group,
partial: {
derivedPanelConstraints: calculatePanelConstraints(group)
}
});
}
},
Expand Down
8 changes: 6 additions & 2 deletions lib/global/event-handlers/onDocumentPointerMove.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,12 @@ export function onDocumentPointerMove(event: PointerEvent) {
return;
}
const groupState = getMountedGroupState(hitRegion.group.id, true);
updateMountedGroup(hitRegion.group, groupState, {
isUserInteraction: true
updateMountedGroup({
group: hitRegion.group,
meta: {
isUserInteraction: true
},
state: groupState
});
});

Expand Down
23 changes: 23 additions & 0 deletions lib/global/event-handlers/onDocumentVisibilityChange.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { getMountedGroups } from "../mutable-state/groups";
import { resizeObserverCallback } from "./resizeObserverCallback";

export function onDocumentVisibilityChange(event: Event) {
const ownerDocument = event.currentTarget as Document;

switch (document.visibilityState) {
case "visible": {
const mountedGroups = getMountedGroups();

for (const [group, state] of mountedGroups) {
if (group.element.ownerDocument === ownerDocument) {
for (const target of state.pendingResizeEventsForElements) {
resizeObserverCallback({
group,
target
});
}
}
}
}
}
}
101 changes: 101 additions & 0 deletions lib/global/event-handlers/resizeObserverCallback.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import type { RegisteredGroup } from "../../components/group/types";
import { calculateAvailableGroupSize } from "../dom/calculateAvailableGroupSize";
import { calculatePanelConstraints } from "../dom/calculatePanelConstraints";
import {
getMountedGroupState,
updateMountedGroup
} from "../mutable-state/groups";
import { calculateDefaultLayout } from "../utils/calculateDefaultLayout";
import { layoutsEqual } from "../utils/layoutsEqual";
import { notifyPanelOnResize } from "../utils/notifyPanelOnResize";
import { objectsEqual } from "../utils/objectsEqual";
import { preserveFixedPanelSizes } from "../utils/preserveFixedPanelSizes";
import { validatePanelGroupLayout } from "../utils/validatePanelGroupLayout";

export function resizeObserverCallback({
group,
target
}: {
group: RegisteredGroup;
target: Element;
}) {
const ownerDocument = target.ownerDocument;

const groupState = getMountedGroupState(group.id);
if (!groupState) {
return;
}

const pendingResizeEventsForElements = new Set(
groupState.pendingResizeEventsForElements
);

switch (ownerDocument.visibilityState) {
case "hidden": {
// Ignore ResizeObserver updates when hidden; see #731
pendingResizeEventsForElements.add(target);

updateMountedGroup({
group,
partial: { pendingResizeEventsForElements }
});
return;
}
case "visible": {
pendingResizeEventsForElements.delete(target);
}
}

if (target === group.element) {
const groupSize = calculateAvailableGroupSize({ group });
if (groupSize === 0) {
// Can't calculate anything meaningful if the group has a width/height of 0
// (This could indicate that it's within a hidden subtree)
return;
}

// Update non-percentage based constraints
const nextDerivedPanelConstraints = calculatePanelConstraints(group);

// Revalidate layout in case constraints have changed or group size changed
const prevLayout = groupState.defaultLayoutDeferred
? calculateDefaultLayout(nextDerivedPanelConstraints)
: groupState.layout;
const unsafeLayout = preserveFixedPanelSizes({
group,
nextGroupSize: groupSize,
prevGroupSize: groupState.groupSize,
prevLayout
});
const nextLayout = validatePanelGroupLayout({
layout: unsafeLayout,
panelConstraints: nextDerivedPanelConstraints
});

if (
!groupState.defaultLayoutDeferred &&
layoutsEqual(groupState.layout, nextLayout) &&
objectsEqual(
groupState.derivedPanelConstraints,
nextDerivedPanelConstraints
) &&
groupState.groupSize === groupSize
) {
return;
}

updateMountedGroup({
group,
state: {
defaultLayoutDeferred: false,
derivedPanelConstraints: nextDerivedPanelConstraints,
groupSize,
layout: nextLayout,
pendingResizeEventsForElements,
separatorToPanels: groupState.separatorToPanels
}
});
} else {
notifyPanelOnResize(group, target as HTMLElement);
}
}
103 changes: 31 additions & 72 deletions lib/global/mountGroup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,11 @@ import { onDocumentPointerLeave } from "./event-handlers/onDocumentPointerLeave"
import { onDocumentPointerMove } from "./event-handlers/onDocumentPointerMove";
import { onDocumentPointerOut } from "./event-handlers/onDocumentPointerOut";
import { onDocumentPointerUp } from "./event-handlers/onDocumentPointerUp";
import {
deleteMutableGroup,
getMountedGroupState,
updateMountedGroup
} from "./mutable-state/groups";
import { onDocumentVisibilityChange } from "./event-handlers/onDocumentVisibilityChange";
import { resizeObserverCallback } from "./event-handlers/resizeObserverCallback";
import { deleteMutableGroup, updateMountedGroup } from "./mutable-state/groups";
import type { SeparatorToPanelsMap } from "./mutable-state/types";
import { calculateDefaultLayout } from "./utils/calculateDefaultLayout";
import { layoutsEqual } from "./utils/layoutsEqual";
import { notifyPanelOnResize } from "./utils/notifyPanelOnResize";
import { objectsEqual } from "./utils/objectsEqual";
import { preserveFixedPanelSizes } from "./utils/preserveFixedPanelSizes";
import { validateLayoutKeys } from "./utils/validateLayoutKeys";
import { validatePanelGroupLayout } from "./utils/validatePanelGroupLayout";

Expand All @@ -43,64 +37,17 @@ export function mountGroup(group: RegisteredGroup) {
// Add Panels with onResize callbacks to ResizeObserver
// Add Group to ResizeObserver also in order to sync % based constraints
const resizeObserver = new ResizeObserver((entries) => {
if (!isMounted) {
return;
}

for (const entry of entries) {
const { borderBoxSize, target } = entry;
if (target === group.element) {
if (isMounted) {
const groupSize = calculateAvailableGroupSize({ group });
if (groupSize === 0) {
// Can't calculate anything meaningful if the group has a width/height of 0
// (This could indicate that it's within a hidden subtree)
return;
}

const groupState = getMountedGroupState(group.id);
if (!groupState) {
// Not mounted yet
return;
}

// Update non-percentage based constraints
const nextDerivedPanelConstraints = calculatePanelConstraints(group);

// Revalidate layout in case constraints have changed or group size changed
const prevLayout = groupState.defaultLayoutDeferred
? calculateDefaultLayout(nextDerivedPanelConstraints)
: groupState.layout;
const unsafeLayout = preserveFixedPanelSizes({
group,
nextGroupSize: groupSize,
prevGroupSize: groupState.groupSize,
prevLayout
});
const nextLayout = validatePanelGroupLayout({
layout: unsafeLayout,
panelConstraints: nextDerivedPanelConstraints
});

if (
!groupState.defaultLayoutDeferred &&
layoutsEqual(groupState.layout, nextLayout) &&
objectsEqual(
groupState.derivedPanelConstraints,
nextDerivedPanelConstraints
) &&
groupState.groupSize === groupSize
) {
return;
}

updateMountedGroup(group, {
defaultLayoutDeferred: false,
derivedPanelConstraints: nextDerivedPanelConstraints,
groupSize,
layout: nextLayout,
separatorToPanels: groupState.separatorToPanels
});
}
} else {
notifyPanelOnResize(group, target as HTMLElement, borderBoxSize);
}
const { target } = entry;

resizeObserverCallback({
group,
target
});
}
});

Expand Down Expand Up @@ -159,12 +106,16 @@ export function mountGroup(group: RegisteredGroup) {
}
});

updateMountedGroup(group, {
defaultLayoutDeferred: groupSize === 0,
derivedPanelConstraints,
groupSize,
layout: defaultLayoutSafe,
separatorToPanels
updateMountedGroup({
group,
partial: {
defaultLayoutDeferred: groupSize === 0,
derivedPanelConstraints,
groupSize,
pendingResizeEventsForElements: new Set(),
layout: defaultLayoutSafe,
separatorToPanels
}
});

group.separators.forEach((separator) => {
Expand All @@ -187,6 +138,10 @@ export function mountGroup(group: RegisteredGroup) {
ownerDocument.addEventListener("pointermove", onDocumentPointerMove);
ownerDocument.addEventListener("pointerout", onDocumentPointerOut);
ownerDocument.addEventListener("pointerup", onDocumentPointerUp, true);
ownerDocument.addEventListener(
"visibilitychange",
onDocumentVisibilityChange
);
}

return function unmountGroup() {
Expand Down Expand Up @@ -224,6 +179,10 @@ export function mountGroup(group: RegisteredGroup) {
ownerDocument.removeEventListener("pointermove", onDocumentPointerMove);
ownerDocument.removeEventListener("pointerout", onDocumentPointerOut);
ownerDocument.removeEventListener("pointerup", onDocumentPointerUp, true);
ownerDocument.removeEventListener(
"onvisibilitychange",
onDocumentVisibilityChange
);
}

resizeObserver.disconnect();
Expand Down
40 changes: 35 additions & 5 deletions lib/global/mutable-state/groups.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ type State = {
defaultLayoutDeferred: boolean;
derivedPanelConstraints: PanelConstraints[];
groupSize: number;
pendingResizeEventsForElements: Set<Element>;
layout: Layout;
separatorToPanels: SeparatorToPanelsMap;
};
Expand Down Expand Up @@ -90,13 +91,42 @@ export function subscribeToMountedGroup(
});
}

export function updateMountedGroup(
group: RegisteredGroup,
next: State,
meta?: { isUserInteraction?: boolean }
) {
export function updateMountedGroup({
group,
meta,
partial,
state
}: {
group: RegisteredGroup;
meta?: { isUserInteraction?: boolean };
} & (
| {
partial?: never;
state: State;
}
| {
partial: Partial<State>;
state?: never;
}
)) {
const prev = map.get(group);

let next: State;
if (state) {
next = state;
} else {
next = {
defaultLayoutDeferred: false,
derivedPanelConstraints: [],
groupSize: 0,
pendingResizeEventsForElements: new Set(),
layout: {},
separatorToPanels: new Map(),
...prev,
...partial
};
}

map = new Map(map);
map.set(group, next);

Expand Down
18 changes: 9 additions & 9 deletions lib/global/utils/adjustLayoutForSeparator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,20 +43,20 @@ export function adjustLayoutForSeparator(
});

if (!layoutsEqual(prevLayout, nextLayout)) {
updateMountedGroup(
updateMountedGroup({
group,
{
// Keyboard resizes (arrow keys, Home/End, Enter collapse/expand) originate
// from a real DOM event on the separator, so they are user interactions
// just like pointer drags. This function is only reached from
// onDocumentKeyDown. See #716.
meta: { isUserInteraction: true },
partial: {
defaultLayoutDeferred: groupState.defaultLayoutDeferred,
derivedPanelConstraints: groupState.derivedPanelConstraints,
groupSize: groupState.groupSize,
layout: nextLayout,
separatorToPanels: groupState.separatorToPanels
},
// Keyboard resizes (arrow keys, Home/End, Enter collapse/expand) originate
// from a real DOM event on the separator, so they are user interactions
// just like pointer drags. This function is only reached from
// onDocumentKeyDown. See #716.
{ isUserInteraction: true }
);
}
});
}
}
Loading
Loading