From 2ce175dbf3045fca059fe26b67ee6efbe59f233e Mon Sep 17 00:00:00 2001 From: mathuo <6710312+mathuo@users.noreply.github.com> Date: Sun, 27 Aug 2023 21:59:53 +0100 Subject: [PATCH 1/4] docs: add note --- packages/docs/docs/components/dockview.mdx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/docs/docs/components/dockview.mdx b/packages/docs/docs/components/dockview.mdx index 01cbb2717..18904de45 100644 --- a/packages/docs/docs/components/dockview.mdx +++ b/packages/docs/docs/components/dockview.mdx @@ -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'; From 007b2a8feaac0151072e2196d1250ff562d90335 Mon Sep 17 00:00:00 2001 From: mathuo <6710312+mathuo@users.noreply.github.com> Date: Mon, 28 Aug 2023 20:02:48 +0100 Subject: [PATCH 2/4] tests: test locked behaviours --- .../src/__tests__/__test_utils__/utils.ts | 13 +++ .../src/__tests__/dnd/droptarget.spec.ts | 14 +-- .../dockview/dockviewGroupPanelModel.spec.ts | 94 ++++++++++++++++++- 3 files changed, 107 insertions(+), 14 deletions(-) diff --git a/packages/dockview-core/src/__tests__/__test_utils__/utils.ts b/packages/dockview-core/src/__tests__/__test_utils__/utils.ts index ab8dc0a9e..8204d797e 100644 --- a/packages/dockview-core/src/__tests__/__test_utils__/utils.ts +++ b/packages/dockview-core/src/__tests__/__test_utils__/utils.ts @@ -12,3 +12,16 @@ export function setMockRefElement(node: Partial): 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; +} diff --git a/packages/dockview-core/src/__tests__/dnd/droptarget.spec.ts b/packages/dockview-core/src/__tests__/dnd/droptarget.spec.ts index 32de9729e..517fa40aa 100644 --- a/packages/dockview-core/src/__tests__/dnd/droptarget.spec.ts +++ b/packages/dockview-core/src/__tests__/dnd/droptarget.spec.ts @@ -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; diff --git a/packages/dockview-core/src/__tests__/dockview/dockviewGroupPanelModel.spec.ts b/packages/dockview-core/src/__tests__/dockview/dockviewGroupPanelModel.spec.ts index cd1ba209b..2052ef1be 100644 --- a/packages/dockview-core/src/__tests__/dockview/dockviewGroupPanelModel.spec.ts +++ b/packages/dockview-core/src/__tests__/dockview/dockviewGroupPanelModel.spec.ts @@ -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, []>(() => { + 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, []>( + () => { + return { + canDisplayOverlay: jest.fn(), + }; + } + ); + + const groupView = new groupviewMock() as DockviewGroupPanelModel; + + const groupPanelMock = jest.fn, []>( + () => { + 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, []>(() => { return { From e64d96cef79d24b71d8751c8d84b3b2f1c62a11c Mon Sep 17 00:00:00 2001 From: mathuo <6710312+mathuo@users.noreply.github.com> Date: Tue, 29 Aug 2023 20:03:11 +0100 Subject: [PATCH 3/4] bug: preventDefault on close btn mouseDown --- .../__tests__/dockview/defaultTab.spec.tsx | 29 ++++++++++++++++++- packages/dockview/src/dockview/defaultTab.tsx | 16 ++++++++-- 2 files changed, 42 insertions(+), 3 deletions(-) diff --git a/packages/dockview/src/__tests__/dockview/defaultTab.spec.tsx b/packages/dockview/src/__tests__/dockview/defaultTab.spec.tsx index 4d52dd18f..46e49184d 100644 --- a/packages/dockview/src/__tests__/dockview/defaultTab.spec.tsx +++ b/packages/dockview/src/__tests__/dockview/defaultTab.spec.tsx @@ -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'; @@ -57,4 +57,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({ + setActive: jest.fn(), + }); + const containerApi = fromPartial({}); + const params = {}; + + render( + + ); + + 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); + }); }); diff --git a/packages/dockview/src/dockview/defaultTab.tsx b/packages/dockview/src/dockview/defaultTab.tsx index 1faee461c..280ebd7ed 100644 --- a/packages/dockview/src/dockview/defaultTab.tsx +++ b/packages/dockview/src/dockview/defaultTab.tsx @@ -16,14 +16,22 @@ export const DockviewDefaultTab: React.FunctionComponent< }) => { const onClose = React.useCallback( (event: React.MouseEvent) => { - event.stopPropagation(); + event.preventDefault(); api.close(); }, [api] ); + const onMouseDown = React.useCallback((e: React.MouseEvent) => { + e.preventDefault(); + }, []); + const onClick = React.useCallback( (event: React.MouseEvent) => { + if (event.defaultPrevented) { + return; + } + api.setActive(); if (rest.onClick) { @@ -42,7 +50,11 @@ export const DockviewDefaultTab: React.FunctionComponent< > {api.title} {!hideClose && ( -
+
)} From 4aa707132f1fc1eb81924a17217e1f8a8af05a31 Mon Sep 17 00:00:00 2001 From: mathuo <6710312+mathuo@users.noreply.github.com> Date: Sun, 3 Sep 2023 16:01:13 +0100 Subject: [PATCH 4/4] feat: override close option --- .../__tests__/dockview/defaultTab.spec.tsx | 52 +++++++++++++++++++ packages/dockview/src/dockview/defaultTab.tsx | 15 ++++-- 2 files changed, 64 insertions(+), 3 deletions(-) diff --git a/packages/dockview/src/__tests__/dockview/defaultTab.spec.tsx b/packages/dockview/src/__tests__/dockview/defaultTab.spec.tsx index 46e49184d..e6e757efa 100644 --- a/packages/dockview/src/__tests__/dockview/defaultTab.spec.tsx +++ b/packages/dockview/src/__tests__/dockview/defaultTab.spec.tsx @@ -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({ + close: jest.fn(), + }); + const containerApi = fromPartial({}); + const params = {}; + + const closeActionOverride = jest.fn(); + + render( + + ); + + 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({ + close: jest.fn(), + }); + const containerApi = fromPartial({}); + const params = {}; + + render( + + ); + + 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({}); const containerApi = fromPartial({}); diff --git a/packages/dockview/src/dockview/defaultTab.tsx b/packages/dockview/src/dockview/defaultTab.tsx index 280ebd7ed..5032cfb03 100644 --- a/packages/dockview/src/dockview/defaultTab.tsx +++ b/packages/dockview/src/dockview/defaultTab.tsx @@ -3,7 +3,10 @@ import * as React from 'react'; import { CloseButton } from '../svg'; export type IDockviewDefaultTabProps = IDockviewPanelHeaderProps & - React.DOMAttributes & { hideClose?: boolean }; + React.DOMAttributes & { + hideClose?: boolean; + closeActionOverride?: () => void; + }; export const DockviewDefaultTab: React.FunctionComponent< IDockviewDefaultTabProps @@ -12,14 +15,20 @@ export const DockviewDefaultTab: React.FunctionComponent< containerApi: _containerApi, params: _params, hideClose, + closeActionOverride, ...rest }) => { const onClose = React.useCallback( (event: React.MouseEvent) => { event.preventDefault(); - api.close(); + + if (closeActionOverride) { + closeActionOverride(); + } else { + api.close(); + } }, - [api] + [api, closeActionOverride] ); const onMouseDown = React.useCallback((e: React.MouseEvent) => {