|
7 | 7 | import './dataGridWaffle.css'; |
8 | 8 |
|
9 | 9 | // 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'; |
11 | 11 |
|
12 | 12 | // Other dependencies. |
13 | 13 | import { DataGridRow } from './dataGridRow.js'; |
| 14 | +import * as DOM from '../../../../base/browser/dom.js'; |
14 | 15 | import { DataGridScrollbar } from './dataGridScrollbar.js'; |
15 | 16 | import { DataGridRowHeaders } from './dataGridRowHeaders.js'; |
16 | 17 | import { generateUuid } from '../../../../base/common/uuid.js'; |
17 | | -import { isMacintosh } from '../../../../base/common/platform.js'; |
| 18 | +import { mainWindow } from '../../../../base/browser/window.js'; |
18 | 19 | import { DataGridCornerTopLeft } from './dataGridCornerTopLeft.js'; |
19 | 20 | import { DataGridColumnHeaders } from './dataGridColumnHeaders.js'; |
20 | 21 | import { DisposableStore } from '../../../../base/common/lifecycle.js'; |
21 | 22 | import { DataGridScrollbarCorner } from './dataGridScrollbarCorner.js'; |
22 | 23 | import { pinToRange } from '../../../../base/common/positronUtilities.js'; |
23 | 24 | import { usePositronDataGridContext } from '../positronDataGridContext.js'; |
24 | 25 | import { FontConfigurationManager } from '../../fontConfigurationManager.js'; |
| 26 | +import { isElectron, isMacintosh } from '../../../../base/common/platform.js'; |
25 | 27 | import { ExtendColumnSelectionBy, ExtendRowSelectionBy } from '../classes/dataGridInstance.js'; |
26 | 28 | import { usePositronReactServicesContext } from '../../../../base/browser/positronReactRendererContext.js'; |
27 | | - |
28 | 29 | /** |
29 | 30 | * DataGridWaffle component. |
30 | 31 | * @param ref The foreard ref. |
@@ -71,50 +72,78 @@ export const DataGridWaffle = forwardRef<HTMLDivElement>((_: unknown, ref) => { |
71 | 72 | return () => disposableStore.dispose(); |
72 | 73 | }, [services.configurationService, context.instance]); |
73 | 74 |
|
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 | + } |
79 | 81 |
|
80 | 82 | /** |
81 | | - * Sets the screen size. |
| 83 | + * Sets the size. |
82 | 84 | * @returns A Promise<void> that resolves when the operation is complete. |
83 | 85 | */ |
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. |
86 | 92 | await context.instance.setSize(width, height); |
87 | 93 | }; |
88 | 94 |
|
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) |
93 | 101 | ); |
94 | 102 |
|
95 | 103 | // If automatic layout isn't enabled, return. |
96 | 104 | if (!context.instance.automaticLayout) { |
97 | 105 | return; |
98 | 106 | } |
99 | 107 |
|
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 | + } |
118 | 147 | }, [context.instance, dataGridWaffleRef]); |
119 | 148 |
|
120 | 149 | /** |
|
0 commit comments