Skip to content

Commit

Permalink
Add support for eventing inside of a shadow dom
Browse files Browse the repository at this point in the history
  • Loading branch information
jassmith committed Feb 7, 2024
1 parent 78e69f3 commit 51c9b62
Show file tree
Hide file tree
Showing 3 changed files with 127 additions and 7 deletions.
2 changes: 1 addition & 1 deletion packages/core/src/common/utils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { deepEqual } from "./support.js";
export function useEventListener<K extends keyof HTMLElementEventMap>(
eventName: K,
handler: (this: HTMLElement, ev: HTMLElementEventMap[K]) => any,
element: HTMLElement | Window | null,
element: HTMLElement | Window | Document | null,
passive: boolean,
capture = false
) {
Expand Down
108 changes: 108 additions & 0 deletions packages/core/src/docs/examples/shadow-dom.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import React from "react";

import { DataEditorAll as DataEditor } from "../../data-editor-all.js";
import {
BeautifulWrapper,
Description,
PropName,
useMockDataGenerator,
defaultProps,
} from "../../data-editor/stories/utils.js";
import { SimpleThemeWrapper } from "../../stories/story-utils.js";
import ReactDOM from "react-dom";

export default {
title: "Glide-Data-Grid/DataEditor Demos",

decorators: [
(Story: React.ComponentType) => (
<SimpleThemeWrapper>
<BeautifulWrapper
title="Shadow DOM"
description={
<Description>
Columns in the data grid may be grouped by setting their <PropName>group</PropName>{" "}
property.
</Description>
}>
<Story />
</BeautifulWrapper>
</SimpleThemeWrapper>
),
],
};

export const ShadowDOM: React.VFC = () => {
const { cols, getCellContent } = useMockDataGenerator(20, false, false);

return (
<ShadowDOMWrapper
render={() => (
<DataEditor
{...defaultProps}
getCellContent={getCellContent}
columns={cols}
rows={1000}
height={"100%"}
rowMarkers="both"
/>
)}
/>
);
};

const copyStylesToShadowRoot = (shadowRoot: ShadowRoot) => {
const styleElement = document.createElement("style");
for (const sheet of document.styleSheets) {
try {
if (sheet.cssRules !== undefined) {
// Check if cssRules are accessible
const rules = [...sheet.cssRules].map(rule => rule.cssText).join("\n");
styleElement.append(document.createTextNode(rules));
}
} catch (error: unknown) {
// eslint-disable-next-line no-console
console.warn("Cannot access stylesheet rules due to CORS policy", error);
}
}
shadowRoot.append(styleElement);
};

const ShadowDOMWrapper: React.FC<{
className?: string;
render: () => React.ReactElement;
}> = ({ className, render }) => {
const hostRef = React.useRef<HTMLDivElement | null>(null);
const didMountRef = React.useRef(true);

React.useEffect(() => {
if (hostRef.current === null || didMountRef.current) {
didMountRef.current = false;
return;
}

const host = hostRef.current;
const shadowRoot = host.attachShadow({ mode: "open" });

(window as any).glideShadowRoot = shadowRoot;

copyStylesToShadowRoot(shadowRoot);

// Create a div to serve as the react root container inside the shadow DOM
const reactRootContainer = document.createElement("div");
reactRootContainer.style.height = "100%";
shadowRoot.append(reactRootContainer);
shadowRoot.pare;

// Create a React root and render the children inside it
ReactDOM.render(<>{render()}</>, reactRootContainer);

// Clean up when the component unmounts
return () => {
// reactRoot.unmount()
reactRootContainer.remove();
};
}, [render]);

return <div ref={hostRef} className={className} style={{ height: "100%" }} />;
};
24 changes: 18 additions & 6 deletions packages/core/src/internal/data-grid/data-grid.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -383,6 +383,9 @@ const DataGrid: React.ForwardRefRenderFunction<DataGridRef, DataGridProps> = (p,
const cellXOffset = Math.max(freezeColumns, Math.min(columns.length - 1, cellXOffsetReal));

const ref = React.useRef<HTMLCanvasElement | null>(null);
const windowEventTargetRef = React.useRef<Document | Window>(window);
const windowEventTarget = windowEventTargetRef.current;

const imageLoader = imageWindowLoader;
const damageRegion = React.useRef<CellSet | undefined>();
const [scrolling, setScrolling] = React.useState<boolean>(false);
Expand Down Expand Up @@ -1076,8 +1079,8 @@ const DataGrid: React.ForwardRefRenderFunction<DataGridRef, DataGridProps> = (p,
onMouseDown,
]
);
useEventListener("touchstart", onMouseDownImpl, window, false);
useEventListener("mousedown", onMouseDownImpl, window, false);
useEventListener("touchstart", onMouseDownImpl, windowEventTarget, false);
useEventListener("mousedown", onMouseDownImpl, windowEventTarget, false);

const lastUpTime = React.useRef(0);

Expand Down Expand Up @@ -1153,8 +1156,8 @@ const DataGrid: React.ForwardRefRenderFunction<DataGridRef, DataGridProps> = (p,
},
[onMouseUp, eventTargetRef, getMouseArgsForPosition, isOverHeaderElement, groupHeaderActionForEvent]
);
useEventListener("mouseup", onMouseUpImpl, window, false);
useEventListener("touchend", onMouseUpImpl, window, false);
useEventListener("mouseup", onMouseUpImpl, windowEventTarget, false);
useEventListener("touchend", onMouseUpImpl, windowEventTarget, false);

const onClickImpl = React.useCallback(
(ev: MouseEvent | TouchEvent) => {
Expand Down Expand Up @@ -1217,7 +1220,7 @@ const DataGrid: React.ForwardRefRenderFunction<DataGridRef, DataGridProps> = (p,
groupHeaderActionForEvent,
]
);
useEventListener("click", onClickImpl, window, false);
useEventListener("click", onClickImpl, windowEventTarget, false);

const onContextMenuImpl = React.useCallback(
(ev: MouseEvent) => {
Expand Down Expand Up @@ -1342,7 +1345,7 @@ const DataGrid: React.ForwardRefRenderFunction<DataGridRef, DataGridProps> = (p,
damageInternal,
]
);
useEventListener("mousemove", onMouseMoveImpl, window, true);
useEventListener("mousemove", onMouseMoveImpl, windowEventTarget, true);

const onKeyDownImpl = React.useCallback(
(event: React.KeyboardEvent<HTMLCanvasElement>) => {
Expand Down Expand Up @@ -1410,6 +1413,15 @@ const DataGrid: React.ForwardRefRenderFunction<DataGridRef, DataGridProps> = (p,
if (canvasRef !== undefined) {
canvasRef.current = instance;
}

if (instance === null) {
windowEventTargetRef.current = window;
} else {
const docRoot = instance.getRootNode();

if (docRoot === document) windowEventTargetRef.current = window;
windowEventTargetRef.current = docRoot as any;
}
},
[canvasRef]
);
Expand Down

0 comments on commit 51c9b62

Please sign in to comment.