mirror of
https://github.com/mathuo/dockview
synced 2025-03-09 23:42:05 +00:00
bug: panel gap styling
This commit is contained in:
parent
b81afd45e8
commit
ea9dc13992
@ -772,4 +772,26 @@ describe('splitview', () => {
|
|||||||
view1.fireChangeEvent({ size: 300 });
|
view1.fireChangeEvent({ size: 300 });
|
||||||
expect([view1.size, view2.size, view3.size]).toEqual([300, 300, 300]);
|
expect([view1.size, view2.size, view3.size]).toEqual([300, 300, 300]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('that margins are applied to view sizing', () => {
|
||||||
|
const splitview = new Splitview(container, {
|
||||||
|
orientation: Orientation.HORIZONTAL,
|
||||||
|
proportionalLayout: false,
|
||||||
|
margin: 24,
|
||||||
|
});
|
||||||
|
splitview.layout(924, 500);
|
||||||
|
|
||||||
|
const view1 = new Testview(0, 1000);
|
||||||
|
const view2 = new Testview(0, 1000, LayoutPriority.High);
|
||||||
|
const view3 = new Testview(0, 1000);
|
||||||
|
|
||||||
|
splitview.addView(view1);
|
||||||
|
expect([view1.size]).toEqual([924]);
|
||||||
|
|
||||||
|
splitview.addView(view2);
|
||||||
|
expect([view1.size, view2.size]).toEqual([450, 450]); // 450 + 24 + 450 = 924
|
||||||
|
|
||||||
|
splitview.addView(view3);
|
||||||
|
expect([view1.size, view2.size, view3.size]).toEqual([292, 292, 292]); // 292 + 24 + 292 + 24 + 292 = 924
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -870,4 +870,8 @@ export class DockviewApi implements CommonApi<SerializedDockview> {
|
|||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
return this.component.addPopoutGroup(item, options);
|
return this.component.addPopoutGroup(item, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setGap(gap: number | undefined): void {
|
||||||
|
this.component.updateOptions({ gap });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,41 +14,6 @@
|
|||||||
.dv-overlay-render-container {
|
.dv-overlay-render-container {
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.split-view-container {
|
|
||||||
&.horizontal {
|
|
||||||
> .view-container > .view {
|
|
||||||
&:not(:last-child) {
|
|
||||||
.groupview {
|
|
||||||
border-right: var(--dv-group-gap-size) solid transparent;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&:not(:first-child) {
|
|
||||||
.groupview {
|
|
||||||
border-left: var(--dv-group-gap-size) solid transparent;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.vertical {
|
|
||||||
> .view-container > .view {
|
|
||||||
&:not(:last-child) {
|
|
||||||
.groupview {
|
|
||||||
border-bottom: var(--dv-group-gap-size) solid
|
|
||||||
transparent;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&:not(:first-child) {
|
|
||||||
.groupview {
|
|
||||||
border-top: var(--dv-group-gap-size) solid transparent;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.groupview {
|
.groupview {
|
||||||
|
@ -345,6 +345,7 @@ export class DockviewComponent
|
|||||||
parentElement: options.parentElement,
|
parentElement: options.parentElement,
|
||||||
disableAutoResizing: options.disableAutoResizing,
|
disableAutoResizing: options.disableAutoResizing,
|
||||||
locked: options.locked,
|
locked: options.locked,
|
||||||
|
margin: options.gap,
|
||||||
});
|
});
|
||||||
|
|
||||||
const gready = document.createElement('div');
|
const gready = document.createElement('div');
|
||||||
@ -977,6 +978,14 @@ export class DockviewComponent
|
|||||||
this._rootDropTarget.setOverlayModel(options.rootOverlayModel!);
|
this._rootDropTarget.setOverlayModel(options.rootOverlayModel!);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.gridview.margin !== 0 && options.gap === undefined) {
|
||||||
|
this.gridview.margin = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof options.gap === 'number') {
|
||||||
|
this.gridview.margin = options.gap;
|
||||||
|
}
|
||||||
|
|
||||||
this.layout(this.gridview.width, this.gridview.height, true);
|
this.layout(this.gridview.width, this.gridview.height, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -48,6 +48,7 @@ export interface DockviewOptions {
|
|||||||
rootOverlayModel?: DroptargetOverlayModel;
|
rootOverlayModel?: DroptargetOverlayModel;
|
||||||
locked?: boolean;
|
locked?: boolean;
|
||||||
disableDnd?: boolean;
|
disableDnd?: boolean;
|
||||||
|
gap?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DockviewDndOverlayEvent {
|
export interface DockviewDndOverlayEvent {
|
||||||
@ -98,6 +99,7 @@ export const PROPERTY_KEYS: (keyof DockviewOptions)[] = (() => {
|
|||||||
rootOverlayModel: undefined,
|
rootOverlayModel: undefined,
|
||||||
locked: undefined,
|
locked: undefined,
|
||||||
disableDnd: undefined,
|
disableDnd: undefined,
|
||||||
|
gap: undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
return Object.keys(properties) as (keyof DockviewOptions)[];
|
return Object.keys(properties) as (keyof DockviewOptions)[];
|
||||||
|
@ -35,6 +35,7 @@ export interface BaseGridOptions {
|
|||||||
readonly parentElement: HTMLElement;
|
readonly parentElement: HTMLElement;
|
||||||
readonly disableAutoResizing?: boolean;
|
readonly disableAutoResizing?: boolean;
|
||||||
readonly locked?: boolean;
|
readonly locked?: boolean;
|
||||||
|
readonly margin?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IGridPanelView extends IGridView, IPanel {
|
export interface IGridPanelView extends IGridView, IPanel {
|
||||||
@ -148,7 +149,9 @@ export abstract class BaseGrid<T extends IGridPanelView>
|
|||||||
this.gridview = new Gridview(
|
this.gridview = new Gridview(
|
||||||
!!options.proportionalLayout,
|
!!options.proportionalLayout,
|
||||||
options.styles,
|
options.styles,
|
||||||
options.orientation
|
options.orientation,
|
||||||
|
options.locked,
|
||||||
|
options.margin
|
||||||
);
|
);
|
||||||
|
|
||||||
this.gridview.locked = !!options.locked;
|
this.gridview.locked = !!options.locked;
|
||||||
|
@ -139,6 +139,20 @@ export class BranchNode extends CompositeDisposable implements IView {
|
|||||||
this.splitview.disabled = value;
|
this.splitview.disabled = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get margin(): number {
|
||||||
|
return this.splitview.margin;
|
||||||
|
}
|
||||||
|
|
||||||
|
set margin(value: number) {
|
||||||
|
this.splitview.margin = value;
|
||||||
|
|
||||||
|
this.children.forEach((child) => {
|
||||||
|
if (child instanceof BranchNode) {
|
||||||
|
child.margin = value;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
readonly orientation: Orientation,
|
readonly orientation: Orientation,
|
||||||
readonly proportionalLayout: boolean,
|
readonly proportionalLayout: boolean,
|
||||||
@ -146,6 +160,7 @@ export class BranchNode extends CompositeDisposable implements IView {
|
|||||||
size: number,
|
size: number,
|
||||||
orthogonalSize: number,
|
orthogonalSize: number,
|
||||||
disabled: boolean,
|
disabled: boolean,
|
||||||
|
margin: number | undefined,
|
||||||
childDescriptors?: INodeDescriptor[]
|
childDescriptors?: INodeDescriptor[]
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
@ -160,6 +175,7 @@ export class BranchNode extends CompositeDisposable implements IView {
|
|||||||
orientation: this.orientation,
|
orientation: this.orientation,
|
||||||
proportionalLayout,
|
proportionalLayout,
|
||||||
styles,
|
styles,
|
||||||
|
margin,
|
||||||
});
|
});
|
||||||
this.splitview.layout(this.size, this.orthogonalSize);
|
this.splitview.layout(this.size, this.orthogonalSize);
|
||||||
} else {
|
} else {
|
||||||
@ -184,6 +200,7 @@ export class BranchNode extends CompositeDisposable implements IView {
|
|||||||
descriptor,
|
descriptor,
|
||||||
proportionalLayout,
|
proportionalLayout,
|
||||||
styles,
|
styles,
|
||||||
|
margin,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -42,7 +42,8 @@ function flipNode<T extends Node>(
|
|||||||
node.styles,
|
node.styles,
|
||||||
size,
|
size,
|
||||||
orthogonalSize,
|
orthogonalSize,
|
||||||
node.disabled
|
node.disabled,
|
||||||
|
node.margin
|
||||||
);
|
);
|
||||||
|
|
||||||
let totalSize = 0;
|
let totalSize = 0;
|
||||||
@ -275,6 +276,7 @@ export class Gridview implements IDisposable {
|
|||||||
|
|
||||||
private _root: BranchNode | undefined;
|
private _root: BranchNode | undefined;
|
||||||
private _locked = false;
|
private _locked = false;
|
||||||
|
private _margin = 0;
|
||||||
private _maximizedNode:
|
private _maximizedNode:
|
||||||
| { leaf: LeafNode; hiddenOnMaximize: LeafNode[] }
|
| { leaf: LeafNode; hiddenOnMaximize: LeafNode[] }
|
||||||
| undefined = undefined;
|
| undefined = undefined;
|
||||||
@ -356,6 +358,15 @@ export class Gridview implements IDisposable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get margin(): number {
|
||||||
|
return this._margin;
|
||||||
|
}
|
||||||
|
|
||||||
|
set margin(value: number) {
|
||||||
|
this._margin = value;
|
||||||
|
this.root.margin = value;
|
||||||
|
}
|
||||||
|
|
||||||
maximizedView(): IGridView | undefined {
|
maximizedView(): IGridView | undefined {
|
||||||
return this._maximizedNode?.leaf.view;
|
return this._maximizedNode?.leaf.view;
|
||||||
}
|
}
|
||||||
@ -466,7 +477,8 @@ export class Gridview implements IDisposable {
|
|||||||
this.styles,
|
this.styles,
|
||||||
this.root.size,
|
this.root.size,
|
||||||
this.root.orthogonalSize,
|
this.root.orthogonalSize,
|
||||||
this._locked
|
this.locked,
|
||||||
|
this.margin
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -527,7 +539,8 @@ export class Gridview implements IDisposable {
|
|||||||
this.styles,
|
this.styles,
|
||||||
node.size, // <- orthogonal size - flips at each depth
|
node.size, // <- orthogonal size - flips at each depth
|
||||||
orthogonalSize, // <- size - flips at each depth,
|
orthogonalSize, // <- size - flips at each depth,
|
||||||
this._locked,
|
this.locked,
|
||||||
|
this.margin,
|
||||||
children
|
children
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
@ -580,7 +593,8 @@ export class Gridview implements IDisposable {
|
|||||||
this.styles,
|
this.styles,
|
||||||
this.root.orthogonalSize,
|
this.root.orthogonalSize,
|
||||||
this.root.size,
|
this.root.size,
|
||||||
this._locked
|
this.locked,
|
||||||
|
this.margin
|
||||||
);
|
);
|
||||||
|
|
||||||
if (oldRoot.children.length === 0) {
|
if (oldRoot.children.length === 0) {
|
||||||
@ -686,17 +700,24 @@ export class Gridview implements IDisposable {
|
|||||||
constructor(
|
constructor(
|
||||||
readonly proportionalLayout: boolean,
|
readonly proportionalLayout: boolean,
|
||||||
readonly styles: ISplitviewStyles | undefined,
|
readonly styles: ISplitviewStyles | undefined,
|
||||||
orientation: Orientation
|
orientation: Orientation,
|
||||||
|
locked?: boolean,
|
||||||
|
margin?: number
|
||||||
) {
|
) {
|
||||||
this.element = document.createElement('div');
|
this.element = document.createElement('div');
|
||||||
this.element.className = 'grid-view';
|
this.element.className = 'grid-view';
|
||||||
|
|
||||||
|
this._locked = locked ?? false;
|
||||||
|
this._margin = margin ?? 0;
|
||||||
|
|
||||||
this.root = new BranchNode(
|
this.root = new BranchNode(
|
||||||
orientation,
|
orientation,
|
||||||
proportionalLayout,
|
proportionalLayout,
|
||||||
styles,
|
styles,
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
this._locked
|
this.locked,
|
||||||
|
this.margin
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -781,7 +802,8 @@ export class Gridview implements IDisposable {
|
|||||||
this.styles,
|
this.styles,
|
||||||
parent.size,
|
parent.size,
|
||||||
parent.orthogonalSize,
|
parent.orthogonalSize,
|
||||||
this._locked
|
this.locked,
|
||||||
|
this.margin
|
||||||
);
|
);
|
||||||
grandParent.addChild(newParent, parent.size, parentIndex);
|
grandParent.addChild(newParent, parent.size, parentIndex);
|
||||||
|
|
||||||
|
@ -36,6 +36,7 @@ export interface SplitViewOptions {
|
|||||||
readonly descriptor?: ISplitViewDescriptor;
|
readonly descriptor?: ISplitViewDescriptor;
|
||||||
readonly proportionalLayout?: boolean;
|
readonly proportionalLayout?: boolean;
|
||||||
readonly styles?: ISplitviewStyles;
|
readonly styles?: ISplitviewStyles;
|
||||||
|
readonly margin?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum LayoutPriority {
|
export enum LayoutPriority {
|
||||||
@ -110,6 +111,7 @@ export class Splitview {
|
|||||||
private _startSnappingEnabled = true;
|
private _startSnappingEnabled = true;
|
||||||
private _endSnappingEnabled = true;
|
private _endSnappingEnabled = true;
|
||||||
private _disabled = false;
|
private _disabled = false;
|
||||||
|
private _margin = 0;
|
||||||
|
|
||||||
private readonly _onDidSashEnd = new Emitter<void>();
|
private readonly _onDidSashEnd = new Emitter<void>();
|
||||||
readonly onDidSashEnd = this._onDidSashEnd.event;
|
readonly onDidSashEnd = this._onDidSashEnd.event;
|
||||||
@ -211,6 +213,14 @@ export class Splitview {
|
|||||||
toggleClass(this.element, 'dv-splitview-disabled', value);
|
toggleClass(this.element, 'dv-splitview-disabled', value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get margin(): number {
|
||||||
|
return this._margin;
|
||||||
|
}
|
||||||
|
|
||||||
|
set margin(value: number) {
|
||||||
|
this._margin = value;
|
||||||
|
}
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly container: HTMLElement,
|
private readonly container: HTMLElement,
|
||||||
options: SplitViewOptions
|
options: SplitViewOptions
|
||||||
@ -218,6 +228,8 @@ export class Splitview {
|
|||||||
this._orientation = options.orientation;
|
this._orientation = options.orientation;
|
||||||
this.element = this.createContainer();
|
this.element = this.createContainer();
|
||||||
|
|
||||||
|
this.margin = options.margin ?? 0;
|
||||||
|
|
||||||
this.proportionalLayout =
|
this.proportionalLayout =
|
||||||
options.proportionalLayout === undefined
|
options.proportionalLayout === undefined
|
||||||
? true
|
? true
|
||||||
@ -785,18 +797,39 @@ export class Splitview {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Margin explain:
|
||||||
|
*
|
||||||
|
* For `n` views in a splitview there will be `n-1` margins `m`.
|
||||||
|
*
|
||||||
|
* To fit the margins each view must reduce in size by `(m * (n - 1)) / n`.
|
||||||
|
*
|
||||||
|
* For each view `i` the offet must be adjusted by `m * i/(n - 1)`.
|
||||||
|
*/
|
||||||
private layoutViews(): void {
|
private layoutViews(): void {
|
||||||
this._contentSize = this.viewItems.reduce((r, i) => r + i.size, 0);
|
this._contentSize = this.viewItems.reduce((r, i) => r + i.size, 0);
|
||||||
let sum = 0;
|
|
||||||
const x: number[] = [];
|
|
||||||
|
|
||||||
this.updateSashEnablement();
|
this.updateSashEnablement();
|
||||||
|
|
||||||
for (let i = 0; i < this.viewItems.length - 1; i++) {
|
if (this.viewItems.length === 0) {
|
||||||
sum += this.viewItems[i].size;
|
return;
|
||||||
x.push(sum);
|
}
|
||||||
|
|
||||||
const offset = Math.min(Math.max(0, sum - 2), this.size - 4);
|
const sashCount = this.viewItems.length - 1;
|
||||||
|
const marginReducedSize =
|
||||||
|
(this.margin * sashCount) / this.viewItems.length;
|
||||||
|
|
||||||
|
let totalLeftOffset = 0;
|
||||||
|
const viewLeftOffsets: number[] = [];
|
||||||
|
|
||||||
|
for (let i = 0; i < this.viewItems.length - 1; i++) {
|
||||||
|
totalLeftOffset += this.viewItems[i].size;
|
||||||
|
viewLeftOffsets.push(totalLeftOffset);
|
||||||
|
|
||||||
|
const offset = Math.min(
|
||||||
|
Math.max(0, totalLeftOffset - 2),
|
||||||
|
this.size - this.margin
|
||||||
|
);
|
||||||
|
|
||||||
if (this._orientation === Orientation.HORIZONTAL) {
|
if (this._orientation === Orientation.HORIZONTAL) {
|
||||||
this.sashes[i].container.style.left = `${offset}px`;
|
this.sashes[i].container.style.left = `${offset}px`;
|
||||||
@ -808,20 +841,30 @@ export class Splitview {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.viewItems.forEach((view, i) => {
|
this.viewItems.forEach((view, i) => {
|
||||||
|
const size = view.size - marginReducedSize;
|
||||||
|
const offset =
|
||||||
|
i === 0
|
||||||
|
? 0
|
||||||
|
: viewLeftOffsets[i - 1] +
|
||||||
|
(i / sashCount) * marginReducedSize;
|
||||||
|
|
||||||
if (this._orientation === Orientation.HORIZONTAL) {
|
if (this._orientation === Orientation.HORIZONTAL) {
|
||||||
view.container.style.width = `${view.size}px`;
|
view.container.style.width = `${size}px`;
|
||||||
view.container.style.left = i == 0 ? '0px' : `${x[i - 1]}px`;
|
view.container.style.left = `${offset}px`;
|
||||||
view.container.style.top = '';
|
view.container.style.top = '';
|
||||||
view.container.style.height = '';
|
view.container.style.height = '';
|
||||||
}
|
}
|
||||||
if (this._orientation === Orientation.VERTICAL) {
|
if (this._orientation === Orientation.VERTICAL) {
|
||||||
view.container.style.height = `${view.size}px`;
|
view.container.style.height = `${size}px`;
|
||||||
view.container.style.top = i == 0 ? '0px' : `${x[i - 1]}px`;
|
view.container.style.top = `${offset}px`;
|
||||||
view.container.style.width = '';
|
view.container.style.width = '';
|
||||||
view.container.style.left = '';
|
view.container.style.left = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
view.view.layout(view.size, this._orthogonalSize);
|
view.view.layout(
|
||||||
|
view.size - marginReducedSize,
|
||||||
|
this._orthogonalSize
|
||||||
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -229,8 +229,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
@mixin dockview-design-replit-mixin {
|
@mixin dockview-design-replit-mixin {
|
||||||
--dv-group-gap-size: 3px;
|
|
||||||
|
|
||||||
.dv-resize-container:has(> .groupview) {
|
.dv-resize-container:has(> .groupview) {
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
}
|
}
|
||||||
|
@ -9,10 +9,10 @@ import {
|
|||||||
import { setMockRefElement } from '../__test_utils__/utils';
|
import { setMockRefElement } from '../__test_utils__/utils';
|
||||||
|
|
||||||
describe('gridview react', () => {
|
describe('gridview react', () => {
|
||||||
let components: Record<
|
let components: Record<
|
||||||
string,
|
string,
|
||||||
React.FunctionComponent<IGridviewPanelProps>
|
React.FunctionComponent<IGridviewPanelProps>
|
||||||
>
|
>;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
components = {
|
components = {
|
||||||
|
@ -51,6 +51,12 @@ export const GridActions = (props: {
|
|||||||
props.api?.addGroup();
|
props.api?.addGroup();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const [gap, setGap] = React.useState(0);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
props.api?.setGap(gap);
|
||||||
|
}, [gap, props.api]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="action-container">
|
<div className="action-container">
|
||||||
<button className="text-button" onClick={() => onAddPanel()}>
|
<button className="text-button" onClick={() => onAddPanel()}>
|
||||||
@ -89,6 +95,18 @@ export const GridActions = (props: {
|
|||||||
<button className="text-button" onClick={onReset}>
|
<button className="text-button" onClick={onReset}>
|
||||||
Reset
|
Reset
|
||||||
</button>
|
</button>
|
||||||
|
<span style={{ flexGrow: 1 }} />
|
||||||
|
<div style={{ display: 'flex' }}>
|
||||||
|
<span style={{ paddingRight: '4px' }}>Gap</span>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
min={0}
|
||||||
|
max={20}
|
||||||
|
step={1}
|
||||||
|
value={gap}
|
||||||
|
onChange={(event) => setGap(Number(event.target.value))}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user