mirror of
https://github.com/mathuo/dockview
synced 2025-05-02 09:38:26 +00:00
Merge pull request #103 from mathuo/102-drag-event-on-tab-content-not-bubbled-to-tab
102 drag event on tab content not bubbled to tab
This commit is contained in:
commit
c249d44017
@ -10,7 +10,6 @@ import {
|
||||
IWatermarkPanelProps,
|
||||
IDockviewPanel,
|
||||
PanelCollection,
|
||||
DockviewComponents,
|
||||
} from 'dockview';
|
||||
import { CustomTab } from './customTab';
|
||||
import { Settings } from './settingsPanel';
|
||||
@ -47,22 +46,10 @@ const Test = (props: IDockviewPanelProps) => {
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<DockviewComponents.Panel>
|
||||
{counter % 4 === 0 && (
|
||||
<DockviewComponents.Tab>
|
||||
<div>{`custom tab ${counter}`}</div>
|
||||
</DockviewComponents.Tab>
|
||||
)}
|
||||
<DockviewComponents.Content>
|
||||
<div>
|
||||
<div>{`custom body ${counter}`}</div>
|
||||
<button>Toggle</button>
|
||||
</div>
|
||||
</DockviewComponents.Content>
|
||||
<DockviewComponents.Actions>
|
||||
<div>{`custom action ${counter}`}</div>
|
||||
</DockviewComponents.Actions>
|
||||
</DockviewComponents.Panel>
|
||||
<div>
|
||||
<div>{`custom body ${counter}`}</div>
|
||||
<button>Toggle</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { DockviewComponents, IDockviewPanelProps } from 'dockview';
|
||||
import { IDockviewPanelProps } from 'dockview';
|
||||
import * as React from 'react';
|
||||
import { CompositeDisposable } from '../../lifecycle';
|
||||
import './exampleFunctions.scss';
|
||||
@ -49,80 +49,52 @@ export const ExampleFunctions = (
|
||||
};
|
||||
|
||||
return (
|
||||
<DockviewComponents.Panel>
|
||||
<DockviewComponents.Actions>
|
||||
<div
|
||||
style={{
|
||||
height: '100%',
|
||||
display: 'flex',
|
||||
backgroundColor: 'rgba(255,255,255,0.1)',
|
||||
padding: '0px 4px',
|
||||
}}
|
||||
>
|
||||
<div className="example-functions-panel">
|
||||
<div className="example-functions-panel-header-bar">
|
||||
<span style={{ padding: '0px 8px' }}>
|
||||
<span>{'isGroupActive: '}</span>
|
||||
<span
|
||||
onClick={onClose}
|
||||
style={{
|
||||
height: '100%',
|
||||
width: '25px',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
color: panelState.isGroupActive
|
||||
? '#23d16f'
|
||||
: '#cd312b',
|
||||
}}
|
||||
>
|
||||
<a className="material-icons">menu</a>
|
||||
{`${panelState.isGroupActive}`}
|
||||
</span>
|
||||
</div>
|
||||
</DockviewComponents.Actions>
|
||||
<DockviewComponents.Content>
|
||||
<div className="example-functions-panel">
|
||||
<div className="example-functions-panel-header-bar">
|
||||
<span style={{ padding: '0px 8px' }}>
|
||||
<span>{'isGroupActive: '}</span>
|
||||
<span
|
||||
style={{
|
||||
color: panelState.isGroupActive
|
||||
? '#23d16f'
|
||||
: '#cd312b',
|
||||
}}
|
||||
>
|
||||
{`${panelState.isGroupActive}`}
|
||||
</span>
|
||||
</span>
|
||||
<span style={{ padding: '0px 8px' }}>
|
||||
<span>{'isPanelVisible: '}</span>
|
||||
<span
|
||||
style={{
|
||||
color: panelState.isPanelVisible
|
||||
? '#23d16f'
|
||||
: '#cd312b',
|
||||
}}
|
||||
>
|
||||
{`${panelState.isPanelVisible}`}
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<div className="example-functions-panel-section">
|
||||
<input
|
||||
value={panelName}
|
||||
placeholder="New Panel Name"
|
||||
type="text"
|
||||
onChange={(event) =>
|
||||
setPanelName(event.target.value)
|
||||
}
|
||||
/>
|
||||
<button onClick={onRename}>Rename</button>
|
||||
</div>
|
||||
</span>
|
||||
<span style={{ padding: '0px 8px' }}>
|
||||
<span>{'isPanelVisible: '}</span>
|
||||
<span
|
||||
style={{
|
||||
color: panelState.isPanelVisible
|
||||
? '#23d16f'
|
||||
: '#cd312b',
|
||||
}}
|
||||
>
|
||||
{`${panelState.isPanelVisible}`}
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<div className="example-functions-panel-section">
|
||||
<input
|
||||
value={panelName}
|
||||
placeholder="New Panel Name"
|
||||
type="text"
|
||||
onChange={(event) => setPanelName(event.target.value)}
|
||||
/>
|
||||
<button onClick={onRename}>Rename</button>
|
||||
</div>
|
||||
|
||||
<input
|
||||
style={{ width: '175px' }}
|
||||
ref={input}
|
||||
placeholder="This is focused by the panel"
|
||||
/>
|
||||
<input
|
||||
style={{ width: '175px' }}
|
||||
placeholder="This is also focusable"
|
||||
/>
|
||||
</div>
|
||||
</DockviewComponents.Content>
|
||||
</DockviewComponents.Panel>
|
||||
<input
|
||||
style={{ width: '175px' }}
|
||||
ref={input}
|
||||
placeholder="This is focused by the panel"
|
||||
/>
|
||||
<input
|
||||
style={{ width: '175px' }}
|
||||
placeholder="This is also focusable"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
@ -1,6 +1,5 @@
|
||||
import {
|
||||
DockviewApi,
|
||||
DockviewComponents,
|
||||
DockviewReact,
|
||||
DockviewReadyEvent,
|
||||
IDockviewPanelProps,
|
||||
@ -18,35 +17,9 @@ const components: PanelCollection<IDockviewPanelProps<any>> = {
|
||||
ticker: (props: IDockviewPanelProps<{ ticker: number }>) => {
|
||||
const close = () => props.api.close();
|
||||
return (
|
||||
<DockviewComponents.Panel>
|
||||
<DockviewComponents.Tab>
|
||||
<div
|
||||
style={{
|
||||
border: '1px solid pink',
|
||||
height: '100%',
|
||||
boxSizing: 'border-box',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
padding: '0px 8px',
|
||||
width: '200px',
|
||||
}}
|
||||
>
|
||||
<span>{props.api.title}</span>
|
||||
<span
|
||||
style={{ fontSize: '9px' }}
|
||||
>{`(${props.params.ticker})`}</span>
|
||||
{!props.api.suppressClosable && (
|
||||
<span onClick={close}>X</span>
|
||||
)}
|
||||
</div>
|
||||
</DockviewComponents.Tab>
|
||||
<DockviewComponents.Content>
|
||||
<div style={{ padding: '10px', height: '100%' }}>
|
||||
{`The current ticker value is ${props.params.ticker}`}
|
||||
</div>
|
||||
</DockviewComponents.Content>
|
||||
</DockviewComponents.Panel>
|
||||
<div style={{ padding: '10px', height: '100%' }}>
|
||||
{`The current ticker value is ${props.params.ticker}`}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
iframe: (props) => {
|
||||
|
@ -1,6 +1,5 @@
|
||||
import {
|
||||
DockviewApi,
|
||||
DockviewComponents,
|
||||
DockviewReact,
|
||||
DockviewReadyEvent,
|
||||
IDockviewPanelProps,
|
||||
@ -13,35 +12,7 @@ const components: PanelCollection<IDockviewPanelProps> = {
|
||||
default: (props) => {
|
||||
const close = () => props.api.close();
|
||||
return (
|
||||
<DockviewComponents.Panel>
|
||||
<DockviewComponents.Tab>
|
||||
<div
|
||||
style={{
|
||||
border: '1px solid pink',
|
||||
height: '100%',
|
||||
boxSizing: 'border-box',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
padding: '0px 8px',
|
||||
width: '200px',
|
||||
}}
|
||||
>
|
||||
<span>{props.api.title}</span>
|
||||
<span style={{ fontSize: '9px' }}>
|
||||
{'(Custom tab component)'}
|
||||
</span>
|
||||
{!props.api.suppressClosable && (
|
||||
<span onClick={close}>X</span>
|
||||
)}
|
||||
</div>
|
||||
</DockviewComponents.Tab>
|
||||
<DockviewComponents.Content>
|
||||
<div style={{ padding: '10px', height: '100%' }}>
|
||||
hello world
|
||||
</div>
|
||||
</DockviewComponents.Content>
|
||||
</DockviewComponents.Panel>
|
||||
<div style={{ padding: '10px', height: '100%' }}>hello world</div>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
@ -7,67 +7,6 @@ import { addDisposableListener } from '../../../events';
|
||||
import { PanelUpdateEvent } from '../../../panel/types';
|
||||
import { GroupPanel } from '../../../groupview/groupviewPanel';
|
||||
|
||||
export class WrappedTab implements ITabRenderer {
|
||||
private readonly _element: HTMLElement;
|
||||
|
||||
constructor(private readonly renderer: ITabRenderer) {
|
||||
this._element = document.createElement('element');
|
||||
this.show();
|
||||
}
|
||||
|
||||
get innerRenderer() {
|
||||
return this.renderer;
|
||||
}
|
||||
|
||||
get element() {
|
||||
return this._element;
|
||||
}
|
||||
|
||||
get id() {
|
||||
return this.renderer.id;
|
||||
}
|
||||
|
||||
show() {
|
||||
if (!this.renderer.element.parentElement) {
|
||||
this._element.appendChild(this.renderer.element);
|
||||
}
|
||||
}
|
||||
|
||||
hide() {
|
||||
if (this.renderer.element.parentElement) {
|
||||
this.renderer.element.remove();
|
||||
}
|
||||
}
|
||||
|
||||
layout(width: number, height: number): void {
|
||||
this.renderer.layout(width, height);
|
||||
}
|
||||
|
||||
update(event: PanelUpdateEvent): void {
|
||||
this.renderer.update(event);
|
||||
}
|
||||
|
||||
toJSON(): object {
|
||||
return this.renderer.toJSON();
|
||||
}
|
||||
|
||||
focus(): void {
|
||||
this.renderer.focus();
|
||||
}
|
||||
|
||||
init(parameters: GroupPanelPartInitParameters): void {
|
||||
this.renderer.init(parameters);
|
||||
}
|
||||
|
||||
updateParentGroup(group: GroupPanel, isPanelVisible: boolean): void {
|
||||
this.renderer.updateParentGroup(group, isPanelVisible);
|
||||
}
|
||||
|
||||
dispose() {
|
||||
this.renderer.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
export class DefaultTab extends CompositeDisposable implements ITabRenderer {
|
||||
private _element: HTMLElement;
|
||||
|
||||
@ -148,10 +87,16 @@ export class DefaultTab extends CompositeDisposable implements ITabRenderer {
|
||||
}
|
||||
|
||||
public updateParentGroup(group: GroupPanel, isPanelVisible: boolean) {
|
||||
const changed =
|
||||
this._isPanelVisible !== isPanelVisible ||
|
||||
this._isGroupActive !== group.isActive;
|
||||
|
||||
this._isPanelVisible = isPanelVisible;
|
||||
this._isGroupActive = group.isActive;
|
||||
|
||||
this.render();
|
||||
if (changed) {
|
||||
this.render();
|
||||
}
|
||||
}
|
||||
|
||||
public layout(_width: number, _height: number) {
|
||||
@ -159,6 +104,8 @@ export class DefaultTab extends CompositeDisposable implements ITabRenderer {
|
||||
}
|
||||
|
||||
private render() {
|
||||
this._content.textContent = this.params.title;
|
||||
if (this._content.textContent !== this.params.title) {
|
||||
this._content.textContent = this.params.title;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { DefaultTab, WrappedTab } from './components/tab/defaultTab';
|
||||
import { DefaultTab } from './components/tab/defaultTab';
|
||||
import {
|
||||
GroupPanelPartInitParameters,
|
||||
IActionsRenderer,
|
||||
@ -22,7 +22,7 @@ export interface IGroupPanelView extends IDisposable {
|
||||
|
||||
export class DefaultGroupPanelView implements IGroupPanelView {
|
||||
private readonly _content: IContentRenderer;
|
||||
private readonly _tab: WrappedTab;
|
||||
private readonly _tab: ITabRenderer;
|
||||
private readonly _actions: IActionsRenderer | undefined;
|
||||
|
||||
get content() {
|
||||
@ -43,7 +43,7 @@ export class DefaultGroupPanelView implements IGroupPanelView {
|
||||
actions?: IActionsRenderer;
|
||||
}) {
|
||||
this._content = renderers.content;
|
||||
this._tab = new WrappedTab(renderers.tab ?? new DefaultTab());
|
||||
this._tab = renderers.tab ?? new DefaultTab();
|
||||
this._actions =
|
||||
renderers.actions ||
|
||||
(this.content.actions
|
||||
@ -78,10 +78,7 @@ export class DefaultGroupPanelView implements IGroupPanelView {
|
||||
toJSON(): {} {
|
||||
return {
|
||||
content: this.content.toJSON(),
|
||||
tab:
|
||||
this.tab.innerRenderer instanceof DefaultTab
|
||||
? undefined
|
||||
: this.tab.toJSON(),
|
||||
tab: this.tab instanceof DefaultTab ? undefined : this.tab.toJSON(),
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -5,7 +5,6 @@ import { PanelInitParameters, IPanel } from '../panel/types';
|
||||
import { DockviewApi } from '../api/component.api';
|
||||
import { GroupPanel } from './groupviewPanel';
|
||||
import { Event } from '../events';
|
||||
import { WrappedTab } from '../dockview/components/tab/defaultTab';
|
||||
|
||||
export interface IRenderable {
|
||||
id: string;
|
||||
@ -28,7 +27,7 @@ export interface GroupPanelPartInitParameters
|
||||
|
||||
export interface GroupPanelContentPartInitParameters
|
||||
extends GroupPanelPartInitParameters {
|
||||
tab: WrappedTab;
|
||||
tab: ITabRenderer;
|
||||
}
|
||||
|
||||
export interface IWatermarkRenderer extends IPanel {
|
||||
@ -54,7 +53,6 @@ export interface IContentRenderer extends IPanel {
|
||||
readonly onDidBlur?: Event<void>;
|
||||
updateParentGroup(group: GroupPanel, isPanelVisible: boolean): void;
|
||||
init(parameters: GroupPanelContentPartInitParameters): void;
|
||||
close?(): Promise<boolean>;
|
||||
}
|
||||
|
||||
// watermark component
|
||||
|
@ -1,89 +0,0 @@
|
||||
import * as React from 'react';
|
||||
import * as ReactDOM from 'react-dom';
|
||||
import { isReactElement, ReactPartContext } from '../react';
|
||||
import { ReactContentPartContext } from './reactContentPart';
|
||||
|
||||
interface WithChildren {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
const Tab: React.FunctionComponent<WithChildren> = (props: WithChildren) => {
|
||||
return <>{props.children}</>;
|
||||
};
|
||||
|
||||
const Content: React.FunctionComponent<WithChildren> = (
|
||||
props: WithChildren
|
||||
) => {
|
||||
return <>{props.children}</>;
|
||||
};
|
||||
const Actions: React.FunctionComponent<WithChildren> = (
|
||||
props: WithChildren
|
||||
) => {
|
||||
return <>{props.children}</>;
|
||||
};
|
||||
|
||||
function isValidComponent(element: React.ReactElement) {
|
||||
return [Content, Actions, Tab].find((comp) => element.type === comp);
|
||||
}
|
||||
|
||||
const Panel: React.FunctionComponent<WithChildren> = (props: WithChildren) => {
|
||||
const context = React.useContext(
|
||||
ReactPartContext
|
||||
) as ReactContentPartContext;
|
||||
|
||||
const sections = React.useMemo(() => {
|
||||
const childs =
|
||||
React.Children.map(props.children, (_) => _)?.filter(
|
||||
isReactElement
|
||||
) || [];
|
||||
|
||||
const isInvalid = !!childs.find((_) => !isValidComponent(_));
|
||||
|
||||
if (isInvalid) {
|
||||
throw new Error(
|
||||
'Children of DockviewComponents.Panel must be one of the following: DockviewComponents.Content, DockviewComponents.Actions, DockviewComponents.Tab'
|
||||
);
|
||||
}
|
||||
|
||||
const body = childs.find((_) => _.type === Content);
|
||||
const actions = childs.find((_) => _.type === Actions);
|
||||
const tab = childs.find((_) => _.type === Tab);
|
||||
|
||||
return { body, actions, tab };
|
||||
}, [props.children]);
|
||||
|
||||
React.useEffect(() => {
|
||||
/**
|
||||
* hide or show the default tab behavior based on whether we want to override
|
||||
* with our own React tab.
|
||||
*/
|
||||
if (sections.tab) {
|
||||
context.tabPortalElement.hide();
|
||||
} else {
|
||||
context.tabPortalElement.show();
|
||||
}
|
||||
}, [sections.tab]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{sections.actions &&
|
||||
ReactDOM.createPortal(
|
||||
sections.actions,
|
||||
context.actionsPortalElement
|
||||
)}
|
||||
{sections.tab &&
|
||||
ReactDOM.createPortal(
|
||||
sections.tab,
|
||||
context.tabPortalElement.element
|
||||
)}
|
||||
{sections.body || props.children}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const DockviewComponents = {
|
||||
Tab,
|
||||
Content,
|
||||
Actions,
|
||||
Panel,
|
||||
};
|
@ -2,6 +2,7 @@ import * as React from 'react';
|
||||
import {
|
||||
IContentRenderer,
|
||||
GroupPanelContentPartInitParameters,
|
||||
ITabRenderer,
|
||||
} from '../../groupview/types';
|
||||
import { ReactPart, ReactPortalStore } from '../react';
|
||||
import { IDockviewPanelProps } from '../dockview/dockview';
|
||||
@ -10,7 +11,6 @@ import { DockviewPanelApi } from '../../api/groupPanelApi';
|
||||
import { DockviewApi } from '../../api/component.api';
|
||||
import { GroupPanel } from '../../groupview/groupviewPanel';
|
||||
import { Emitter, Event } from '../../events';
|
||||
import { WrappedTab } from '../../dockview/components/tab/defaultTab';
|
||||
|
||||
export interface IGroupPanelActionbarProps {
|
||||
api: DockviewPanelApi;
|
||||
@ -21,7 +21,7 @@ export interface ReactContentPartContext {
|
||||
api: DockviewPanelApi;
|
||||
containerApi: DockviewApi;
|
||||
actionsPortalElement: HTMLElement;
|
||||
tabPortalElement: WrappedTab;
|
||||
tabPortalElement: ITabRenderer;
|
||||
}
|
||||
|
||||
export class ReactPanelContentPart implements IContentRenderer {
|
||||
@ -104,10 +104,6 @@ export class ReactPanelContentPart implements IContentRenderer {
|
||||
// noop
|
||||
}
|
||||
|
||||
public close(): Promise<boolean> {
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
|
||||
public dispose() {
|
||||
this._onDidFocus.dispose();
|
||||
this._onDidBlur.dispose();
|
||||
|
@ -99,10 +99,6 @@ export class ReactContentRenderer implements IContentRenderer {
|
||||
this._hostedContainer.layout(this.element);
|
||||
}
|
||||
|
||||
public close(): Promise<boolean> {
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
|
||||
public dispose() {
|
||||
this.part?.dispose();
|
||||
}
|
||||
|
@ -1,5 +1,4 @@
|
||||
export * from './dockview/dockview';
|
||||
export * from './dockview/components';
|
||||
export * from './splitview/splitview';
|
||||
export * from './gridview/gridview';
|
||||
export * from './dockview/reactContentPart';
|
||||
|
Loading…
Reference in New Issue
Block a user