Skip to content

Commit fb64136

Browse files
authored
Address ResizeObserver not working in secondary windows by polling for size changes in the DataGridWaffle (#9000)
Addresses #8695. The root cause of this issue is that ResizeObserver does not work well on elements inside secondary Electron windows. This causes issues with the Data Grid's automatic layout feature, which relies on knowing when the size of the Data Grid waffle changes. In a secondary Electron window, the workaround is to poll for changes in the Data Grid waffle’s size.
1 parent cb1af90 commit fb64136

File tree

2 files changed

+63
-33
lines changed

2 files changed

+63
-33
lines changed

src/vs/workbench/browser/positronDataExplorer/components/dataExplorerPanel/components/addEditRowFilterModalPopup/components/columnSelectorModalPopup.css

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515

1616
.column-selector .column-selector-search {
1717
height: 34px;
18+
box-sizing: border-box;
1819
grid-row: column-selector-search / column-selector-data-grid;
1920
border-bottom: 1px solid var(--vscode-positronDataExplorer-border);
2021
}

src/vs/workbench/browser/positronDataGrid/components/dataGridWaffle.tsx

Lines changed: 62 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -7,24 +7,25 @@
77
import './dataGridWaffle.css';
88

99
// React.
10-
import React, { forwardRef, JSX, KeyboardEvent, useEffect, useImperativeHandle, useRef, useState, WheelEvent } from 'react';
10+
import React, { forwardRef, JSX, KeyboardEvent, useEffect, useImperativeHandle, useLayoutEffect, useRef, useState, WheelEvent } from 'react';
1111

1212
// Other dependencies.
1313
import { DataGridRow } from './dataGridRow.js';
14+
import * as DOM from '../../../../base/browser/dom.js';
1415
import { DataGridScrollbar } from './dataGridScrollbar.js';
1516
import { DataGridRowHeaders } from './dataGridRowHeaders.js';
1617
import { generateUuid } from '../../../../base/common/uuid.js';
17-
import { isMacintosh } from '../../../../base/common/platform.js';
18+
import { mainWindow } from '../../../../base/browser/window.js';
1819
import { DataGridCornerTopLeft } from './dataGridCornerTopLeft.js';
1920
import { DataGridColumnHeaders } from './dataGridColumnHeaders.js';
2021
import { DisposableStore } from '../../../../base/common/lifecycle.js';
2122
import { DataGridScrollbarCorner } from './dataGridScrollbarCorner.js';
2223
import { pinToRange } from '../../../../base/common/positronUtilities.js';
2324
import { usePositronDataGridContext } from '../positronDataGridContext.js';
2425
import { FontConfigurationManager } from '../../fontConfigurationManager.js';
26+
import { isElectron, isMacintosh } from '../../../../base/common/platform.js';
2527
import { ExtendColumnSelectionBy, ExtendRowSelectionBy } from '../classes/dataGridInstance.js';
2628
import { usePositronReactServicesContext } from '../../../../base/browser/positronReactRendererContext.js';
27-
2829
/**
2930
* DataGridWaffle component.
3031
* @param ref The foreard ref.
@@ -71,50 +72,78 @@ export const DataGridWaffle = forwardRef<HTMLDivElement>((_: unknown, ref) => {
7172
return () => disposableStore.dispose();
7273
}, [services.configurationService, context.instance]);
7374

74-
// Layout useEffect.
75-
useEffect(() => {
76-
// Set the initial width and height.
77-
setWidth(dataGridWaffleRef.current.offsetWidth);
78-
setHeight(dataGridWaffleRef.current.offsetHeight);
75+
// Automatic layout useLayoutEffect.
76+
useLayoutEffect(() => {
77+
// Wait for the data grid waffle to be mounted.
78+
if (!dataGridWaffleRef.current) {
79+
return;
80+
}
7981

8082
/**
81-
* Sets the screen size.
83+
* Sets the size.
8284
* @returns A Promise<void> that resolves when the operation is complete.
8385
*/
84-
const setScreenSize = async (width: number, height: number) => {
85-
// Set the screen size.
86+
const setSize = async (width: number, height: number) => {
87+
// Set the width and height in this component.
88+
setWidth(width);
89+
setHeight(height);
90+
91+
// Set the size in the data grid instance.
8692
await context.instance.setSize(width, height);
8793
};
8894

89-
// Set the initial screen size.
90-
setScreenSize(
91-
dataGridWaffleRef.current.offsetWidth,
92-
dataGridWaffleRef.current.offsetHeight
95+
// ResizeObserver does not work well on elements inside secondary Electron windows. This causes issues
96+
// with the data grid's automatic layout feature, which relies on knowing when the size of the data grid
97+
// waffle changes. See https://github.com/posit-dev/positron/issues/8695. Using `requestAnimationFrame`
98+
// ensures that the initial size of the data grid waffle is set when the component is mounted.
99+
DOM.getWindow(dataGridWaffleRef.current).requestAnimationFrame(async () =>
100+
await setSize(dataGridWaffleRef.current.offsetWidth, dataGridWaffleRef.current.offsetHeight)
93101
);
94102

95103
// If automatic layout isn't enabled, return.
96104
if (!context.instance.automaticLayout) {
97105
return;
98106
}
99107

100-
// Allocate and initialize the waffle resize observer.
101-
const resizeObserver = new ResizeObserver(async entries => {
102-
// Set the width and height.
103-
setWidth(entries[0].contentRect.width);
104-
setHeight(entries[0].contentRect.height);
105-
106-
// Set the screen size.
107-
await setScreenSize(
108-
entries[0].contentRect.width,
109-
entries[0].contentRect.height
110-
);
111-
});
112-
113-
// Start observing the size of the waffle.
114-
resizeObserver.observe(dataGridWaffleRef.current);
115-
116-
// Return the cleanup function that will disconnect the resize observer.
117-
return () => resizeObserver.disconnect();
108+
// If we're not in Electron, or the data grid waffle is in the main window, allocate and
109+
// initialize the data grid waffle resize observer. Otherwise, poll the size of the data
110+
// grid waffle every 250 milliseconds. This is a workaround for ResizeObserver not working
111+
// in secondary Electron windows.
112+
if (!isElectron || DOM.getWindow(dataGridWaffleRef.current) === mainWindow) {
113+
// Allocate and initialize the data grid waffle resize observer.
114+
const dataGridWaffleResizeObserver = new ResizeObserver(async entries => {
115+
await setSize(
116+
entries[0].contentRect.width,
117+
entries[0].contentRect.height
118+
);
119+
});
120+
121+
// Start observing the size of the data grid waffle.
122+
dataGridWaffleResizeObserver.observe(dataGridWaffleRef.current);
123+
124+
// Return the cleanup function that will disconnect the resize observers.
125+
return () => dataGridWaffleResizeObserver.disconnect();
126+
} else {
127+
// Get the window of the data grid waffle.
128+
const window = DOM.getWindow(dataGridWaffleRef.current);
129+
130+
// Set the last width and height.
131+
let lastWidth = dataGridWaffleRef.current.offsetWidth;
132+
let lastHeight = dataGridWaffleRef.current.offsetHeight;
133+
134+
// Poll the size of the data grid waffle every 250 milliseconds.
135+
const interval = window.setInterval(async () => {
136+
// If the size of the data grid waffle has changed, update the last width and height and set the screen size.
137+
if (lastWidth !== dataGridWaffleRef.current.offsetWidth || lastHeight !== dataGridWaffleRef.current.offsetHeight) {
138+
lastWidth = dataGridWaffleRef.current.offsetWidth;
139+
lastHeight = dataGridWaffleRef.current.offsetHeight;
140+
await setSize(lastWidth, lastHeight);
141+
}
142+
}, 250);
143+
144+
// Return the cleanup function that will clear the interval.
145+
return () => window.clearInterval(interval);
146+
}
118147
}, [context.instance, dataGridWaffleRef]);
119148

120149
/**

0 commit comments

Comments
 (0)