From 0bf9c63700a55e3668d7b5335636953e85ace59e Mon Sep 17 00:00:00 2001 From: NaNgets Date: Thu, 28 Dec 2023 21:27:03 +0200 Subject: [PATCH] RTL support. --- .../dockview-core/src/dnd/droptarget.scss | 8 ++--- packages/dockview-core/src/dnd/overlay.scss | 4 +-- .../components/greadyRenderContainer.ts | 6 ++-- .../components/titlebar/tabsContainer.scss | 9 +++++- .../components/titlebar/tabsContainer.ts | 12 +++---- .../src/dockview/dockviewComponent.scss | 10 +++++- .../src/dockview/dockviewComponent.ts | 19 ++++++++---- .../dockview/dockviewFloatingGroupPanel.ts | 4 +-- .../dockview-core/src/dockview/options.ts | 1 + packages/dockview-core/src/dom.ts | 20 ++++++++++-- .../src/gridview/baseComponentGridview.ts | 5 +++ .../dockview-core/src/gridview/gridview.scss | 8 +++++ .../dockview-core/src/gridview/gridview.ts | 7 +++-- .../src/gridview/gridviewComponent.ts | 13 +++++--- .../dockview-core/src/gridview/options.ts | 1 + .../src/paneview/draggablePaneviewPanel.ts | 23 +++++++++++--- .../dockview-core/src/paneview/options.ts | 1 + .../dockview-core/src/paneview/paneview.scss | 18 +++++++++-- .../dockview-core/src/paneview/paneview.ts | 8 +++-- .../src/paneview/paneviewComponent.ts | 3 ++ .../src/splitview/splitview.scss | 17 +++++++++- .../dockview-core/src/splitview/splitview.ts | 26 +++++++++++----- .../src/splitview/splitviewComponent.ts | 1 + packages/dockview-core/src/theme.scss | 4 +-- packages/dockview/src/dockview/dockview.tsx | 2 ++ packages/dockview/src/gridview/gridview.tsx | 2 ++ packages/dockview/src/paneview/paneview.tsx | 2 ++ packages/dockview/src/splitview/splitview.tsx | 2 ++ packages/docs/docs/components/dockview.mdx | 31 +++++++++++++++++++ packages/docs/docs/components/gridview.mdx | 13 +++++++- packages/docs/docs/components/paneview.mdx | 12 +++++-- packages/docs/docs/components/splitview.mdx | 15 +++++++++ .../floatinggroup-dockview/src/app.tsx | 3 +- .../headeractions-dockview/src/app.tsx | 3 +- .../sandboxes/nativeapp-dockview/src/app.tsx | 9 ++++-- .../sandboxes/simple-dockview/src/app.tsx | 3 +- .../sandboxes/simple-gridview/src/app.tsx | 3 +- .../sandboxes/simple-paneview/src/app.tsx | 3 +- .../sandboxes/watermark-dockview/src/app.tsx | 3 +- .../docs/src/components/simpleSplitview.tsx | 3 +- packages/docs/src/components/ui/container.tsx | 4 ++- packages/docs/src/generated/api.output.json | 17 +++++++++- 42 files changed, 289 insertions(+), 69 deletions(-) diff --git a/packages/dockview-core/src/dnd/droptarget.scss b/packages/dockview-core/src/dnd/droptarget.scss index 2767c2c24..8d26ed3b3 100644 --- a/packages/dockview-core/src/dnd/droptarget.scss +++ b/packages/dockview-core/src/dnd/droptarget.scss @@ -3,10 +3,10 @@ > .drop-target-dropzone { position: absolute; - left: 0px; top: 0px; + left: 0px; + right: 0px; height: 100%; - width: 100%; z-index: 1000; pointer-events: none; @@ -17,8 +17,8 @@ width: 100%; background-color: var(--dv-drag-over-background-color); transition: top 70ms ease-out, left 70ms ease-out, - width 70ms ease-out, height 70ms ease-out, - opacity 0.15s ease-out; + right 70ms ease-out, width 70ms ease-out, + height 70ms ease-out, opacity 0.15s ease-out; will-change: transform; pointer-events: none; diff --git a/packages/dockview-core/src/dnd/overlay.scss b/packages/dockview-core/src/dnd/overlay.scss index 5f95b379a..8cc2d344d 100644 --- a/packages/dockview-core/src/dnd/overlay.scss +++ b/packages/dockview-core/src/dnd/overlay.scss @@ -42,8 +42,8 @@ .dv-resize-handle-top { height: 4px; - width: calc(100% - 8px); left: 4px; + right: 4px; top: -2px; z-index: 999; position: absolute; @@ -52,8 +52,8 @@ .dv-resize-handle-bottom { height: 4px; - width: calc(100% - 8px); left: 4px; + right: 4px; bottom: -2px; z-index: 999; position: absolute; diff --git a/packages/dockview-core/src/dockview/components/greadyRenderContainer.ts b/packages/dockview-core/src/dockview/components/greadyRenderContainer.ts index 387e6af75..b7371c1ca 100644 --- a/packages/dockview-core/src/dockview/components/greadyRenderContainer.ts +++ b/packages/dockview-core/src/dockview/components/greadyRenderContainer.ts @@ -1,6 +1,6 @@ import { DragAndDropObserver } from '../../dnd/dnd'; import { Droptarget } from '../../dnd/droptarget'; -import { getDomNodePagePosition, toggleClass } from '../../dom'; +import { getDomNodePagePosition, hasClassInTree, toggleClass } from '../../dom'; import { CompositeDisposable, Disposable, IDisposable } from '../../lifecycle'; import { IDockviewPanel } from '../dockviewPanel'; @@ -77,7 +77,9 @@ export class GreadyRenderContainer extends CompositeDisposable { // TODO propagate position to avoid getDomNodePagePosition calls const box = getDomNodePagePosition(referenceContainer.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.width = `${box.width}px`; focusContainer.style.height = `${box.height}px`; diff --git a/packages/dockview-core/src/dockview/components/titlebar/tabsContainer.scss b/packages/dockview-core/src/dockview/components/titlebar/tabsContainer.scss index 4a3b57f74..f077fdc05 100644 --- a/packages/dockview-core/src/dockview/components/titlebar/tabsContainer.scss +++ b/packages/dockview-core/src/dockview/components/titlebar/tabsContainer.scss @@ -63,12 +63,19 @@ content: ' '; position: absolute; top: 0; - left: 0; z-index: 5; pointer-events: none; background-color: var(--dv-tab-divider-color); width: 1px; height: 100%; + + .dv-ltr & { + left: 0; + } + + .dv-rtl & { + right: 0; + } } } } diff --git a/packages/dockview-core/src/dockview/components/titlebar/tabsContainer.ts b/packages/dockview-core/src/dockview/components/titlebar/tabsContainer.ts index 77a3f7182..9c077bbaa 100644 --- a/packages/dockview-core/src/dockview/components/titlebar/tabsContainer.ts +++ b/packages/dockview-core/src/dockview/components/titlebar/tabsContainer.ts @@ -251,15 +251,15 @@ export class TabsContainer ) { event.preventDefault(); - const { top, left } = + const { top, left, right } = this.element.getBoundingClientRect(); - const { top: rootTop, left: rootLeft } = + const { top: rootTop, left: rootLeft, right: rootRight } = this.accessor.element.getBoundingClientRect(); this.accessor.addFloatingGroup( this.group, { - x: left - rootLeft + 20, + x: (this.accessor.options.isRtl ? (right - rootRight) : (left - rootLeft)) + 20, y: top - rootTop + 20, }, { inDragMode: true } @@ -361,14 +361,14 @@ export class TabsContainer const panel = this.accessor.getGroupPanel(tab.panel.id); - const { top, left } = tab.element.getBoundingClientRect(); - const { top: rootTop, left: rootLeft } = + const { top, left, right } = tab.element.getBoundingClientRect(); + const { top: rootTop, left: rootLeft, right: rootRight } = this.accessor.element.getBoundingClientRect(); this.accessor.addFloatingGroup( panel as DockviewPanel, { - x: left - rootLeft, + x: (this.accessor.options.isRtl ? (right - rootRight) : (left - rootLeft)) + 20, y: top - rootTop, }, { inDragMode: true } diff --git a/packages/dockview-core/src/dockview/dockviewComponent.scss b/packages/dockview-core/src/dockview/dockviewComponent.scss index 3bcfb7a57..ab7afb9b3 100644 --- a/packages/dockview-core/src/dockview/dockviewComponent.scss +++ b/packages/dockview-core/src/dockview/dockviewComponent.scss @@ -2,12 +2,20 @@ position: relative; background-color: var(--dv-group-view-background-color); + &.dv-ltr { + direction: ltr; + } + + &.dv-rtl { + direction: rtl; + } + .dv-watermark-container { position: absolute; top: 0px; left: 0px; + right: 0px; height: 100%; - width: 100%; z-index: 1; } diff --git a/packages/dockview-core/src/dockview/dockviewComponent.ts b/packages/dockview-core/src/dockview/dockviewComponent.ts index 41325fb3c..559069aeb 100644 --- a/packages/dockview-core/src/dockview/dockviewComponent.ts +++ b/packages/dockview-core/src/dockview/dockviewComponent.ts @@ -316,6 +316,7 @@ export class DockviewComponent styles: options.styles, parentElement: options.parentElement, disableAutoResizing: options.disableAutoResizing, + isRtl: options.isRtl, }); const gready = document.createElement('div'); @@ -1007,7 +1008,8 @@ export class DockviewComponent const relativeLocation = getRelativeLocation( this.gridview.orientation, location, - target + target, + this.options.isRtl ); const group = this.createGroupAtLocation(relativeLocation); panel = this.createPanel(options, group); @@ -1155,7 +1157,8 @@ export class DockviewComponent const relativeLocation = getRelativeLocation( this.gridview.orientation, location, - target + target, + this.options.isRtl ); this.doAddGroup(group, relativeLocation); return group; @@ -1270,7 +1273,8 @@ export class DockviewComponent const targetLocation = getRelativeLocation( this.gridview.orientation, referenceLocation, - destinationTarget + destinationTarget, + this.options.isRtl ); if (sourceGroup && sourceGroup.size < 2) { @@ -1310,7 +1314,8 @@ export class DockviewComponent const location = getRelativeLocation( this.gridview.orientation, updatedReferenceLocation, - destinationTarget + destinationTarget, + this.options.isRtl ); this.doAddGroup(targetGroup, location); } else { @@ -1325,7 +1330,8 @@ export class DockviewComponent const dropLocation = getRelativeLocation( this.gridview.orientation, referenceLocation, - destinationTarget + destinationTarget, + this.options.isRtl ); const group = this.createGroupAtLocation(dropLocation); @@ -1374,7 +1380,8 @@ export class DockviewComponent const dropLocation = getRelativeLocation( this.gridview.orientation, referenceLocation, - target + target, + this.options.isRtl ); this.gridview.addView( diff --git a/packages/dockview-core/src/dockview/dockviewFloatingGroupPanel.ts b/packages/dockview-core/src/dockview/dockviewFloatingGroupPanel.ts index 65d768c94..6b2bc667d 100644 --- a/packages/dockview-core/src/dockview/dockviewFloatingGroupPanel.ts +++ b/packages/dockview-core/src/dockview/dockviewFloatingGroupPanel.ts @@ -7,7 +7,7 @@ export interface IDockviewFloatingGroupPanel { position( bounds: Partial<{ top: number; - left: number; + side: number; height: number; width: number; }> @@ -27,7 +27,7 @@ export class DockviewFloatingGroupPanel position( bounds: Partial<{ top: number; - left: number; + side: number; height: number; width: number; }> diff --git a/packages/dockview-core/src/dockview/options.ts b/packages/dockview-core/src/dockview/options.ts index 97587f1a6..08f15a695 100644 --- a/packages/dockview-core/src/dockview/options.ts +++ b/packages/dockview-core/src/dockview/options.ts @@ -98,6 +98,7 @@ export interface DockviewComponentOptions extends DockviewRenderFunctions { minimumWidthWithinViewport?: number; }; defaultRenderer?: DockviewPanelRenderer; + isRtl?: boolean; debug?: boolean; } diff --git a/packages/dockview-core/src/dom.ts b/packages/dockview-core/src/dom.ts index 12cac06e8..3bcf5d911 100644 --- a/packages/dockview-core/src/dom.ts +++ b/packages/dockview-core/src/dom.ts @@ -186,15 +186,29 @@ export function quasiDefaultPrevented(event: Event): boolean { 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): { - left: number; + left?: number; + right?: number; top: number; width: 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 { - left: left + window.scrollX, + left: isRtl ? undefined : left + window.scrollX, + right: isRtl ? right + window.scrollX : undefined, top: top + window.scrollY, width: width, height: height, diff --git a/packages/dockview-core/src/gridview/baseComponentGridview.ts b/packages/dockview-core/src/gridview/baseComponentGridview.ts index 2e9ff31c6..c57ae2138 100644 --- a/packages/dockview-core/src/gridview/baseComponentGridview.ts +++ b/packages/dockview-core/src/gridview/baseComponentGridview.ts @@ -7,6 +7,7 @@ import { ISplitviewStyles, Orientation, Sizing } from '../splitview/splitview'; import { IPanel } from '../panel/types'; import { MovementOptions2 } from '../dockview/options'; import { Resizable } from '../resizable'; +import { toggleClass } from '../dom'; const nextLayoutId = sequentialNumberGenerator(); @@ -34,6 +35,7 @@ export interface BaseGridOptions { readonly styles?: ISplitviewStyles; readonly parentElement?: HTMLElement; readonly disableAutoResizing?: boolean; + readonly isRtl?: boolean; } export interface IGridPanelView extends IGridView, IPanel { @@ -137,6 +139,9 @@ export abstract class BaseGrid 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.layout(0, 0, true); // set some elements height/widths diff --git a/packages/dockview-core/src/gridview/gridview.scss b/packages/dockview-core/src/gridview/gridview.scss index 3cc2708b7..05e23121a 100644 --- a/packages/dockview-core/src/gridview/gridview.scss +++ b/packages/dockview-core/src/gridview/gridview.scss @@ -2,4 +2,12 @@ .branch-node { height: 100%; width: 100%; + + &.dv-ltr { + direction: ltr; + } + + &.dv-rtl { + direction: rtl; + } } diff --git a/packages/dockview-core/src/gridview/gridview.ts b/packages/dockview-core/src/gridview/gridview.ts index 477286f20..36e64022f 100644 --- a/packages/dockview-core/src/gridview/gridview.ts +++ b/packages/dockview-core/src/gridview/gridview.ts @@ -123,7 +123,8 @@ export function getGridLocation(element: HTMLElement): number[] { export function getRelativeLocation( rootOrientation: Orientation, location: number[], - direction: Position + direction: Position, + isRtl?: boolean ): number[] { const orientation = getLocationOrientation(rootOrientation, location); const directionOrientation = getDirectionOrientation(direction); @@ -132,13 +133,13 @@ export function getRelativeLocation( const [rest, _index] = tail(location); let index = _index; - if (direction === 'right' || direction === 'bottom') { + if ((isRtl ? direction === 'left' : direction === 'right') || direction === 'bottom') { index += 1; } return [...rest, index]; } else { - const index = direction === 'right' || direction === 'bottom' ? 1 : 0; + const index = (isRtl ? direction === 'left' : direction === 'right') || direction === 'bottom' ? 1 : 0; return [...location, index]; } } diff --git a/packages/dockview-core/src/gridview/gridviewComponent.ts b/packages/dockview-core/src/gridview/gridviewComponent.ts index d1e4a09b5..1616c05fb 100644 --- a/packages/dockview-core/src/gridview/gridviewComponent.ts +++ b/packages/dockview-core/src/gridview/gridviewComponent.ts @@ -110,6 +110,7 @@ export class GridviewComponent orientation: options.orientation, styles: options.styles, disableAutoResizing: options.disableAutoResizing, + isRtl: options.isRtl, }); this._options = options; @@ -299,7 +300,8 @@ export class GridviewComponent relativeLocation = getRelativeLocation( this.gridview.orientation, location, - target + target, + this.options.isRtl ); } @@ -330,7 +332,8 @@ export class GridviewComponent relativeLocation = getRelativeLocation( this.gridview.orientation, location, - target + target, + this.options.isRtl ); } } @@ -406,7 +409,8 @@ export class GridviewComponent const targetLocation = getRelativeLocation( this.gridview.orientation, referenceLocation, - target + target, + this.options.isRtl ); const [targetParentLocation, to] = tail(targetLocation); @@ -435,7 +439,8 @@ export class GridviewComponent const location = getRelativeLocation( this.gridview.orientation, updatedReferenceLocation, - target + target, + this.options.isRtl ); this.doAddGroup(targetGroup, location); } diff --git a/packages/dockview-core/src/gridview/options.ts b/packages/dockview-core/src/gridview/options.ts index be37ce4d2..d447ac499 100644 --- a/packages/dockview-core/src/gridview/options.ts +++ b/packages/dockview-core/src/gridview/options.ts @@ -17,5 +17,6 @@ export interface GridviewComponentOptions { }; frameworkComponentFactory?: FrameworkFactory; styles?: ISplitviewStyles; + isRtl?: boolean; parentElement?: HTMLElement; } diff --git a/packages/dockview-core/src/paneview/draggablePaneviewPanel.ts b/packages/dockview-core/src/paneview/draggablePaneviewPanel.ts index b86c06d33..1fda6ec67 100644 --- a/packages/dockview-core/src/paneview/draggablePaneviewPanel.ts +++ b/packages/dockview-core/src/paneview/draggablePaneviewPanel.ts @@ -146,14 +146,29 @@ export abstract class DraggablePaneviewPanel extends PaneviewPanel { const fromIndex = allPanels.indexOf(existingPanel); let toIndex = containerApi.panels.indexOf(this); + console.log('onDrop', this.accessor.options.isRtl, fromIndex, toIndex, event.position); + 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 (fromIndex > toIndex) { - toIndex++; + if (this.accessor.options.isRtl) { + 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); diff --git a/packages/dockview-core/src/paneview/options.ts b/packages/dockview-core/src/paneview/options.ts index 3523528d1..378d5af41 100644 --- a/packages/dockview-core/src/paneview/options.ts +++ b/packages/dockview-core/src/paneview/options.ts @@ -26,4 +26,5 @@ export interface PaneviewComponentOptions { disableDnd?: boolean; showDndOverlay?: (event: PaneviewDndOverlayEvent) => boolean; parentElement?: HTMLElement; + isRtl?: boolean; } diff --git a/packages/dockview-core/src/paneview/paneview.scss b/packages/dockview-core/src/paneview/paneview.scss index 72a789916..0747f40ef 100644 --- a/packages/dockview-core/src/paneview/paneview.scss +++ b/packages/dockview-core/src/paneview/paneview.scss @@ -2,6 +2,18 @@ height: 100%; width: 100%; + &.dv-ltr { + direction: ltr; + } + + &.dv-rtl { + direction: rtl; + + .view .default-header .dockview-pane-header-icon { + transform: scale(-1, 1); + } + } + &.animated { .view { transition-duration: 0.15s; @@ -38,8 +50,8 @@ } > span { - padding-left: 8px; flex-grow: 1; + padding-inline-start: 8px; } } } @@ -70,7 +82,7 @@ position: absolute; top: 0; left: 0; - width: 100%; + right: 0; height: 100%; z-index: 5; content: ''; @@ -96,7 +108,7 @@ position: absolute; top: 0; left: 0; - width: 100%; + right: 0; height: 100%; z-index: 5; content: ''; diff --git a/packages/dockview-core/src/paneview/paneview.ts b/packages/dockview-core/src/paneview/paneview.ts index 3bebc07f5..e33f0f811 100644 --- a/packages/dockview-core/src/paneview/paneview.ts +++ b/packages/dockview-core/src/paneview/paneview.ts @@ -6,7 +6,7 @@ import { } from '../splitview/splitview'; import { CompositeDisposable, IDisposable } from '../lifecycle'; import { Emitter, Event } from '../events'; -import { addClasses, removeClasses } from '../dom'; +import { addClasses, removeClasses, toggleClass } from '../dom'; import { PaneviewPanel } from './paneviewPanel'; interface PaneItem { @@ -54,7 +54,7 @@ export class Paneview extends CompositeDisposable implements IDisposable { constructor( container: HTMLElement, - options: { orientation: Orientation; descriptor?: ISplitViewDescriptor } + options: { orientation: Orientation; isRtl?: boolean; descriptor?: ISplitViewDescriptor } ) { super(); @@ -63,12 +63,16 @@ export class Paneview extends CompositeDisposable implements IDisposable { this.element = document.createElement('div'); 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); this.splitview = new Splitview(this.element, { orientation: this._orientation, proportionalLayout: false, descriptor: options.descriptor, + isRtl: options.isRtl, }); // if we've added views from the descriptor we need to diff --git a/packages/dockview-core/src/paneview/paneviewComponent.ts b/packages/dockview-core/src/paneview/paneviewComponent.ts index 087f28d27..c39ffc1c2 100644 --- a/packages/dockview-core/src/paneview/paneviewComponent.ts +++ b/packages/dockview-core/src/paneview/paneviewComponent.ts @@ -24,6 +24,7 @@ import { sequentialNumberGenerator } from '../math'; import { PaneTransfer } from '../dnd/dataTransfer'; import { Resizable } from '../resizable'; import { Parameters } from '../panel/types'; +import { toggleClass } from '../dom'; const nextLayoutId = sequentialNumberGenerator(); @@ -221,6 +222,7 @@ export class PaneviewComponent extends Resizable implements IPaneviewComponent { this.paneview = new Paneview(this.element, { // only allow paneview in the vertical orientation for now orientation: Orientation.VERTICAL, + isRtl: options.isRtl, }); this.addDisposables(this._disposable); @@ -369,6 +371,7 @@ export class PaneviewComponent extends Resizable implements IPaneviewComponent { this.paneview = new Paneview(this.element, { orientation: Orientation.VERTICAL, + isRtl: this.options.isRtl, descriptor: { size, views: views.map((view) => { diff --git a/packages/dockview-core/src/splitview/splitview.scss b/packages/dockview-core/src/splitview/splitview.scss index d03908434..f988adc9a 100644 --- a/packages/dockview-core/src/splitview/splitview.scss +++ b/packages/dockview-core/src/splitview/splitview.scss @@ -25,6 +25,22 @@ height: 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 { .view, .sash { @@ -145,7 +161,6 @@ content: ' '; position: absolute; top: 0; - left: 0; z-index: 5; pointer-events: none; background-color: var(--dv-separator-border); diff --git a/packages/dockview-core/src/splitview/splitview.ts b/packages/dockview-core/src/splitview/splitview.ts index 0d56cce24..60db49619 100644 --- a/packages/dockview-core/src/splitview/splitview.ts +++ b/packages/dockview-core/src/splitview/splitview.ts @@ -8,6 +8,7 @@ import { addClasses, toggleClass, getElementsByTagName, + hasClassInTree, } from '../dom'; import { Event, Emitter } from '../events'; import { pushToStart, pushToEnd, firstIndex } from '../array'; @@ -36,6 +37,7 @@ export interface SplitViewOptions { readonly descriptor?: ISplitViewDescriptor; readonly proportionalLayout?: boolean; readonly styles?: ISplitviewStyles; + readonly isRtl?: boolean; } export enum LayoutPriority { @@ -201,7 +203,7 @@ export class Splitview { options: SplitViewOptions ) { this._orientation = options.orientation; - this.element = this.createContainer(); + this.element = this.createContainer(options.isRtl); this.proportionalLayout = options.proportionalLayout === undefined @@ -756,6 +758,7 @@ export class Splitview { private layoutViews(): void { this.contentSize = this.viewItems.reduce((r, i) => r + i.size, 0); + const isRtl = hasClassInTree(this.element, 'dv-rtl'); let sum = 0; const x: number[] = []; @@ -768,18 +771,21 @@ export class Splitview { const offset = Math.min(Math.max(0, sum - 2), this.size - 4); 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`; } 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.viewItems.forEach((view, i) => { if (this._orientation === Orientation.HORIZONTAL) { 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.height = ''; } @@ -788,6 +794,7 @@ export class Splitview { view.container.style.top = i == 0 ? '0px' : `${x[i - 1]}px`; view.container.style.width = ''; view.container.style.left = ''; + view.container.style.right = ''; } view.view.layout(view.size, this._orthogonalSize); @@ -918,8 +925,10 @@ export class Splitview { return 0; } - const upIndexes = range(index, -1); - const downIndexes = range(index + 1, this.viewItems.length); + const isHorizontal = this._orientation === Orientation.HORIZONTAL; + 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) { for (const i of highPriorityIndexes) { @@ -955,7 +964,6 @@ export class Splitview { ? Number.POSITIVE_INFINITY : downIndexes.reduce( (_, i) => _ + sizes[i] - this.viewItems[i].minimumSize, - 0 ); const minDeltaDown = @@ -1044,13 +1052,15 @@ export class Splitview { return element; } - private createContainer(): HTMLElement { + private createContainer(isRtl?: boolean): HTMLElement { const element = document.createElement('div'); const orientationClassname = this._orientation === Orientation.HORIZONTAL ? 'horizontal' : 'vertical'; element.className = `split-view-container ${orientationClassname}`; + toggleClass(element, 'dv-rtl', isRtl === true); + toggleClass(element, 'dv-ltr', isRtl === false); return element; } diff --git a/packages/dockview-core/src/splitview/splitviewComponent.ts b/packages/dockview-core/src/splitview/splitviewComponent.ts index 22e091127..7710f7f02 100644 --- a/packages/dockview-core/src/splitview/splitviewComponent.ts +++ b/packages/dockview-core/src/splitview/splitviewComponent.ts @@ -349,6 +349,7 @@ export class SplitviewComponent this.splitview = new Splitview(this.element, { orientation, proportionalLayout: this.options.proportionalLayout, + isRtl: this.options.isRtl, descriptor: { size, views: views.map((view) => { diff --git a/packages/dockview-core/src/theme.scss b/packages/dockview-core/src/theme.scss index 6a10a1a35..f3fa2d6c1 100644 --- a/packages/dockview-core/src/theme.scss +++ b/packages/dockview-core/src/theme.scss @@ -185,9 +185,9 @@ &::after { position: absolute; left: 0px; + right: 0px; top: 0px; content: ''; - width: 100%; height: 1px; background-color: #94527e; z-index: 999; @@ -205,9 +205,9 @@ &::after { position: absolute; left: 0px; + right: 0px; bottom: 0px; content: ''; - width: 100%; height: 1px; background-color: #5e3d5a; z-index: 999; diff --git a/packages/dockview/src/dockview/dockview.tsx b/packages/dockview/src/dockview/dockview.tsx index 3a7c2ac35..791a1bffc 100644 --- a/packages/dockview/src/dockview/dockview.tsx +++ b/packages/dockview/src/dockview/dockview.tsx @@ -77,6 +77,7 @@ export interface IDockviewReactProps { minimumHeightWithinViewport?: number; minimumWidthWithinViewport?: number; }; + isRtl?: boolean; debug?: boolean; defaultRenderer?: DockviewPanelRenderer; } @@ -179,6 +180,7 @@ export const DockviewReact = React.forwardRef( disableFloatingGroups: props.disableFloatingGroups, floatingGroupBounds: props.floatingGroupBounds, defaultRenderer: props.defaultRenderer, + isRtl: props.isRtl, debug: props.debug, }); diff --git a/packages/dockview/src/gridview/gridview.tsx b/packages/dockview/src/gridview/gridview.tsx index fd6374c12..222a3a095 100644 --- a/packages/dockview/src/gridview/gridview.tsx +++ b/packages/dockview/src/gridview/gridview.tsx @@ -28,6 +28,7 @@ export interface IGridviewReactProps { className?: string; proportionalLayout?: boolean; disableAutoResizing?: boolean; + isRtl?: boolean; } export const GridviewReact = React.forwardRef( @@ -69,6 +70,7 @@ export const GridviewReact = React.forwardRef( styles: props.hideBorders ? { separatorBorder: 'transparent' } : undefined, + isRtl: props.isRtl, }); const { clientWidth, clientHeight } = domRef.current; diff --git a/packages/dockview/src/paneview/paneview.tsx b/packages/dockview/src/paneview/paneview.tsx index 001bcfc3a..6cf8d15a2 100644 --- a/packages/dockview/src/paneview/paneview.tsx +++ b/packages/dockview/src/paneview/paneview.tsx @@ -27,6 +27,7 @@ export interface IPaneviewReactProps { components: PanelCollection; headerComponents?: PanelCollection; className?: string; + isRtl?: boolean; disableAutoResizing?: boolean; disableDnd?: boolean; showDndOverlay?: (event: PaneviewDndOverlayEvent) => boolean; @@ -68,6 +69,7 @@ export const PaneviewReact = React.forwardRef( }, }, showDndOverlay: props.showDndOverlay, + isRtl: props.isRtl, }); const api = new PaneviewApi(paneview); diff --git a/packages/dockview/src/splitview/splitview.tsx b/packages/dockview/src/splitview/splitview.tsx index 218721632..c167598f9 100644 --- a/packages/dockview/src/splitview/splitview.tsx +++ b/packages/dockview/src/splitview/splitview.tsx @@ -27,6 +27,7 @@ export interface ISplitviewReactProps { proportionalLayout?: boolean; hideBorders?: boolean; className?: string; + isRtl?: boolean; disableAutoResizing?: boolean; } @@ -62,6 +63,7 @@ export const SplitviewReact = React.forwardRef( styles: props.hideBorders ? { separatorBorder: 'transparent' } : undefined, + isRtl: props.isRtl, }); const { clientWidth, clientHeight } = domRef.current!; diff --git a/packages/docs/docs/components/dockview.mdx b/packages/docs/docs/components/dockview.mdx index b6e599341..242399d5f 100644 --- a/packages/docs/docs/components/dockview.mdx +++ b/packages/docs/docs/components/dockview.mdx @@ -890,3 +890,34 @@ If you wish to interact with the drop event from one dockview instance in anothe ### Window-like mananger with tabs + +## RTL support + +You can set the dockview to RTL using the `isRtl` property. + + + + + + + + + + diff --git a/packages/docs/docs/components/gridview.mdx b/packages/docs/docs/components/gridview.mdx index 5d61ed21a..6f77f5c4a 100644 --- a/packages/docs/docs/components/gridview.mdx +++ b/packages/docs/docs/components/gridview.mdx @@ -27,7 +27,7 @@ Gridview serves a purpose when you want only the nested splitviews with no tabs ## GridviewReact Component ```tsx -import { ReactGridview } from 'dockview'; +import { GridviewReact } from 'dockview'; ``` @@ -233,3 +233,14 @@ You can find more details on theming here. react={EditorGridview} hideThemePicker={true} /> + +## RTL support + +You can set the gridview to RTL using the `isRtl` property. + + diff --git a/packages/docs/docs/components/paneview.mdx b/packages/docs/docs/components/paneview.mdx index 95e025478..5f3b31f98 100644 --- a/packages/docs/docs/components/paneview.mdx +++ b/packages/docs/docs/components/paneview.mdx @@ -93,10 +93,10 @@ SimplePaneview = () => { ## 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 -import { ReactPaneview } from 'dockview'; +import { PaneviewReact } from 'dockview'; ``` @@ -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 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. + +## RTL support + +You can set the paneview to RTL using the `isRtl` property. + + diff --git a/packages/docs/docs/components/splitview.mdx b/packages/docs/docs/components/splitview.mdx index ede28b404..923dcaeb5 100644 --- a/packages/docs/docs/components/splitview.mdx +++ b/packages/docs/docs/components/splitview.mdx @@ -194,3 +194,18 @@ api.setConstraints({ minimumSize: 400, }); ``` + +## RTL support + +You can set the splitview to RTL using the `isRtl` property. + +
+ +
diff --git a/packages/docs/sandboxes/floatinggroup-dockview/src/app.tsx b/packages/docs/sandboxes/floatinggroup-dockview/src/app.tsx index 778fe290e..2ffe77005 100644 --- a/packages/docs/sandboxes/floatinggroup-dockview/src/app.tsx +++ b/packages/docs/sandboxes/floatinggroup-dockview/src/app.tsx @@ -127,7 +127,7 @@ const useLocalStorage = ( ]; }; -export const DockviewPersistance = (props: { theme?: string }) => { +export const DockviewPersistance = (props: { isRtl?: boolean; theme?: string; }) => { const [api, setApi] = React.useState(); const [layout, setLayout] = useLocalStorage('floating.layout'); @@ -235,6 +235,7 @@ export const DockviewPersistance = (props: { theme?: string }) => { rightHeaderActionsComponent={RightComponent} disableFloatingGroups={disableFloatingGroups} floatingGroupBounds={options} + isRtl={props.isRtl} className={`${props.theme || 'dockview-theme-abyss'}`} /> diff --git a/packages/docs/sandboxes/headeractions-dockview/src/app.tsx b/packages/docs/sandboxes/headeractions-dockview/src/app.tsx index 611346503..8c1261bf3 100644 --- a/packages/docs/sandboxes/headeractions-dockview/src/app.tsx +++ b/packages/docs/sandboxes/headeractions-dockview/src/app.tsx @@ -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 panel1 = event.api.addPanel({ id: 'panel_1', @@ -97,6 +97,7 @@ const DockviewGroupControl = (props: { theme: string }) => { components={components} leftHeaderActionsComponent={LeftHeaderActions} rightHeaderActionsComponent={RightHeaderActions} + isRtl={props.isRtl} className={`${props.theme || 'dockview-theme-abyss'}`} /> ); diff --git a/packages/docs/sandboxes/nativeapp-dockview/src/app.tsx b/packages/docs/sandboxes/nativeapp-dockview/src/app.tsx index a7c19f3a6..deea137f7 100644 --- a/packages/docs/sandboxes/nativeapp-dockview/src/app.tsx +++ b/packages/docs/sandboxes/nativeapp-dockview/src/app.tsx @@ -25,7 +25,7 @@ const components = { ); }, isolatedApp: ( - props: IDockviewPanelProps<{ title: string; x?: number }> + props: IDockviewPanelProps<{ title: string; isRtl?: boolean; x?: number }> ) => { const onReady = (event: DockviewReadyEvent) => { const panel1 = event.api.addPanel({ @@ -55,6 +55,7 @@ const components = { onReady={onReady} components={components} tabComponents={tabComponents} + isRtl={props.params.isRtl} 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 panel1 = event.api.addPanel({ id: 'panel_1', @@ -90,6 +91,7 @@ const DockviewNative2 = (props: { theme?: string }) => { tabComponent: 'default', params: { title: 'Window 1', + isRtl: props.isRtl, }, }); panel1.group.locked = true; @@ -100,6 +102,7 @@ const DockviewNative2 = (props: { theme?: string }) => { tabComponent: 'default', params: { title: 'Window 2', + isRtl: props.isRtl, }, position: { direction: 'right', @@ -113,6 +116,7 @@ const DockviewNative2 = (props: { theme?: string }) => { tabComponent: 'default', params: { title: 'Window 3', + isRtl: props.isRtl, }, position: { direction: 'below', @@ -133,6 +137,7 @@ const DockviewNative2 = (props: { theme?: string }) => { onReady={onReady} components={components} tabComponents={tabComponents} + isRtl={props.isRtl} className={`${props.theme || 'dockview-theme-abyss'}`} singleTabMode="fullwidth" /> diff --git a/packages/docs/sandboxes/simple-dockview/src/app.tsx b/packages/docs/sandboxes/simple-dockview/src/app.tsx index 258e0a114..7d8eba9a5 100644 --- a/packages/docs/sandboxes/simple-dockview/src/app.tsx +++ b/packages/docs/sandboxes/simple-dockview/src/app.tsx @@ -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 panel = event.api.addPanel({ id: 'panel_1', @@ -87,6 +87,7 @@ export const App: React.FC = (props: { theme?: string }) => { return ( diff --git a/packages/docs/sandboxes/simple-gridview/src/app.tsx b/packages/docs/sandboxes/simple-gridview/src/app.tsx index 5b78eb186..acdfacd36 100644 --- a/packages/docs/sandboxes/simple-gridview/src/app.tsx +++ b/packages/docs/sandboxes/simple-gridview/src/app.tsx @@ -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(); const onReady = (event: GridviewReadyEvent) => { @@ -135,6 +135,7 @@ export const App: React.FC = (props: { theme?: string }) => { onReady={onReady} // proportionalLayout={false} orientation={Orientation.VERTICAL} + isRtl={props.isRtl} className={props.theme || 'dockview-theme-abyss'} /> diff --git a/packages/docs/sandboxes/simple-paneview/src/app.tsx b/packages/docs/sandboxes/simple-paneview/src/app.tsx index 5f982595e..474ad3e36 100644 --- a/packages/docs/sandboxes/simple-paneview/src/app.tsx +++ b/packages/docs/sandboxes/simple-paneview/src/app.tsx @@ -61,7 +61,7 @@ const headerComponents = { myHeaderComponent: MyHeaderComponent, }; -export const App: React.FC = (props: { theme?: string }) => { +export const App: React.FC = (props: { isRtl?: boolean; theme?: string; }) => { const onReady = (event: PaneviewReadyEvent) => { event.api.addPanel({ id: 'panel_1', @@ -96,6 +96,7 @@ export const App: React.FC = (props: { theme?: string }) => { components={components} headerComponents={headerComponents} onReady={onReady} + isRtl={props.isRtl} className={props.theme || 'dockview-theme-abyss'} /> ); diff --git a/packages/docs/sandboxes/watermark-dockview/src/app.tsx b/packages/docs/sandboxes/watermark-dockview/src/app.tsx index 5380fc356..e0159150b 100644 --- a/packages/docs/sandboxes/watermark-dockview/src/app.tsx +++ b/packages/docs/sandboxes/watermark-dockview/src/app.tsx @@ -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(); const onReady = (event: DockviewReadyEvent) => { @@ -126,6 +126,7 @@ const DockviewWatermark = (props: { theme?: string }) => { onReady={onReady} components={components} watermarkComponent={Watermark} + isRtl={props.isRtl} className={`${props.theme || 'dockview-theme-abyss'}`} /> diff --git a/packages/docs/src/components/simpleSplitview.tsx b/packages/docs/src/components/simpleSplitview.tsx index 4551cf442..ad7249d47 100644 --- a/packages/docs/src/components/simpleSplitview.tsx +++ b/packages/docs/src/components/simpleSplitview.tsx @@ -12,7 +12,7 @@ const components = { }, }; -export const SimpleSplitview = (props: { proportional?: boolean }) => { +export const SimpleSplitview = (props: { isRtl?: boolean; proportional?: boolean }) => { const onReady = (event: SplitviewReadyEvent) => { event.api.addPanel({ id: 'panel_1', @@ -48,6 +48,7 @@ export const SimpleSplitview = (props: { proportional?: boolean }) => { proportionalLayout={props.proportional} onReady={onReady} orientation={Orientation.HORIZONTAL} + isRtl={props.isRtl} className="dockview-theme-abyss" /> ); diff --git a/packages/docs/src/components/ui/container.tsx b/packages/docs/src/components/ui/container.tsx index d1a153444..cd760dc3f 100644 --- a/packages/docs/src/components/ui/container.tsx +++ b/packages/docs/src/components/ui/container.tsx @@ -149,6 +149,7 @@ export const MultiFrameworkContainer2 = (props: { typescript?: (parent: HTMLElement) => { dispose: () => void }; sandboxId: string; height?: number; + isRtl?: boolean; hideThemePicker?: boolean; }) => { const ref = React.useRef(null); @@ -220,7 +221,7 @@ export const MultiFrameworkContainer2 = (props: { )} - {framework === 'React' && } + {framework === 'React' && }
{ dispose: () => void }; sandboxId: string; height?: number; + isRtl?: boolean; hideThemePicker?: boolean; }) => { return ( diff --git a/packages/docs/src/generated/api.output.json b/packages/docs/src/generated/api.output.json index 4c3fdca77..2c59139ec 100644 --- a/packages/docs/src/generated/api.output.json +++ b/packages/docs/src/generated/api.output.json @@ -2074,6 +2074,11 @@ "name": "watermarkComponent", "signature": "FunctionComponent", "type": "property" + }, + { + "name": "isRtl", + "signature": "boolean", + "type": "property" } ], "IGridviewReactProps": [ @@ -2111,6 +2116,11 @@ "name": "proportionalLayout", "signature": "boolean", "type": "property" + }, + { + "name": "isRtl", + "signature": "boolean", + "type": "property" } ], "IPaneviewReactProps": [ @@ -2153,6 +2163,11 @@ "name": "onDidDrop", "signature": "(event: PaneviewDropEvent): void", "type": "method" + }, + { + "name": "isRtl", + "signature": "boolean", + "type": "property" } ], "ISplitviewReactProps": [ @@ -2192,4 +2207,4 @@ "type": "property" } ] -} \ No newline at end of file +}