Merge pull request #411 from NaNgets/388-rtl-support-gpgsigned

RTL support.
This commit is contained in:
mathuo 2023-12-28 21:42:05 +00:00 committed by GitHub
commit c2d65db045
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
42 changed files with 289 additions and 69 deletions

View File

@ -3,10 +3,10 @@
> .drop-target-dropzone { > .drop-target-dropzone {
position: absolute; position: absolute;
left: 0px;
top: 0px; top: 0px;
left: 0px;
right: 0px;
height: 100%; height: 100%;
width: 100%;
z-index: 1000; z-index: 1000;
pointer-events: none; pointer-events: none;
@ -17,8 +17,8 @@
width: 100%; width: 100%;
background-color: var(--dv-drag-over-background-color); background-color: var(--dv-drag-over-background-color);
transition: top 70ms ease-out, left 70ms ease-out, transition: top 70ms ease-out, left 70ms ease-out,
width 70ms ease-out, height 70ms ease-out, right 70ms ease-out, width 70ms ease-out,
opacity 0.15s ease-out; height 70ms ease-out, opacity 0.15s ease-out;
will-change: transform; will-change: transform;
pointer-events: none; pointer-events: none;

View File

@ -42,8 +42,8 @@
.dv-resize-handle-top { .dv-resize-handle-top {
height: 4px; height: 4px;
width: calc(100% - 8px);
left: 4px; left: 4px;
right: 4px;
top: -2px; top: -2px;
z-index: 999; z-index: 999;
position: absolute; position: absolute;
@ -52,8 +52,8 @@
.dv-resize-handle-bottom { .dv-resize-handle-bottom {
height: 4px; height: 4px;
width: calc(100% - 8px);
left: 4px; left: 4px;
right: 4px;
bottom: -2px; bottom: -2px;
z-index: 999; z-index: 999;
position: absolute; position: absolute;

View File

@ -1,6 +1,6 @@
import { DragAndDropObserver } from '../../dnd/dnd'; import { DragAndDropObserver } from '../../dnd/dnd';
import { Droptarget } from '../../dnd/droptarget'; import { Droptarget } from '../../dnd/droptarget';
import { getDomNodePagePosition, toggleClass } from '../../dom'; import { getDomNodePagePosition, hasClassInTree, toggleClass } from '../../dom';
import { CompositeDisposable, Disposable, IDisposable } from '../../lifecycle'; import { CompositeDisposable, Disposable, IDisposable } from '../../lifecycle';
import { IDockviewPanel } from '../dockviewPanel'; import { IDockviewPanel } from '../dockviewPanel';
@ -77,7 +77,9 @@ export class GreadyRenderContainer extends CompositeDisposable {
// TODO propagate position to avoid getDomNodePagePosition calls // TODO propagate position to avoid getDomNodePagePosition calls
const box = getDomNodePagePosition(referenceContainer.element); const box = getDomNodePagePosition(referenceContainer.element);
const box2 = getDomNodePagePosition(this.element); const box2 = getDomNodePagePosition(this.element);
focusContainer.style.left = `${box.left - box2.left}px`; const isRtl = hasClassInTree(this.element, 'dv-rtl');
focusContainer.style.left = isRtl ? '' : `${(box.left || 0) - (box2.left || 0)}px`;
focusContainer.style.right = isRtl ? `${(box.right || 0) - (box2.right || 0)}px` : '';
focusContainer.style.top = `${box.top - box2.top}px`; focusContainer.style.top = `${box.top - box2.top}px`;
focusContainer.style.width = `${box.width}px`; focusContainer.style.width = `${box.width}px`;
focusContainer.style.height = `${box.height}px`; focusContainer.style.height = `${box.height}px`;

View File

@ -63,12 +63,19 @@
content: ' '; content: ' ';
position: absolute; position: absolute;
top: 0; top: 0;
left: 0;
z-index: 5; z-index: 5;
pointer-events: none; pointer-events: none;
background-color: var(--dv-tab-divider-color); background-color: var(--dv-tab-divider-color);
width: 1px; width: 1px;
height: 100%; height: 100%;
.dv-ltr & {
left: 0;
}
.dv-rtl & {
right: 0;
}
} }
} }
} }

View File

@ -251,15 +251,15 @@ export class TabsContainer
) { ) {
event.preventDefault(); event.preventDefault();
const { top, left } = const { top, left, right } =
this.element.getBoundingClientRect(); this.element.getBoundingClientRect();
const { top: rootTop, left: rootLeft } = const { top: rootTop, left: rootLeft, right: rootRight } =
this.accessor.element.getBoundingClientRect(); this.accessor.element.getBoundingClientRect();
this.accessor.addFloatingGroup( this.accessor.addFloatingGroup(
this.group, this.group,
{ {
x: left - rootLeft + 20, x: (this.accessor.options.isRtl ? (right - rootRight) : (left - rootLeft)) + 20,
y: top - rootTop + 20, y: top - rootTop + 20,
}, },
{ inDragMode: true } { inDragMode: true }
@ -361,14 +361,14 @@ export class TabsContainer
const panel = this.accessor.getGroupPanel(tab.panel.id); const panel = this.accessor.getGroupPanel(tab.panel.id);
const { top, left } = tab.element.getBoundingClientRect(); const { top, left, right } = tab.element.getBoundingClientRect();
const { top: rootTop, left: rootLeft } = const { top: rootTop, left: rootLeft, right: rootRight } =
this.accessor.element.getBoundingClientRect(); this.accessor.element.getBoundingClientRect();
this.accessor.addFloatingGroup( this.accessor.addFloatingGroup(
panel as DockviewPanel, panel as DockviewPanel,
{ {
x: left - rootLeft, x: (this.accessor.options.isRtl ? (right - rootRight) : (left - rootLeft)) + 20,
y: top - rootTop, y: top - rootTop,
}, },
{ inDragMode: true } { inDragMode: true }

View File

@ -2,12 +2,20 @@
position: relative; position: relative;
background-color: var(--dv-group-view-background-color); background-color: var(--dv-group-view-background-color);
&.dv-ltr {
direction: ltr;
}
&.dv-rtl {
direction: rtl;
}
.dv-watermark-container { .dv-watermark-container {
position: absolute; position: absolute;
top: 0px; top: 0px;
left: 0px; left: 0px;
right: 0px;
height: 100%; height: 100%;
width: 100%;
z-index: 1; z-index: 1;
} }

View File

@ -316,6 +316,7 @@ export class DockviewComponent
styles: options.styles, styles: options.styles,
parentElement: options.parentElement, parentElement: options.parentElement,
disableAutoResizing: options.disableAutoResizing, disableAutoResizing: options.disableAutoResizing,
isRtl: options.isRtl,
}); });
const gready = document.createElement('div'); const gready = document.createElement('div');
@ -1007,7 +1008,8 @@ export class DockviewComponent
const relativeLocation = getRelativeLocation( const relativeLocation = getRelativeLocation(
this.gridview.orientation, this.gridview.orientation,
location, location,
target target,
this.options.isRtl
); );
const group = this.createGroupAtLocation(relativeLocation); const group = this.createGroupAtLocation(relativeLocation);
panel = this.createPanel(options, group); panel = this.createPanel(options, group);
@ -1155,7 +1157,8 @@ export class DockviewComponent
const relativeLocation = getRelativeLocation( const relativeLocation = getRelativeLocation(
this.gridview.orientation, this.gridview.orientation,
location, location,
target target,
this.options.isRtl
); );
this.doAddGroup(group, relativeLocation); this.doAddGroup(group, relativeLocation);
return group; return group;
@ -1270,7 +1273,8 @@ export class DockviewComponent
const targetLocation = getRelativeLocation( const targetLocation = getRelativeLocation(
this.gridview.orientation, this.gridview.orientation,
referenceLocation, referenceLocation,
destinationTarget destinationTarget,
this.options.isRtl
); );
if (sourceGroup && sourceGroup.size < 2) { if (sourceGroup && sourceGroup.size < 2) {
@ -1310,7 +1314,8 @@ export class DockviewComponent
const location = getRelativeLocation( const location = getRelativeLocation(
this.gridview.orientation, this.gridview.orientation,
updatedReferenceLocation, updatedReferenceLocation,
destinationTarget destinationTarget,
this.options.isRtl
); );
this.doAddGroup(targetGroup, location); this.doAddGroup(targetGroup, location);
} else { } else {
@ -1325,7 +1330,8 @@ export class DockviewComponent
const dropLocation = getRelativeLocation( const dropLocation = getRelativeLocation(
this.gridview.orientation, this.gridview.orientation,
referenceLocation, referenceLocation,
destinationTarget destinationTarget,
this.options.isRtl
); );
const group = this.createGroupAtLocation(dropLocation); const group = this.createGroupAtLocation(dropLocation);
@ -1374,7 +1380,8 @@ export class DockviewComponent
const dropLocation = getRelativeLocation( const dropLocation = getRelativeLocation(
this.gridview.orientation, this.gridview.orientation,
referenceLocation, referenceLocation,
target target,
this.options.isRtl
); );
this.gridview.addView( this.gridview.addView(

View File

@ -7,7 +7,7 @@ export interface IDockviewFloatingGroupPanel {
position( position(
bounds: Partial<{ bounds: Partial<{
top: number; top: number;
left: number; side: number;
height: number; height: number;
width: number; width: number;
}> }>
@ -27,7 +27,7 @@ export class DockviewFloatingGroupPanel
position( position(
bounds: Partial<{ bounds: Partial<{
top: number; top: number;
left: number; side: number;
height: number; height: number;
width: number; width: number;
}> }>

View File

@ -98,6 +98,7 @@ export interface DockviewComponentOptions extends DockviewRenderFunctions {
minimumWidthWithinViewport?: number; minimumWidthWithinViewport?: number;
}; };
defaultRenderer?: DockviewPanelRenderer; defaultRenderer?: DockviewPanelRenderer;
isRtl?: boolean;
debug?: boolean; debug?: boolean;
} }

View File

@ -186,15 +186,29 @@ export function quasiDefaultPrevented(event: Event): boolean {
return (event as any)[QUASI_PREVENT_DEFAULT_KEY]; return (event as any)[QUASI_PREVENT_DEFAULT_KEY];
} }
// Gets whether the given class exists in the element or its parent tree
export function hasClassInTree(domNode: Element, className: string): boolean {
if (domNode.classList.contains(className)) {
return true;
}
if (domNode.parentElement) {
return hasClassInTree(domNode.parentElement, className);
}
return false;
}
export function getDomNodePagePosition(domNode: Element): { export function getDomNodePagePosition(domNode: Element): {
left: number; left?: number;
right?: number;
top: number; top: number;
width: number; width: number;
height: number; height: number;
} { } {
const { left, top, width, height } = domNode.getBoundingClientRect(); const isRtl = hasClassInTree(domNode, 'dv-rtl');
const { left, right, top, width, height } = domNode.getBoundingClientRect();
return { return {
left: left + window.scrollX, left: isRtl ? undefined : left + window.scrollX,
right: isRtl ? right + window.scrollX : undefined,
top: top + window.scrollY, top: top + window.scrollY,
width: width, width: width,
height: height, height: height,

View File

@ -7,6 +7,7 @@ import { ISplitviewStyles, Orientation, Sizing } from '../splitview/splitview';
import { IPanel } from '../panel/types'; import { IPanel } from '../panel/types';
import { MovementOptions2 } from '../dockview/options'; import { MovementOptions2 } from '../dockview/options';
import { Resizable } from '../resizable'; import { Resizable } from '../resizable';
import { toggleClass } from '../dom';
const nextLayoutId = sequentialNumberGenerator(); const nextLayoutId = sequentialNumberGenerator();
@ -34,6 +35,7 @@ export interface BaseGridOptions {
readonly styles?: ISplitviewStyles; readonly styles?: ISplitviewStyles;
readonly parentElement?: HTMLElement; readonly parentElement?: HTMLElement;
readonly disableAutoResizing?: boolean; readonly disableAutoResizing?: boolean;
readonly isRtl?: boolean;
} }
export interface IGridPanelView extends IGridView, IPanel { export interface IGridPanelView extends IGridView, IPanel {
@ -137,6 +139,9 @@ export abstract class BaseGrid<T extends IGridPanelView>
options.orientation options.orientation
); );
toggleClass(this.gridview.element, 'dv-rtl', options.isRtl === true);
toggleClass(this.gridview.element, 'dv-ltr', options.isRtl === false);
this.element.appendChild(this.gridview.element); this.element.appendChild(this.gridview.element);
this.layout(0, 0, true); // set some elements height/widths this.layout(0, 0, true); // set some elements height/widths

View File

@ -2,4 +2,12 @@
.branch-node { .branch-node {
height: 100%; height: 100%;
width: 100%; width: 100%;
&.dv-ltr {
direction: ltr;
}
&.dv-rtl {
direction: rtl;
}
} }

View File

@ -123,7 +123,8 @@ export function getGridLocation(element: HTMLElement): number[] {
export function getRelativeLocation( export function getRelativeLocation(
rootOrientation: Orientation, rootOrientation: Orientation,
location: number[], location: number[],
direction: Position direction: Position,
isRtl?: boolean
): number[] { ): number[] {
const orientation = getLocationOrientation(rootOrientation, location); const orientation = getLocationOrientation(rootOrientation, location);
const directionOrientation = getDirectionOrientation(direction); const directionOrientation = getDirectionOrientation(direction);
@ -132,13 +133,13 @@ export function getRelativeLocation(
const [rest, _index] = tail(location); const [rest, _index] = tail(location);
let index = _index; let index = _index;
if (direction === 'right' || direction === 'bottom') { if ((isRtl ? direction === 'left' : direction === 'right') || direction === 'bottom') {
index += 1; index += 1;
} }
return [...rest, index]; return [...rest, index];
} else { } else {
const index = direction === 'right' || direction === 'bottom' ? 1 : 0; const index = (isRtl ? direction === 'left' : direction === 'right') || direction === 'bottom' ? 1 : 0;
return [...location, index]; return [...location, index];
} }
} }

View File

@ -110,6 +110,7 @@ export class GridviewComponent
orientation: options.orientation, orientation: options.orientation,
styles: options.styles, styles: options.styles,
disableAutoResizing: options.disableAutoResizing, disableAutoResizing: options.disableAutoResizing,
isRtl: options.isRtl,
}); });
this._options = options; this._options = options;
@ -299,7 +300,8 @@ export class GridviewComponent
relativeLocation = getRelativeLocation( relativeLocation = getRelativeLocation(
this.gridview.orientation, this.gridview.orientation,
location, location,
target target,
this.options.isRtl
); );
} }
@ -330,7 +332,8 @@ export class GridviewComponent
relativeLocation = getRelativeLocation( relativeLocation = getRelativeLocation(
this.gridview.orientation, this.gridview.orientation,
location, location,
target target,
this.options.isRtl
); );
} }
} }
@ -406,7 +409,8 @@ export class GridviewComponent
const targetLocation = getRelativeLocation( const targetLocation = getRelativeLocation(
this.gridview.orientation, this.gridview.orientation,
referenceLocation, referenceLocation,
target target,
this.options.isRtl
); );
const [targetParentLocation, to] = tail(targetLocation); const [targetParentLocation, to] = tail(targetLocation);
@ -435,7 +439,8 @@ export class GridviewComponent
const location = getRelativeLocation( const location = getRelativeLocation(
this.gridview.orientation, this.gridview.orientation,
updatedReferenceLocation, updatedReferenceLocation,
target target,
this.options.isRtl
); );
this.doAddGroup(targetGroup, location); this.doAddGroup(targetGroup, location);
} }

View File

@ -17,5 +17,6 @@ export interface GridviewComponentOptions {
}; };
frameworkComponentFactory?: FrameworkFactory<GridviewPanel>; frameworkComponentFactory?: FrameworkFactory<GridviewPanel>;
styles?: ISplitviewStyles; styles?: ISplitviewStyles;
isRtl?: boolean;
parentElement?: HTMLElement; parentElement?: HTMLElement;
} }

View File

@ -146,14 +146,29 @@ export abstract class DraggablePaneviewPanel extends PaneviewPanel {
const fromIndex = allPanels.indexOf(existingPanel); const fromIndex = allPanels.indexOf(existingPanel);
let toIndex = containerApi.panels.indexOf(this); let toIndex = containerApi.panels.indexOf(this);
console.log('onDrop', this.accessor.options.isRtl, fromIndex, toIndex, event.position);
if (event.position === 'left' || event.position === 'top') { if (event.position === 'left' || event.position === 'top') {
toIndex = Math.max(0, toIndex - 1); if (this.accessor.options.isRtl) {
if (fromIndex > toIndex) {
toIndex++;
}
toIndex = Math.min(allPanels.length - 1, toIndex);
}
else {
toIndex = Math.max(0, toIndex - 1);
}
} }
if (event.position === 'right' || event.position === 'bottom') { if (event.position === 'right' || event.position === 'bottom') {
if (fromIndex > toIndex) { if (this.accessor.options.isRtl) {
toIndex++; toIndex = Math.max(0, toIndex - 1);
}
else {
if (fromIndex > toIndex) {
toIndex++;
}
toIndex = Math.min(allPanels.length - 1, toIndex);
} }
toIndex = Math.min(allPanels.length - 1, toIndex);
} }
containerApi.movePanel(fromIndex, toIndex); containerApi.movePanel(fromIndex, toIndex);

View File

@ -26,4 +26,5 @@ export interface PaneviewComponentOptions {
disableDnd?: boolean; disableDnd?: boolean;
showDndOverlay?: (event: PaneviewDndOverlayEvent) => boolean; showDndOverlay?: (event: PaneviewDndOverlayEvent) => boolean;
parentElement?: HTMLElement; parentElement?: HTMLElement;
isRtl?: boolean;
} }

View File

@ -2,6 +2,18 @@
height: 100%; height: 100%;
width: 100%; width: 100%;
&.dv-ltr {
direction: ltr;
}
&.dv-rtl {
direction: rtl;
.view .default-header .dockview-pane-header-icon {
transform: scale(-1, 1);
}
}
&.animated { &.animated {
.view { .view {
transition-duration: 0.15s; transition-duration: 0.15s;
@ -38,8 +50,8 @@
} }
> span { > span {
padding-left: 8px;
flex-grow: 1; flex-grow: 1;
padding-inline-start: 8px;
} }
} }
} }
@ -70,7 +82,7 @@
position: absolute; position: absolute;
top: 0; top: 0;
left: 0; left: 0;
width: 100%; right: 0;
height: 100%; height: 100%;
z-index: 5; z-index: 5;
content: ''; content: '';
@ -96,7 +108,7 @@
position: absolute; position: absolute;
top: 0; top: 0;
left: 0; left: 0;
width: 100%; right: 0;
height: 100%; height: 100%;
z-index: 5; z-index: 5;
content: ''; content: '';

View File

@ -6,7 +6,7 @@ import {
} from '../splitview/splitview'; } from '../splitview/splitview';
import { CompositeDisposable, IDisposable } from '../lifecycle'; import { CompositeDisposable, IDisposable } from '../lifecycle';
import { Emitter, Event } from '../events'; import { Emitter, Event } from '../events';
import { addClasses, removeClasses } from '../dom'; import { addClasses, removeClasses, toggleClass } from '../dom';
import { PaneviewPanel } from './paneviewPanel'; import { PaneviewPanel } from './paneviewPanel';
interface PaneItem { interface PaneItem {
@ -54,7 +54,7 @@ export class Paneview extends CompositeDisposable implements IDisposable {
constructor( constructor(
container: HTMLElement, container: HTMLElement,
options: { orientation: Orientation; descriptor?: ISplitViewDescriptor } options: { orientation: Orientation; isRtl?: boolean; descriptor?: ISplitViewDescriptor }
) { ) {
super(); super();
@ -63,12 +63,16 @@ export class Paneview extends CompositeDisposable implements IDisposable {
this.element = document.createElement('div'); this.element = document.createElement('div');
this.element.className = 'pane-container'; this.element.className = 'pane-container';
toggleClass(this.element, 'dv-rtl', options.isRtl === true);
toggleClass(this.element, 'dv-ltr', options.isRtl === false);
container.appendChild(this.element); container.appendChild(this.element);
this.splitview = new Splitview(this.element, { this.splitview = new Splitview(this.element, {
orientation: this._orientation, orientation: this._orientation,
proportionalLayout: false, proportionalLayout: false,
descriptor: options.descriptor, descriptor: options.descriptor,
isRtl: options.isRtl,
}); });
// if we've added views from the descriptor we need to // if we've added views from the descriptor we need to

View File

@ -24,6 +24,7 @@ import { sequentialNumberGenerator } from '../math';
import { PaneTransfer } from '../dnd/dataTransfer'; import { PaneTransfer } from '../dnd/dataTransfer';
import { Resizable } from '../resizable'; import { Resizable } from '../resizable';
import { Parameters } from '../panel/types'; import { Parameters } from '../panel/types';
import { toggleClass } from '../dom';
const nextLayoutId = sequentialNumberGenerator(); const nextLayoutId = sequentialNumberGenerator();
@ -221,6 +222,7 @@ export class PaneviewComponent extends Resizable implements IPaneviewComponent {
this.paneview = new Paneview(this.element, { this.paneview = new Paneview(this.element, {
// only allow paneview in the vertical orientation for now // only allow paneview in the vertical orientation for now
orientation: Orientation.VERTICAL, orientation: Orientation.VERTICAL,
isRtl: options.isRtl,
}); });
this.addDisposables(this._disposable); this.addDisposables(this._disposable);
@ -369,6 +371,7 @@ export class PaneviewComponent extends Resizable implements IPaneviewComponent {
this.paneview = new Paneview(this.element, { this.paneview = new Paneview(this.element, {
orientation: Orientation.VERTICAL, orientation: Orientation.VERTICAL,
isRtl: this.options.isRtl,
descriptor: { descriptor: {
size, size,
views: views.map((view) => { views: views.map((view) => {

View File

@ -25,6 +25,22 @@
height: 100%; height: 100%;
width: 100%; width: 100%;
&.dv-ltr {
direction: ltr;
&.separator-border .view:not(:first-child)::before {
left: 0;
}
}
&.dv-rtl {
direction: rtl;
&.separator-border .view:not(:first-child)::before {
right: 0;
}
}
&.animation { &.animation {
.view, .view,
.sash { .sash {
@ -145,7 +161,6 @@
content: ' '; content: ' ';
position: absolute; position: absolute;
top: 0; top: 0;
left: 0;
z-index: 5; z-index: 5;
pointer-events: none; pointer-events: none;
background-color: var(--dv-separator-border); background-color: var(--dv-separator-border);

View File

@ -8,6 +8,7 @@ import {
addClasses, addClasses,
toggleClass, toggleClass,
getElementsByTagName, getElementsByTagName,
hasClassInTree,
} from '../dom'; } from '../dom';
import { Event, Emitter } from '../events'; import { Event, Emitter } from '../events';
import { pushToStart, pushToEnd, firstIndex } from '../array'; import { pushToStart, pushToEnd, firstIndex } from '../array';
@ -36,6 +37,7 @@ export interface SplitViewOptions {
readonly descriptor?: ISplitViewDescriptor; readonly descriptor?: ISplitViewDescriptor;
readonly proportionalLayout?: boolean; readonly proportionalLayout?: boolean;
readonly styles?: ISplitviewStyles; readonly styles?: ISplitviewStyles;
readonly isRtl?: boolean;
} }
export enum LayoutPriority { export enum LayoutPriority {
@ -201,7 +203,7 @@ export class Splitview {
options: SplitViewOptions options: SplitViewOptions
) { ) {
this._orientation = options.orientation; this._orientation = options.orientation;
this.element = this.createContainer(); this.element = this.createContainer(options.isRtl);
this.proportionalLayout = this.proportionalLayout =
options.proportionalLayout === undefined options.proportionalLayout === undefined
@ -756,6 +758,7 @@ export class Splitview {
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);
const isRtl = hasClassInTree(this.element, 'dv-rtl');
let sum = 0; let sum = 0;
const x: number[] = []; const x: number[] = [];
@ -768,18 +771,21 @@ export class Splitview {
const offset = Math.min(Math.max(0, sum - 2), this.size - 4); const offset = Math.min(Math.max(0, sum - 2), this.size - 4);
if (this._orientation === Orientation.HORIZONTAL) { if (this._orientation === Orientation.HORIZONTAL) {
this.sashes[i].container.style.left = `${offset}px`; this.sashes[i].container.style.left = isRtl ? '' : `${offset}px`;
this.sashes[i].container.style.right = isRtl ? `${offset}px` : '';
this.sashes[i].container.style.top = `0px`; this.sashes[i].container.style.top = `0px`;
} }
if (this._orientation === Orientation.VERTICAL) { if (this._orientation === Orientation.VERTICAL) {
this.sashes[i].container.style.left = `0px`; this.sashes[i].container.style.left = isRtl ? '' : `0px`;
this.sashes[i].container.style.right = isRtl ? `0px` : '';
this.sashes[i].container.style.top = `${offset}px`; this.sashes[i].container.style.top = `${offset}px`;
} }
} }
this.viewItems.forEach((view, i) => { this.viewItems.forEach((view, i) => {
if (this._orientation === Orientation.HORIZONTAL) { if (this._orientation === Orientation.HORIZONTAL) {
view.container.style.width = `${view.size}px`; view.container.style.width = `${view.size}px`;
view.container.style.left = i == 0 ? '0px' : `${x[i - 1]}px`; view.container.style.left = isRtl ? '' : (i == 0 ? '0px' : `${x[i - 1]}px`);
view.container.style.right = isRtl ? (i == 0 ? '0px' : `${x[i - 1]}px`) : '';
view.container.style.top = ''; view.container.style.top = '';
view.container.style.height = ''; view.container.style.height = '';
} }
@ -788,6 +794,7 @@ export class Splitview {
view.container.style.top = i == 0 ? '0px' : `${x[i - 1]}px`; view.container.style.top = i == 0 ? '0px' : `${x[i - 1]}px`;
view.container.style.width = ''; view.container.style.width = '';
view.container.style.left = ''; view.container.style.left = '';
view.container.style.right = '';
} }
view.view.layout(view.size, this._orthogonalSize); view.view.layout(view.size, this._orthogonalSize);
@ -918,8 +925,10 @@ export class Splitview {
return 0; return 0;
} }
const upIndexes = range(index, -1); const isHorizontal = this._orientation === Orientation.HORIZONTAL;
const downIndexes = range(index + 1, this.viewItems.length); const isRtl = hasClassInTree(this.element, 'dv-rtl');
const upIndexes = isHorizontal && isRtl && this.viewItems.length > 1 ? range(index + 1, this.viewItems.length) : range(index, -1);
const downIndexes = isHorizontal && isRtl && this.viewItems.length > 1 ? range(index, -1) : range(index + 1, this.viewItems.length);
// //
if (highPriorityIndexes) { if (highPriorityIndexes) {
for (const i of highPriorityIndexes) { for (const i of highPriorityIndexes) {
@ -955,7 +964,6 @@ export class Splitview {
? Number.POSITIVE_INFINITY ? Number.POSITIVE_INFINITY
: downIndexes.reduce( : downIndexes.reduce(
(_, i) => _ + sizes[i] - this.viewItems[i].minimumSize, (_, i) => _ + sizes[i] - this.viewItems[i].minimumSize,
0 0
); );
const minDeltaDown = const minDeltaDown =
@ -1044,13 +1052,15 @@ export class Splitview {
return element; return element;
} }
private createContainer(): HTMLElement { private createContainer(isRtl?: boolean): HTMLElement {
const element = document.createElement('div'); const element = document.createElement('div');
const orientationClassname = const orientationClassname =
this._orientation === Orientation.HORIZONTAL this._orientation === Orientation.HORIZONTAL
? 'horizontal' ? 'horizontal'
: 'vertical'; : 'vertical';
element.className = `split-view-container ${orientationClassname}`; element.className = `split-view-container ${orientationClassname}`;
toggleClass(element, 'dv-rtl', isRtl === true);
toggleClass(element, 'dv-ltr', isRtl === false);
return element; return element;
} }

View File

@ -349,6 +349,7 @@ export class SplitviewComponent
this.splitview = new Splitview(this.element, { this.splitview = new Splitview(this.element, {
orientation, orientation,
proportionalLayout: this.options.proportionalLayout, proportionalLayout: this.options.proportionalLayout,
isRtl: this.options.isRtl,
descriptor: { descriptor: {
size, size,
views: views.map((view) => { views: views.map((view) => {

View File

@ -185,9 +185,9 @@
&::after { &::after {
position: absolute; position: absolute;
left: 0px; left: 0px;
right: 0px;
top: 0px; top: 0px;
content: ''; content: '';
width: 100%;
height: 1px; height: 1px;
background-color: #94527e; background-color: #94527e;
z-index: 999; z-index: 999;
@ -205,9 +205,9 @@
&::after { &::after {
position: absolute; position: absolute;
left: 0px; left: 0px;
right: 0px;
bottom: 0px; bottom: 0px;
content: ''; content: '';
width: 100%;
height: 1px; height: 1px;
background-color: #5e3d5a; background-color: #5e3d5a;
z-index: 999; z-index: 999;

View File

@ -77,6 +77,7 @@ export interface IDockviewReactProps {
minimumHeightWithinViewport?: number; minimumHeightWithinViewport?: number;
minimumWidthWithinViewport?: number; minimumWidthWithinViewport?: number;
}; };
isRtl?: boolean;
debug?: boolean; debug?: boolean;
defaultRenderer?: DockviewPanelRenderer; defaultRenderer?: DockviewPanelRenderer;
} }
@ -179,6 +180,7 @@ export const DockviewReact = React.forwardRef(
disableFloatingGroups: props.disableFloatingGroups, disableFloatingGroups: props.disableFloatingGroups,
floatingGroupBounds: props.floatingGroupBounds, floatingGroupBounds: props.floatingGroupBounds,
defaultRenderer: props.defaultRenderer, defaultRenderer: props.defaultRenderer,
isRtl: props.isRtl,
debug: props.debug, debug: props.debug,
}); });

View File

@ -28,6 +28,7 @@ export interface IGridviewReactProps {
className?: string; className?: string;
proportionalLayout?: boolean; proportionalLayout?: boolean;
disableAutoResizing?: boolean; disableAutoResizing?: boolean;
isRtl?: boolean;
} }
export const GridviewReact = React.forwardRef( export const GridviewReact = React.forwardRef(
@ -69,6 +70,7 @@ export const GridviewReact = React.forwardRef(
styles: props.hideBorders styles: props.hideBorders
? { separatorBorder: 'transparent' } ? { separatorBorder: 'transparent' }
: undefined, : undefined,
isRtl: props.isRtl,
}); });
const { clientWidth, clientHeight } = domRef.current; const { clientWidth, clientHeight } = domRef.current;

View File

@ -27,6 +27,7 @@ export interface IPaneviewReactProps {
components: PanelCollection<IPaneviewPanelProps>; components: PanelCollection<IPaneviewPanelProps>;
headerComponents?: PanelCollection<IPaneviewPanelProps>; headerComponents?: PanelCollection<IPaneviewPanelProps>;
className?: string; className?: string;
isRtl?: boolean;
disableAutoResizing?: boolean; disableAutoResizing?: boolean;
disableDnd?: boolean; disableDnd?: boolean;
showDndOverlay?: (event: PaneviewDndOverlayEvent) => boolean; showDndOverlay?: (event: PaneviewDndOverlayEvent) => boolean;
@ -68,6 +69,7 @@ export const PaneviewReact = React.forwardRef(
}, },
}, },
showDndOverlay: props.showDndOverlay, showDndOverlay: props.showDndOverlay,
isRtl: props.isRtl,
}); });
const api = new PaneviewApi(paneview); const api = new PaneviewApi(paneview);

View File

@ -27,6 +27,7 @@ export interface ISplitviewReactProps {
proportionalLayout?: boolean; proportionalLayout?: boolean;
hideBorders?: boolean; hideBorders?: boolean;
className?: string; className?: string;
isRtl?: boolean;
disableAutoResizing?: boolean; disableAutoResizing?: boolean;
} }
@ -62,6 +63,7 @@ export const SplitviewReact = React.forwardRef(
styles: props.hideBorders styles: props.hideBorders
? { separatorBorder: 'transparent' } ? { separatorBorder: 'transparent' }
: undefined, : undefined,
isRtl: props.isRtl,
}); });
const { clientWidth, clientHeight } = domRef.current!; const { clientWidth, clientHeight } = domRef.current!;

View File

@ -890,3 +890,34 @@ If you wish to interact with the drop event from one dockview instance in anothe
### Window-like mananger with tabs ### Window-like mananger with tabs
<DockviewNative2 /> <DockviewNative2 />
## RTL support
You can set the dockview to RTL using the `isRtl` property.
<MultiFrameworkContainer
sandboxId="simple-dockview"
react={SimpleDockview}
isRtl={true}
/>
<MultiFrameworkContainer
sandboxId="watermark-dockview"
react={DockviewWatermark}
isRtl={true}
/>
<MultiFrameworkContainer
height={600}
sandboxId="floatinggroup-dockview"
react={DockviewFloating}
isRtl={true}
/>
<MultiFrameworkContainer
sandboxId="groupcontrol-dockview"
react={DockviewGroupControl}
isRtl={true}
/>
<DockviewNative2 isRtl={true} />

View File

@ -27,7 +27,7 @@ Gridview serves a purpose when you want only the nested splitviews with no tabs
## GridviewReact Component ## GridviewReact Component
```tsx ```tsx
import { ReactGridview } from 'dockview'; import { GridviewReact } from 'dockview';
``` ```
<DocRef declaration="IGridviewReactProps" /> <DocRef declaration="IGridviewReactProps" />
@ -233,3 +233,14 @@ You can find more details on theming <Link to="../theme">here</Link>.
react={EditorGridview} react={EditorGridview}
hideThemePicker={true} hideThemePicker={true}
/> />
## RTL support
You can set the gridview to RTL using the `isRtl` property.
<MultiFrameworkContainer
height={600}
sandboxId="simple-gridview"
react={SimpleGridview}
isRtl={true}
/>

View File

@ -93,10 +93,10 @@ SimplePaneview = () => {
## PaneviewReact Component ## PaneviewReact Component
You can create a Paneview through the use of the `ReactPaneview` component. You can create a Paneview through the use of the `PaneviewReact` component.
```tsx ```tsx
import { ReactPaneview } from 'dockview'; import { PaneviewReact } from 'dockview';
``` ```
<DocRef declaration="IPaneviewReactProps" /> <DocRef declaration="IPaneviewReactProps" />
@ -167,7 +167,7 @@ const onReady = (event: PaneviewReadyEvent) => {
}; };
``` ```
This header must be defined in the collection of components provided to the `headerComponents` props for `ReactPaneivew` This header must be defined in the collection of components provided to the `headerComponents` props for `PaneviewReact`
```tsx ```tsx
import { IPaneviewPanelProps } from 'dockview'; import { IPaneviewPanelProps } from 'dockview';
@ -226,3 +226,9 @@ If you wish to interact with the drop event from one paneview instance in anothe
As an example see how dragging a header from one control to another will only trigger an interactable event for the developer if the checkbox is enabled. As an example see how dragging a header from one control to another will only trigger an interactable event for the developer if the checkbox is enabled.
<SideBySidePaneview /> <SideBySidePaneview />
## RTL support
You can set the paneview to RTL using the `isRtl` property.
<MultiFrameworkContainer sandboxId="simple-paneview" react={SimplePaneview} isRtl={true} />

View File

@ -194,3 +194,18 @@ api.setConstraints({
minimumSize: 400, minimumSize: 400,
}); });
``` ```
## RTL support
You can set the splitview to RTL using the `isRtl` property.
<div
style={{
height: '100px',
backgroundColor: 'rgb(30,30,30)',
color: 'white',
margin: '20px 0px',
}}
>
<SimpleSplitview isRtl={true} />
</div>

View File

@ -127,7 +127,7 @@ const useLocalStorage = <T,>(
]; ];
}; };
export const DockviewPersistance = (props: { theme?: string }) => { export const DockviewPersistance = (props: { isRtl?: boolean; theme?: string; }) => {
const [api, setApi] = React.useState<DockviewApi>(); const [api, setApi] = React.useState<DockviewApi>();
const [layout, setLayout] = const [layout, setLayout] =
useLocalStorage<SerializedDockview>('floating.layout'); useLocalStorage<SerializedDockview>('floating.layout');
@ -235,6 +235,7 @@ export const DockviewPersistance = (props: { theme?: string }) => {
rightHeaderActionsComponent={RightComponent} rightHeaderActionsComponent={RightComponent}
disableFloatingGroups={disableFloatingGroups} disableFloatingGroups={disableFloatingGroups}
floatingGroupBounds={options} floatingGroupBounds={options}
isRtl={props.isRtl}
className={`${props.theme || 'dockview-theme-abyss'}`} className={`${props.theme || 'dockview-theme-abyss'}`}
/> />
</div> </div>

View File

@ -55,7 +55,7 @@ const LeftHeaderActions = (props: IDockviewHeaderActionsProps) => {
); );
}; };
const DockviewGroupControl = (props: { theme: string }) => { const DockviewGroupControl = (props: { isRtl?: boolean; theme: string; }) => {
const onReady = (event: DockviewReadyEvent) => { const onReady = (event: DockviewReadyEvent) => {
const panel1 = event.api.addPanel({ const panel1 = event.api.addPanel({
id: 'panel_1', id: 'panel_1',
@ -97,6 +97,7 @@ const DockviewGroupControl = (props: { theme: string }) => {
components={components} components={components}
leftHeaderActionsComponent={LeftHeaderActions} leftHeaderActionsComponent={LeftHeaderActions}
rightHeaderActionsComponent={RightHeaderActions} rightHeaderActionsComponent={RightHeaderActions}
isRtl={props.isRtl}
className={`${props.theme || 'dockview-theme-abyss'}`} className={`${props.theme || 'dockview-theme-abyss'}`}
/> />
); );

View File

@ -25,7 +25,7 @@ const components = {
); );
}, },
isolatedApp: ( isolatedApp: (
props: IDockviewPanelProps<{ title: string; x?: number }> props: IDockviewPanelProps<{ title: string; isRtl?: boolean; x?: number }>
) => { ) => {
const onReady = (event: DockviewReadyEvent) => { const onReady = (event: DockviewReadyEvent) => {
const panel1 = event.api.addPanel({ const panel1 = event.api.addPanel({
@ -55,6 +55,7 @@ const components = {
onReady={onReady} onReady={onReady}
components={components} components={components}
tabComponents={tabComponents} tabComponents={tabComponents}
isRtl={props.params.isRtl}
className="dockview-theme-abyss" className="dockview-theme-abyss"
/> />
); );
@ -82,7 +83,7 @@ const tabComponents = {
}, },
}; };
const DockviewNative2 = (props: { theme?: string }) => { const DockviewNative2 = (props: { isRtl?: boolean; theme?: string; }) => {
const onReady = (event: DockviewReadyEvent) => { const onReady = (event: DockviewReadyEvent) => {
const panel1 = event.api.addPanel({ const panel1 = event.api.addPanel({
id: 'panel_1', id: 'panel_1',
@ -90,6 +91,7 @@ const DockviewNative2 = (props: { theme?: string }) => {
tabComponent: 'default', tabComponent: 'default',
params: { params: {
title: 'Window 1', title: 'Window 1',
isRtl: props.isRtl,
}, },
}); });
panel1.group.locked = true; panel1.group.locked = true;
@ -100,6 +102,7 @@ const DockviewNative2 = (props: { theme?: string }) => {
tabComponent: 'default', tabComponent: 'default',
params: { params: {
title: 'Window 2', title: 'Window 2',
isRtl: props.isRtl,
}, },
position: { position: {
direction: 'right', direction: 'right',
@ -113,6 +116,7 @@ const DockviewNative2 = (props: { theme?: string }) => {
tabComponent: 'default', tabComponent: 'default',
params: { params: {
title: 'Window 3', title: 'Window 3',
isRtl: props.isRtl,
}, },
position: { position: {
direction: 'below', direction: 'below',
@ -133,6 +137,7 @@ const DockviewNative2 = (props: { theme?: string }) => {
onReady={onReady} onReady={onReady}
components={components} components={components}
tabComponents={tabComponents} tabComponents={tabComponents}
isRtl={props.isRtl}
className={`${props.theme || 'dockview-theme-abyss'}`} className={`${props.theme || 'dockview-theme-abyss'}`}
singleTabMode="fullwidth" singleTabMode="fullwidth"
/> />

View File

@ -15,7 +15,7 @@ const components = {
}, },
}; };
export const App: React.FC = (props: { theme?: string }) => { export const App: React.FC = (props: { isRtl?: boolean; theme?: string; }) => {
const onReady = (event: DockviewReadyEvent) => { const onReady = (event: DockviewReadyEvent) => {
const panel = event.api.addPanel({ const panel = event.api.addPanel({
id: 'panel_1', id: 'panel_1',
@ -87,6 +87,7 @@ export const App: React.FC = (props: { theme?: string }) => {
return ( return (
<DockviewReact <DockviewReact
components={components} components={components}
isRtl={props.isRtl}
onReady={onReady} onReady={onReady}
className={props.theme || 'dockview-theme-abyss'} className={props.theme || 'dockview-theme-abyss'}
/> />

View File

@ -18,7 +18,7 @@ const components = {
}, },
}; };
export const App: React.FC = (props: { theme?: string }) => { export const App: React.FC = (props: { isRtl?: boolean; theme?: string; }) => {
const [api, setApi] = React.useState<GridviewApi>(); const [api, setApi] = React.useState<GridviewApi>();
const onReady = (event: GridviewReadyEvent) => { const onReady = (event: GridviewReadyEvent) => {
@ -135,6 +135,7 @@ export const App: React.FC = (props: { theme?: string }) => {
onReady={onReady} onReady={onReady}
// proportionalLayout={false} // proportionalLayout={false}
orientation={Orientation.VERTICAL} orientation={Orientation.VERTICAL}
isRtl={props.isRtl}
className={props.theme || 'dockview-theme-abyss'} className={props.theme || 'dockview-theme-abyss'}
/> />
</div> </div>

View File

@ -61,7 +61,7 @@ const headerComponents = {
myHeaderComponent: MyHeaderComponent, myHeaderComponent: MyHeaderComponent,
}; };
export const App: React.FC = (props: { theme?: string }) => { export const App: React.FC = (props: { isRtl?: boolean; theme?: string; }) => {
const onReady = (event: PaneviewReadyEvent) => { const onReady = (event: PaneviewReadyEvent) => {
event.api.addPanel({ event.api.addPanel({
id: 'panel_1', id: 'panel_1',
@ -96,6 +96,7 @@ export const App: React.FC = (props: { theme?: string }) => {
components={components} components={components}
headerComponents={headerComponents} headerComponents={headerComponents}
onReady={onReady} onReady={onReady}
isRtl={props.isRtl}
className={props.theme || 'dockview-theme-abyss'} className={props.theme || 'dockview-theme-abyss'}
/> />
); );

View File

@ -81,7 +81,7 @@ const Watermark = (props: IWatermarkPanelProps) => {
); );
}; };
const DockviewWatermark = (props: { theme?: string }) => { const DockviewWatermark = (props: { isRtl?: boolean; theme?: string; }) => {
const [api, setApi] = React.useState<DockviewApi>(); const [api, setApi] = React.useState<DockviewApi>();
const onReady = (event: DockviewReadyEvent) => { const onReady = (event: DockviewReadyEvent) => {
@ -126,6 +126,7 @@ const DockviewWatermark = (props: { theme?: string }) => {
onReady={onReady} onReady={onReady}
components={components} components={components}
watermarkComponent={Watermark} watermarkComponent={Watermark}
isRtl={props.isRtl}
className={`${props.theme || 'dockview-theme-abyss'}`} className={`${props.theme || 'dockview-theme-abyss'}`}
/> />
</div> </div>

View File

@ -12,7 +12,7 @@ const components = {
}, },
}; };
export const SimpleSplitview = (props: { proportional?: boolean }) => { export const SimpleSplitview = (props: { isRtl?: boolean; proportional?: boolean }) => {
const onReady = (event: SplitviewReadyEvent) => { const onReady = (event: SplitviewReadyEvent) => {
event.api.addPanel({ event.api.addPanel({
id: 'panel_1', id: 'panel_1',
@ -48,6 +48,7 @@ export const SimpleSplitview = (props: { proportional?: boolean }) => {
proportionalLayout={props.proportional} proportionalLayout={props.proportional}
onReady={onReady} onReady={onReady}
orientation={Orientation.HORIZONTAL} orientation={Orientation.HORIZONTAL}
isRtl={props.isRtl}
className="dockview-theme-abyss" className="dockview-theme-abyss"
/> />
); );

View File

@ -149,6 +149,7 @@ export const MultiFrameworkContainer2 = (props: {
typescript?: (parent: HTMLElement) => { dispose: () => void }; typescript?: (parent: HTMLElement) => { dispose: () => void };
sandboxId: string; sandboxId: string;
height?: number; height?: number;
isRtl?: boolean;
hideThemePicker?: boolean; hideThemePicker?: boolean;
}) => { }) => {
const ref = React.useRef<HTMLDivElement>(null); const ref = React.useRef<HTMLDivElement>(null);
@ -220,7 +221,7 @@ export const MultiFrameworkContainer2 = (props: {
<Spinner /> <Spinner />
</div> </div>
)} )}
{framework === 'React' && <props.react theme={theme} />} {framework === 'React' && <props.react isRtl={props.isRtl} theme={theme} />}
</div> </div>
<div <div
style={{ style={{
@ -281,6 +282,7 @@ export const MultiFrameworkContainer = (props: {
typescript?: (parent: HTMLElement) => { dispose: () => void }; typescript?: (parent: HTMLElement) => { dispose: () => void };
sandboxId: string; sandboxId: string;
height?: number; height?: number;
isRtl?: boolean;
hideThemePicker?: boolean; hideThemePicker?: boolean;
}) => { }) => {
return ( return (

View File

@ -2074,6 +2074,11 @@
"name": "watermarkComponent", "name": "watermarkComponent",
"signature": "FunctionComponent<IWatermarkPanelProps>", "signature": "FunctionComponent<IWatermarkPanelProps>",
"type": "property" "type": "property"
},
{
"name": "isRtl",
"signature": "boolean",
"type": "property"
} }
], ],
"IGridviewReactProps": [ "IGridviewReactProps": [
@ -2111,6 +2116,11 @@
"name": "proportionalLayout", "name": "proportionalLayout",
"signature": "boolean", "signature": "boolean",
"type": "property" "type": "property"
},
{
"name": "isRtl",
"signature": "boolean",
"type": "property"
} }
], ],
"IPaneviewReactProps": [ "IPaneviewReactProps": [
@ -2153,6 +2163,11 @@
"name": "onDidDrop", "name": "onDidDrop",
"signature": "(event: PaneviewDropEvent): void", "signature": "(event: PaneviewDropEvent): void",
"type": "method" "type": "method"
},
{
"name": "isRtl",
"signature": "boolean",
"type": "property"
} }
], ],
"ISplitviewReactProps": [ "ISplitviewReactProps": [
@ -2192,4 +2207,4 @@
"type": "property" "type": "property"
} }
] ]
} }