mirror of
https://github.com/mathuo/dockview
synced 2025-08-25 11:36:33 +00:00
bug: fix always renderers initial position
This commit is contained in:
parent
e9df48e294
commit
49014345d9
@ -1363,6 +1363,104 @@ describe('dockviewComponent', () => {
|
||||
|
||||
expect(state).toEqual(api.toJSON());
|
||||
});
|
||||
|
||||
test('always visible renderer positioning after fromJSON', async () => {
|
||||
dockview.layout(1000, 1000);
|
||||
|
||||
// Create a layout with both onlyWhenVisible and always visible panels
|
||||
dockview.fromJSON({
|
||||
activeGroup: 'group-1',
|
||||
grid: {
|
||||
root: {
|
||||
type: 'branch',
|
||||
data: [
|
||||
{
|
||||
type: 'leaf',
|
||||
data: {
|
||||
views: ['panel1', 'panel2'],
|
||||
id: 'group-1',
|
||||
activeView: 'panel1',
|
||||
},
|
||||
size: 500,
|
||||
},
|
||||
{
|
||||
type: 'leaf',
|
||||
data: {
|
||||
views: ['panel3'],
|
||||
id: 'group-2',
|
||||
activeView: 'panel3',
|
||||
},
|
||||
size: 500,
|
||||
},
|
||||
],
|
||||
size: 1000,
|
||||
},
|
||||
height: 1000,
|
||||
width: 1000,
|
||||
orientation: Orientation.HORIZONTAL,
|
||||
},
|
||||
panels: {
|
||||
panel1: {
|
||||
id: 'panel1',
|
||||
contentComponent: 'default',
|
||||
title: 'panel1',
|
||||
renderer: 'onlyWhenVisible',
|
||||
},
|
||||
panel2: {
|
||||
id: 'panel2',
|
||||
contentComponent: 'default',
|
||||
title: 'panel2',
|
||||
renderer: 'always',
|
||||
},
|
||||
panel3: {
|
||||
id: 'panel3',
|
||||
contentComponent: 'default',
|
||||
title: 'panel3',
|
||||
renderer: 'always',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// Wait for next animation frame to ensure positioning is complete
|
||||
await new Promise((resolve) => requestAnimationFrame(resolve));
|
||||
|
||||
const panel2 = dockview.getGroupPanel('panel2')!;
|
||||
const panel3 = dockview.getGroupPanel('panel3')!;
|
||||
|
||||
// Verify that always visible panels have been positioned
|
||||
const overlayContainer = dockview.overlayRenderContainer;
|
||||
|
||||
// Check that panels with renderer: 'always' are attached to overlay container
|
||||
expect(panel2.api.renderer).toBe('always');
|
||||
expect(panel3.api.renderer).toBe('always');
|
||||
|
||||
// Get the overlay elements for always visible panels
|
||||
const panel2Overlay = overlayContainer.element.querySelector('[data-panel-id]') as HTMLElement;
|
||||
const panel3Overlay = overlayContainer.element.querySelector('[data-panel-id]:not(:first-child)') as HTMLElement;
|
||||
|
||||
// Verify positioning has been applied (should not be 0 after layout)
|
||||
if (panel2Overlay) {
|
||||
const style = getComputedStyle(panel2Overlay);
|
||||
expect(style.position).toBe('absolute');
|
||||
expect(style.left).not.toBe('0px');
|
||||
expect(style.top).not.toBe('0px');
|
||||
expect(style.width).not.toBe('0px');
|
||||
expect(style.height).not.toBe('0px');
|
||||
}
|
||||
|
||||
// Test that updateAllPositions method works correctly
|
||||
const updateSpy = jest.spyOn(overlayContainer, 'updateAllPositions');
|
||||
|
||||
// Call fromJSON again to trigger position updates
|
||||
dockview.fromJSON(dockview.toJSON());
|
||||
|
||||
// Wait for the position update to be called
|
||||
await new Promise((resolve) => requestAnimationFrame(resolve));
|
||||
|
||||
expect(updateSpy).toHaveBeenCalled();
|
||||
|
||||
updateSpy.mockRestore();
|
||||
});
|
||||
});
|
||||
|
||||
test('add panel', () => {
|
||||
|
@ -347,4 +347,111 @@ describe('overlayRenderContainer', () => {
|
||||
expect(referenceContainer.element.getBoundingClientRect).toHaveBeenCalledTimes(2);
|
||||
expect(parentContainer.getBoundingClientRect).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
test('updateAllPositions forces position recalculation for visible panels', async () => {
|
||||
const cut = new OverlayRenderContainer(
|
||||
parentContainer,
|
||||
fromPartial<DockviewComponent>({})
|
||||
);
|
||||
|
||||
const panelContentEl1 = document.createElement('div');
|
||||
const panelContentEl2 = document.createElement('div');
|
||||
|
||||
const onDidVisibilityChange1 = new Emitter<any>();
|
||||
const onDidDimensionsChange1 = new Emitter<any>();
|
||||
const onDidLocationChange1 = new Emitter<any>();
|
||||
|
||||
const onDidVisibilityChange2 = new Emitter<any>();
|
||||
const onDidDimensionsChange2 = new Emitter<any>();
|
||||
const onDidLocationChange2 = new Emitter<any>();
|
||||
|
||||
const panel1 = fromPartial<IDockviewPanel>({
|
||||
api: {
|
||||
id: 'panel1',
|
||||
onDidVisibilityChange: onDidVisibilityChange1.event,
|
||||
onDidDimensionsChange: onDidDimensionsChange1.event,
|
||||
onDidLocationChange: onDidLocationChange1.event,
|
||||
isVisible: true,
|
||||
location: { type: 'grid' },
|
||||
},
|
||||
view: {
|
||||
content: {
|
||||
element: panelContentEl1,
|
||||
},
|
||||
},
|
||||
group: {
|
||||
api: {
|
||||
location: { type: 'grid' },
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const panel2 = fromPartial<IDockviewPanel>({
|
||||
api: {
|
||||
id: 'panel2',
|
||||
onDidVisibilityChange: onDidVisibilityChange2.event,
|
||||
onDidDimensionsChange: onDidDimensionsChange2.event,
|
||||
onDidLocationChange: onDidLocationChange2.event,
|
||||
isVisible: false, // This panel is not visible
|
||||
location: { type: 'grid' },
|
||||
},
|
||||
view: {
|
||||
content: {
|
||||
element: panelContentEl2,
|
||||
},
|
||||
},
|
||||
group: {
|
||||
api: {
|
||||
location: { type: 'grid' },
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// Mock getBoundingClientRect for consistent testing
|
||||
jest.spyOn(referenceContainer.element, 'getBoundingClientRect')
|
||||
.mockReturnValue(
|
||||
fromPartial<DOMRect>({
|
||||
left: 100,
|
||||
top: 200,
|
||||
width: 150,
|
||||
height: 250,
|
||||
})
|
||||
);
|
||||
|
||||
jest.spyOn(parentContainer, 'getBoundingClientRect').mockReturnValue(
|
||||
fromPartial<DOMRect>({
|
||||
left: 50,
|
||||
top: 100,
|
||||
width: 200,
|
||||
height: 300,
|
||||
})
|
||||
);
|
||||
|
||||
// Attach both panels
|
||||
const container1 = cut.attach({ panel: panel1, referenceContainer });
|
||||
const container2 = cut.attach({ panel: panel2, referenceContainer });
|
||||
|
||||
await exhaustMicrotaskQueue();
|
||||
await exhaustAnimationFrame();
|
||||
|
||||
// Clear previous calls to getBoundingClientRect
|
||||
jest.clearAllMocks();
|
||||
|
||||
// Call updateAllPositions
|
||||
cut.updateAllPositions();
|
||||
|
||||
// Should trigger resize for visible panels only
|
||||
await exhaustAnimationFrame();
|
||||
|
||||
// Verify that positioning was updated for visible panel
|
||||
expect(container1.style.left).toBe('50px');
|
||||
expect(container1.style.top).toBe('100px');
|
||||
expect(container1.style.width).toBe('150px');
|
||||
expect(container1.style.height).toBe('250px');
|
||||
|
||||
// Verify getBoundingClientRect was called for visible panel only
|
||||
// updateAllPositions should call the resize function which triggers getBoundingClientRect
|
||||
expect(referenceContainer.element.getBoundingClientRect).toHaveBeenCalled();
|
||||
expect(parentContainer.getBoundingClientRect).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
@ -1621,6 +1621,11 @@ export class DockviewComponent
|
||||
|
||||
this.updateWatermark();
|
||||
|
||||
// Force position updates for always visible panels after DOM layout is complete
|
||||
requestAnimationFrame(() => {
|
||||
this.overlayRenderContainer.updateAllPositions();
|
||||
});
|
||||
|
||||
this._onDidLayoutFromJSON.fire();
|
||||
}
|
||||
|
||||
|
@ -61,6 +61,7 @@ export class OverlayRenderContainer extends CompositeDisposable {
|
||||
disposable: IDisposable;
|
||||
destroy: IDisposable;
|
||||
element: HTMLElement;
|
||||
resize?: () => void;
|
||||
}
|
||||
> = {};
|
||||
|
||||
@ -85,6 +86,22 @@ export class OverlayRenderContainer extends CompositeDisposable {
|
||||
);
|
||||
}
|
||||
|
||||
updateAllPositions(): void {
|
||||
if (this._disposed) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Invalidate position cache to force recalculation
|
||||
this.positionCache.invalidate();
|
||||
|
||||
// Call resize function directly for all visible panels
|
||||
for (const entry of Object.values(this.map)) {
|
||||
if (entry.panel.api.isVisible && entry.resize) {
|
||||
entry.resize();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
detatch(panel: IDockviewPanel): boolean {
|
||||
if (this.map[panel.api.id]) {
|
||||
const { disposable, destroy } = this.map[panel.api.id];
|
||||
@ -290,6 +307,8 @@ export class OverlayRenderContainer extends CompositeDisposable {
|
||||
this.map[panel.api.id].disposable.dispose();
|
||||
// and reset the disposable to the active reference-container
|
||||
this.map[panel.api.id].disposable = disposable;
|
||||
// store the resize function for direct access
|
||||
this.map[panel.api.id].resize = resize;
|
||||
|
||||
return focusContainer;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user