Merge branch 'master' of https://github.com/mathuo/dockview into 676-is-it-possiable-that-the-id-and-title-of-group-can-be-assigned

This commit is contained in:
mathuo 2025-03-16 20:48:41 +00:00
commit a44fc01f34
No known key found for this signature in database
GPG Key ID: C6EEDEFD6CA07281
8 changed files with 161 additions and 59 deletions

View File

@ -3,7 +3,6 @@ import {
Emitter,
Event,
addDisposableListener,
addDisposableWindowListener,
} from '../events';
describe('events', () => {
@ -143,7 +142,7 @@ describe('events', () => {
expect(value).toBe(3);
});
it('addDisposableWindowListener with capture options', () => {
it('addDisposableListener with capture options', () => {
const element = {
addEventListener: jest.fn(),
removeEventListener: jest.fn(),
@ -151,7 +150,7 @@ describe('events', () => {
const handler = jest.fn();
const disposable = addDisposableWindowListener(
const disposable = addDisposableListener(
element as any,
'pointerdown',
handler,
@ -177,7 +176,7 @@ describe('events', () => {
);
});
it('addDisposableWindowListener without capture options', () => {
it('addDisposableListener without capture options', () => {
const element = {
addEventListener: jest.fn(),
removeEventListener: jest.fn(),
@ -185,7 +184,7 @@ describe('events', () => {
const handler = jest.fn();
const disposable = addDisposableWindowListener(
const disposable = addDisposableListener(
element as any,
'pointerdown',
handler

View File

@ -3,6 +3,8 @@ import {
FloatingGroupOptions,
IDockviewComponent,
MovePanelEvent,
PopoutGroupChangePositionEvent,
PopoutGroupChangeSizeEvent,
SerializedDockview,
} from '../dockview/dockviewComponent';
import {
@ -629,7 +631,6 @@ export class DockviewApi implements CommonApi<SerializedDockview> {
return this.component.totalPanels;
}
/**
* Invoked when the active group changes. May be undefined if no group is active.
*/
@ -740,6 +741,14 @@ export class DockviewApi implements CommonApi<SerializedDockview> {
return this.component.onUnhandledDragOverEvent;
}
get onDidPopoutGroupSizeChange(): Event<PopoutGroupChangeSizeEvent> {
return this.component.onDidPopoutGroupSizeChange;
}
get onDidPopoutGroupPositionChange(): Event<PopoutGroupChangePositionEvent> {
return this.component.onDidPopoutGroupPositionChange;
}
/**
* All panel objects.
*/

View File

@ -1,4 +1,4 @@
import { addDisposableWindowListener } from '../../events';
import { addDisposableListener } from '../../events';
import {
CompositeDisposable,
Disposable,
@ -50,7 +50,7 @@ export class PopupService extends CompositeDisposable {
this._active = wrapper;
this._activeDisposable.value = new CompositeDisposable(
addDisposableWindowListener(window, 'pointerdown', (event) => {
addDisposableListener(window, 'pointerdown', (event) => {
const target = event.target;
if (!(target instanceof HTMLElement)) {

View File

@ -14,7 +14,7 @@ import {
import { tail, sequenceEquals, remove } from '../array';
import { DockviewPanel, IDockviewPanel } from './dockviewPanel';
import { CompositeDisposable, Disposable } from '../lifecycle';
import { Event, Emitter, addDisposableWindowListener } from '../events';
import { Event, Emitter, addDisposableListener } from '../events';
import { Watermark } from './components/watermark/watermark';
import { IWatermarkRenderer, GroupviewPanelState } from './types';
import { sequentialNumberGenerator } from '../math';
@ -56,6 +56,8 @@ import {
addTestId,
Classnames,
getDockviewTheme,
onDidWindowResizeEnd,
onDidWindowMoveEnd,
toggleClass,
watchElementResize,
} from '../dom';
@ -190,6 +192,18 @@ export interface DockviewMaximizedGroupChanged {
isMaximized: boolean;
}
export interface PopoutGroupChangeSizeEvent {
width: number;
height: number;
group: DockviewGroupPanel;
}
export interface PopoutGroupChangePositionEvent {
screenX: number;
screenY: number;
group: DockviewGroupPanel;
}
export interface IDockviewComponent extends IBaseGrid<DockviewGroupPanel> {
readonly activePanel: IDockviewPanel | undefined;
readonly totalPanels: number;
@ -210,6 +224,8 @@ export interface IDockviewComponent extends IBaseGrid<DockviewGroupPanel> {
readonly onUnhandledDragOverEvent: Event<DockviewDndOverlayEvent>;
readonly onDidMovePanel: Event<MovePanelEvent>;
readonly onDidMaximizedGroupChange: Event<DockviewMaximizedGroupChanged>;
readonly onDidPopoutGroupSizeChange: Event<PopoutGroupChangeSizeEvent>;
readonly onDidPopoutGroupPositionChange: Event<PopoutGroupChangePositionEvent>;
readonly options: DockviewComponentOptions;
updateOptions(options: DockviewOptions): void;
moveGroupOrPanel(options: MoveGroupOrPanelOptions): void;
@ -293,6 +309,16 @@ export class DockviewComponent
private readonly _onDidAddPanel = new Emitter<IDockviewPanel>();
readonly onDidAddPanel: Event<IDockviewPanel> = this._onDidAddPanel.event;
private readonly _onDidPopoutGroupSizeChange =
new Emitter<PopoutGroupChangeSizeEvent>();
readonly onDidPopoutGroupSizeChange: Event<PopoutGroupChangeSizeEvent> =
this._onDidPopoutGroupSizeChange.event;
private readonly _onDidPopoutGroupPositionChange =
new Emitter<PopoutGroupChangePositionEvent>();
readonly onDidPopoutGroupPositionChange: Event<PopoutGroupChangePositionEvent> =
this._onDidPopoutGroupPositionChange.event;
private readonly _onDidLayoutFromJSON = new Emitter<void>();
readonly onDidLayoutFromJSON: Event<void> = this._onDidLayoutFromJSON.event;
@ -427,6 +453,8 @@ export class DockviewComponent
this._onUnhandledDragOverEvent,
this._onDidMaximizedGroupChange,
this._onDidOptionsChange,
this._onDidPopoutGroupSizeChange,
this._onDidPopoutGroupPositionChange,
this.onDidViewVisibilityChangeMicroTaskQueue(() => {
this.updateWatermark();
}),
@ -463,7 +491,9 @@ export class DockviewComponent
this.onDidAddGroup,
this.onDidRemove,
this.onDidMovePanel,
this.onDidActivePanelChange
this.onDidActivePanelChange,
this.onDidPopoutGroupPositionChange,
this.onDidPopoutGroupSizeChange
)(() => {
this._bufferOnDidLayoutChange.fire();
}),
@ -832,22 +862,37 @@ export class DockviewComponent
},
};
const _onDidWindowPositionChange = onDidWindowMoveEnd(
_window.window!
);
popoutWindowDisposable.addDisposables(
_onDidWindowPositionChange,
onDidWindowResizeEnd(_window.window!, () => {
this._onDidPopoutGroupSizeChange.fire({
width: _window.window!.innerWidth,
height: _window.window!.innerHeight,
group,
});
}),
_onDidWindowPositionChange.event(() => {
this._onDidPopoutGroupPositionChange.fire({
screenX: _window.window!.screenX,
screenY: _window.window!.screenX,
group,
});
}),
/**
* ResizeObserver seems slow here, I do not know why but we don't need it
* since we can reply on the window resize event as we will occupy the full
* window dimensions
*/
addDisposableWindowListener(
_window.window!,
'resize',
() => {
group.layout(
_window.window!.innerWidth,
_window.window!.innerHeight
);
}
),
addDisposableListener(_window.window!, 'resize', () => {
group.layout(
_window.window!.innerWidth,
_window.window!.innerHeight
);
}),
overlayRenderContainer,
Disposable.from(() => {
if (this.isDisposed) {

View File

@ -2,7 +2,6 @@ import {
Event as DockviewEvent,
Emitter,
addDisposableListener,
addDisposableWindowListener,
} from './events';
import { IDisposable, CompositeDisposable } from './lifecycle';
@ -125,7 +124,7 @@ export interface IFocusTracker extends IDisposable {
refreshState?(): void;
}
export function trackFocus(element: HTMLElement | Window): IFocusTracker {
export function trackFocus(element: HTMLElement): IFocusTracker {
return new FocusTracker(element);
}
@ -141,7 +140,7 @@ class FocusTracker extends CompositeDisposable implements IFocusTracker {
private readonly _refreshStateHandler: () => void;
constructor(element: HTMLElement | Window) {
constructor(element: HTMLElement) {
super();
this.addDisposables(this._onDidFocus, this._onDidBlur);
@ -184,21 +183,12 @@ class FocusTracker extends CompositeDisposable implements IFocusTracker {
}
};
if (element instanceof HTMLElement) {
this.addDisposables(
addDisposableListener(element, 'focus', onFocus, true)
);
this.addDisposables(
addDisposableListener(element, 'blur', onBlur, true)
);
} else {
this.addDisposables(
addDisposableWindowListener(element, 'focus', onFocus, true)
);
this.addDisposables(
addDisposableWindowListener(element, 'blur', onBlur, true)
);
}
this.addDisposables(
addDisposableListener(element, 'focus', onFocus, true)
);
this.addDisposables(
addDisposableListener(element, 'blur', onBlur, true)
);
}
refreshState(): void {
@ -386,6 +376,8 @@ export class Classnames {
}
}
const DEBOUCE_DELAY = 100;
export function isChildEntirelyVisibleWithinParent(
child: HTMLElement,
parent: HTMLElement
@ -407,3 +399,55 @@ export function isChildEntirelyVisibleWithinParent(
return true;
}
export function onDidWindowMoveEnd(window: Window): Emitter<void> {
const emitter = new Emitter<void>();
let previousScreenX = window.screenX;
let previousScreenY = window.screenY;
let timeout: any;
const checkMovement = () => {
if (window.closed) {
return;
}
const currentScreenX = window.screenX;
const currentScreenY = window.screenY;
if (
currentScreenX !== previousScreenX ||
currentScreenY !== previousScreenY
) {
clearTimeout(timeout);
timeout = setTimeout(() => {
emitter.fire();
}, DEBOUCE_DELAY);
previousScreenX = currentScreenX;
previousScreenY = currentScreenY;
}
requestAnimationFrame(checkMovement);
};
checkMovement();
return emitter;
}
export function onDidWindowResizeEnd(element: Window, cb: () => void) {
let resizeTimeout: any;
const disposable = new CompositeDisposable(
addDisposableListener(element, 'resize', () => {
clearTimeout(resizeTimeout);
resizeTimeout = setTimeout(() => {
cb();
}, DEBOUCE_DELAY);
})
);
return disposable;
}

View File

@ -193,32 +193,38 @@ export class Emitter<T> implements IDisposable {
}
}
export function addDisposableWindowListener<K extends keyof WindowEventMap>(
export function addDisposableListener<K extends keyof WindowEventMap>(
element: Window,
type: K,
listener: (this: Window, ev: WindowEventMap[K]) => any,
options?: boolean | AddEventListenerOptions
): IDisposable {
element.addEventListener(type, listener, options);
return {
dispose: () => {
element.removeEventListener(type, listener, options);
},
};
}
): IDisposable;
export function addDisposableListener<K extends keyof HTMLElementEventMap>(
element: HTMLElement,
type: K,
listener: (this: HTMLElement, ev: HTMLElementEventMap[K]) => any,
options?: boolean | AddEventListenerOptions
): IDisposable;
export function addDisposableListener<
K extends keyof HTMLElementEventMap | keyof WindowEventMap
>(
element: HTMLElement | Window,
type: K,
listener: (
this: K extends keyof HTMLElementEventMap ? HTMLElement : Window,
ev: K extends keyof HTMLElementEventMap
? HTMLElementEventMap[K]
: K extends keyof WindowEventMap
? WindowEventMap[K]
: never
) => any,
options?: boolean | AddEventListenerOptions
): IDisposable {
element.addEventListener(type, listener, options);
element.addEventListener(type, <any>listener, options);
return {
dispose: () => {
element.removeEventListener(type, listener, options);
element.removeEventListener(type, <any>listener, options);
},
};
}

View File

@ -7,7 +7,6 @@ import {
Emitter,
Event,
addDisposableListener,
addDisposableWindowListener,
} from '../events';
import { CompositeDisposable, MutableDisposable } from '../lifecycle';
import { clamp } from '../math';
@ -258,7 +257,7 @@ export class Overlay extends CompositeDisposable {
iframes.release();
},
},
addDisposableWindowListener(window, 'pointermove', (e) => {
addDisposableListener(window, 'pointermove', (e) => {
const containerRect =
this.options.container.getBoundingClientRect();
const x = e.clientX - containerRect.left;
@ -344,7 +343,7 @@ export class Overlay extends CompositeDisposable {
this.setBounds(bounds);
}),
addDisposableWindowListener(window, 'pointerup', () => {
addDisposableListener(window, 'pointerup', () => {
toggleClass(
this._element,
'dv-resize-container-dragging',
@ -439,7 +438,7 @@ export class Overlay extends CompositeDisposable {
const iframes = disableIframePointEvents();
move.value = new CompositeDisposable(
addDisposableWindowListener(window, 'pointermove', (e) => {
addDisposableListener(window, 'pointermove', (e) => {
const containerRect =
this.options.container.getBoundingClientRect();
const overlayRect =
@ -610,7 +609,7 @@ export class Overlay extends CompositeDisposable {
iframes.release();
},
},
addDisposableWindowListener(window, 'pointerup', () => {
addDisposableListener(window, 'pointerup', () => {
move.dispose();
this._onDidChangeEnd.fire();
})

View File

@ -1,5 +1,5 @@
import { addStyles } from './dom';
import { Emitter, addDisposableWindowListener } from './events';
import { Emitter, addDisposableListener } from './events';
import { CompositeDisposable, Disposable, IDisposable } from './lifecycle';
import { Box } from './types';
@ -101,7 +101,7 @@ export class PopoutWindow extends CompositeDisposable {
Disposable.from(() => {
externalWindow.close();
}),
addDisposableWindowListener(window, 'beforeunload', () => {
addDisposableListener(window, 'beforeunload', () => {
/**
* before the main window closes we should close this popup too
* to be good citizens
@ -146,7 +146,7 @@ export class PopoutWindow extends CompositeDisposable {
* beforeunload must be registered after load for reasons I could not determine
* otherwise the beforeunload event will not fire when the window is closed
*/
addDisposableWindowListener(
addDisposableListener(
externalWindow,
'beforeunload',
() => {