Merge branch 'master' of https://github.com/mathuo/dockview into 326-possible-to-stop-floating-windows-being-dragged-outside-visible-dockview-area

This commit is contained in:
mathuo 2023-09-03 16:26:41 +01:00
commit f6cfb4418c
No known key found for this signature in database
GPG Key ID: C6EEDEFD6CA07281
6 changed files with 215 additions and 20 deletions

View File

@ -12,3 +12,16 @@ export function setMockRefElement(node: Partial<HTMLElement>): void {
jest.spyOn(React, 'useRef').mockReturnValueOnce(mockRef);
}
export function createOffsetDragOverEvent(params: {
clientX: number;
clientY: number;
}): Event {
const event = new Event('dragover', {
bubbles: true,
cancelable: true,
});
Object.defineProperty(event, 'clientX', { get: () => params.clientX });
Object.defineProperty(event, 'clientY', { get: () => params.clientY });
return event;
}

View File

@ -7,19 +7,7 @@ import {
positionToDirection,
} from '../../dnd/droptarget';
import { fireEvent } from '@testing-library/dom';
function createOffsetDragOverEvent(params: {
clientX: number;
clientY: number;
}): Event {
const event = new Event('dragover', {
bubbles: true,
cancelable: true,
});
Object.defineProperty(event, 'clientX', { get: () => params.clientX });
Object.defineProperty(event, 'clientY', { get: () => params.clientY });
return event;
}
import { createOffsetDragOverEvent } from '../__test_utils__/utils';
describe('droptarget', () => {
let element: HTMLElement;

View File

@ -1,4 +1,4 @@
import {DockviewComponent} from '../../dockview/dockviewComponent';
import { DockviewComponent } from '../../dockview/dockviewComponent';
import {
GroupviewPanelState,
IGroupPanelInitParameters,
@ -20,6 +20,7 @@ import { IDockviewPanel } from '../../dockview/dockviewPanel';
import { IDockviewPanelModel } from '../../dockview/dockviewPanelModel';
import { DockviewGroupPanel } from '../../dockview/dockviewGroupPanel';
import { WatermarkRendererInitParameters } from '../../dockview/types';
import { createOffsetDragOverEvent } from '../__test_utils__/utils';
enum GroupChangeKind2 {
ADD_PANEL,
@ -658,6 +659,97 @@ describe('groupview', () => {
).toBe(0);
});
test('that the .locked behaviour is as', () => {
const accessorMock = jest.fn<Partial<DockviewComponent>, []>(() => {
return {
id: 'testcomponentid',
options: {
showDndOverlay: () => true,
},
getPanel: jest.fn(),
onDidAddPanel: jest.fn(),
onDidRemovePanel: jest.fn(),
};
});
const accessor = new accessorMock() as DockviewComponent;
const groupviewMock = jest.fn<Partial<DockviewGroupPanelModel>, []>(
() => {
return {
canDisplayOverlay: jest.fn(),
};
}
);
const groupView = new groupviewMock() as DockviewGroupPanelModel;
const groupPanelMock = jest.fn<Partial<DockviewGroupPanelModel>, []>(
() => {
return {
id: 'testgroupid',
model: groupView,
};
}
);
const container = document.createElement('div');
const cut = new DockviewGroupPanelModel(
container,
accessor,
'groupviewid',
{},
new groupPanelMock() as DockviewGroupPanel
);
const element = container
.getElementsByClassName('content-container')
.item(0)!;
jest.spyOn(element, 'clientHeight', 'get').mockImplementation(
() => 100
);
jest.spyOn(element, 'clientWidth', 'get').mockImplementation(() => 100);
function run(value: number) {
fireEvent.dragEnter(element);
fireEvent(
element,
createOffsetDragOverEvent({ clientX: value, clientY: value })
);
}
// base case - not locked
cut.locked = false;
run(10);
expect(
element.getElementsByClassName('drop-target-dropzone').length
).toBe(1);
fireEvent.dragEnd(element);
// special case - locked with no possible target
cut.locked = 'no-drop-target';
run(10);
expect(
element.getElementsByClassName('drop-target-dropzone').length
).toBe(0);
fireEvent.dragEnd(element);
// standard locked - only show if not center target
cut.locked = true;
run(10);
expect(
element.getElementsByClassName('drop-target-dropzone').length
).toBe(1);
fireEvent.dragEnd(element);
// standard locked but for center target - expect not shown
cut.locked = true;
run(25);
expect(
element.getElementsByClassName('drop-target-dropzone').length
).toBe(0);
fireEvent.dragEnd(element);
});
test('that should not show drop target if dropping on self', () => {
const accessorMock = jest.fn<Partial<DockviewComponent>, []>(() => {
return {

View File

@ -1,4 +1,4 @@
import { render, screen } from '@testing-library/react';
import { fireEvent, render, screen } from '@testing-library/react';
import { DockviewDefaultTab } from '../../dockview/defaultTab';
import * as React from 'react';
import { fromPartial } from '@total-typescript/shoehorn';
@ -40,6 +40,58 @@ describe('defaultTab', () => {
expect(element.querySelector('.dv-react-tab-close-btn')).toBeNull();
});
test('that settings closeActionOverride skips api.close()', async () => {
const api = fromPartial<DockviewPanelApi>({
close: jest.fn(),
});
const containerApi = fromPartial<DockviewApi>({});
const params = {};
const closeActionOverride = jest.fn();
render(
<DockviewDefaultTab
api={api}
containerApi={containerApi}
params={params}
closeActionOverride={closeActionOverride}
/>
);
const element = await screen.getByTestId('dockview-default-tab');
const btn = element.querySelector(
'.dv-react-tab-close-btn'
) as HTMLElement;
fireEvent.click(btn);
expect(closeActionOverride).toBeCalledTimes(1);
expect(api.close).toBeCalledTimes(0);
});
test('that clicking close calls api.close()', async () => {
const api = fromPartial<DockviewPanelApi>({
close: jest.fn(),
});
const containerApi = fromPartial<DockviewApi>({});
const params = {};
render(
<DockviewDefaultTab
api={api}
containerApi={containerApi}
params={params}
/>
);
const element = await screen.getByTestId('dockview-default-tab');
const btn = element.querySelector(
'.dv-react-tab-close-btn'
) as HTMLElement;
fireEvent.click(btn);
expect(api.close).toBeCalledTimes(1);
});
test('has close button when hideClose=false', async () => {
const api = fromPartial<DockviewPanelApi>({});
const containerApi = fromPartial<DockviewApi>({});
@ -57,4 +109,31 @@ describe('defaultTab', () => {
const element = await screen.getByTestId('dockview-default-tab');
expect(element.querySelector('.dv-react-tab-close-btn')).toBeTruthy();
});
test('that mouseDown on close button prevents panel becoming active', async () => {
const api = fromPartial<DockviewPanelApi>({
setActive: jest.fn(),
});
const containerApi = fromPartial<DockviewApi>({});
const params = {};
render(
<DockviewDefaultTab
api={api}
containerApi={containerApi}
params={params}
/>
);
const element = await screen.getByTestId('dockview-default-tab');
const btn = element.querySelector(
'.dv-react-tab-close-btn'
) as HTMLElement;
fireEvent.mouseDown(btn);
expect(api.setActive).toBeCalledTimes(0);
fireEvent.click(element);
expect(api.setActive).toBeCalledTimes(1);
});
});

View File

@ -3,7 +3,10 @@ import * as React from 'react';
import { CloseButton } from '../svg';
export type IDockviewDefaultTabProps = IDockviewPanelHeaderProps &
React.DOMAttributes<HTMLDivElement> & { hideClose?: boolean };
React.DOMAttributes<HTMLDivElement> & {
hideClose?: boolean;
closeActionOverride?: () => void;
};
export const DockviewDefaultTab: React.FunctionComponent<
IDockviewDefaultTabProps
@ -12,18 +15,32 @@ export const DockviewDefaultTab: React.FunctionComponent<
containerApi: _containerApi,
params: _params,
hideClose,
closeActionOverride,
...rest
}) => {
const onClose = React.useCallback(
(event: React.MouseEvent<HTMLSpanElement>) => {
event.stopPropagation();
api.close();
event.preventDefault();
if (closeActionOverride) {
closeActionOverride();
} else {
api.close();
}
},
[api]
[api, closeActionOverride]
);
const onMouseDown = React.useCallback((e: React.MouseEvent) => {
e.preventDefault();
}, []);
const onClick = React.useCallback(
(event: React.MouseEvent<HTMLDivElement>) => {
if (event.defaultPrevented) {
return;
}
api.setActive();
if (rest.onClick) {
@ -42,7 +59,11 @@ export const DockviewDefaultTab: React.FunctionComponent<
>
<span className="dockview-react-tab-title">{api.title}</span>
{!hideClose && (
<div className="dv-react-tab-close-btn" onClick={onClose}>
<div
className="dv-react-tab-close-btn"
onMouseDown={onMouseDown}
onClick={onClose}
>
<CloseButton />
</div>
)}

View File

@ -632,6 +632,8 @@ A default implementation of `DockviewDefaultTab` is provided should you only wis
changes and events that do not alter the default behaviour, for example to add a custom context menu event
handler.
The `DockviewDefaulTab` component accepts a `hideClose` prop if you wish only to hide the close button.
```tsx title="Attaching a custom context menu event handlers to a custom header"
import { IDockviewPanelHeaderProps, DockviewDefaultTab } from 'dockview';