bug: disable iframes within shadowdom during dnd

This commit is contained in:
mathuo 2024-12-10 20:57:53 +00:00
parent 429177436e
commit a4b9255f43
No known key found for this signature in database
GPG Key ID: C6EEDEFD6CA07281
4 changed files with 94 additions and 10 deletions

View File

@ -112,8 +112,11 @@ export function isAncestor(
return false; return false;
} }
export function getElementsByTagName(tag: string): HTMLElement[] { export function getElementsByTagName(
return Array.prototype.slice.call(document.getElementsByTagName(tag), 0); tag: string,
document: ParentNode
): HTMLElement[] {
return Array.prototype.slice.call(document.querySelectorAll(tag), 0);
} }
export interface IFocusTracker extends IDisposable { export interface IFocusTracker extends IDisposable {
@ -288,10 +291,29 @@ export function addTestId(element: HTMLElement, id: string): void {
element.setAttribute('data-testid', id); element.setAttribute('data-testid', id);
} }
export function disableIframePointEvents() { export function disableIframePointEvents(rootNode: ParentNode = document) {
const includeShadowDom = true;
const shadowRoots = [];
if (includeShadowDom) {
const items = rootNode.querySelectorAll('*');
for (let i = 0; i < items.length; i++) {
const item = items[i];
if (item.shadowRoot) {
shadowRoots.push(item.shadowRoot);
}
}
}
const iframes: HTMLElement[] = [ const iframes: HTMLElement[] = [
...getElementsByTagName('iframe'), ...getElementsByTagName('iframe', rootNode),
...getElementsByTagName('webview'), ...getElementsByTagName('webview', rootNode),
...shadowRoots.flatMap((root) => [
...getElementsByTagName('iframe', root),
...getElementsByTagName('webview', root),
]),
]; ];
const original = new WeakMap<HTMLElement, string>(); // don't hold onto HTMLElement references longer than required const original = new WeakMap<HTMLElement, string>(); // don't hold onto HTMLElement references longer than required

View File

@ -7,6 +7,7 @@ import {
DockviewApi, DockviewApi,
} from 'dockview'; } from 'dockview';
import * as React from 'react'; import * as React from 'react';
import * as ReactDOM from 'react-dom/client';
import './app.scss'; import './app.scss';
import { defaultConfig } from './defaultLayout'; import { defaultConfig } from './defaultLayout';
import { GridActions } from './gridActions'; import { GridActions } from './gridActions';
@ -30,6 +31,20 @@ const Option = (props: {
); );
}; };
const ShadowIframe = (props: IDockviewPanelProps) => {
return (
<iframe
onMouseDown={() => {
if (!props.api.isActive) {
props.api.setActive();
}
}}
style={{ border: 'none', width: '100%', height: '100%' }}
src="https://dockview.dev"
/>
);
};
const components = { const components = {
default: (props: IDockviewPanelProps) => { default: (props: IDockviewPanelProps) => {
const isDebug = React.useContext(DebugContext); const isDebug = React.useContext(DebugContext);
@ -105,6 +120,7 @@ const components = {
} }
}} }}
style={{ style={{
border: 'none',
width: '100%', width: '100%',
height: '100%', height: '100%',
}} }}
@ -112,6 +128,33 @@ const components = {
/> />
); );
}, },
shadowDom: (props: IDockviewPanelProps) => {
const ref = React.useRef<HTMLDivElement>(null);
React.useEffect(() => {
if (!ref.current) {
return;
}
const shadow = ref.current.attachShadow({
mode: 'open',
});
const shadowRoot = document.createElement('div');
shadowRoot.style.height = '100%';
shadow.appendChild(shadowRoot);
const root = ReactDOM.createRoot(shadowRoot);
root.render(<ShadowIframe {...props} />);
return () => {
root.unmount();
};
}, []);
return <div style={{ height: '100%' }} ref={ref}></div>;
},
}; };
const headerComponents = { const headerComponents = {
@ -208,7 +251,7 @@ const DockviewDemo = (props: { theme?: string }) => {
event.api.onDidMaximizedGroupChange((event) => { event.api.onDidMaximizedGroupChange((event) => {
addLogLine( addLogLine(
`Group Maximized Changed ${event.view.id} [${event.isMaximized}]` `Group Maximized Changed ${event.group.id} [${event.isMaximized}]`
); );
}); });

View File

@ -132,7 +132,10 @@ export const GridActions = (props: {
const popover = usePopover(); const popover = usePopover();
const onAddPanel = (options?: { advanced: boolean }) => { const onAddPanel = (options?: {
advanced: boolean;
component?: string;
}) => {
if (options?.advanced) { if (options?.advanced) {
popover.open(({ close }) => { popover.open(({ close }) => {
return <PanelBuilder api={props.api!} done={close} />; return <PanelBuilder api={props.api!} done={close} />;
@ -140,7 +143,7 @@ export const GridActions = (props: {
} else { } else {
props.api?.addPanel({ props.api?.addPanel({
id: `id_${Date.now().toString()}`, id: `id_${Date.now().toString()}`,
component: 'default', component: options?.component ?? 'default',
title: `Tab ${nextId()}`, title: `Tab ${nextId()}`,
renderer: 'always', renderer: 'always',
}); });
@ -170,6 +173,22 @@ export const GridActions = (props: {
<span className="material-symbols-outlined">tune</span> <span className="material-symbols-outlined">tune</span>
</button> </button>
</div> </div>
<div className="button-group">
<button
className="text-button"
onClick={() =>
onAddPanel({ component: 'shadowDom', advanced: false })
}
>
Add Panel 2
</button>
<button
className="demo-icon-button"
onClick={() => onAddPanel({ advanced: true })}
>
<span className="material-symbols-outlined">tune</span>
</button>
</div>
<button className="text-button" onClick={onAddGroup}> <button className="text-button" onClick={onAddGroup}>
Add Group Add Group
</button> </button>

View File

@ -75,9 +75,9 @@ const PanelAction = (props: {
const panel = props.api.getPanel(props.panelId); const panel = props.api.getPanel(props.panelId);
if (panel) { if (panel) {
props.api.addFloatingGroup(panel, { props.api.addFloatingGroup(panel, {
position: {
width: 400, width: 400,
height: 300, height: 300,
position: {
bottom: 50, bottom: 50,
right: 50, right: 50,
}, },