Skip to content

Commit ea0ff47

Browse files
citizensasSassoun Derderian
andauthored
feat: add portalElementRef prop to support custom portal elements (#1064)
Co-authored-by: Sassoun Derderian <[email protected]>
1 parent 3041b6f commit ea0ff47

File tree

6 files changed

+41
-8
lines changed

6 files changed

+41
-8
lines changed

packages/cells/src/cells/dropdown-cell.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ const ReadOnlyWrap = styled.div`
6262
`;
6363

6464
const Editor: ReturnType<ProvideEditorCallback<DropdownCell>> = p => {
65-
const { value: cell, onFinishedEditing, initialValue } = p;
65+
const { value: cell, onFinishedEditing, initialValue, portalElementRef } = p;
6666
const { allowedValues, value: valueIn } = cell.data;
6767

6868
const [value, setValue] = React.useState(valueIn);
@@ -151,7 +151,7 @@ const Editor: ReturnType<ProvideEditorCallback<DropdownCell>> = p => {
151151
},
152152
};
153153
}}
154-
menuPortalTarget={document.getElementById("portal")}
154+
menuPortalTarget={portalElementRef?.current ?? document.getElementById("portal")}
155155
autoFocus={true}
156156
openMenuOnFocus={true}
157157
components={{

packages/cells/src/cells/multi-select-cell.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ const CustomMenu: React.FC<CustomMenuProps> = p => {
131131
export type MultiSelectCell = CustomCell<MultiSelectCellProps>;
132132

133133
const Editor: ReturnType<ProvideEditorCallback<MultiSelectCell>> = p => {
134-
const { value: cell, initialValue, onChange, onFinishedEditing } = p;
134+
const { value: cell, initialValue, onChange, onFinishedEditing, portalElementRef } = p;
135135
const { options: optionsIn, values: valuesIn, allowCreation, allowDuplicates } = cell.data;
136136

137137
const theme = useTheme();
@@ -343,7 +343,7 @@ const Editor: ReturnType<ProvideEditorCallback<MultiSelectCell>> = p => {
343343
value={resolveValues(value, options, allowDuplicates)}
344344
onKeyDown={cell.readonly ? undefined : handleKeyDown}
345345
menuPlacement={"auto"}
346-
menuPortalTarget={document.getElementById("portal")}
346+
menuPortalTarget={portalElementRef?.current ?? document.getElementById("portal")}
347347
autoFocus={true}
348348
openMenuOnFocus={true}
349349
openMenuOnClick={true}

packages/core/API.md

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,27 @@
22

33
## HTML/CSS Prerequisites
44

5-
Currently the Grid depends on there being a root level "portal" div in your HTML. Insert this snippet as the last child of your `<body>` tag:
5+
The Grid depends on there being a root level "portal" div in your HTML. Insert this snippet as the last child of your `<body>` tag:
66

77
```HTML
88
<div id="portal" style="position: fixed; left: 0; top: 0; z-index: 9999;" />
99
```
1010

11+
or you can create a portal element yourself using the `createPortal` function from `react-dom` and pass it to the DataEditor via the `portalElementRef` prop.
12+
13+
```jsx
14+
const portalRef = useRef(null);
15+
<>
16+
{
17+
createPortal(
18+
<div ref={portalRef} style="position: fixed; left: 0; top: 0; z-index: 9999;" />,
19+
document.body
20+
)
21+
}
22+
<DataEditor width={500} height={300} portalElementRef={portalRef} {...props} />
23+
</>
24+
```
25+
1126
Once you've got that done, the easiest way to use the Data Grid is to give it a fixed size:
1227

1328
```jsx
@@ -71,14 +86,15 @@ All data grids must set these props. These props are the bare minimum required t
7186
Most data grids will want to set the majority of these props one way or another.
7287

7388
| Name | Description |
74-
| ------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
89+
|---------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
7590
| [fixedShadowX](#fixedshadow) | Enable/disable a shadow behind fixed columns on the X axis. |
7691
| [fixedShadowY](#fixedshadow) | Enable/disable a shadow behind the header(s) on the Y axis. |
7792
| [freezeColumns](#freezecolumns) | The number of columns which should remain in place when scrolling horizontally. The row marker column, if enabled is always frozen and is not included in this count. |
7893
| [getCellsForSelection](#getcellsforselection) | Used to fetch large amounts of cells at once. Used for copy/paste, if unset copy will not work. |
7994
| [markdownDivCreateNode](#markdowndivcreatenode) | If specified, it will be used to render Markdown, instead of the default Markdown renderer used by the Grid. You'll want to use this if you need to process your Markdown for security purposes, or if you want to use a renderer with different Markdown features. |
8095
| [onVisibleRegionChanged](#onvisibleregionchanged) | Emits whenever the visible rows/columns changes. |
8196
| [provideEditor](#provideeditor) | Callback for providing a custom editor for a cell. |
97+
| [portalElementRef](#portalelementref) | A ref to the portal element to use for the overlay editor. |
8298
| [rowHeight](#rowheight) | Callback or number used to specify the height of a given row. |
8399
| [rowMarkers](#rowmarkers) | Enable/disable row marker column on the left. Can show row numbers, selection boxes, or both. |
84100
| [smoothScrollX](#smoothscroll) | Enable/disable smooth scrolling on the X axis. |
@@ -610,6 +626,12 @@ When provided the `provideEditor` callbacks job is to be a constructor for funct
610626

611627
---
612628

629+
## portalElementRef
630+
631+
Defaults to div#portal
632+
633+
---
634+
613635
## rowHeight
614636

615637
```ts

packages/core/src/data-editor/data-editor.tsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -677,6 +677,11 @@ export interface DataEditorProps extends Props, Pick<DataGridSearchProps, "image
677677
readonly scrollToActiveCell?: boolean;
678678

679679
readonly drawFocusRing?: boolean | "no-editor";
680+
681+
/**
682+
* Allows overriding the default portal element.
683+
*/
684+
readonly portalElementRef?: React.RefObject<HTMLElement>;
680685
}
681686

682687
type ScrollToFn = (
@@ -875,6 +880,7 @@ const DataEditorImpl: React.ForwardRefRenderFunction<DataEditorRef, DataEditorPr
875880
resizeIndicator,
876881
scrollToActiveCell = true,
877882
drawFocusRing: drawFocusRingIn = true,
883+
portalElementRef,
878884
} = p;
879885

880886
const drawFocusRing = drawFocusRingIn === "no-editor" ? overlay === undefined : drawFocusRingIn;
@@ -4172,6 +4178,7 @@ const DataEditorImpl: React.ForwardRefRenderFunction<DataEditorRef, DataEditorPr
41724178
className={experimental?.isSubGrid === true ? "click-outside-ignore" : undefined}
41734179
provideEditor={provideEditor}
41744180
imageEditorOverride={imageEditorOverride}
4181+
portalElementRef={portalElementRef}
41754182
onFinishEditing={onFinishEditing}
41764183
markdownDivCreateNode={markdownDivCreateNode}
41774184
isOutsideClick={isOutsideClick}

packages/core/src/internal/data-grid-overlay-editor/data-grid-overlay-editor.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ interface DataGridOverlayEditorProps {
3434
readonly onFinishEditing: (newCell: GridCell | undefined, movement: readonly [-1 | 0 | 1, -1 | 0 | 1]) => void;
3535
readonly forceEditMode: boolean;
3636
readonly highlight: boolean;
37+
readonly portalElementRef?: React.RefObject<HTMLElement>
3738
readonly imageEditorOverride?: ImageEditorType;
3839
readonly getCellRenderer: GetCellRendererCallback;
3940
readonly markdownDivCreateNode?: (content: string) => DocumentFragment;
@@ -62,6 +63,7 @@ const DataGridOverlayEditor: React.FunctionComponent<DataGridOverlayEditorProps>
6263
id,
6364
cell,
6465
bloom,
66+
portalElementRef,
6567
validateCell,
6668
getCellRenderer,
6769
provideEditor,
@@ -177,6 +179,7 @@ const DataGridOverlayEditor: React.FunctionComponent<DataGridOverlayEditorProps>
177179
const CustomEditor = isObjectEditor ? editorProvider.editor : editorProvider;
178180
editor = (
179181
<CustomEditor
182+
portalElementRef={portalElementRef}
180183
isHighlighted={highlight}
181184
onChange={setTempValue}
182185
value={targetValue}
@@ -196,11 +199,11 @@ const DataGridOverlayEditor: React.FunctionComponent<DataGridOverlayEditorProps>
196199
styleOverride = { ...styleOverride, ...stayOnScreenStyle };
197200

198201
// Consider imperatively creating and adding the element to the dom?
199-
const portalElement = document.getElementById("portal");
202+
const portalElement = portalElementRef?.current ?? document.getElementById("portal");
200203
if (portalElement === null) {
201204
// eslint-disable-next-line no-console
202205
console.error(
203-
'Cannot open Data Grid overlay editor, because portal not found. Please add `<div id="portal" />` as the last child of your `<body>`.'
206+
'Cannot open Data Grid overlay editor, because portal not found. Please, either provide a portalElementRef or add `<div id="portal" />` as the last child of your `<body>`.'
204207
);
205208
return null;
206209
}

packages/core/src/internal/data-grid/data-grid-types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -395,6 +395,7 @@ export type ProvideEditorComponent<T extends InnerGridCell> = React.FunctionComp
395395
readonly forceEditMode: boolean;
396396
readonly isValid?: boolean;
397397
readonly theme: Theme;
398+
readonly portalElementRef?: React.RefObject<HTMLElement>;
398399
}>;
399400

400401
type ObjectEditorCallbackResult<T extends InnerGridCell> = {

0 commit comments

Comments
 (0)