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

View File

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

View File

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

View File

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

View File

@ -2,7 +2,6 @@ import {
Event as DockviewEvent, Event as DockviewEvent,
Emitter, Emitter,
addDisposableListener, addDisposableListener,
addDisposableWindowListener,
} from './events'; } from './events';
import { IDisposable, CompositeDisposable } from './lifecycle'; import { IDisposable, CompositeDisposable } from './lifecycle';
@ -125,7 +124,7 @@ export interface IFocusTracker extends IDisposable {
refreshState?(): void; refreshState?(): void;
} }
export function trackFocus(element: HTMLElement | Window): IFocusTracker { export function trackFocus(element: HTMLElement): IFocusTracker {
return new FocusTracker(element); return new FocusTracker(element);
} }
@ -141,7 +140,7 @@ class FocusTracker extends CompositeDisposable implements IFocusTracker {
private readonly _refreshStateHandler: () => void; private readonly _refreshStateHandler: () => void;
constructor(element: HTMLElement | Window) { constructor(element: HTMLElement) {
super(); super();
this.addDisposables(this._onDidFocus, this._onDidBlur); this.addDisposables(this._onDidFocus, this._onDidBlur);
@ -184,21 +183,12 @@ class FocusTracker extends CompositeDisposable implements IFocusTracker {
} }
}; };
if (element instanceof HTMLElement) {
this.addDisposables( this.addDisposables(
addDisposableListener(element, 'focus', onFocus, true) addDisposableListener(element, 'focus', onFocus, true)
); );
this.addDisposables( this.addDisposables(
addDisposableListener(element, 'blur', onBlur, true) addDisposableListener(element, 'blur', onBlur, true)
); );
} else {
this.addDisposables(
addDisposableWindowListener(element, 'focus', onFocus, true)
);
this.addDisposables(
addDisposableWindowListener(element, 'blur', onBlur, true)
);
}
} }
refreshState(): void { refreshState(): void {
@ -386,6 +376,8 @@ export class Classnames {
} }
} }
const DEBOUCE_DELAY = 100;
export function isChildEntirelyVisibleWithinParent( export function isChildEntirelyVisibleWithinParent(
child: HTMLElement, child: HTMLElement,
parent: HTMLElement parent: HTMLElement
@ -407,3 +399,55 @@ export function isChildEntirelyVisibleWithinParent(
return true; 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, element: Window,
type: K, type: K,
listener: (this: Window, ev: WindowEventMap[K]) => any, listener: (this: Window, ev: WindowEventMap[K]) => any,
options?: boolean | AddEventListenerOptions options?: boolean | AddEventListenerOptions
): IDisposable { ): IDisposable;
element.addEventListener(type, listener, options);
return {
dispose: () => {
element.removeEventListener(type, listener, options);
},
};
}
export function addDisposableListener<K extends keyof HTMLElementEventMap>( export function addDisposableListener<K extends keyof HTMLElementEventMap>(
element: HTMLElement, element: HTMLElement,
type: K, type: K,
listener: (this: HTMLElement, ev: HTMLElementEventMap[K]) => any, listener: (this: HTMLElement, ev: HTMLElementEventMap[K]) => any,
options?: boolean | AddEventListenerOptions 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 { ): IDisposable {
element.addEventListener(type, listener, options); element.addEventListener(type, <any>listener, options);
return { return {
dispose: () => { dispose: () => {
element.removeEventListener(type, listener, options); element.removeEventListener(type, <any>listener, options);
}, },
}; };
} }

View File

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

View File

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