From 9ccc7add41c054ed02799184b0009c71c80d4ce2 Mon Sep 17 00:00:00 2001 From: mathuo <6710312+mathuo@users.noreply.github.com> Date: Fri, 19 Jan 2024 15:16:14 +0000 Subject: [PATCH 01/20] feat: drop target classnames --- .../dockview-core/src/dnd/droptarget.scss | 16 +++++++------- packages/dockview-core/src/dnd/droptarget.ts | 21 +++++++++++++------ 2 files changed, 23 insertions(+), 14 deletions(-) diff --git a/packages/dockview-core/src/dnd/droptarget.scss b/packages/dockview-core/src/dnd/droptarget.scss index 9baf55508..d03cf502a 100644 --- a/packages/dockview-core/src/dnd/droptarget.scss +++ b/packages/dockview-core/src/dnd/droptarget.scss @@ -22,26 +22,26 @@ will-change: transform; pointer-events: none; - &.dv-overlay-top { - &.dv-overlay-small-vertical { + &.dv-droptarget-top { + &.dv-droptarget-small-vertical { border-top: 1px solid var(--dv-drag-over-border-color); } } - &.dv-overlay-bottom { - &.dv-overlay-small-vertical { + &.dv-droptarget-bottom { + &.dv-droptarget-small-vertical { border-bottom: 1px solid var(--dv-drag-over-border-color); } } - &.dv-overlay-left { - &.dv-overlay-small-horizontal { + &.dv-droptarget-left { + &.dv-droptarget-small-horizontal { border-left: 1px solid var(--dv-drag-over-border-color); } } - &.dv-overlay-right { - &.dv-overlay-small-horizontal { + &.dv-droptarget-right { + &.dv-droptarget-small-horizontal { border-right: 1px solid var(--dv-drag-over-border-color); } } diff --git a/packages/dockview-core/src/dnd/droptarget.ts b/packages/dockview-core/src/dnd/droptarget.ts index c9c638f82..79be0c3a7 100644 --- a/packages/dockview-core/src/dnd/droptarget.ts +++ b/packages/dockview-core/src/dnd/droptarget.ts @@ -269,16 +269,25 @@ export class Droptarget extends CompositeDisposable { this.overlayElement.style.transform = transform; - toggleClass(this.overlayElement, 'dv-overlay-small-vertical', isSmallY); toggleClass( this.overlayElement, - 'dv-overlay-small-horizontal', + 'dv-droptarget-small-vertical', + isSmallY + ); + toggleClass( + this.overlayElement, + 'dv-droptarget-small-horizontal', isSmallX ); - toggleClass(this.overlayElement, 'dv-overlay-left', isLeft); - toggleClass(this.overlayElement, 'dv-overlay-right', isRight); - toggleClass(this.overlayElement, 'dv-overlay-top', isTop); - toggleClass(this.overlayElement, 'dv-overlay-bottom', isBottom); + toggleClass(this.overlayElement, 'dv-droptarget-left', isLeft); + toggleClass(this.overlayElement, 'dv-droptarget-right', isRight); + toggleClass(this.overlayElement, 'dv-droptarget-top', isTop); + toggleClass(this.overlayElement, 'dv-droptarget-bottom', isBottom); + toggleClass( + this.overlayElement, + 'dv-droptarget-center', + quadrant === 'center' + ); } private setState(quadrant: Position): void { From 16e09712758c3a4d17f4ffd2bb1361f547d24a41 Mon Sep 17 00:00:00 2001 From: mathuo <6710312+mathuo@users.noreply.github.com> Date: Sat, 20 Jan 2024 10:02:18 +0000 Subject: [PATCH 02/20] feat: rename class --- packages/dockview-core/src/dnd/droptarget.scss | 16 ++++++++-------- packages/dockview-core/src/dnd/droptarget.ts | 14 +++++++------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/packages/dockview-core/src/dnd/droptarget.scss b/packages/dockview-core/src/dnd/droptarget.scss index d03cf502a..181ecbdab 100644 --- a/packages/dockview-core/src/dnd/droptarget.scss +++ b/packages/dockview-core/src/dnd/droptarget.scss @@ -22,26 +22,26 @@ will-change: transform; pointer-events: none; - &.dv-droptarget-top { - &.dv-droptarget-small-vertical { + &.dv-drop-target-top { + &.dv-drop-target-small-vertical { border-top: 1px solid var(--dv-drag-over-border-color); } } - &.dv-droptarget-bottom { - &.dv-droptarget-small-vertical { + &.dv-drop-target-bottom { + &.dv-drop-target-small-vertical { border-bottom: 1px solid var(--dv-drag-over-border-color); } } - &.dv-droptarget-left { - &.dv-droptarget-small-horizontal { + &.dv-drop-target-left { + &.dv-drop-target-small-horizontal { border-left: 1px solid var(--dv-drag-over-border-color); } } - &.dv-droptarget-right { - &.dv-droptarget-small-horizontal { + &.dv-drop-target-right { + &.dv-drop-target-small-horizontal { border-right: 1px solid var(--dv-drag-over-border-color); } } diff --git a/packages/dockview-core/src/dnd/droptarget.ts b/packages/dockview-core/src/dnd/droptarget.ts index 79be0c3a7..67cc9c78b 100644 --- a/packages/dockview-core/src/dnd/droptarget.ts +++ b/packages/dockview-core/src/dnd/droptarget.ts @@ -271,21 +271,21 @@ export class Droptarget extends CompositeDisposable { toggleClass( this.overlayElement, - 'dv-droptarget-small-vertical', + 'dv-drop-target-small-vertical', isSmallY ); toggleClass( this.overlayElement, - 'dv-droptarget-small-horizontal', + 'dv-drop-target-small-horizontal', isSmallX ); - toggleClass(this.overlayElement, 'dv-droptarget-left', isLeft); - toggleClass(this.overlayElement, 'dv-droptarget-right', isRight); - toggleClass(this.overlayElement, 'dv-droptarget-top', isTop); - toggleClass(this.overlayElement, 'dv-droptarget-bottom', isBottom); + toggleClass(this.overlayElement, 'dv-drop-target-left', isLeft); + toggleClass(this.overlayElement, 'dv-drop-target-right', isRight); + toggleClass(this.overlayElement, 'dv-drop-target-top', isTop); + toggleClass(this.overlayElement, 'dv-drop-target-bottom', isBottom); toggleClass( this.overlayElement, - 'dv-droptarget-center', + 'dv-drop-target-center', quadrant === 'center' ); } From 87b999fe460bca5a07c99c06fee009a66be41f0e Mon Sep 17 00:00:00 2001 From: mathuo <6710312+mathuo@users.noreply.github.com> Date: Fri, 19 Jan 2024 15:10:26 +0000 Subject: [PATCH 03/20] chore: 1.9.1 docs --- .../docs/blog/2024-01-20-dockview-1.9.1.md | 21 + packages/docs/docs/components/dockview.mdx | 12 + .../docs/sandboxes/dnd-dockview/src/app.tsx | 4 + .../popoutgroup-dockview/src/app.tsx | 9 - .../version-1.8.4/components/dockview.mdx | 873 ------------------ .../version-1.8.4/contributing.mdx | 52 -- .../versioned_docs/version-1.8.4/index.mdx | 91 -- .../version-1.9.0/components/_category_.json | 10 - .../version-1.9.0/components/gridview.mdx | 235 ----- .../version-1.9.0/components/paneview.mdx | 228 ----- .../version-1.9.0/components/splitview.mdx | 196 ---- .../versioned_docs/version-1.9.0/theme.mdx | 85 -- .../components/_category_.json | 0 .../components/dockview.mdx | 29 + .../components/gridview.mdx | 0 .../components/paneview.mdx | 0 .../components/splitview.mdx | 0 .../contributing.mdx | 0 .../index.mdx | 0 .../theme.mdx | 0 .../version-1.9.0-sidebars.json | 8 - ...ebars.json => version-1.9.1-sidebars.json} | 0 packages/docs/versions.json | 3 +- 23 files changed, 67 insertions(+), 1789 deletions(-) create mode 100644 packages/docs/blog/2024-01-20-dockview-1.9.1.md delete mode 100644 packages/docs/versioned_docs/version-1.8.4/components/dockview.mdx delete mode 100644 packages/docs/versioned_docs/version-1.8.4/contributing.mdx delete mode 100644 packages/docs/versioned_docs/version-1.8.4/index.mdx delete mode 100644 packages/docs/versioned_docs/version-1.9.0/components/_category_.json delete mode 100644 packages/docs/versioned_docs/version-1.9.0/components/gridview.mdx delete mode 100644 packages/docs/versioned_docs/version-1.9.0/components/paneview.mdx delete mode 100644 packages/docs/versioned_docs/version-1.9.0/components/splitview.mdx delete mode 100644 packages/docs/versioned_docs/version-1.9.0/theme.mdx rename packages/docs/versioned_docs/{version-1.8.4 => version-1.9.1}/components/_category_.json (100%) rename packages/docs/versioned_docs/{version-1.9.0 => version-1.9.1}/components/dockview.mdx (96%) rename packages/docs/versioned_docs/{version-1.8.4 => version-1.9.1}/components/gridview.mdx (100%) rename packages/docs/versioned_docs/{version-1.8.4 => version-1.9.1}/components/paneview.mdx (100%) rename packages/docs/versioned_docs/{version-1.8.4 => version-1.9.1}/components/splitview.mdx (100%) rename packages/docs/versioned_docs/{version-1.9.0 => version-1.9.1}/contributing.mdx (100%) rename packages/docs/versioned_docs/{version-1.9.0 => version-1.9.1}/index.mdx (100%) rename packages/docs/versioned_docs/{version-1.8.4 => version-1.9.1}/theme.mdx (100%) delete mode 100644 packages/docs/versioned_sidebars/version-1.9.0-sidebars.json rename packages/docs/versioned_sidebars/{version-1.8.4-sidebars.json => version-1.9.1-sidebars.json} (100%) diff --git a/packages/docs/blog/2024-01-20-dockview-1.9.1.md b/packages/docs/blog/2024-01-20-dockview-1.9.1.md new file mode 100644 index 000000000..a4a2491ac --- /dev/null +++ b/packages/docs/blog/2024-01-20-dockview-1.9.1.md @@ -0,0 +1,21 @@ +--- +slug: dockview-1.9.1-release +title: Dockview 1.9.1 +tags: [release] +--- + +# Release Notes + +Please reference to docs @ [dockview.dev](https://dockview.dev). + +## 🚀 Features + +- Drop target overlay classnames [#452](https://github.com/mathuo/dockview/issues/452) + +- Expose root drop target configuration options [#431](https://github.com/mathuo/dockview/issues/431) + +## 🛠 Miscs + +- Bug: Floating groups position reset when display:none applied to component [#458](https://github.com/mathuo/dockview/issues/458) + +## 🔥 Breaking changes diff --git a/packages/docs/docs/components/dockview.mdx b/packages/docs/docs/components/dockview.mdx index e31864d21..df42e4e06 100644 --- a/packages/docs/docs/components/dockview.mdx +++ b/packages/docs/docs/components/dockview.mdx @@ -264,6 +264,18 @@ which will be rendered when there are no panels or groups. ## Drag And Drop +You can override the conditions of the far edge overlays through the `rootOverlayModel` prop. + +```tsx + +``` + ### Built-in behaviours Dockview supports a wide variety of built-in Drag and Drop possibilities. diff --git a/packages/docs/sandboxes/dnd-dockview/src/app.tsx b/packages/docs/sandboxes/dnd-dockview/src/app.tsx index b930cd968..b2015b834 100644 --- a/packages/docs/sandboxes/dnd-dockview/src/app.tsx +++ b/packages/docs/sandboxes/dnd-dockview/src/app.tsx @@ -180,6 +180,10 @@ const DndDockview = (props: { renderVisibleOnly: boolean; theme?: string }) => { className={`${props.theme || 'dockview-theme-abyss'}`} onDidDrop={onDidDrop} showDndOverlay={showDndOverlay} + rootOverlayModel={{ + size: { value: 100, type: 'pixels' }, + activationSize: { value: 5, type: 'percentage' }, + }} /> ); diff --git a/packages/docs/sandboxes/popoutgroup-dockview/src/app.tsx b/packages/docs/sandboxes/popoutgroup-dockview/src/app.tsx index 99770d3b9..df732b7c0 100644 --- a/packages/docs/sandboxes/popoutgroup-dockview/src/app.tsx +++ b/packages/docs/sandboxes/popoutgroup-dockview/src/app.tsx @@ -1,6 +1,5 @@ import { DockviewApi, - DockviewGroupPanel, DockviewReact, DockviewReadyEvent, IDockviewHeaderActionsProps, @@ -26,14 +25,6 @@ const components = { }, }; -const counter = (() => { - let i = 0; - - return { - next: () => ++i, - }; -})(); - function loadDefaultLayout(api: DockviewApi) { api.addPanel({ id: 'panel_1', diff --git a/packages/docs/versioned_docs/version-1.8.4/components/dockview.mdx b/packages/docs/versioned_docs/version-1.8.4/components/dockview.mdx deleted file mode 100644 index 6c9528f88..000000000 --- a/packages/docs/versioned_docs/version-1.8.4/components/dockview.mdx +++ /dev/null @@ -1,873 +0,0 @@ ---- -description: Dockview Documentation ---- - -import { MultiFrameworkContainer } from '@site/src/components/ui/container'; - -import Link from '@docusaurus/Link'; -import useBaseUrl from '@docusaurus/useBaseUrl'; - -import DockviewPersistance from '@site/sandboxes/layout-dockview/src/app'; -import SimpleDockview from '@site/sandboxes/simple-dockview/src/app'; -import ResizeDockview from '@site/sandboxes/resize-dockview/src/app'; -import DockviewWatermark from '@site/sandboxes/watermark-dockview/src/app'; -import DockviewConstraints from '@site/sandboxes/constraints-dockview/src/app'; -import DndDockview from '@site/sandboxes/dnd-dockview/src/app'; -import NestedDockview from '@site/sandboxes/nested-dockview/src/app'; -import EventsDockview from '@site/sandboxes/events-dockview/src/app'; -import DockviewGroupControl from '@site/sandboxes/headeractions-dockview/src/app'; -import CustomHeadersDockview from '@site/sandboxes/customheader-dockview/src/app'; -import DockviewNative from '@site/sandboxes/fullwidthtab-dockview/src/app'; -import DockviewNative2 from '@site/sandboxes/nativeapp-dockview/src/app'; -import DockviewSetTitle from '@site/sandboxes/updatetitle-dockview/src/app'; -import RenderingDockview from '@site/sandboxes/rendering-dockview/src/app'; -import DockviewExternalDnd from '@site/sandboxes/externaldnd-dockview/src/app'; -import DockviewResizeContainer from '@site/sandboxes/resizecontainer-dockview/src/app'; -import DockviewTabheight from '@site/sandboxes/tabheight-dockview/src/app'; -import DockviewWithIFrames from '@site/sandboxes/iframe-dockview/src/app'; -import DockviewFloating from '@site/sandboxes/floatinggroup-dockview/src/app'; -import DockviewLockedGroup from '@site/sandboxes/lockedgroup-dockview/src/app'; -import DockviewKeyboard from '@site/sandboxes/keyboard-dockview/src/app'; - -import { DocRef, Markdown } from '@site/src/components/ui/reference/docRef'; - -import { attach as attachDockviewVanilla } from '@site/sandboxes/javascript/vanilla-dockview/src/app'; -import { attach as attachSimpleDockview } from '@site/sandboxes/javascript/simple-dockview/src/app'; -import { attach as attachTabHeightDockview } from '@site/sandboxes/javascript/tabheight-dockview/src/app'; -import { attach as attachNativeDockview } from '@site/sandboxes/javascript/fullwidthtab-dockview/src/app'; - -# Dockview - -## Introduction - -Dockview is an abstraction built on top of [Gridviews](./gridview) where each view is a container of many tabbed panels. - - - -
- -> You can access the panels associated group through the `panel.group` variable. -> The group will always be defined and will change if a panel is moved into another group. - -## DockviewReact Component - -You can create a Dockview through the use of the `DockviewReact` component. - -

- {'All of these are React props available through the '} - DockviewReact - {' component.'} -

- - -## Dockview API - -The Dockview API is exposed both at the `onReady` event and on each panel through `props.containerApi`. -Through this API you can control general features of the component and access all added panels. - -```tsx title="Dockview API via Panel component" -const MyComponent = (props: IDockviewPanelProps<{ title: string }>) => { - // props.containerApi... - - return
{`My first panel has the title: ${props.params.title}`}
; -}; -``` - -```tsx title="Dockview API via the onReady callback" -const onReady = (event: DockviewReadyEvent) => { - // event.api... -}; -``` - -

- {'All of these methods are available through the '} - api - {' property of '} - DockviewComponent - {' and the '} - containerApi - {' property of '} - IDockviewPanel - . -

- - - -## Dockview Panel API - -```tsx -const MyComponent = (props: IDockviewPanelProps<{ title: string }>) => { - // props.api... - - return
{`My first panel has the title: ${props.params.title}`}
; -}; -``` - -

- {'All of these are methods are available through the '} - api - {' property of '} - IDockviewPanel - . -

- - - -## Theme - -As well as importing the `dockview` stylesheet you must provide a class-based theme somewhere in your application. For example. - -```tsx -// Providing a theme directly through the DockviewReact component props - - -// Providing a theme somewhere in the DOM tree -
-
- {/**... */} - -
-
-``` - -You can find more details on theming here. - -## Layout Persistance - -Layouts are loaded and saved via to `fromJSON` and `toJSON` methods on the Dockview api. -The api also exposes an event `onDidLayoutChange` you can listen on to determine when the layout has changed. -Below are some snippets showing how you might load from and save to localStorage. - -```tsx title="Saving the layout state to localStorage" -React.useEffect(() => { - if (!api) { - return; - } - - const disposable = api.onDidLayoutChange(() => { - const layout = api.toJSON(); - - localStorage.setItem( - 'dockview_persistance_layout', - JSON.stringify(layout) - ); - }); - - return () => { - disposable.dispose(); - }; -}, [api]); -``` - -```tsx title="Loading a layout from localStorage" -const onReady = (event: DockviewReadyEvent) => { - const layoutString = localStorage.getItem('dockview_persistance_layout'); - - let success = false; - - if (layoutString) { - try { - const layout = JSON.parse(layoutString); - event.api.fromJSON(layout); - success = true; - } catch (err) { - // - } - } - - if (!success) { - // do something if there is no layout or there was a loading error - } -}; -``` - -Here is an example using the above code loading from and saving to localStorage. -If you refresh the page you should notice your layout is loaded as you left it. - - - -## Resizing - -### Panel Resizing - -Each Dockview contains of a number of groups and each group has a number of panels. -Logically a user may want to resize a panel, but this translates to resizing the group which contains that panel. - -You can set the size of a panel using `props.api.setSize(...)`. -You can also set the size of the group associated with the panel using `props.api.group.api.setSize(...)` although this isn't recommended -due to the clunky syntax. - -```tsx -// it's mandatory to provide either a height or a width, providing both is optional -props.api.setSize({ - height: 100, - width: 200, -}); - -// you could also resize the panels group, although not recommended it achieved the same result -props.api.group.api.setSize({ - height: 100, - width: 200, -}); -``` - -You can see an example invoking both approaches below. - - - -### Container Resizing - -The component will automatically resize to it's container. - - - -## Watermark - -When the dockview is empty you may want to display some fallback content, this is refered to as the `watermark`. -By default there the watermark has no content but you can provide as a prop to `DockviewReact` a `watermarkComponent` -which will be rendered when there are no panels or groups. - - - -## Drag And Drop - -### Built-in behaviours - -Dockview supports a wide variety of built-in Drag and Drop possibilities. -Below are some examples of the operations you can perform. - - - -> Drag a tab onto another tab to place it inbetween existing tabs. - - - -> Drag a tab to the right of the last tab to place it after the existing tabs. - - - -> Drag a group onto an existing group to merge the two groups. - -
- - -
- -> Drag into the left/right/top/bottom target zone of a panel to create a new group in the selected direction. - -> Drag into the center of a panel to add to that group. - -> Drag to the edge of the dockview component to create a new group on the selected edge. - -### Extended behaviours - -For interaction with the Drag events directly the component exposes some method to help determine whether external drag events should be interacted with or not. - -```tsx -/** - * called when an ondrop event which does not originate from the dockview libray and - * passes the showDndOverlay condition occurs - **/ -const onDidDrop = (event: DockviewDropEvent) => { - const { group } = event; - - event.api.addPanel({ - id: 'test', - component: 'default', - position: { - referencePanel: group.activePanel.id, - direction: 'within', - }, - }); -}; - -/** - * called for drag over events which do not originate from the dockview library - * allowing the developer to decide where the overlay should be shown for a - * particular drag event - **/ -const showDndOverlay = (event: DockviewDndOverlayEvent) => { - return true; -}; - -return ( - -); -``` - -### Intercepting Drag Events - -You can intercept drag events to attach your own metadata using the `onWillDragPanel` and `onWillDragGroup` api methods. - - - -### Third Party Dnd Libraries - -This shows a simple example of a third-party library used inside a panel that relies on drag -and drop functionalities. This examples serves to show that `dockview` doesn't interfer with -any drag and drop logic for other controls. - - - -## Floating Groups - -Dockview has built-in support for floating groups. Each floating container can contain a single group with many panels -and you can have as many floating containers as needed. You cannot dock multiple groups together in the same floating container. - -Floating groups can be interacted with whilst holding the `shift` key activating the `event.shiftKey` boolean property on `KeyboardEvent` events. - -> Float an existing tab by holding `shift` whilst interacting with the tab - - - -> Move a floating tab by holding `shift` whilst moving the cursor or dragging the empty -> header space - - - -> Move an entire floating group by holding `shift` whilst dragging the empty header space - - - -Floating groups can be programatically added through the dockview `api` method `api.addFloatingGroup(...)` and you can check whether -a group is floating via the `group.api.isFloating` property. See examples for full code. - -You can control the bounding box of floating groups through the optional `floatingGroupBounds` options: - -- `boundedWithinViewport` will force the entire floating group to be bounded within the docks viewport. -- `{minimumHeightWithinViewport?: number, minimumWidthWithinViewport?: number}` sets the respective dimension minimums that must appears within the docks viewport -- If no options are provided the defaults of `100px` minimum height and width within the viewport are set. - - - -## Panels - -### Add Panel - -Using the dockview API you can access the `addPanel` method which returns an instance of the created panel. -The minimum method signature is: - -```ts -const panel = api.addPanel({ - id: 'my_unique_panel_id', - component: 'my_component', -}); -``` - -where `id` is the unique id of the panel and `component` is the implenentation which -will be used to render the panel. You will have registered this using the `components` prop of the `DockviewReactComponent` component. - -You can optionally provide a `tabComponent` parameters to the `addPanel` method which will render the tab using a custom renderer. -You will have registered this using the `tabComponents` prop of the `DockviewReactComponent` component. - -```ts -const panel = api.addPanel({ - id: 'my_unique_panel_id', - component: 'my_component', - tabComponent: 'my_tab_component', -}); -``` - -You can pass properties to the panel using the `params` key. -You can update these properties through the panels `api` object and its `updateParameters` method. - -```ts -const panel = api.addPanel({ - id: 'my_unique_panel_id', - component: 'my_component', - params: { - myCustomKey: 'my_custom_value', - }, -}); - -panel.api.updateParameters({ - myCustomKey: 'my_custom_value', - myOtherCustomKey: 'my_other_custom_key', -}); -``` - -> Note `updateParameters` does not accept partial parameter updates, you should call it with the entire set of parameters -> you want the panel to receive. - -Finally `addPanel` accepts a `position` object which tells dockview where to place the panel. - -- This object optionally accepts either a `referencePanel` or `referenceGroup` which can be the associated id as a string - or the panel/group object reference. -- This object accepts a `direction` property which dictates where, - relative to the provided reference the new panel will be placed. - -> If neither a `referencePanel` or `referenceGroup` is provided then the `direction` will be treated as absolute. - -> If no `direction` is provided the library will place the new panel in a pre-determined position. - -```ts -const panel = api.addPanel({ - id: 'panel_1', - component: 'default', -}); - -const panel2 = api.addPanel({ - id: 'panel_2', - component: 'default', - position: { - referencePanel: panel1, - direction: 'right', - }, -}); -``` - -To add a floating panel you should include the `floating` variable which can be either a `boolean` or an object defining it's bounds. -These bounds are relative to the dockview component. - -```ts -const panel1 = api.addPanel({ - id: 'panel_2', - component: 'default', - floating: true, -}); - -const panel2 = api.addPanel({ - id: 'panel_2', - component: 'default', - floating: { x: 10, y: 10, width: 300, height: 300 }, -}); -``` - -### Update Panel - -You can programatically update the `params` passed through to the panel through the panal api using `api.updateParameters`. - -```ts -const panel = api.addPanel({ - id: 'panel_1', - component: 'default', - params: { - keyA: 'valueA', - }, -}); - -// ... - -panel.api.updateParameters({ - keyB: 'valueB', -}); - -// ... - -panel.api.updateParameters({ - keyA: 'anotherValueA', -}); -``` - -To delete a parameter you should pass a value of `undefined` for the key. - -```ts -panel.api.updateParameters({ - keyA: undefined, // this will delete 'keyA'. -}); -``` - -### Move panel - -You can programatically move a panel using the panel `api`. - -```ts -panel.api.moveTo({ group, position, index }); -``` - -An equivalent method for moving groups is avaliable on the group `api`. - -```ts -const group = panel.api.group; -group.api.moveTo({ group, position }); -``` - -### Remove panel - -You can programatically remove a panel using the panel `api`. - -```ts -panel.api.close(); -``` - -Given a reference to the panel you can also use the component `api` to remove it. - -```ts -const panel = api.getPanel('myPanel'); -api.removePanel(panel); -``` - -### Panel Rendering - -By default `DockviewReact` only adds to the DOM those panels that are visible, -if a panel is not the active tab and not shown the contents of the hidden panel will be removed from the DOM. - -However the React Components associated with each panel are only created once and will always exist for as long as the panel exists, hidden or not. - -> For example this means that any hooks in those components will run whether the panel is visible or not which may lead to excessive background work depending -> on the panels implementation. - -This is the default behaviour to ensure the greatest flexibility for the user but through the panels `props.api` you can listen to the visiblity state of the panel -and write additional logic to optimize your application. - -For example if you wanted to unmount the React Components when the panel is not visible you could create a Higher-Order-Component that listens to the panels -visiblity state and only renders the panel when visible. - -```tsx title="Only rendering the React Component when the panel is visible, otherwise rendering a null React Component" -import { IDockviewPanelProps } from 'dockview'; -import * as React from 'react'; - -function RenderWhenVisible( - component: React.FunctionComponent -) { - const HigherOrderComponent = (props: IDockviewPanelProps) => { - const [visible, setVisible] = React.useState( - props.api.isVisible - ); - - React.useEffect(() => { - const disposable = props.api.onDidVisibilityChange((event) => - setVisible(event.isVisible) - ); - - return () => { - disposable.dispose(); - }; - }, [props.api]); - - if (!visible) { - return null; - } - - return React.createElement(component, props); - }; - return HigherOrderComponent; -} -``` - -```tsx -const components = { default: RenderWhenVisible(MyComponent) }; -``` - -Toggling the checkbox you can see that when you only render those panels which are visible the underling React component is destroyed when it becomes hidden and re-created when it becomes visible. - - - -## Headers - -### Custom Tab Headers - -You can provide custom renderers for your tab headers for maximum customization. -A default implementation of `DockviewDefaultTab` is provided should you only wish to attach minor -changes and events that do not alter the default behaviour, for example to add a custom context menu event -handler. - -The `DockviewDefaulTab` component accepts a `hideClose` prop if you wish only to hide the close button. - -```tsx title="Attaching a custom context menu event handlers to a custom header" -import { IDockviewPanelHeaderProps, DockviewDefaultTab } from 'dockview'; - -const MyCustomheader = (props: IDockviewPanelHeaderProps) => { - const onContextMenu = (event: React.MouseEvent) => { - event.preventDefault(); - alert('context menu'); - }; - return ; -}; -``` - -You are also free to define a custom renderer entirely from scratch and not make use of the `DockviewDefaultTab` component. -To use a custom renderer you can must register a collection of tab components. - -```tsx -const tabComponents = { - myCustomHeader: MyCustomHeader, -}; - -return ; -``` - -```tsx -api.addPanel({ - id: 'panel_1', - component: 'default', - tabComponent: 'myCustomHeader', // <-- your registered renderers - title: 'Panel 1', -}); -``` - -You can also override the default tab renderer which will be used when no `tabComponent` is provided to the `addPanel` function. - -```tsx -; -``` - -As a simple example the below attaches a custom event handler for the context menu on all tabs as a default tab renderer - -The below example uses a custom tab renderer to reigster a popover when the user right clicked on a tab. -This still makes use of the `DockviewDefaultTab` since it's only a minor change. - - - -### Default Tab Title - -If you are using the default tab renderer you can set the title of a tab when creating it - -```tsx -api.addPanel({ - id: 'panel_1', - component: 'my_component', - title: 'my_custom_title', // <-- special param for title -}); -``` - -You can update the title through the panel api which can be accessed via `props.api` if you are inside the panel -component or via `api.getPanel('panel1').api` if you are accessing from outside of the panel component. - -```tsx -api.setTitle('my_new_custom_title'); -``` - -> Note this only works when using the default tab implementation. - - - -### Custom Tab Title - -If you are using a custom tab implementation you should pass variables through as a parameter and render them -through your tab components implementation. - -```tsx title="Add a panel with custom parameters" -api.addPanel({ - id: 'panel_2', - component: 'my_component', - tabComponent: 'my_tab', - params: { - myTitle: 'Window 2', // <-- passing a variable to use as a title - }, -}); -``` - -```tsx title="Accessing custom parameters from a custom tab renderer" -const tabComponents = { - default: (props: IDockviewPanelHeaderProps<{ myTitle: string }>) => { - const title = props.params.myTitle; // <-- accessing my custom varaible - return
{/** tab implementation as chosen by developer */}
; - }, -}; -``` - -### Hidden Headers - -You may wish to hide the header section of a group. This can achieved through the `hidden` variable on `panel.group.header`. - -```tsx -panel.group.header.hidden = true; -``` - -### Full width tabs - -`DockviewReactComponent` accepts the prop `singleTabMode`. If set `singleTabMode=fullwidth` then when there is only one tab in a group this tab will expand -to the entire width of the group. For example: - -> This can be conmbined with Locked Groups to create an application that feels more like a Window Manager -> rather than a collection of groups and tabs. - -```tsx - -``` - - - -### Tab Height - -Tab height can be controlled through CSS. - - - -## Groups - -### Locked group - -Locking a group will disable all drop events for this group ensuring no additional panels can be added to the group through drop events. -You can still add groups to a locked panel programatically using the API though. - -```tsx -panel.group.locked = true; - -// Or - -panel.group.locked = 'no-drop-target'; -``` - -Use `true` to keep drop zones top, right, bottom, left for the group. Use `no-drop-target` to disable all drop zones. For you to get a -better understanding of what this means, try and drag the panels in the example below to the locked groups. - - - -### Group Controls Panel - -`DockviewReact` accepts `leftHeaderActionsComponent`, `rightHeaderActionsComponent` and `prefixHeaderActionsComponent` which expect a React component with props `IDockviewHeaderActionsProps`. -These controls are rendered to left and right side of the space to the right of the tabs in the header bar as well as before the first tab in the case of the prefix header prop. - -```tsx -const Component: React.FunctionComponent = () => { - return
{'...'}
; -}; - -return ; -``` - -As a simple example the below uses the `groupControlComponent` to render a small control that indicates whether the group -is active and which panel is active in that group. - -```tsx -const RightHeaderActionsComponent = (props: IDockviewHeaderActionsProps) => { - const isGroupActive = props.isGroupActive; - const activePanel = props.activePanel; - - return ( -
- - {isGroupActive ? 'Group Active' : 'Group Inactive'} - - {`activePanel: ${ - activePanel?.id || 'null' - }`} -
- ); -}; -``` - - - -### Constraints - -You may wish to specify a minimum or maximum height or width for a group which can be done through the group api. - -```tsx -api.group.api.setConstraints(...) -``` - -> Constraints are currently only supported for groups and not individual panels. -> If you specific a constraint on a group and move a panel within that group to another group it will no -> longer be subject to those constraints since those constraints were on the group and not on the individual panel. - - - -## iFrames - -iFrames required special attention because of a particular behaviour in how iFrames render: - -> Re-parenting an iFrame will reload the contents of the iFrame or the rephrase this, moving an iFrame within the DOM will cause a reload of its contents. - -You can find many examples of discussions on this. Two reputable forums for example are linked [here](https://bugzilla.mozilla.org/show_bug.cgi?id=254144) and [here](https://github.com/whatwg/html/issues/5484). - -The problem with iFrames and `dockview` is that when you hide or move a panel that panels DOM element may be moved within the DOM or removed from the DOM completely. -If your panel contains an iFrame then that iFrame will reload after being re-positioned within the DOM tree and all state in that iFrame will most likely be lost. - -`dockview` does not provide a built-in solution to this because it's too specific of a problem to include in the library. -However the below example does show an implementation of a higher-order component `HoistedDockviewPanel`that you could use to work around this problems and make iFrames behave in `dockview`. - -What the higher-order component is doing is to hoist the panels contents into a DOM element that is always present and then `position: absolute` that element to match the dimensions of it's linked panel. -The visibility of these hoisted elements is then controlled through some exposed api methods to hide elements that shouldn't be currently shown. - -You should open this example in CodeSandbox using the provided link to understand the code and make use of this implemention if required. - - - -## Events - -A simple example showing events fired by `dockviewz that can be interacted with. - - - -## Keyboard Navigation - -Keyboard shortcuts - - - -### Nested Dockviews - -You can safely create multiple dockview instances within one page and nest dockviews within other dockviews. -If you wish to interact with the drop event from one dockview instance in another dockview instance you can implement the `showDndOverlay` and `onDidDrop` props on `DockviewReact`. - - - -### Window-like mananger with tabs - - diff --git a/packages/docs/versioned_docs/version-1.8.4/contributing.mdx b/packages/docs/versioned_docs/version-1.8.4/contributing.mdx deleted file mode 100644 index e35ed70d4..000000000 --- a/packages/docs/versioned_docs/version-1.8.4/contributing.mdx +++ /dev/null @@ -1,52 +0,0 @@ ---- -sidebar_position: 3 -description: Contributing ---- - -# Contributing - -# Project description - -Dockview is a layout manager library designed to provide a complete layouting solution. -It is written in plain TypeScript and can be used without any framework although -an extensive React wrapper has always and will always be provided for those using the React framework. - -The project is hosted on GitHub and developed within a Monorepo powered by [Lerna](https://github.com/lerna/lerna). -It is developed using the `yarn` package manager since at the time of creation `yarn` was far superior when it came to managing monorepos. -The Monorepo contains three packages: - -#### packages/dockview-core - -The core project is entirely written in plain TypeScript without any frameworks or dependencies and it's source-code can be found -within the `dockview-core` package which is also published to npm. - -#### packages/dockview - -A complete collection of React components for use through the React framework to use dockview seamlessly -and is published to npm. It depends explicitly on `dockview-core` so there is no need to additionally install `dockview-core`. - -> Dockview was originally a React-only library which is why the React version maintains the name `dockview` after -> splitting the core logic into a seperate package named `dockview-core`. - -#### packages/docs - -This package contains the code for this documentation website and examples hosted through **CodeSandbox**. It is **not** a published package on npm. - -# Run the project locally - -1. After you have cloned the project from GitHub run `yarn` at the root of the project which will install all project dependencies. -2. In order build `packages/dockview-core` then `packages/dockview`. -3. Run the docs website through `npm run start` in the `packages/docs` directory and go to _http://localhost:3000_ which - will now be running the local copy of `dockview` that you have just built. - -### Examples - -All examples can be found under [**packages/docs/sandboxes**](https://github.com/mathuo/dockview/tree/master/packages/docs/sandboxes). -Each example is an independently runnable example through **CodeSandbox**. -Through the documentation you will see links to runnable **CodeSandbox** examples. - -## FAQ - -#### Are there any plans to publish wrapper libraries for other frameworks such as Angular and Vue? - -Currently no but this is open for contributors to try. diff --git a/packages/docs/versioned_docs/version-1.8.4/index.mdx b/packages/docs/versioned_docs/version-1.8.4/index.mdx deleted file mode 100644 index 9ea7f0923..000000000 --- a/packages/docs/versioned_docs/version-1.8.4/index.mdx +++ /dev/null @@ -1,91 +0,0 @@ ---- -sidebar_position: 0 -description: A zero dependency layout manager supporting ReactJS and Vanilla TypeScript ---- - -import { SimpleSplitview } from '@site/src/components/simpleSplitview'; -import { SimpleGridview } from '@site/src/components/simpleGridview'; -import { SimplePaneview } from '@site/src/components/simplePaneview'; -import SimpleDockview from '@site/sandboxes/simple-dockview/src/app'; -import Link from '@docusaurus/Link'; - -# Introduction - -**dockview** is a zero dependency layout manager that supports tab, grids and splitviews. - -## Quick start - -`dockview` has a peer dependency on `react >= 16.8.0` and `react-dom >= 16.8.0`. To install `dockview` you can run: - -```shell -npm install dockview -``` - -You must also import the dockview stylesheet found under [`dockview/dict/styles/dockview.css`](https://unpkg.com/browse/dockview@latest/dist/styles/dockview.css), -depending on your solution this might be: - -```css -@import './node_modules/dockview/dist/styles/dockview.css'; -``` - -There are 4 components you may want to use: - - -

Dockview

- - -
- -
- - -

Splitview

- - -
- -
- - -

Gridview

- - -
- -
- - -

Paneview

- - -
- -
diff --git a/packages/docs/versioned_docs/version-1.9.0/components/_category_.json b/packages/docs/versioned_docs/version-1.9.0/components/_category_.json deleted file mode 100644 index 07caf3db9..000000000 --- a/packages/docs/versioned_docs/version-1.9.0/components/_category_.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "label": "Components", - "collapsible": true, - "collapsed": false, - "position": 2, - "link": { - "type": "generated-index", - "title": "Components" - } -} \ No newline at end of file diff --git a/packages/docs/versioned_docs/version-1.9.0/components/gridview.mdx b/packages/docs/versioned_docs/version-1.9.0/components/gridview.mdx deleted file mode 100644 index 5d61ed21a..000000000 --- a/packages/docs/versioned_docs/version-1.9.0/components/gridview.mdx +++ /dev/null @@ -1,235 +0,0 @@ ---- -description: Gridview Documentation ---- - -import { MultiFrameworkContainer } from '@site/src/components/ui/container'; -import SimpleGridview from '@site/sandboxes/simple-gridview/src/app'; -import EditorGridview from '@site/sandboxes/editor-gridview/src/app'; -// import SimpleGridview from '@site/sandboxes/simple-gridview/src/app'; -import { EventsGridview } from '@site/src/components/gridview/events'; -// import IDEExample from '@site/sandboxes/ide-example/src/app'; -import Link from '@docusaurus/Link'; -import { DocRef } from '@site/src/components/ui/reference/docRef'; - -# Gridview - -Gridview is a collection of nested splitviews and is the foundation for the [Dockview](./dockview) component. -Gridview serves a purpose when you want only the nested splitviews with no tabs and no headers. - -## Introduction - - - -## GridviewReact Component - -```tsx -import { ReactGridview } from 'dockview'; -``` - - - -## Gridview API - -```tsx -const MyComponent = (props: IGridviewPanelProps<{ title: string }>) => { - // props.containerApi... - - return
{`My first panel has the title: ${props.params.title}`}
; -}; -``` - -```tsx -const onReady = (event: GridviewReadyEvent) => { - // event.api... -}; -``` - - - -## Gridview Panel API - -```tsx -const MyComponent = (props: IGridviewPanelProps<{ title: string }>) => { - // props.api... - - return
{`My first panel has the title: ${props.params.title}`}
; -}; -``` - - - -## Resizing - -### Panel Resizing - -You can set the size of a panel using `props.api.setSize(...)`. - -```tsx -// it's mandatory to provide either a height or a width, providing both is optional -props.api.setSize({ - height: 100, - width: 200, -}); -``` - -You can update any constraints on the panel. All parameters are optional. - -```tsx -props.api.setConstraints({ - minimumHeight: 100, - maximumHeight: 1000 - minimumWidth: 100, - maximumWidth: 1000 -}); -``` - -You can hide a panel by setting it's visibility to `false`. Hidden panels retain their size -at the point of being hidden, if made visible again they will try to resize to the remembered size. - -```tsx -props.api.setVisible(false); -``` - -## Panels - -### Add Panel - -Using the gridview API you can access the `addPanel` method which returns an instance of the created panel. -The minimum method signature is: - -```ts -const panel = api.addPanel({ - id: 'my_unique_panel_id', - component: 'my_component', -}); -``` - -where `id` is the unique id of the panel and `component` is the implenentation which -will be used to render the panel. You will have registered this using the `components` prop of the `GridviewReactComponent` component. - -You can pass bounding constraints to limit the size of the panel. - -```ts -const panel = api.addPanel({ - id: 'my_unique_panel_id', - component: 'my_component', - minimumHeight: 100, - maximumHeight: 1000, - minimumWidth: 100, - maximumWidth: 1000, -}); -``` - -You can pass a `snap` parameter which will hide the panel when an attempt is made to move it beyond a minimum width or height if one exists. - -```ts -const panel = api.addPanel({ - id: 'my_unique_panel_id', - component: 'my_component', - minimumHeight: 100, - snap: true, -}); -``` - -You can pass a `priority` parameter which will keep the panel a certain priority when being resized. This is useful when you know you want this -panel to always take the first available or last available space. The default is `LayoutPriority.Normal` which defers space allocations to the libraries discression. - -```ts -const panel = api.addPanel({ - id: 'my_unique_panel_id', - component: 'my_component', - minimumHeight: 100, - priority: LayoutPriority.High, -}); -``` - -You can pass properties to the panel using the `params` key. -You can update these properties through the panels `api` object and its `updateParameters` method. - -```ts -const panel = api.addPanel({ - id: 'my_unique_panel_id', - component: 'my_component', - params: { - myCustomKey: 'my_custom_value', - }, -}); -``` - -```tsx -panel.api.updateParameters({ - myCustomKey: 'my_custom_value', - myOtherCustomKey: 'my_other_custom_key', -}); -``` - -> Note `updateParameters` does not accept partial parameter updates, you should call it with the entire set of parameters -> you want the panel to receive. - -Finally `addPanel` accepts a `position` object which tells dockview where to place the panel. - -- This object accepts a `referencePanel` which can be the associated id as a string - or the panel object reference. -- This object accepts a `direction` property which dictates where, - relative to the provided reference the new panel will be placed. - -> If a `referencePanel` is not passed then the `direction` will be treated as absolute. - -> If no `direction` is provided the library will place the new panel in a pre-determined position. - -```ts -const panel = api.addPanel({ - id: 'panel_1', - component: 'default', -}); - -const panel2 = api.addPanel({ - id: 'panel_2', - component: 'default', - position: { - referencePanel: panel1, - direction: 'right', - }, -}); -``` - -> Note `updateParameters` does not accept partial parameter updates, you should call it with the entire set of parameters -> you want the panel to receive. - -## Theme - -As well as importing the `dockview` stylesheet you must provide a class-based theme somewhere in your application. For example. - -```tsx -// Providing a theme directly through the DockviewReact component props - - -// Providing a theme somewhere in the DOM tree -
-
- {/**... */} - -
-
-``` - -You can find more details on theming here. - -## Events - -`GridviewReact` exposes a number of events that the developer can listen to and below is a simple example with a log panel showing those events that occur. - - - -## Complex Example - - diff --git a/packages/docs/versioned_docs/version-1.9.0/components/paneview.mdx b/packages/docs/versioned_docs/version-1.9.0/components/paneview.mdx deleted file mode 100644 index 95e025478..000000000 --- a/packages/docs/versioned_docs/version-1.9.0/components/paneview.mdx +++ /dev/null @@ -1,228 +0,0 @@ ---- -description: Paneview Documentation ---- - -import { MultiFrameworkContainer } from '@site/src/components/ui/container'; -import SimplePaneview from '@site/sandboxes/simple-paneview/src/app'; -import { CustomHeaderPaneview } from '@site/src/components/paneview/customHeader'; -import { DragAndDropPaneview } from '@site/src/components/paneview/dragAndDrop'; -import { SideBySidePaneview } from '@site/src/components/paneview/sideBySide'; -import Link from '@docusaurus/Link'; -import { DocRef } from '@site/src/components/ui/reference/docRef'; - -# Paneview - -A paneview is a collapsed collection of vertically stacked panels and panel headers. -The panel header will always remain visible however the panel will only be visible when the panel is expanded. - -:::info - -Paneview panels can be re-ordered by dragging and dropping the panel headers. - -::: - ---- - -# Introduction - - - -```tsx title="Simple Paneview example" -import { - IPaneviewPanelProps, - PaneviewReact, - PaneviewReadyEvent, -} from 'dockview'; - -const components = { - default: (props: IPaneviewPanelProps<{ title: string }>) => { - return ( -
- {props.params.title} -
- ); - }, -}; - -SimplePaneview = () => { - const onReady = (event: PaneviewReadyEvent) => { - event.api.addPanel({ - id: 'panel_1', - component: 'default', - params: { - title: 'Panel 1', - }, - title: 'Panel 1', - }); - - event.api.addPanel({ - id: 'panel_2', - component: 'default', - params: { - title: 'Panel 2', - }, - title: 'Panel 2', - }); - - event.api.addPanel({ - id: 'panel_3', - component: 'default', - params: { - title: 'Panel 3', - }, - title: 'Panel 3', - }); - }; - - return ( - - ); -}; -``` - -## PaneviewReact Component - -You can create a Paneview through the use of the `ReactPaneview` component. - -```tsx -import { ReactPaneview } from 'dockview'; -``` - - - -## Paneview API - -The Paneview API is exposed both at the `onReady` event and on each panel through `props.containerApi`. -Through this API you can control general features of the component and access all added panels. - -```tsx title="Paneview API via Panel component" -const MyComponent = (props: IGridviewPanelProps<{ title: string }>) => { - // props.containerApi... - - return
{`My first panel has the title: ${props.params.title}`}
; -}; -``` - -```tsx title="Paneview API via the onReady callback" -const onReady = (event: GridviewReadyEvent) => { - // event.api... -}; -``` - - - -## Paneview Panel API - -```tsx -const MyComponent = (props: IGridviewPanelProps<{ title: string }>) => { - // props.api... - - return
{`My first panel has the title: ${props.params.title}`}
; -}; -``` - - - -## Advanced Features - -### Custom Header - -You can provide a custom component to render an alternative header. - -
- -
- -You can provide a `headerComponent` option when creating a panel to tell the library to use a custom header component. - -```tsx -const onReady = (event: PaneviewReadyEvent) => { - event.api.addPanel({ - id: 'panel_1', - component: 'default', - headerComponent: 'myHeaderComponent', - params: { - valueA: 'A', - }, - title: 'Panel 1', - }); -}; -``` - -This header must be defined in the collection of components provided to the `headerComponents` props for `ReactPaneivew` - -```tsx -import { IPaneviewPanelProps } from 'dockview'; - -const MyHeaderComponent = (props: IPaneviewPanelProps<{ title: string }>) => { - const [expanded, setExpanded] = React.useState( - props.api.isExpanded - ); - - React.useEffect(() => { - const disposable = props.api.onDidExpansionChange((event) => { - setExpanded(event.isExpanded); - }); - - return () => { - disposable.dispose(); - }; - }, []); - - const onClick = () => { - props.api.setExpanded(!expanded); - }; - - return ( - - ); -}; - -const headerComponents = { myHeaderComponent: MyHeaderComponent }; -``` - -### Drag And Drop - -If you provide the `PaneviewReact` component with the prop `onDidDrop` you will be able to interact with custom drop events. - - - -### Interactions - -You can safely create multiple paneview instances within one page. They will not interact with each other by default. - -If you wish to interact with the drop event from one paneview instance in another paneview instance you can implement the `showDndOverlay` and `onDidDrop` props on `PaneviewReact`. - -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. - - diff --git a/packages/docs/versioned_docs/version-1.9.0/components/splitview.mdx b/packages/docs/versioned_docs/version-1.9.0/components/splitview.mdx deleted file mode 100644 index ede28b404..000000000 --- a/packages/docs/versioned_docs/version-1.9.0/components/splitview.mdx +++ /dev/null @@ -1,196 +0,0 @@ ---- -description: Splitview Documentation ---- - -import { SimpleSplitview } from '@site/src/components/simpleSplitview'; -import { SplitviewExample1 } from '@site/src/components/splitview/active'; -import Link from '@docusaurus/Link'; -import { DocRef } from '@site/src/components/ui/reference/docRef'; - -# Splitview - -## Introduction - -A Splitview is a collection of resizable horizontally or vertically stacked panels. - -
- -
- -```tsx title="Simple Splitview example" -import { - ISplitviewPanelProps, - Orientation, - SplitviewReact, - SplitviewReadyEvent, -} from 'dockview'; - -const components = { - default: (props: ISplitviewPanelProps<{ title: string }>) => { - return
{props.params.title}
; - }, -}; - -export const SimpleSplitview = () => { - const onReady = (event: SplitviewReadyEvent) => { - event.api.addPanel({ - id: 'panel_1', - component: 'default', - params: { - title: 'Panel 1', - }, - }); - - event.api.addPanel({ - id: 'panel_2', - component: 'default', - params: { - title: 'Panel 2', - }, - }); - - event.api.addPanel({ - id: 'panel_3', - component: 'default', - params: { - title: 'Panel 3', - }, - }); - }; - - return ( - - ); -}; -``` - -## SplitviewReact Component - -You can create a Splitview through the use of the `ReactSplitview` component. - -```tsx -import { ReactSplitview } from 'dockview'; -``` - -Using the `onReady` prop you can access to the component `api` and add panels either through deserialization or the individual addition of panels. - - - -## Splitview API - -The Splitview API is exposed both at the `onReady` event and on each panel through `props.containerApi`. -Through this API you can control general features of the component and access all added panels. - -```tsx title="Splitview API via Panel component" -const MyComponent = (props: ISplitviewPanelProps<{ title: string }>) => { - // props.containerApi... - - return
{`My first panel has the title: ${props.params.title}`}
; -}; -``` - -```tsx title="Splitview API via the onReady callback" -const onReady = (event: SplitviewReadyEvent) => { - // event.api... -}; -``` - - - -## Splitview Panel API - -The Splitview panel API is exposed on each panel containing actions and variables specific to that panel. - -```tsx title="Splitview panel API via Panel component" -const MyComponent = (props: ISplitviewPanelProps<{ title: string }>) => { - // props.api... - - return
{`My first panel has the title: ${props.params.title}`}
; -}; -``` - - - -## Advanced Features - -Listed below are some functionalities avalaible through both the panel and component APIs. The live demo shows examples of these in real-time. - -
- -
- -### Visibility - -A panels visibility can be controlled and monitored through the following code. -A panel with visibility set to `false` will remain as a part of the components list of panels but will not be rendered. - -```tsx -const disposable = props.api.onDidVisibilityChange(({ isVisible }) => { - // -}); -``` - -```tsx -api.setVisible(true); -``` - -### Active - -Only one panel in the `splitview` can be the active panel at any one time. -Setting a panel as active will set all the others as inactive. -A focused panel is always the active panel but an active panel is not always focused. - -```tsx -const disposable = props.api.onDidActiveChange(({ isActive }) => { - // -}); -``` - -```tsx -api.setActive(); -``` - -### Contraints - -When adding a panel you can specify pixel size contraints - -```tsx -event.api.addPanel({ - id: 'panel_3', - component: 'default', - minimumSize: 100, - maximumSize: 1000, -}); -``` - -These contraints can be updated throughout the lifecycle of the `splitview` using the panel API - -```tsx -props.api.onDidConstraintsChange(({ maximumSize, minimumSize }) => { - // -}); -``` - -```tsx -api.setConstraints({ - maximumSize: 200, - minimumSize: 400, -}); -``` diff --git a/packages/docs/versioned_docs/version-1.9.0/theme.mdx b/packages/docs/versioned_docs/version-1.9.0/theme.mdx deleted file mode 100644 index a3d119323..000000000 --- a/packages/docs/versioned_docs/version-1.9.0/theme.mdx +++ /dev/null @@ -1,85 +0,0 @@ ---- -sidebar_position: 1 -description: Theming Dockview Components ---- - -# Theme - -## Introduction - -`dockview` requires some CSS to work correctly. -The CSS is exported as one file under [`dockview/dict/styles/dockview.css`](https://unpkg.com/browse/dockview@latest/dist/styles/dockview.css) -and should be imported at some point in your application - -```css title="Example import with .css file" -@import './node_modules/dockview/dist/styles/dockview.css'; -``` - -## Provided themes - -`dockview` comes with a number of themes which are all CSS classes and can be found [here](https://github.com/mathuo/dockview/blob/master/packages/dockview-core/src/theme.scss). -To use a `dockview` theme the CSS must encapsulate the component. The current list of themes is: - -- `dockview-theme-dark` -- `dockview-theme-light` -- `dockview-theme-vs` -- `dockview-theme-abyss` -- `dockview-theme-dracula` -- `dockview-theme-replit` - -## Customizing Theme - -`dockview` supports theming through the use of css properties. -You can view the built-in themes at [`dockview/src/theme.scss`](https://github.com/mathuo/dockview/blob/master/packages/dockview/src/theme.scss) -and are free to build your own themes based on these css properties. - -| CSS Property | Description | -| ---------------------------------------------------- | ----------- | -| --dv-paneview-active-outline-color | | -| --dv-tabs-and-actions-container-font-size | | -| --dv-tabs-and-actions-container-height | | -| --dv-tab-close-icon | | -| --dv-drag-over-background-color | | -| --dv-drag-over-border-color | | -| --dv-tabs-container-scrollbar-color | | -| | | -| --dv-group-view-background-color | | -| | | -| --dv-tabs-and-actions-container-background-color | | -| | | -| --dv-activegroup-visiblepanel-tab-background-color | | -| --dv-activegroup-hiddenpanel-tab-background-color | | -| --dv-inactivegroup-visiblepanel-tab-background-color | | -| --dv-inactivegroup-hiddenpanel-tab-background-color | | -| --dv-tab-divider-color | | -| | | -| --dv-activegroup-visiblepanel-tab-color | | -| --dv-activegroup-hiddenpanel-tab-color | | -| --dv-inactivegroup-visiblepanel-tab-color | | -| --dv-inactivegroup-hiddenpanel-tab-color | | -| | | -| --dv-separator-border | | -| --dv-paneview-header-border-color | | -| | | -| --dv-icon-hover-background-color | | -| --dv-floating-box-shadow | | -| --dv-active-sash-color | | -| --dv-background-color | | - -You can further customise the theme through adjusting class properties but this is up you. -For example if you wanted to add a bottom border to the tab container for an active group in the `DockviewReact` component you could write: - -```css title="Additional CSS to show a bottom border on active groups" -.groupview { - &.active-group { - > .tabs-and-actions-container { - border-bottom: 2px solid var(--dv-activegroup-visiblepanel-tab-background-color); - } - } - &.inactive-group { - > .tabs-and-actions-container { - border-bottom: 2px solid var(--dv-inactivegroup-visiblepanel-tab-background-color); - } - } -} -``` diff --git a/packages/docs/versioned_docs/version-1.8.4/components/_category_.json b/packages/docs/versioned_docs/version-1.9.1/components/_category_.json similarity index 100% rename from packages/docs/versioned_docs/version-1.8.4/components/_category_.json rename to packages/docs/versioned_docs/version-1.9.1/components/_category_.json diff --git a/packages/docs/versioned_docs/version-1.9.0/components/dockview.mdx b/packages/docs/versioned_docs/version-1.9.1/components/dockview.mdx similarity index 96% rename from packages/docs/versioned_docs/version-1.9.0/components/dockview.mdx rename to packages/docs/versioned_docs/version-1.9.1/components/dockview.mdx index 0472c1e1a..df42e4e06 100644 --- a/packages/docs/versioned_docs/version-1.9.0/components/dockview.mdx +++ b/packages/docs/versioned_docs/version-1.9.1/components/dockview.mdx @@ -31,6 +31,7 @@ import DockviewKeyboard from '@site/sandboxes/keyboard-dockview/src/app'; import DockviewPopoutGroup from '@site/sandboxes/popoutgroup-dockview/src/app'; import DockviewMaximizeGroup from '@site/sandboxes/maximizegroup-dockview/src/app'; import DockviewRenderMode from '@site/sandboxes/rendermode-dockview/src/app'; +import DockviewScrollbars from '@site/sandboxes/scrollbars-dockview/src/app'; import { DocRef } from '@site/src/components/ui/reference/docRef'; @@ -196,6 +197,22 @@ If you refresh the page you should notice your layout is loaded as you left it. react={DockviewPersistance} /> +## Scrollbars + +Scrollbars will appear if the contents of your view has a fixed height. If you are using a relative height such +as *100%* you will need to define an inner container with the appropiate `overflow` value to allow scrollbars to appear. + +The following container three views: +- **Panel 1**: Sets `height: 100%` and no scrollbar appears even, the content is clipped. +- **Panel 2**: Sets `height: 2000px` and a scrollbar does appear since a fixed height has been used. +- **Panel 3**: Sets `height: 100%` and defines an inner component with `overflow: auto` to enable the scrollbars. + + + + ## Resizing ### Panel Resizing @@ -247,6 +264,18 @@ which will be rendered when there are no panels or groups. ## Drag And Drop +You can override the conditions of the far edge overlays through the `rootOverlayModel` prop. + +```tsx + +``` + ### Built-in behaviours Dockview supports a wide variety of built-in Drag and Drop possibilities. diff --git a/packages/docs/versioned_docs/version-1.8.4/components/gridview.mdx b/packages/docs/versioned_docs/version-1.9.1/components/gridview.mdx similarity index 100% rename from packages/docs/versioned_docs/version-1.8.4/components/gridview.mdx rename to packages/docs/versioned_docs/version-1.9.1/components/gridview.mdx diff --git a/packages/docs/versioned_docs/version-1.8.4/components/paneview.mdx b/packages/docs/versioned_docs/version-1.9.1/components/paneview.mdx similarity index 100% rename from packages/docs/versioned_docs/version-1.8.4/components/paneview.mdx rename to packages/docs/versioned_docs/version-1.9.1/components/paneview.mdx diff --git a/packages/docs/versioned_docs/version-1.8.4/components/splitview.mdx b/packages/docs/versioned_docs/version-1.9.1/components/splitview.mdx similarity index 100% rename from packages/docs/versioned_docs/version-1.8.4/components/splitview.mdx rename to packages/docs/versioned_docs/version-1.9.1/components/splitview.mdx diff --git a/packages/docs/versioned_docs/version-1.9.0/contributing.mdx b/packages/docs/versioned_docs/version-1.9.1/contributing.mdx similarity index 100% rename from packages/docs/versioned_docs/version-1.9.0/contributing.mdx rename to packages/docs/versioned_docs/version-1.9.1/contributing.mdx diff --git a/packages/docs/versioned_docs/version-1.9.0/index.mdx b/packages/docs/versioned_docs/version-1.9.1/index.mdx similarity index 100% rename from packages/docs/versioned_docs/version-1.9.0/index.mdx rename to packages/docs/versioned_docs/version-1.9.1/index.mdx diff --git a/packages/docs/versioned_docs/version-1.8.4/theme.mdx b/packages/docs/versioned_docs/version-1.9.1/theme.mdx similarity index 100% rename from packages/docs/versioned_docs/version-1.8.4/theme.mdx rename to packages/docs/versioned_docs/version-1.9.1/theme.mdx diff --git a/packages/docs/versioned_sidebars/version-1.9.0-sidebars.json b/packages/docs/versioned_sidebars/version-1.9.0-sidebars.json deleted file mode 100644 index caea0c03b..000000000 --- a/packages/docs/versioned_sidebars/version-1.9.0-sidebars.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "tutorialSidebar": [ - { - "type": "autogenerated", - "dirName": "." - } - ] -} diff --git a/packages/docs/versioned_sidebars/version-1.8.4-sidebars.json b/packages/docs/versioned_sidebars/version-1.9.1-sidebars.json similarity index 100% rename from packages/docs/versioned_sidebars/version-1.8.4-sidebars.json rename to packages/docs/versioned_sidebars/version-1.9.1-sidebars.json diff --git a/packages/docs/versions.json b/packages/docs/versions.json index c1af97d2c..ddb516b3a 100644 --- a/packages/docs/versions.json +++ b/packages/docs/versions.json @@ -1,4 +1,3 @@ [ - "1.9.0", - "1.8.4" + "1.9.1" ] From 502a984d2bac43698ff72b43d0c02abe651399fc Mon Sep 17 00:00:00 2001 From: mathuo <6710312+mathuo@users.noreply.github.com> Date: Sat, 20 Jan 2024 15:05:40 +0000 Subject: [PATCH 04/20] chore(release): publish v1.9.1 --- lerna.json | 2 +- packages/dockview-core/package.json | 2 +- packages/dockview/package.json | 4 ++-- packages/docs/package.json | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/lerna.json b/lerna.json index be70152c9..d50dd43a1 100644 --- a/lerna.json +++ b/lerna.json @@ -2,7 +2,7 @@ "packages": [ "packages/*" ], - "version": "1.9.0", + "version": "1.9.1", "npmClient": "yarn", "command": { "publish": { diff --git a/packages/dockview-core/package.json b/packages/dockview-core/package.json index 5b081b762..431fa886e 100644 --- a/packages/dockview-core/package.json +++ b/packages/dockview-core/package.json @@ -1,6 +1,6 @@ { "name": "dockview-core", - "version": "1.9.0", + "version": "1.9.1", "description": "Zero dependency layout manager supporting tabs, grids and splitviews with ReactJS support", "keywords": [ "splitview", diff --git a/packages/dockview/package.json b/packages/dockview/package.json index 3245b3dbd..56b660e8a 100644 --- a/packages/dockview/package.json +++ b/packages/dockview/package.json @@ -1,6 +1,6 @@ { "name": "dockview", - "version": "1.9.0", + "version": "1.9.1", "description": "Zero dependency layout manager supporting tabs, grids and splitviews with ReactJS support", "keywords": [ "splitview", @@ -54,6 +54,6 @@ "test:cov": "cross-env ../../node_modules/.bin/jest --selectProjects dockview --coverage" }, "dependencies": { - "dockview-core": "^1.9.0" + "dockview-core": "^1.9.1" } } diff --git a/packages/docs/package.json b/packages/docs/package.json index bb2b3e9eb..ebf17fb47 100644 --- a/packages/docs/package.json +++ b/packages/docs/package.json @@ -1,6 +1,6 @@ { "name": "dockview-docs", - "version": "1.9.0", + "version": "1.9.1", "private": true, "scripts": { "build": "docusaurus build", @@ -30,7 +30,7 @@ "@radix-ui/react-popover": "^1.0.7", "axios": "^1.6.3", "clsx": "^2.1.0", - "dockview": "^1.9.0", + "dockview": "^1.9.1", "prism-react-renderer": "^2.3.1", "react-dnd": "^16.0.1", "recoil": "^0.7.7", From 03a39507dc1d2d63c6fa5d730a7f4b3f53b6a77d Mon Sep 17 00:00:00 2001 From: NaNgets Date: Sun, 21 Jan 2024 15:00:35 +0200 Subject: [PATCH 05/20] Add group with options. --- .../dockview-core/src/api/component.api.ts | 7 ++-- .../src/api/dockviewGroupPanelApi.ts | 9 +++-- .../src/dockview/dockviewComponent.ts | 34 +++++++++---------- 3 files changed, 27 insertions(+), 23 deletions(-) diff --git a/packages/dockview-core/src/api/component.api.ts b/packages/dockview-core/src/api/component.api.ts index d4a8aae02..2512808bf 100644 --- a/packages/dockview-core/src/api/component.api.ts +++ b/packages/dockview-core/src/api/component.api.ts @@ -36,12 +36,13 @@ import { IDockviewGroupPanel, } from '../dockview/dockviewGroupPanel'; import { Emitter, Event } from '../events'; +import { GroupOptions } from '../dockview/dockviewGroupPanelModel'; import { IDockviewPanel } from '../dockview/dockviewPanel'; import { PaneviewDropEvent } from '../paneview/draggablePaneviewPanel'; import { GroupDragEvent, TabDragEvent, -} from '../dockview/components/titlebar/tabsContainer'; + } from '../dockview/components/titlebar/tabsContainer'; import { Box } from '../types'; export interface CommonApi { @@ -736,8 +737,8 @@ export class DockviewApi implements CommonApi { /** * Add a group and return the created object. */ - addGroup(options?: AddGroupOptions): DockviewGroupPanel { - return this.component.addGroup(options); + addGroup(groupOptions?: GroupOptions, positionOptions?: AddGroupOptions): DockviewGroupPanel { + return this.component.addGroup(groupOptions, positionOptions); } /** diff --git a/packages/dockview-core/src/api/dockviewGroupPanelApi.ts b/packages/dockview-core/src/api/dockviewGroupPanelApi.ts index c4b349bdf..82a40204e 100644 --- a/packages/dockview-core/src/api/dockviewGroupPanelApi.ts +++ b/packages/dockview-core/src/api/dockviewGroupPanelApi.ts @@ -49,9 +49,12 @@ export class DockviewGroupPanelApiImpl extends GridviewPanelApiImpl { const group = options.group ?? - this.accessor.addGroup({ - direction: positionToDirection(options.position ?? 'right'), - }); + this.accessor.addGroup( + undefined, + { + direction: positionToDirection(options.position ?? 'right'), + } + ); this.accessor.moveGroupOrPanel( group, diff --git a/packages/dockview-core/src/dockview/dockviewComponent.ts b/packages/dockview-core/src/dockview/dockviewComponent.ts index 82e5283a4..ac3cfbe58 100644 --- a/packages/dockview-core/src/dockview/dockviewComponent.ts +++ b/packages/dockview-core/src/dockview/dockviewComponent.ts @@ -261,7 +261,7 @@ export interface IDockviewComponent extends IBaseGrid { getGroupPanel: (id: string) => IDockviewPanel | undefined; createWatermarkComponent(): IWatermarkRenderer; // lifecycle - addGroup(options?: AddGroupOptions): DockviewGroupPanel; + addGroup(groupOptions?: GroupOptions, positionOptions?: AddGroupOptions): DockviewGroupPanel; closeAllGroups(): void; // events moveToNext(options?: MovementOptions): void; @@ -1280,23 +1280,23 @@ export class DockviewComponent } } - addGroup(options?: AddGroupOptions): DockviewGroupPanel { - const group = this.createGroup(); + addGroup(groupOptions?: GroupOptions, positionOptions?: AddGroupOptions): DockviewGroupPanel { + const group = this.createGroup(groupOptions); - if (options) { + if (positionOptions) { let referenceGroup: DockviewGroupPanel | undefined; - if (isGroupOptionsWithPanel(options)) { + if (isGroupOptionsWithPanel(positionOptions)) { const referencePanel = - typeof options.referencePanel === 'string' + typeof positionOptions.referencePanel === 'string' ? this.panels.find( - (panel) => panel.id === options.referencePanel + (panel) => panel.id === positionOptions.referencePanel ) - : options.referencePanel; + : positionOptions.referencePanel; if (!referencePanel) { throw new Error( - `reference panel ${options.referencePanel} does not exist` + `reference panel ${positionOptions.referencePanel} does not exist` ); } @@ -1304,28 +1304,28 @@ export class DockviewComponent if (!referenceGroup) { throw new Error( - `reference group for reference panel ${options.referencePanel} does not exist` + `reference group for reference panel ${positionOptions.referencePanel} does not exist` ); } - } else if (isGroupOptionsWithGroup(options)) { + } else if (isGroupOptionsWithGroup(positionOptions)) { referenceGroup = - typeof options.referenceGroup === 'string' - ? this._groups.get(options.referenceGroup)?.value - : options.referenceGroup; + typeof positionOptions.referenceGroup === 'string' + ? this._groups.get(positionOptions.referenceGroup)?.value + : positionOptions.referenceGroup; if (!referenceGroup) { throw new Error( - `reference group ${options.referenceGroup} does not exist` + `reference group ${positionOptions.referenceGroup} does not exist` ); } } else { const group = this.orthogonalize( - directionToPosition(options.direction) + directionToPosition(positionOptions.direction) ); return group; } - const target = toTarget(options.direction || 'within'); + const target = toTarget(positionOptions.direction || 'within'); const location = getGridLocation(referenceGroup.element); const relativeLocation = getRelativeLocation( From a6a6ffc058f54915b884769bb15d8559908e4a27 Mon Sep 17 00:00:00 2001 From: NaNgets Date: Sun, 21 Jan 2024 15:02:45 +0200 Subject: [PATCH 06/20] Fix indentation. --- packages/dockview-core/src/api/component.api.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/dockview-core/src/api/component.api.ts b/packages/dockview-core/src/api/component.api.ts index 2512808bf..97ddd6834 100644 --- a/packages/dockview-core/src/api/component.api.ts +++ b/packages/dockview-core/src/api/component.api.ts @@ -42,7 +42,7 @@ import { PaneviewDropEvent } from '../paneview/draggablePaneviewPanel'; import { GroupDragEvent, TabDragEvent, - } from '../dockview/components/titlebar/tabsContainer'; +} from '../dockview/components/titlebar/tabsContainer'; import { Box } from '../types'; export interface CommonApi { From 02af66a10f6c0c116ef2bc0f3e770d055d174bd9 Mon Sep 17 00:00:00 2001 From: NaNgets Date: Sun, 21 Jan 2024 22:51:19 +0200 Subject: [PATCH 07/20] PR comments. --- .../dockview-core/src/api/component.api.ts | 5 ++- .../src/api/dockviewGroupPanelApi.ts | 9 ++---- .../src/dockview/dockviewComponent.ts | 32 +++++++++---------- .../dockview-core/src/dockview/options.ts | 7 ++-- 4 files changed, 26 insertions(+), 27 deletions(-) diff --git a/packages/dockview-core/src/api/component.api.ts b/packages/dockview-core/src/api/component.api.ts index 97ddd6834..d4a8aae02 100644 --- a/packages/dockview-core/src/api/component.api.ts +++ b/packages/dockview-core/src/api/component.api.ts @@ -36,7 +36,6 @@ import { IDockviewGroupPanel, } from '../dockview/dockviewGroupPanel'; import { Emitter, Event } from '../events'; -import { GroupOptions } from '../dockview/dockviewGroupPanelModel'; import { IDockviewPanel } from '../dockview/dockviewPanel'; import { PaneviewDropEvent } from '../paneview/draggablePaneviewPanel'; import { @@ -737,8 +736,8 @@ export class DockviewApi implements CommonApi { /** * Add a group and return the created object. */ - addGroup(groupOptions?: GroupOptions, positionOptions?: AddGroupOptions): DockviewGroupPanel { - return this.component.addGroup(groupOptions, positionOptions); + addGroup(options?: AddGroupOptions): DockviewGroupPanel { + return this.component.addGroup(options); } /** diff --git a/packages/dockview-core/src/api/dockviewGroupPanelApi.ts b/packages/dockview-core/src/api/dockviewGroupPanelApi.ts index 82a40204e..c4b349bdf 100644 --- a/packages/dockview-core/src/api/dockviewGroupPanelApi.ts +++ b/packages/dockview-core/src/api/dockviewGroupPanelApi.ts @@ -49,12 +49,9 @@ export class DockviewGroupPanelApiImpl extends GridviewPanelApiImpl { const group = options.group ?? - this.accessor.addGroup( - undefined, - { - direction: positionToDirection(options.position ?? 'right'), - } - ); + this.accessor.addGroup({ + direction: positionToDirection(options.position ?? 'right'), + }); this.accessor.moveGroupOrPanel( group, diff --git a/packages/dockview-core/src/dockview/dockviewComponent.ts b/packages/dockview-core/src/dockview/dockviewComponent.ts index ac3cfbe58..be7e1a732 100644 --- a/packages/dockview-core/src/dockview/dockviewComponent.ts +++ b/packages/dockview-core/src/dockview/dockviewComponent.ts @@ -1280,23 +1280,23 @@ export class DockviewComponent } } - addGroup(groupOptions?: GroupOptions, positionOptions?: AddGroupOptions): DockviewGroupPanel { - const group = this.createGroup(groupOptions); + addGroup(options?: AddGroupOptions): DockviewGroupPanel { + const group = this.createGroup(options); - if (positionOptions) { + if (options) { let referenceGroup: DockviewGroupPanel | undefined; - if (isGroupOptionsWithPanel(positionOptions)) { + if (isGroupOptionsWithPanel(options)) { const referencePanel = - typeof positionOptions.referencePanel === 'string' + typeof options.referencePanel === 'string' ? this.panels.find( - (panel) => panel.id === positionOptions.referencePanel + (panel) => panel.id === options.referencePanel ) - : positionOptions.referencePanel; + : options.referencePanel; if (!referencePanel) { throw new Error( - `reference panel ${positionOptions.referencePanel} does not exist` + `reference panel ${options.referencePanel} does not exist` ); } @@ -1304,28 +1304,28 @@ export class DockviewComponent if (!referenceGroup) { throw new Error( - `reference group for reference panel ${positionOptions.referencePanel} does not exist` + `reference group for reference panel ${options.referencePanel} does not exist` ); } - } else if (isGroupOptionsWithGroup(positionOptions)) { + } else if (isGroupOptionsWithGroup(options)) { referenceGroup = - typeof positionOptions.referenceGroup === 'string' - ? this._groups.get(positionOptions.referenceGroup)?.value - : positionOptions.referenceGroup; + typeof options.referenceGroup === 'string' + ? this._groups.get(options.referenceGroup)?.value + : options.referenceGroup; if (!referenceGroup) { throw new Error( - `reference group ${positionOptions.referenceGroup} does not exist` + `reference group ${options.referenceGroup} does not exist` ); } } else { const group = this.orthogonalize( - directionToPosition(positionOptions.direction) + directionToPosition(options.direction) ); return group; } - const target = toTarget(positionOptions.direction || 'within'); + const target = toTarget(options.direction || 'within'); const location = getGridLocation(referenceGroup.element); const relativeLocation = getRelativeLocation( diff --git a/packages/dockview-core/src/dockview/options.ts b/packages/dockview-core/src/dockview/options.ts index 34ab98989..60a3953e4 100644 --- a/packages/dockview-core/src/dockview/options.ts +++ b/packages/dockview-core/src/dockview/options.ts @@ -14,6 +14,7 @@ import { ISplitviewStyles, Orientation } from '../splitview/splitview'; import { PanelTransfer } from '../dnd/dataTransfer'; import { IDisposable } from '../lifecycle'; import { DroptargetOverlayModel, Position } from '../dnd/droptarget'; +import { GroupOptions } from './dockviewGroupPanelModel'; import { IDockviewPanel } from './dockviewPanel'; import { ComponentConstructor, @@ -186,10 +187,12 @@ type AddGroupOptionsWithGroup = { direction?: Omit; }; -export type AddGroupOptions = +export type AddGroupOptions = ( | AddGroupOptionsWithGroup | AddGroupOptionsWithPanel - | AbsolutePosition; + | AbsolutePosition +) & + GroupOptions; export function isGroupOptionsWithPanel( data: AddGroupOptions From 6499e78544885bb9990101938a528e24ef4ef80f Mon Sep 17 00:00:00 2001 From: NaNgets Date: Sun, 21 Jan 2024 22:52:44 +0200 Subject: [PATCH 08/20] Fix dockview component interface addGroup. --- packages/dockview-core/src/dockview/dockviewComponent.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/dockview-core/src/dockview/dockviewComponent.ts b/packages/dockview-core/src/dockview/dockviewComponent.ts index be7e1a732..7595d723a 100644 --- a/packages/dockview-core/src/dockview/dockviewComponent.ts +++ b/packages/dockview-core/src/dockview/dockviewComponent.ts @@ -261,7 +261,7 @@ export interface IDockviewComponent extends IBaseGrid { getGroupPanel: (id: string) => IDockviewPanel | undefined; createWatermarkComponent(): IWatermarkRenderer; // lifecycle - addGroup(groupOptions?: GroupOptions, positionOptions?: AddGroupOptions): DockviewGroupPanel; + addGroup(options?: AddGroupOptions): DockviewGroupPanel; closeAllGroups(): void; // events moveToNext(options?: MovementOptions): void; From 23b1edb0033941fb9bdd57b1104ae97993a22e1c Mon Sep 17 00:00:00 2001 From: sachnk <5503945+sachnk@users.noreply.github.com> Date: Mon, 22 Jan 2024 19:02:53 +0000 Subject: [PATCH 09/20] add window-lifecycle callbacks --- packages/dockview-core/src/api/component.api.ts | 2 ++ .../dockview-core/src/dockview/dockviewComponent.ts | 6 ++++++ .../src/dockview/dockviewPopoutGroupPanel.ts | 4 ++++ packages/dockview-core/src/popoutWindow.ts | 10 ++++++++++ 4 files changed, 22 insertions(+) diff --git a/packages/dockview-core/src/api/component.api.ts b/packages/dockview-core/src/api/component.api.ts index d4a8aae02..02f4292a7 100644 --- a/packages/dockview-core/src/api/component.api.ts +++ b/packages/dockview-core/src/api/component.api.ts @@ -830,6 +830,8 @@ export class DockviewApi implements CommonApi { options?: { position?: Box; popoutUrl?: string; + onOpened?: (id: string, window: Window) => void; + onClosing?: (id: string, window: Window) => void; } ): void { this.component.addPopoutGroup(item, options); diff --git a/packages/dockview-core/src/dockview/dockviewComponent.ts b/packages/dockview-core/src/dockview/dockviewComponent.ts index 82e5283a4..0080c93a3 100644 --- a/packages/dockview-core/src/dockview/dockviewComponent.ts +++ b/packages/dockview-core/src/dockview/dockviewComponent.ts @@ -286,6 +286,8 @@ export interface IDockviewComponent extends IBaseGrid { options?: { position?: Box; popoutUrl?: string; + onOpened?: (id: string, window: Window) => void; + onClosing?: (id: string, window: Window) => void; } ): void; } @@ -513,6 +515,8 @@ export class DockviewComponent skipRemoveGroup?: boolean; position?: Box; popoutUrl?: string; + onOpened?: (id: string, window: Window) => void; + onClosing?: (id: string, window: Window) => void; } ): void { let group: DockviewGroupPanel; @@ -561,6 +565,8 @@ export class DockviewComponent width: box.width, height: box.height, }, + onOpened: options?.onOpened, + onClosing: options?.onClosing } ); diff --git a/packages/dockview-core/src/dockview/dockviewPopoutGroupPanel.ts b/packages/dockview-core/src/dockview/dockviewPopoutGroupPanel.ts index 803fa5411..6d8e7d1b0 100644 --- a/packages/dockview-core/src/dockview/dockviewPopoutGroupPanel.ts +++ b/packages/dockview-core/src/dockview/dockviewPopoutGroupPanel.ts @@ -13,6 +13,8 @@ export class DockviewPopoutGroupPanel extends CompositeDisposable { className: string; popoutUrl: string; box: Box; + onOpened?: (id: string, window: Window) => void; + onClosing?: (id: string, window: Window) => void; } ) { super(); @@ -23,6 +25,8 @@ export class DockviewPopoutGroupPanel extends CompositeDisposable { top: this.options.box.top, width: this.options.box.width, height: this.options.box.height, + onOpened: this.options.onOpened, + onClosing: this.options.onClosing, }); group.model.location = 'popout'; diff --git a/packages/dockview-core/src/popoutWindow.ts b/packages/dockview-core/src/popoutWindow.ts index c73334549..1e26e4257 100644 --- a/packages/dockview-core/src/popoutWindow.ts +++ b/packages/dockview-core/src/popoutWindow.ts @@ -5,6 +5,8 @@ import { Box } from './types'; export type PopoutWindowOptions = { url: string; + onOpened?: (id: string, window: Window) => void; + onClosing?: (id: string, window: Window) => void; } & Box; export class PopoutWindow extends CompositeDisposable { @@ -42,6 +44,10 @@ export class PopoutWindow extends CompositeDisposable { close(): void { if (this._window) { + if (this.options.onClosing) { + this.options.onClosing(this.id, this._window.value); + } + this._window.disposable.dispose(); this._window.value.close(); this._window = null; @@ -114,5 +120,9 @@ export class PopoutWindow extends CompositeDisposable { cleanUp(); }); }); + + if (this.options.onOpened) { + this.options.onOpened(this.id, externalWindow); + } } } From 8f24d6b3666638e37abbb518ea281e232e91237f Mon Sep 17 00:00:00 2001 From: Floyd Wang Date: Tue, 23 Jan 2024 19:21:17 +0800 Subject: [PATCH 10/20] fix: typo --- packages/docs/docs/components/dockview.mdx | 10 +++++----- .../docs/sandboxes/floatinggroup-dockview/src/app.tsx | 4 ++-- packages/docs/sandboxes/layout-dockview/src/app.tsx | 10 +++++----- .../version-1.9.1/components/dockview.mdx | 10 +++++----- 4 files changed, 17 insertions(+), 17 deletions(-) diff --git a/packages/docs/docs/components/dockview.mdx b/packages/docs/docs/components/dockview.mdx index df42e4e06..40f020fb0 100644 --- a/packages/docs/docs/components/dockview.mdx +++ b/packages/docs/docs/components/dockview.mdx @@ -7,7 +7,7 @@ import { MultiFrameworkContainer } from '@site/src/components/ui/container'; import Link from '@docusaurus/Link'; import useBaseUrl from '@docusaurus/useBaseUrl'; -import DockviewPersistance from '@site/sandboxes/layout-dockview/src/app'; +import DockviewPersistence from '@site/sandboxes/layout-dockview/src/app'; import SimpleDockview from '@site/sandboxes/simple-dockview/src/app'; import ResizeDockview from '@site/sandboxes/resize-dockview/src/app'; import DockviewWatermark from '@site/sandboxes/watermark-dockview/src/app'; @@ -140,7 +140,7 @@ As well as importing the `dockview` stylesheet you must provide a class-based th You can find more details on theming here. -## Layout Persistance +## Layout Persistence Layouts are loaded and saved via to `fromJSON` and `toJSON` methods on the Dockview api. The api also exposes an event `onDidLayoutChange` you can listen on to determine when the layout has changed. @@ -156,7 +156,7 @@ React.useEffect(() => { const layout = api.toJSON(); localStorage.setItem( - 'dockview_persistance_layout', + 'dockview_persistence_layout', JSON.stringify(layout) ); }); @@ -169,7 +169,7 @@ React.useEffect(() => { ```tsx title="Loading a layout from localStorage" const onReady = (event: DockviewReadyEvent) => { - const layoutString = localStorage.getItem('dockview_persistance_layout'); + const layoutString = localStorage.getItem('dockview_persistence_layout'); let success = false; @@ -194,7 +194,7 @@ If you refresh the page you should notice your layout is loaded as you left it. ## Scrollbars diff --git a/packages/docs/sandboxes/floatinggroup-dockview/src/app.tsx b/packages/docs/sandboxes/floatinggroup-dockview/src/app.tsx index 990913d2e..e3c066bbd 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 DockviewPersistence = (props: { theme?: string }) => { const [api, setApi] = React.useState(); const [layout, setLayout] = useLocalStorage('floating.layout'); @@ -289,7 +289,7 @@ const RightComponent = (props: IDockviewHeaderActionsProps) => { ); }; -export default DockviewPersistance; +export default DockviewPersistence; const Watermark = () => { return
watermark
; diff --git a/packages/docs/sandboxes/layout-dockview/src/app.tsx b/packages/docs/sandboxes/layout-dockview/src/app.tsx index 740202840..85d76bd39 100644 --- a/packages/docs/sandboxes/layout-dockview/src/app.tsx +++ b/packages/docs/sandboxes/layout-dockview/src/app.tsx @@ -47,11 +47,11 @@ function loadDefaultLayout(api: DockviewApi) { }); } -export const DockviewPersistance = (props: { theme?: string }) => { +export const DockviewPersistence = (props: { theme?: string }) => { const [api, setApi] = React.useState(); const clearLayout = () => { - localStorage.removeItem('dockview_persistance_layout'); + localStorage.removeItem('dockview_persistence_layout'); if (api) { api.clear(); loadDefaultLayout(api); @@ -60,7 +60,7 @@ export const DockviewPersistance = (props: { theme?: string }) => { const onReady = (event: DockviewReadyEvent) => { const layoutString = localStorage.getItem( - 'dockview_persistance_layout' + 'dockview_persistence_layout' ); let success = false; @@ -91,7 +91,7 @@ export const DockviewPersistance = (props: { theme?: string }) => { const layout = api.toJSON(); localStorage.setItem( - 'dockview_persistance_layout', + 'dockview_persistence_layout', JSON.stringify(layout) ); }); @@ -125,7 +125,7 @@ export const DockviewPersistance = (props: { theme?: string }) => { ); }; -export default DockviewPersistance; +export default DockviewPersistence; const Watermark = () => { return
watermark
; diff --git a/packages/docs/versioned_docs/version-1.9.1/components/dockview.mdx b/packages/docs/versioned_docs/version-1.9.1/components/dockview.mdx index df42e4e06..40f020fb0 100644 --- a/packages/docs/versioned_docs/version-1.9.1/components/dockview.mdx +++ b/packages/docs/versioned_docs/version-1.9.1/components/dockview.mdx @@ -7,7 +7,7 @@ import { MultiFrameworkContainer } from '@site/src/components/ui/container'; import Link from '@docusaurus/Link'; import useBaseUrl from '@docusaurus/useBaseUrl'; -import DockviewPersistance from '@site/sandboxes/layout-dockview/src/app'; +import DockviewPersistence from '@site/sandboxes/layout-dockview/src/app'; import SimpleDockview from '@site/sandboxes/simple-dockview/src/app'; import ResizeDockview from '@site/sandboxes/resize-dockview/src/app'; import DockviewWatermark from '@site/sandboxes/watermark-dockview/src/app'; @@ -140,7 +140,7 @@ As well as importing the `dockview` stylesheet you must provide a class-based th You can find more details on theming here. -## Layout Persistance +## Layout Persistence Layouts are loaded and saved via to `fromJSON` and `toJSON` methods on the Dockview api. The api also exposes an event `onDidLayoutChange` you can listen on to determine when the layout has changed. @@ -156,7 +156,7 @@ React.useEffect(() => { const layout = api.toJSON(); localStorage.setItem( - 'dockview_persistance_layout', + 'dockview_persistence_layout', JSON.stringify(layout) ); }); @@ -169,7 +169,7 @@ React.useEffect(() => { ```tsx title="Loading a layout from localStorage" const onReady = (event: DockviewReadyEvent) => { - const layoutString = localStorage.getItem('dockview_persistance_layout'); + const layoutString = localStorage.getItem('dockview_persistence_layout'); let success = false; @@ -194,7 +194,7 @@ If you refresh the page you should notice your layout is loaded as you left it. ## Scrollbars From a855c551321f0b8778dbe8f183a3a93d28c619ba Mon Sep 17 00:00:00 2001 From: mathuo <6710312+mathuo@users.noreply.github.com> Date: Tue, 23 Jan 2024 17:21:20 +0000 Subject: [PATCH 11/20] bug: fix rendering issue --- .../dockview/components/panel/content.spec.ts | 39 +++++++++++++++++++ .../src/dockview/components/panel/content.ts | 4 +- .../src/dockview/dockviewGroupPanelModel.ts | 2 +- .../docs/sandboxes/demo-dockview/src/app.tsx | 23 ++++++++++- 4 files changed, 63 insertions(+), 5 deletions(-) diff --git a/packages/dockview-core/src/__tests__/dockview/components/panel/content.spec.ts b/packages/dockview-core/src/__tests__/dockview/components/panel/content.spec.ts index 53f59fcbe..eacad7b9a 100644 --- a/packages/dockview-core/src/__tests__/dockview/components/panel/content.spec.ts +++ b/packages/dockview-core/src/__tests__/dockview/components/panel/content.spec.ts @@ -11,6 +11,8 @@ import { IDockviewPanel } from '../../../../dockview/dockviewPanel'; import { IDockviewPanelModel } from '../../../../dockview/dockviewPanelModel'; import { DockviewComponent } from '../../../../dockview/dockviewComponent'; import { OverlayRenderContainer } from '../../../../overlayRenderContainer'; +import { fromPartial } from '@total-typescript/shoehorn'; +import { DockviewGroupPanelModel } from '../../../../dockview/dockviewGroupPanelModel'; class TestContentRenderer extends CompositeDisposable @@ -26,6 +28,7 @@ class TestContentRenderer constructor(public id: string) { super(); this.element = document.createElement('div'); + this.element.id = id; } init(parameters: GroupPanelContentPartInitParameters): void { @@ -146,4 +149,40 @@ describe('contentContainer', () => { disposable.dispose(); }); + + test("that panels renderered as 'onlyWhenVisibile' are removed when closed", () => { + const cut = new ContentContainer( + fromPartial({ + overlayRenderContainer: { + detatch: jest.fn(), + }, + }), + fromPartial({}) + ); + + const panel1 = fromPartial({ + api: { + renderer: 'onlyWhenVisibile', + }, + view: { content: new TestContentRenderer('panel_1') }, + }); + + const panel2 = fromPartial({ + api: { + renderer: 'onlyWhenVisibile', + }, + view: { content: new TestContentRenderer('panel_2') }, + }); + + cut.openPanel(panel1); + + expect(panel1.view.content.element.parentElement).toBe(cut.element); + expect(cut.element.childNodes.length).toBe(1); + + cut.openPanel(panel2); + + expect(panel1.view.content.element.parentElement).toBeNull(); + expect(panel2.view.content.element.parentElement).toBe(cut.element); + expect(cut.element.childNodes.length).toBe(1); + }); }); diff --git a/packages/dockview-core/src/dockview/components/panel/content.ts b/packages/dockview-core/src/dockview/components/panel/content.ts index 91ea210cf..b98b12289 100644 --- a/packages/dockview-core/src/dockview/components/panel/content.ts +++ b/packages/dockview-core/src/dockview/components/panel/content.ts @@ -203,11 +203,11 @@ export class ContentContainer public closePanel(): void { if (this.panel) { - if (this.accessor.options.defaultRenderer === 'onlyWhenVisibile') { + if (this.panel.api.renderer === 'onlyWhenVisibile') { this._element.removeChild(this.panel.view.content.element); } - this.panel = undefined; } + this.panel = undefined; } public dispose(): void { diff --git a/packages/dockview-core/src/dockview/dockviewGroupPanelModel.ts b/packages/dockview-core/src/dockview/dockviewGroupPanelModel.ts index 9d8c9b18c..4462add04 100644 --- a/packages/dockview-core/src/dockview/dockviewGroupPanelModel.ts +++ b/packages/dockview-core/src/dockview/dockviewGroupPanelModel.ts @@ -138,7 +138,6 @@ export class DockviewGroupPanelModel { private readonly tabsContainer: ITabsContainer; private readonly contentContainer: IContentContainer; - // private readonly dropTarget: Droptarget; private _activePanel: IDockviewPanel | undefined; private watermark?: IWatermarkRenderer; private _isGroupActive = false; @@ -527,6 +526,7 @@ export class DockviewGroupPanelModel if (!skipSetGroupActive) { this.accessor.doSetGroupActive(this.groupPanel); } + this.contentContainer.renderPanel(panel, { asActive: true }); return; } diff --git a/packages/docs/sandboxes/demo-dockview/src/app.tsx b/packages/docs/sandboxes/demo-dockview/src/app.tsx index 58d0fd440..79885e0bf 100644 --- a/packages/docs/sandboxes/demo-dockview/src/app.tsx +++ b/packages/docs/sandboxes/demo-dockview/src/app.tsx @@ -14,8 +14,27 @@ import './app.scss'; const components = { default: (props: IDockviewPanelProps<{ title: string }>) => { return ( -
- {''} +
+ + {props.api.title} +
); }, From e9cf1bfd9f9737a229f8116ba7ebd41fde76f1b9 Mon Sep 17 00:00:00 2001 From: mathuo <6710312+mathuo@users.noreply.github.com> Date: Tue, 23 Jan 2024 17:33:53 +0000 Subject: [PATCH 12/20] chore: 1.9.2 docs --- .../docs/blog/2024-01-23-dockview-1.9.2.md | 19 +++++++++++++++++++ packages/docs/package.json | 3 ++- .../components/_category_.json | 0 .../components/dockview.mdx | 0 .../components/gridview.mdx | 0 .../components/paneview.mdx | 0 .../components/splitview.mdx | 0 .../contributing.mdx | 0 .../index.mdx | 0 .../theme.mdx | 0 ...ebars.json => version-1.9.2-sidebars.json} | 0 packages/docs/versions.json | 4 ++-- 12 files changed, 23 insertions(+), 3 deletions(-) create mode 100644 packages/docs/blog/2024-01-23-dockview-1.9.2.md rename packages/docs/versioned_docs/{version-1.9.1 => version-1.9.2}/components/_category_.json (100%) rename packages/docs/versioned_docs/{version-1.9.1 => version-1.9.2}/components/dockview.mdx (100%) rename packages/docs/versioned_docs/{version-1.9.1 => version-1.9.2}/components/gridview.mdx (100%) rename packages/docs/versioned_docs/{version-1.9.1 => version-1.9.2}/components/paneview.mdx (100%) rename packages/docs/versioned_docs/{version-1.9.1 => version-1.9.2}/components/splitview.mdx (100%) rename packages/docs/versioned_docs/{version-1.9.1 => version-1.9.2}/contributing.mdx (100%) rename packages/docs/versioned_docs/{version-1.9.1 => version-1.9.2}/index.mdx (100%) rename packages/docs/versioned_docs/{version-1.9.1 => version-1.9.2}/theme.mdx (100%) rename packages/docs/versioned_sidebars/{version-1.9.1-sidebars.json => version-1.9.2-sidebars.json} (100%) diff --git a/packages/docs/blog/2024-01-23-dockview-1.9.2.md b/packages/docs/blog/2024-01-23-dockview-1.9.2.md new file mode 100644 index 000000000..0ad003326 --- /dev/null +++ b/packages/docs/blog/2024-01-23-dockview-1.9.2.md @@ -0,0 +1,19 @@ +--- +slug: dockview-1.9.2-release +title: Dockview 1.9.2 +tags: [release] +--- + +# Release Notes + +Please reference to docs @ [dockview.dev](https://dockview.dev). + +## 🚀 Features + +- Expose addGroup options [#465](https://github.com/mathuo/dockview/issues/465) + +## 🛠 Miscs + +- Bug: Panel rendering broken when closing adjacent tabs [#472](https://github.com/mathuo/dockview/issues/472) + +## 🔥 Breaking changes diff --git a/packages/docs/package.json b/packages/docs/package.json index ebf17fb47..0e1397e24 100644 --- a/packages/docs/package.json +++ b/packages/docs/package.json @@ -6,6 +6,7 @@ "build": "docusaurus build", "clear": "docusaurus clear", "start": "docusaurus start", + "version": "docusaurus docs:version", "typecheck": "tsc" }, "browserslist": { @@ -42,4 +43,4 @@ "@types/uuid": "^9.0.7", "docusaurus-plugin-sass": "^0.2.5" } -} +} \ No newline at end of file diff --git a/packages/docs/versioned_docs/version-1.9.1/components/_category_.json b/packages/docs/versioned_docs/version-1.9.2/components/_category_.json similarity index 100% rename from packages/docs/versioned_docs/version-1.9.1/components/_category_.json rename to packages/docs/versioned_docs/version-1.9.2/components/_category_.json diff --git a/packages/docs/versioned_docs/version-1.9.1/components/dockview.mdx b/packages/docs/versioned_docs/version-1.9.2/components/dockview.mdx similarity index 100% rename from packages/docs/versioned_docs/version-1.9.1/components/dockview.mdx rename to packages/docs/versioned_docs/version-1.9.2/components/dockview.mdx diff --git a/packages/docs/versioned_docs/version-1.9.1/components/gridview.mdx b/packages/docs/versioned_docs/version-1.9.2/components/gridview.mdx similarity index 100% rename from packages/docs/versioned_docs/version-1.9.1/components/gridview.mdx rename to packages/docs/versioned_docs/version-1.9.2/components/gridview.mdx diff --git a/packages/docs/versioned_docs/version-1.9.1/components/paneview.mdx b/packages/docs/versioned_docs/version-1.9.2/components/paneview.mdx similarity index 100% rename from packages/docs/versioned_docs/version-1.9.1/components/paneview.mdx rename to packages/docs/versioned_docs/version-1.9.2/components/paneview.mdx diff --git a/packages/docs/versioned_docs/version-1.9.1/components/splitview.mdx b/packages/docs/versioned_docs/version-1.9.2/components/splitview.mdx similarity index 100% rename from packages/docs/versioned_docs/version-1.9.1/components/splitview.mdx rename to packages/docs/versioned_docs/version-1.9.2/components/splitview.mdx diff --git a/packages/docs/versioned_docs/version-1.9.1/contributing.mdx b/packages/docs/versioned_docs/version-1.9.2/contributing.mdx similarity index 100% rename from packages/docs/versioned_docs/version-1.9.1/contributing.mdx rename to packages/docs/versioned_docs/version-1.9.2/contributing.mdx diff --git a/packages/docs/versioned_docs/version-1.9.1/index.mdx b/packages/docs/versioned_docs/version-1.9.2/index.mdx similarity index 100% rename from packages/docs/versioned_docs/version-1.9.1/index.mdx rename to packages/docs/versioned_docs/version-1.9.2/index.mdx diff --git a/packages/docs/versioned_docs/version-1.9.1/theme.mdx b/packages/docs/versioned_docs/version-1.9.2/theme.mdx similarity index 100% rename from packages/docs/versioned_docs/version-1.9.1/theme.mdx rename to packages/docs/versioned_docs/version-1.9.2/theme.mdx diff --git a/packages/docs/versioned_sidebars/version-1.9.1-sidebars.json b/packages/docs/versioned_sidebars/version-1.9.2-sidebars.json similarity index 100% rename from packages/docs/versioned_sidebars/version-1.9.1-sidebars.json rename to packages/docs/versioned_sidebars/version-1.9.2-sidebars.json diff --git a/packages/docs/versions.json b/packages/docs/versions.json index ddb516b3a..7c264b1cf 100644 --- a/packages/docs/versions.json +++ b/packages/docs/versions.json @@ -1,3 +1,3 @@ [ - "1.9.1" -] + "1.9.2" +] \ No newline at end of file From f4866c9f8929022a9a50008a783abd8e44accaca Mon Sep 17 00:00:00 2001 From: mathuo <6710312+mathuo@users.noreply.github.com> Date: Tue, 23 Jan 2024 17:36:51 +0000 Subject: [PATCH 13/20] chore: docs --- packages/docs/package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/docs/package.json b/packages/docs/package.json index 0e1397e24..4024a594a 100644 --- a/packages/docs/package.json +++ b/packages/docs/package.json @@ -6,7 +6,7 @@ "build": "docusaurus build", "clear": "docusaurus clear", "start": "docusaurus start", - "version": "docusaurus docs:version", + "docs:version": "docusaurus docs:version", "typecheck": "tsc" }, "browserslist": { @@ -43,4 +43,4 @@ "@types/uuid": "^9.0.7", "docusaurus-plugin-sass": "^0.2.5" } -} \ No newline at end of file +} From c11d65bef7541a30330de25fa38400cb27d76adc Mon Sep 17 00:00:00 2001 From: mathuo <6710312+mathuo@users.noreply.github.com> Date: Tue, 23 Jan 2024 17:37:01 +0000 Subject: [PATCH 14/20] chore(release): publish v1.9.2 --- lerna.json | 2 +- packages/dockview-core/package.json | 2 +- packages/dockview/package.json | 4 ++-- packages/docs/package.json | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/lerna.json b/lerna.json index d50dd43a1..be638038d 100644 --- a/lerna.json +++ b/lerna.json @@ -2,7 +2,7 @@ "packages": [ "packages/*" ], - "version": "1.9.1", + "version": "1.9.2", "npmClient": "yarn", "command": { "publish": { diff --git a/packages/dockview-core/package.json b/packages/dockview-core/package.json index 431fa886e..437bd00d0 100644 --- a/packages/dockview-core/package.json +++ b/packages/dockview-core/package.json @@ -1,6 +1,6 @@ { "name": "dockview-core", - "version": "1.9.1", + "version": "1.9.2", "description": "Zero dependency layout manager supporting tabs, grids and splitviews with ReactJS support", "keywords": [ "splitview", diff --git a/packages/dockview/package.json b/packages/dockview/package.json index 56b660e8a..ab474c2f0 100644 --- a/packages/dockview/package.json +++ b/packages/dockview/package.json @@ -1,6 +1,6 @@ { "name": "dockview", - "version": "1.9.1", + "version": "1.9.2", "description": "Zero dependency layout manager supporting tabs, grids and splitviews with ReactJS support", "keywords": [ "splitview", @@ -54,6 +54,6 @@ "test:cov": "cross-env ../../node_modules/.bin/jest --selectProjects dockview --coverage" }, "dependencies": { - "dockview-core": "^1.9.1" + "dockview-core": "^1.9.2" } } diff --git a/packages/docs/package.json b/packages/docs/package.json index 4024a594a..efc9d4128 100644 --- a/packages/docs/package.json +++ b/packages/docs/package.json @@ -1,6 +1,6 @@ { "name": "dockview-docs", - "version": "1.9.1", + "version": "1.9.2", "private": true, "scripts": { "build": "docusaurus build", @@ -31,7 +31,7 @@ "@radix-ui/react-popover": "^1.0.7", "axios": "^1.6.3", "clsx": "^2.1.0", - "dockview": "^1.9.1", + "dockview": "^1.9.2", "prism-react-renderer": "^2.3.1", "react-dnd": "^16.0.1", "recoil": "^0.7.7", From a24dd21ca2b22d2eb87f230975ef1bfc3ef766f9 Mon Sep 17 00:00:00 2001 From: mathuo <6710312+mathuo@users.noreply.github.com> Date: Sat, 27 Jan 2024 14:11:47 +0000 Subject: [PATCH 15/20] feat: provide means to obtain popoutWindow document --- .../__tests__/dnd/groupDragHandler.spec.ts | 6 +- .../components/titlebar/tabsContainer.spec.ts | 40 ++- .../dockview/dockviewComponent.spec.ts | 244 +++++++++--------- .../__tests__/overlayRenderContainer.spec.ts | 4 +- .../src/api/dockviewGroupPanelApi.ts | 15 +- .../dockview-core/src/api/dockviewPanelApi.ts | 48 +++- .../dockview-core/src/dnd/groupDragHandler.ts | 2 +- .../src/dockview/components/panel/content.ts | 2 +- .../components/titlebar/tabsContainer.ts | 4 +- .../src/dockview/dockviewComponent.ts | 20 +- .../src/dockview/dockviewGroupPanelModel.ts | 9 +- .../src/dockview/dockviewPopoutGroupPanel.ts | 7 +- packages/dockview-core/src/index.ts | 2 + .../src/overlayRenderContainer.ts | 2 +- packages/dockview-core/src/popoutWindow.ts | 74 ++++-- yarn.lock | 9 +- 16 files changed, 288 insertions(+), 200 deletions(-) diff --git a/packages/dockview-core/src/__tests__/dnd/groupDragHandler.spec.ts b/packages/dockview-core/src/__tests__/dnd/groupDragHandler.spec.ts index b2cd3d44d..0ea0f7f5f 100644 --- a/packages/dockview-core/src/__tests__/dnd/groupDragHandler.spec.ts +++ b/packages/dockview-core/src/__tests__/dnd/groupDragHandler.spec.ts @@ -11,7 +11,7 @@ describe('groupDragHandler', () => { const groupMock = jest.fn(() => { const partial: Partial = { id: 'test_group_id', - api: { location: 'grid' } as any, + api: { location: { type: 'grid' } } as any, }; return partial as DockviewGroupPanel; }); @@ -53,7 +53,7 @@ describe('groupDragHandler', () => { const groupMock = jest.fn(() => { const partial: Partial = { - api: { location: 'floating' } as any, + api: { location: { type: 'floating' } } as any, }; return partial as DockviewGroupPanel; }); @@ -85,7 +85,7 @@ describe('groupDragHandler', () => { const groupMock = jest.fn(() => { const partial: Partial = { - api: { location: 'grid' } as any, + api: { location: { type: 'grid' } } as any, }; return partial as DockviewGroupPanel; }); diff --git a/packages/dockview-core/src/__tests__/dockview/components/titlebar/tabsContainer.spec.ts b/packages/dockview-core/src/__tests__/dockview/components/titlebar/tabsContainer.spec.ts index 58a007393..266781742 100644 --- a/packages/dockview-core/src/__tests__/dockview/components/titlebar/tabsContainer.spec.ts +++ b/packages/dockview-core/src/__tests__/dockview/components/titlebar/tabsContainer.spec.ts @@ -9,6 +9,7 @@ import { DockviewGroupPanelModel } from '../../../../dockview/dockviewGroupPanel import { fireEvent } from '@testing-library/dom'; import { TestPanel } from '../../dockviewGroupPanelModel.spec'; import { IDockviewPanel } from '../../../../dockview/dockviewPanel'; +import { fromPartial } from '@total-typescript/shoehorn'; describe('tabsContainer', () => { test('that an external event does not render a drop target and calls through to the group mode', () => { @@ -478,7 +479,7 @@ describe('tabsContainer', () => { const groupPanelMock = jest.fn(() => { return (>{ - api: { location: 'grid' } as any, + api: { location: { type: 'grid' } } as any, }) as DockviewGroupPanel; }); @@ -538,7 +539,7 @@ describe('tabsContainer', () => { const groupPanelMock = jest.fn(() => { return (>{ - api: { location: 'floating' } as any, + api: { location: { type: 'floating' } } as any, }) as DockviewGroupPanel; }); @@ -591,7 +592,7 @@ describe('tabsContainer', () => { const groupPanelMock = jest.fn(() => { return (>{ - api: { location: 'floating' } as any, + api: { location: { type: 'floating' } } as any, model: {} as any, }) as DockviewGroupPanel; }); @@ -601,23 +602,20 @@ describe('tabsContainer', () => { const cut = new TabsContainer(accessor, groupPanel); - const panelMock = jest.fn((id: string) => { - const partial: Partial = { + const createPanel = (id: string) => + fromPartial({ id, - view: { tab: { element: document.createElement('div'), - } as any, + }, content: { element: document.createElement('div'), - } as any, - } as any, - }; - return partial as IDockviewPanel; - }); + }, + }, + }); - const panel = new panelMock('test_id'); + const panel = createPanel('test_id'); cut.openPanel(panel); const el = cut.element.querySelector('.tab')!; @@ -628,15 +626,15 @@ describe('tabsContainer', () => { fireEvent(el, event); // a floating group with a single tab shouldn't be eligible - expect(preventDefaultSpy).toBeCalledTimes(0); - expect(accessor.addFloatingGroup).toBeCalledTimes(0); + expect(preventDefaultSpy).toHaveBeenCalledTimes(0); + expect(accessor.addFloatingGroup).toHaveBeenCalledTimes(0); - const panel2 = new panelMock('test_id_2'); + const panel2 = createPanel('test_id_2'); cut.openPanel(panel2); fireEvent(el, event); - expect(preventDefaultSpy).toBeCalledTimes(1); - expect(accessor.addFloatingGroup).toBeCalledTimes(1); + expect(preventDefaultSpy).toHaveBeenCalledTimes(1); + expect(accessor.addFloatingGroup).toHaveBeenCalledTimes(1); }); test('pre header actions', () => { @@ -653,7 +651,7 @@ describe('tabsContainer', () => { const groupPanelMock = jest.fn(() => { return (>{ - api: { location: 'grid' } as any, + api: { location: { type: 'grid' } } as any, model: {} as any, }) as DockviewGroupPanel; }); @@ -723,7 +721,7 @@ describe('tabsContainer', () => { const groupPanelMock = jest.fn(() => { return (>{ - api: { location: 'grid' } as any, + api: { location: { type: 'grid' } } as any, model: {} as any, }) as DockviewGroupPanel; }); @@ -793,7 +791,7 @@ describe('tabsContainer', () => { const groupPanelMock = jest.fn(() => { return (>{ - api: { location: 'grid' } as any, + api: { location: { type: 'grid' } } as any, model: {} as any, }) as DockviewGroupPanel; }); diff --git a/packages/dockview-core/src/__tests__/dockview/dockviewComponent.spec.ts b/packages/dockview-core/src/__tests__/dockview/dockviewComponent.spec.ts index 9dd25066d..15e38dd61 100644 --- a/packages/dockview-core/src/__tests__/dockview/dockviewComponent.spec.ts +++ b/packages/dockview-core/src/__tests__/dockview/dockviewComponent.spec.ts @@ -3452,8 +3452,8 @@ describe('dockviewComponent', () => { floating: true, }); - expect(panel1.group.api.location).toBe('grid'); - expect(panel2.group.api.location).toBe('floating'); + expect(panel1.group.api.location.type).toBe('grid'); + expect(panel2.group.api.location.type).toBe('floating'); expect(dockview.groups.length).toBe(2); expect(dockview.panels.length).toBe(2); @@ -3464,8 +3464,8 @@ describe('dockviewComponent', () => { 'right' ); - expect(panel1.group.api.location).toBe('grid'); - expect(panel2.group.api.location).toBe('grid'); + expect(panel1.group.api.location.type).toBe('grid'); + expect(panel2.group.api.location.type).toBe('grid'); expect(dockview.groups.length).toBe(2); expect(dockview.panels.length).toBe(2); }); @@ -3497,8 +3497,8 @@ describe('dockviewComponent', () => { floating: true, }); - expect(panel1.group.api.location).toBe('grid'); - expect(panel2.group.api.location).toBe('floating'); + expect(panel1.group.api.location.type).toBe('grid'); + expect(panel2.group.api.location.type).toBe('floating'); expect(dockview.groups.length).toBe(2); expect(dockview.panels.length).toBe(2); @@ -3509,8 +3509,8 @@ describe('dockviewComponent', () => { 'center' ); - expect(panel1.group.api.location).toBe('grid'); - expect(panel2.group.api.location).toBe('grid'); + expect(panel1.group.api.location.type).toBe('grid'); + expect(panel2.group.api.location.type).toBe('grid'); expect(dockview.groups.length).toBe(1); expect(dockview.panels.length).toBe(2); }); @@ -3548,9 +3548,9 @@ describe('dockviewComponent', () => { floating: true, }); - expect(panel1.group.api.location).toBe('grid'); - expect(panel2.group.api.location).toBe('floating'); - expect(panel3.group.api.location).toBe('floating'); + expect(panel1.group.api.location.type).toBe('grid'); + expect(panel2.group.api.location.type).toBe('floating'); + expect(panel3.group.api.location.type).toBe('floating'); expect(dockview.groups.length).toBe(3); expect(dockview.panels.length).toBe(3); @@ -3561,9 +3561,9 @@ describe('dockviewComponent', () => { 'center' ); - expect(panel1.group.api.location).toBe('grid'); - expect(panel2.group.api.location).toBe('floating'); - expect(panel3.group.api.location).toBe('floating'); + expect(panel1.group.api.location.type).toBe('grid'); + expect(panel2.group.api.location.type).toBe('floating'); + expect(panel3.group.api.location.type).toBe('floating'); expect(dockview.groups.length).toBe(2); expect(dockview.panels.length).toBe(3); }); @@ -3601,9 +3601,9 @@ describe('dockviewComponent', () => { position: { referencePanel: panel2 }, }); - expect(panel1.group.api.location).toBe('grid'); - expect(panel2.group.api.location).toBe('floating'); - expect(panel3.group.api.location).toBe('floating'); + expect(panel1.group.api.location.type).toBe('grid'); + expect(panel2.group.api.location.type).toBe('floating'); + expect(panel3.group.api.location.type).toBe('floating'); expect(dockview.groups.length).toBe(2); expect(dockview.panels.length).toBe(3); @@ -3614,9 +3614,9 @@ describe('dockviewComponent', () => { 'right' ); - expect(panel1.group.api.location).toBe('grid'); - expect(panel2.group.api.location).toBe('grid'); - expect(panel3.group.api.location).toBe('grid'); + expect(panel1.group.api.location.type).toBe('grid'); + expect(panel2.group.api.location.type).toBe('grid'); + expect(panel3.group.api.location.type).toBe('grid'); expect(dockview.groups.length).toBe(2); expect(dockview.panels.length).toBe(3); }); @@ -3654,9 +3654,9 @@ describe('dockviewComponent', () => { position: { referencePanel: panel2 }, }); - expect(panel1.group.api.location).toBe('grid'); - expect(panel2.group.api.location).toBe('floating'); - expect(panel3.group.api.location).toBe('floating'); + expect(panel1.group.api.location.type).toBe('grid'); + expect(panel2.group.api.location.type).toBe('floating'); + expect(panel3.group.api.location.type).toBe('floating'); expect(dockview.groups.length).toBe(2); expect(dockview.panels.length).toBe(3); @@ -3667,9 +3667,9 @@ describe('dockviewComponent', () => { 'center' ); - expect(panel1.group.api.location).toBe('grid'); - expect(panel2.group.api.location).toBe('grid'); - expect(panel3.group.api.location).toBe('grid'); + expect(panel1.group.api.location.type).toBe('grid'); + expect(panel2.group.api.location.type).toBe('grid'); + expect(panel3.group.api.location.type).toBe('grid'); expect(dockview.groups.length).toBe(1); expect(dockview.panels.length).toBe(3); }); @@ -3713,10 +3713,10 @@ describe('dockviewComponent', () => { floating: true, }); - expect(panel1.group.api.location).toBe('grid'); - expect(panel2.group.api.location).toBe('floating'); - expect(panel3.group.api.location).toBe('floating'); - expect(panel4.group.api.location).toBe('floating'); + expect(panel1.group.api.location.type).toBe('grid'); + expect(panel2.group.api.location.type).toBe('floating'); + expect(panel3.group.api.location.type).toBe('floating'); + expect(panel4.group.api.location.type).toBe('floating'); expect(dockview.groups.length).toBe(3); expect(dockview.panels.length).toBe(4); @@ -3727,10 +3727,10 @@ describe('dockviewComponent', () => { 'center' ); - expect(panel1.group.api.location).toBe('grid'); - expect(panel2.group.api.location).toBe('floating'); - expect(panel3.group.api.location).toBe('floating'); - expect(panel4.group.api.location).toBe('floating'); + expect(panel1.group.api.location.type).toBe('grid'); + expect(panel2.group.api.location.type).toBe('floating'); + expect(panel3.group.api.location.type).toBe('floating'); + expect(panel4.group.api.location.type).toBe('floating'); expect(dockview.groups.length).toBe(2); expect(dockview.panels.length).toBe(4); }); @@ -3762,8 +3762,8 @@ describe('dockviewComponent', () => { floating: true, }); - expect(panel1.group.api.location).toBe('grid'); - expect(panel2.group.api.location).toBe('floating'); + expect(panel1.group.api.location.type).toBe('grid'); + expect(panel2.group.api.location.type).toBe('floating'); expect(dockview.groups.length).toBe(2); expect(dockview.panels.length).toBe(2); @@ -3774,8 +3774,8 @@ describe('dockviewComponent', () => { 'right' ); - expect(panel1.group.api.location).toBe('grid'); - expect(panel2.group.api.location).toBe('grid'); + expect(panel1.group.api.location.type).toBe('grid'); + expect(panel2.group.api.location.type).toBe('grid'); expect(dockview.groups.length).toBe(2); expect(dockview.panels.length).toBe(2); }); @@ -3807,8 +3807,8 @@ describe('dockviewComponent', () => { floating: true, }); - expect(panel1.group.api.location).toBe('grid'); - expect(panel2.group.api.location).toBe('floating'); + expect(panel1.group.api.location.type).toBe('grid'); + expect(panel2.group.api.location.type).toBe('floating'); expect(dockview.groups.length).toBe(2); expect(dockview.panels.length).toBe(2); @@ -3819,8 +3819,8 @@ describe('dockviewComponent', () => { 'center' ); - expect(panel1.group.api.location).toBe('grid'); - expect(panel2.group.api.location).toBe('grid'); + expect(panel1.group.api.location.type).toBe('grid'); + expect(panel2.group.api.location.type).toBe('grid'); expect(dockview.groups.length).toBe(1); expect(dockview.panels.length).toBe(2); }); @@ -3858,9 +3858,9 @@ describe('dockviewComponent', () => { floating: true, }); - expect(panel1.group.api.location).toBe('grid'); - expect(panel2.group.api.location).toBe('floating'); - expect(panel3.group.api.location).toBe('floating'); + expect(panel1.group.api.location.type).toBe('grid'); + expect(panel2.group.api.location.type).toBe('floating'); + expect(panel3.group.api.location.type).toBe('floating'); expect(dockview.groups.length).toBe(3); expect(dockview.panels.length).toBe(3); @@ -3871,9 +3871,9 @@ describe('dockviewComponent', () => { 'center' ); - expect(panel1.group.api.location).toBe('grid'); - expect(panel2.group.api.location).toBe('floating'); - expect(panel3.group.api.location).toBe('floating'); + expect(panel1.group.api.location.type).toBe('grid'); + expect(panel2.group.api.location.type).toBe('floating'); + expect(panel3.group.api.location.type).toBe('floating'); expect(dockview.groups.length).toBe(2); expect(dockview.panels.length).toBe(3); }); @@ -3911,9 +3911,9 @@ describe('dockviewComponent', () => { position: { referencePanel: panel2 }, }); - expect(panel1.group.api.location).toBe('grid'); - expect(panel2.group.api.location).toBe('floating'); - expect(panel3.group.api.location).toBe('floating'); + expect(panel1.group.api.location.type).toBe('grid'); + expect(panel2.group.api.location.type).toBe('floating'); + expect(panel3.group.api.location.type).toBe('floating'); expect(dockview.groups.length).toBe(2); expect(dockview.panels.length).toBe(3); @@ -3924,9 +3924,9 @@ describe('dockviewComponent', () => { 'right' ); - expect(panel1.group.api.location).toBe('grid'); - expect(panel2.group.api.location).toBe('grid'); - expect(panel3.group.api.location).toBe('floating'); + expect(panel1.group.api.location.type).toBe('grid'); + expect(panel2.group.api.location.type).toBe('grid'); + expect(panel3.group.api.location.type).toBe('floating'); expect(dockview.groups.length).toBe(3); expect(dockview.panels.length).toBe(3); }); @@ -3964,9 +3964,9 @@ describe('dockviewComponent', () => { position: { referencePanel: panel2 }, }); - expect(panel1.group.api.location).toBe('grid'); - expect(panel2.group.api.location).toBe('floating'); - expect(panel3.group.api.location).toBe('floating'); + expect(panel1.group.api.location.type).toBe('grid'); + expect(panel2.group.api.location.type).toBe('floating'); + expect(panel3.group.api.location.type).toBe('floating'); expect(dockview.groups.length).toBe(2); expect(dockview.panels.length).toBe(3); @@ -3977,9 +3977,9 @@ describe('dockviewComponent', () => { 'center' ); - expect(panel1.group.api.location).toBe('grid'); - expect(panel2.group.api.location).toBe('grid'); - expect(panel3.group.api.location).toBe('floating'); + expect(panel1.group.api.location.type).toBe('grid'); + expect(panel2.group.api.location.type).toBe('grid'); + expect(panel3.group.api.location.type).toBe('floating'); expect(dockview.groups.length).toBe(2); expect(dockview.panels.length).toBe(3); }); @@ -4023,10 +4023,10 @@ describe('dockviewComponent', () => { floating: true, }); - expect(panel1.group.api.location).toBe('grid'); - expect(panel2.group.api.location).toBe('floating'); - expect(panel3.group.api.location).toBe('floating'); - expect(panel4.group.api.location).toBe('floating'); + expect(panel1.group.api.location.type).toBe('grid'); + expect(panel2.group.api.location.type).toBe('floating'); + expect(panel3.group.api.location.type).toBe('floating'); + expect(panel4.group.api.location.type).toBe('floating'); expect(dockview.groups.length).toBe(3); expect(dockview.panels.length).toBe(4); @@ -4037,10 +4037,10 @@ describe('dockviewComponent', () => { 'center' ); - expect(panel1.group.api.location).toBe('grid'); - expect(panel2.group.api.location).toBe('floating'); - expect(panel3.group.api.location).toBe('floating'); - expect(panel4.group.api.location).toBe('floating'); + expect(panel1.group.api.location.type).toBe('grid'); + expect(panel2.group.api.location.type).toBe('floating'); + expect(panel3.group.api.location.type).toBe('floating'); + expect(panel4.group.api.location.type).toBe('floating'); expect(dockview.groups.length).toBe(3); expect(dockview.panels.length).toBe(4); }); @@ -4078,9 +4078,9 @@ describe('dockviewComponent', () => { floating: true, }); - expect(panel1.group.api.location).toBe('grid'); - expect(panel2.group.api.location).toBe('grid'); - expect(panel3.group.api.location).toBe('floating'); + expect(panel1.group.api.location.type).toBe('grid'); + expect(panel2.group.api.location.type).toBe('grid'); + expect(panel3.group.api.location.type).toBe('floating'); expect(dockview.groups.length).toBe(3); expect(dockview.panels.length).toBe(3); @@ -4091,9 +4091,9 @@ describe('dockviewComponent', () => { 'center' ); - expect(panel1.group.api.location).toBe('floating'); - expect(panel2.group.api.location).toBe('grid'); - expect(panel3.group.api.location).toBe('floating'); + expect(panel1.group.api.location.type).toBe('floating'); + expect(panel2.group.api.location.type).toBe('grid'); + expect(panel3.group.api.location.type).toBe('floating'); expect(dockview.groups.length).toBe(2); expect(dockview.panels.length).toBe(3); }); @@ -4130,9 +4130,9 @@ describe('dockviewComponent', () => { floating: true, }); - expect(panel1.group.api.location).toBe('grid'); - expect(panel2.group.api.location).toBe('grid'); - expect(panel3.group.api.location).toBe('floating'); + expect(panel1.group.api.location.type).toBe('grid'); + expect(panel2.group.api.location.type).toBe('grid'); + expect(panel3.group.api.location.type).toBe('floating'); expect(dockview.groups.length).toBe(2); expect(dockview.panels.length).toBe(3); @@ -4143,9 +4143,9 @@ describe('dockviewComponent', () => { 'center' ); - expect(panel1.group.api.location).toBe('floating'); - expect(panel2.group.api.location).toBe('grid'); - expect(panel3.group.api.location).toBe('floating'); + expect(panel1.group.api.location.type).toBe('floating'); + expect(panel2.group.api.location.type).toBe('grid'); + expect(panel3.group.api.location.type).toBe('floating'); expect(dockview.groups.length).toBe(2); expect(dockview.panels.length).toBe(3); }); @@ -4183,9 +4183,9 @@ describe('dockviewComponent', () => { floating: true, }); - expect(panel1.group.api.location).toBe('grid'); - expect(panel2.group.api.location).toBe('grid'); - expect(panel3.group.api.location).toBe('floating'); + expect(panel1.group.api.location.type).toBe('grid'); + expect(panel2.group.api.location.type).toBe('grid'); + expect(panel3.group.api.location.type).toBe('floating'); expect(dockview.groups.length).toBe(3); expect(dockview.panels.length).toBe(3); @@ -4196,9 +4196,9 @@ describe('dockviewComponent', () => { 'center' ); - expect(panel1.group.api.location).toBe('floating'); - expect(panel2.group.api.location).toBe('grid'); - expect(panel3.group.api.location).toBe('floating'); + expect(panel1.group.api.location.type).toBe('floating'); + expect(panel2.group.api.location.type).toBe('grid'); + expect(panel3.group.api.location.type).toBe('floating'); expect(dockview.groups.length).toBe(2); expect(dockview.panels.length).toBe(3); }); @@ -4235,9 +4235,9 @@ describe('dockviewComponent', () => { floating: true, }); - expect(panel1.group.api.location).toBe('grid'); - expect(panel2.group.api.location).toBe('grid'); - expect(panel3.group.api.location).toBe('floating'); + expect(panel1.group.api.location.type).toBe('grid'); + expect(panel2.group.api.location.type).toBe('grid'); + expect(panel3.group.api.location.type).toBe('floating'); expect(dockview.groups.length).toBe(2); expect(dockview.panels.length).toBe(3); @@ -4248,9 +4248,9 @@ describe('dockviewComponent', () => { 'center' ); - expect(panel1.group.api.location).toBe('floating'); - expect(panel2.group.api.location).toBe('floating'); - expect(panel3.group.api.location).toBe('floating'); + expect(panel1.group.api.location.type).toBe('floating'); + expect(panel2.group.api.location.type).toBe('floating'); + expect(panel3.group.api.location.type).toBe('floating'); expect(dockview.groups.length).toBe(1); expect(dockview.panels.length).toBe(3); }); @@ -4282,15 +4282,15 @@ describe('dockviewComponent', () => { position: { direction: 'right' }, }); - expect(panel1.group.api.location).toBe('grid'); - expect(panel2.group.api.location).toBe('grid'); + expect(panel1.group.api.location.type).toBe('grid'); + expect(panel2.group.api.location.type).toBe('grid'); expect(dockview.groups.length).toBe(2); expect(dockview.panels.length).toBe(2); dockview.addFloatingGroup(panel2); - expect(panel1.group.api.location).toBe('grid'); - expect(panel2.group.api.location).toBe('floating'); + expect(panel1.group.api.location.type).toBe('grid'); + expect(panel2.group.api.location.type).toBe('floating'); expect(dockview.groups.length).toBe(2); expect(dockview.panels.length).toBe(2); }); @@ -4321,15 +4321,15 @@ describe('dockviewComponent', () => { component: 'default', }); - expect(panel1.group.api.location).toBe('grid'); - expect(panel2.group.api.location).toBe('grid'); + expect(panel1.group.api.location.type).toBe('grid'); + expect(panel2.group.api.location.type).toBe('grid'); expect(dockview.groups.length).toBe(1); expect(dockview.panels.length).toBe(2); dockview.addFloatingGroup(panel2); - expect(panel1.group.api.location).toBe('grid'); - expect(panel2.group.api.location).toBe('floating'); + expect(panel1.group.api.location.type).toBe('grid'); + expect(panel2.group.api.location.type).toBe('floating'); expect(dockview.groups.length).toBe(2); expect(dockview.panels.length).toBe(2); }); @@ -4361,15 +4361,15 @@ describe('dockviewComponent', () => { position: { direction: 'right' }, }); - expect(panel1.group.api.location).toBe('grid'); - expect(panel2.group.api.location).toBe('grid'); + expect(panel1.group.api.location.type).toBe('grid'); + expect(panel2.group.api.location.type).toBe('grid'); expect(dockview.groups.length).toBe(2); expect(dockview.panels.length).toBe(2); dockview.addFloatingGroup(panel2.group); - expect(panel1.group.api.location).toBe('grid'); - expect(panel2.group.api.location).toBe('floating'); + expect(panel1.group.api.location.type).toBe('grid'); + expect(panel2.group.api.location.type).toBe('floating'); expect(dockview.groups.length).toBe(2); expect(dockview.panels.length).toBe(2); }); @@ -4400,15 +4400,15 @@ describe('dockviewComponent', () => { component: 'default', }); - expect(panel1.group.api.location).toBe('grid'); - expect(panel2.group.api.location).toBe('grid'); + expect(panel1.group.api.location.type).toBe('grid'); + expect(panel2.group.api.location.type).toBe('grid'); expect(dockview.groups.length).toBe(1); expect(dockview.panels.length).toBe(2); dockview.addFloatingGroup(panel2.group); - expect(panel1.group.api.location).toBe('floating'); - expect(panel2.group.api.location).toBe('floating'); + expect(panel1.group.api.location.type).toBe('floating'); + expect(panel2.group.api.location.type).toBe('floating'); expect(dockview.groups.length).toBe(1); expect(dockview.panels.length).toBe(2); }); @@ -4440,7 +4440,7 @@ describe('dockviewComponent', () => { expect(dockview.panels.length).toBe(1); expect(dockview.groups.length).toBe(1); - expect(panel1.api.group.api.location).toBe('popout'); + expect(panel1.api.group.api.location.type).toBe('popout'); dockview.removePanel(panel1); @@ -4474,15 +4474,15 @@ describe('dockviewComponent', () => { component: 'default', }); - expect(panel1.group.api.location).toBe('grid'); - expect(panel2.group.api.location).toBe('grid'); + expect(panel1.group.api.location.type).toBe('grid'); + expect(panel2.group.api.location.type).toBe('grid'); expect(dockview.groups.length).toBe(1); expect(dockview.panels.length).toBe(2); dockview.addPopoutGroup(panel2.group); - expect(panel1.group.api.location).toBe('popout'); - expect(panel2.group.api.location).toBe('popout'); + expect(panel1.group.api.location.type).toBe('popout'); + expect(panel2.group.api.location.type).toBe('popout'); expect(dockview.groups.length).toBe(1); expect(dockview.panels.length).toBe(2); }); @@ -4521,17 +4521,17 @@ describe('dockviewComponent', () => { }, }); - expect(panel1.group.api.location).toBe('grid'); - expect(panel2.group.api.location).toBe('grid'); - expect(panel3.group.api.location).toBe('grid'); + expect(panel1.group.api.location.type).toBe('grid'); + expect(panel2.group.api.location.type).toBe('grid'); + expect(panel3.group.api.location.type).toBe('grid'); expect(dockview.groups.length).toBe(2); expect(dockview.panels.length).toBe(3); dockview.addPopoutGroup(panel2.group); - expect(panel1.group.api.location).toBe('popout'); - expect(panel2.group.api.location).toBe('popout'); - expect(panel3.group.api.location).toBe('grid'); + expect(panel1.group.api.location.type).toBe('popout'); + expect(panel2.group.api.location.type).toBe('popout'); + expect(panel3.group.api.location.type).toBe('grid'); expect(dockview.groups.length).toBe(2); expect(dockview.panels.length).toBe(3); @@ -4542,9 +4542,9 @@ describe('dockviewComponent', () => { 'right' ); - expect(panel1.group.api.location).toBe('popout'); - expect(panel2.group.api.location).toBe('grid'); - expect(panel3.group.api.location).toBe('grid'); + expect(panel1.group.api.location.type).toBe('popout'); + expect(panel2.group.api.location.type).toBe('grid'); + expect(panel3.group.api.location.type).toBe('grid'); expect(dockview.groups.length).toBe(3); expect(dockview.panels.length).toBe(3); }); diff --git a/packages/dockview-core/src/__tests__/overlayRenderContainer.spec.ts b/packages/dockview-core/src/__tests__/overlayRenderContainer.spec.ts index db61d319a..81310060b 100644 --- a/packages/dockview-core/src/__tests__/overlayRenderContainer.spec.ts +++ b/packages/dockview-core/src/__tests__/overlayRenderContainer.spec.ts @@ -41,7 +41,7 @@ describe('overlayRenderContainer', () => { }, group: { api: { - location: 'grid', + location: { type: 'grid' }, }, }, }); @@ -77,7 +77,7 @@ describe('overlayRenderContainer', () => { }, group: { api: { - location: 'grid', + location: { type: 'grid' }, }, }, }); diff --git a/packages/dockview-core/src/api/dockviewGroupPanelApi.ts b/packages/dockview-core/src/api/dockviewGroupPanelApi.ts index c4b349bdf..1a999301d 100644 --- a/packages/dockview-core/src/api/dockviewGroupPanelApi.ts +++ b/packages/dockview-core/src/api/dockviewGroupPanelApi.ts @@ -8,6 +8,13 @@ import { GridviewPanelApi, GridviewPanelApiImpl } from './gridviewPanelApi'; export interface DockviewGroupPanelApi extends GridviewPanelApi { readonly onDidLocationChange: Event; readonly location: DockviewGroupLocation; + /** + * + * If you require the documents Window object you can call `document.defaultView`. + * + * @see https://developer.mozilla.org/en-US/docs/Web/API/Document/defaultView + */ + getDocument(): Document; moveTo(options: { group?: DockviewGroupPanel; position?: Position }): void; maximize(): void; isMaximized(): boolean; @@ -42,6 +49,12 @@ export class DockviewGroupPanelApiImpl extends GridviewPanelApiImpl { this.addDisposables(this._onDidLocationChange); } + getDocument(): Document { + return this.location.type === 'popout' + ? this.location.getWindow().document + : window.document; + } + moveTo(options: { group?: DockviewGroupPanel; position?: Position }): void { if (!this._group) { throw new Error(NOT_INITIALIZED_MESSAGE); @@ -66,7 +79,7 @@ export class DockviewGroupPanelApiImpl extends GridviewPanelApiImpl { throw new Error(NOT_INITIALIZED_MESSAGE); } - if (this.location !== 'grid') { + if (this.location.type !== 'grid') { // only grid groups can be maximized return; } diff --git a/packages/dockview-core/src/api/dockviewPanelApi.ts b/packages/dockview-core/src/api/dockviewPanelApi.ts index 6ef8d824a..dffca9297 100644 --- a/packages/dockview-core/src/api/dockviewPanelApi.ts +++ b/packages/dockview-core/src/api/dockviewPanelApi.ts @@ -1,11 +1,13 @@ import { Emitter, Event } from '../events'; import { GridviewPanelApiImpl, GridviewPanelApi } from './gridviewPanelApi'; import { DockviewGroupPanel } from '../dockview/dockviewGroupPanel'; -import { MutableDisposable } from '../lifecycle'; +import { CompositeDisposable, MutableDisposable } from '../lifecycle'; import { DockviewPanel } from '../dockview/dockviewPanel'; import { DockviewComponent } from '../dockview/dockviewComponent'; import { Position } from '../dnd/droptarget'; import { DockviewPanelRenderer } from '../overlayRenderContainer'; +import { DockviewGroupPanelFloatingChangeEvent } from './dockviewGroupPanelApi'; +import { DockviewGroupLocation } from '../dockview/dockviewGroupPanelModel'; export interface TitleEvent { readonly title: string; @@ -28,6 +30,8 @@ export interface DockviewPanelApi readonly onDidActiveGroupChange: Event; readonly onDidGroupChange: Event; readonly onDidRendererChange: Event; + readonly location: DockviewGroupLocation; + readonly onDidLocationChange: Event; close(): void; setTitle(title: string): void; setRenderer(renderer: DockviewPanelRenderer): void; @@ -39,6 +43,13 @@ export interface DockviewPanelApi maximize(): void; isMaximized(): boolean; exitMaximized(): void; + /** + * + * If you require the documents Window object you can call `document.defaultView`. + * + * @see https://developer.mozilla.org/en-US/docs/Web/API/Document/defaultView + */ + getDocument(): Document; } export class DockviewPanelApiImpl @@ -59,7 +70,16 @@ export class DockviewPanelApiImpl readonly _onDidRendererChange = new Emitter(); readonly onDidRendererChange = this._onDidRendererChange.event; - private readonly disposable = new MutableDisposable(); + private readonly _onDidLocationChange = + new Emitter(); + readonly onDidLocationChange: Event = + this._onDidLocationChange.event; + + private readonly groupEventsDisposable = new MutableDisposable(); + + get location(): DockviewGroupLocation { + return this.group.api.location; + } get title(): string | undefined { return this.panel.title; @@ -81,13 +101,22 @@ export class DockviewPanelApiImpl this._onDidGroupChange.fire(); if (this._group) { - this.disposable.value = this._group.api.onDidActiveChange(() => { - this._onDidActiveGroupChange.fire(); - }); + this.groupEventsDisposable.value = new CompositeDisposable( + this.group.api.onDidLocationChange((event) => { + this._onDidLocationChange.fire(event); + }), + this.group.api.onDidActiveChange(() => { + this._onDidActiveGroupChange.fire(); + }) + ); if (this.isGroupActive !== isOldGroupActive) { this._onDidActiveGroupChange.fire(); } + + this._onDidLocationChange.fire({ + location: this.group.api.location, + }); } } @@ -107,14 +136,19 @@ export class DockviewPanelApiImpl this._group = group; this.addDisposables( - this.disposable, + this.groupEventsDisposable, this._onDidRendererChange, this._onDidTitleChange, this._onDidGroupChange, - this._onDidActiveGroupChange + this._onDidActiveGroupChange, + this._onDidLocationChange ); } + getDocument(): Document { + return this.group.api.getDocument(); + } + moveTo(options: { group: DockviewGroupPanel; position?: Position; diff --git a/packages/dockview-core/src/dnd/groupDragHandler.ts b/packages/dockview-core/src/dnd/groupDragHandler.ts index de4a3ef04..7138437bf 100644 --- a/packages/dockview-core/src/dnd/groupDragHandler.ts +++ b/packages/dockview-core/src/dnd/groupDragHandler.ts @@ -38,7 +38,7 @@ export class GroupDragHandler extends DragHandler { } override isCancelled(_event: DragEvent): boolean { - if (this.group.api.location === 'floating' && !_event.shiftKey) { + if (this.group.api.location.type === 'floating' && !_event.shiftKey) { return true; } return false; diff --git a/packages/dockview-core/src/dockview/components/panel/content.ts b/packages/dockview-core/src/dockview/components/panel/content.ts index b98b12289..dd762873a 100644 --- a/packages/dockview-core/src/dockview/components/panel/content.ts +++ b/packages/dockview-core/src/dockview/components/panel/content.ts @@ -71,7 +71,7 @@ export class ContentContainer if ( !data && event.shiftKey && - this.group.location !== 'floating' + this.group.location.type !== 'floating' ) { return false; } diff --git a/packages/dockview-core/src/dockview/components/titlebar/tabsContainer.ts b/packages/dockview-core/src/dockview/components/titlebar/tabsContainer.ts index 123608573..6a7caac29 100644 --- a/packages/dockview-core/src/dockview/components/titlebar/tabsContainer.ts +++ b/packages/dockview-core/src/dockview/components/titlebar/tabsContainer.ts @@ -247,7 +247,7 @@ export class TabsContainer if ( isFloatingGroupsEnabled && event.shiftKey && - this.group.api.location !== 'floating' + this.group.api.location.type !== 'floating' ) { event.preventDefault(); @@ -350,7 +350,7 @@ export class TabsContainer !this.accessor.options.disableFloatingGroups; const isFloatingWithOnePanel = - this.group.api.location === 'floating' && this.size === 1; + this.group.api.location.type === 'floating' && this.size === 1; if ( isFloatingGroupsEnabled && diff --git a/packages/dockview-core/src/dockview/dockviewComponent.ts b/packages/dockview-core/src/dockview/dockviewComponent.ts index 7595d723a..a46177a95 100644 --- a/packages/dockview-core/src/dockview/dockviewComponent.ts +++ b/packages/dockview-core/src/dockview/dockviewComponent.ts @@ -67,6 +67,7 @@ import { DockviewPanelRenderer, OverlayRenderContainer, } from '../overlayRenderContainer'; +import { PopoutWindow } from '../popoutWindow'; const DEFAULT_ROOT_OVERLAY_MODEL: DroptargetOverlayModel = { activationSize: { type: 'pixels', value: 10 }, @@ -608,7 +609,7 @@ export class DockviewComponent } } - group.model.location = 'floating'; + group.model.location = { type: 'floating' }; const overlayLeft = typeof coord?.x === 'number' @@ -683,7 +684,7 @@ export class DockviewComponent dispose: () => { disposable.dispose(); - group.model.location = 'grid'; + group.model.location = { type: 'grid' }; remove(this._floatingGroups, floatingGroupPanel); this.updateWatermark(); }, @@ -1173,7 +1174,7 @@ export class DockviewComponent group.model.openPanel(panel); this.doSetGroupAndPanelActive(group); } else if ( - referenceGroup.api.location === 'floating' || + referenceGroup.api.location.type === 'floating' || target === 'center' ) { panel = this.createPanel(options, referenceGroup); @@ -1259,7 +1260,10 @@ export class DockviewComponent } private updateWatermark(): void { - if (this.groups.filter((x) => x.api.location === 'grid').length === 0) { + if ( + this.groups.filter((x) => x.api.location.type === 'grid').length === + 0 + ) { if (!this.watermark) { this.watermark = this.createWatermarkComponent(); @@ -1377,7 +1381,7 @@ export class DockviewComponent } | undefined ): DockviewGroupPanel { - if (group.api.location === 'floating') { + if (group.api.location.type === 'floating') { const floatingGroup = this._floatingGroups.find( (_) => _.group === group ); @@ -1406,7 +1410,7 @@ export class DockviewComponent throw new Error('failed to find floating group'); } - if (group.api.location === 'popout') { + if (group.api.location.type === 'popout') { const selectedGroup = this._popoutGroups.find( (_) => _.group === group ); @@ -1486,7 +1490,7 @@ export class DockviewComponent if (sourceGroup && sourceGroup.size < 2) { const [targetParentLocation, to] = tail(targetLocation); - if (sourceGroup.api.location === 'grid') { + if (sourceGroup.api.location.type === 'grid') { const sourceLocation = getGridLocation(sourceGroup.element); const [sourceParentLocation, from] = tail(sourceLocation); @@ -1562,7 +1566,7 @@ export class DockviewComponent }); } } else { - switch (sourceGroup.api.location) { + switch (sourceGroup.api.location.type) { case 'grid': this.gridview.removeView( getGridLocation(sourceGroup.element) diff --git a/packages/dockview-core/src/dockview/dockviewGroupPanelModel.ts b/packages/dockview-core/src/dockview/dockviewGroupPanelModel.ts index 4462add04..a80a7baa0 100644 --- a/packages/dockview-core/src/dockview/dockviewGroupPanelModel.ts +++ b/packages/dockview-core/src/dockview/dockviewGroupPanelModel.ts @@ -130,7 +130,10 @@ export interface IDockviewGroupPanelModel extends IPanel { ): boolean; } -export type DockviewGroupLocation = 'grid' | 'floating' | 'popout'; +export type DockviewGroupLocation = + | { type: 'grid' } + | { type: 'floating' } + | { type: 'popout'; getWindow: () => Window }; export class DockviewGroupPanelModel extends CompositeDisposable @@ -146,7 +149,7 @@ export class DockviewGroupPanelModel private _leftHeaderActions: IHeaderActionsRenderer | undefined; private _prefixHeaderActions: IHeaderActionsRenderer | undefined; - private _location: DockviewGroupLocation = 'grid'; + private _location: DockviewGroupLocation = { type: 'grid' }; private mostRecentlyUsed: IDockviewPanel[] = []; @@ -253,7 +256,7 @@ export class DockviewGroupPanelModel toggleClass(this.container, 'dv-groupview-floating', false); toggleClass(this.container, 'dv-groupview-popout', false); - switch (value) { + switch (value.type) { case 'grid': this.contentContainer.dropTarget.setTargetZones([ 'top', diff --git a/packages/dockview-core/src/dockview/dockviewPopoutGroupPanel.ts b/packages/dockview-core/src/dockview/dockviewPopoutGroupPanel.ts index 803fa5411..c95fdce1f 100644 --- a/packages/dockview-core/src/dockview/dockviewPopoutGroupPanel.ts +++ b/packages/dockview-core/src/dockview/dockviewPopoutGroupPanel.ts @@ -25,13 +25,16 @@ export class DockviewPopoutGroupPanel extends CompositeDisposable { height: this.options.box.height, }); - group.model.location = 'popout'; + group.model.location = { + type: 'popout', + getWindow: () => this.window.window!, + }; this.addDisposables( this.window, { dispose: () => { - group.model.location = 'grid'; + group.model.location = { type: 'grid' }; }, }, this.window.onDidClose(() => { diff --git a/packages/dockview-core/src/index.ts b/packages/dockview-core/src/index.ts index 62d415174..3f5c8bf70 100644 --- a/packages/dockview-core/src/index.ts +++ b/packages/dockview-core/src/index.ts @@ -11,6 +11,8 @@ export { CompositeDisposable as DockviewCompositeDisposable, } from './lifecycle'; +export { PopoutWindow } from './popoutWindow'; + export * from './panel/types'; export * from './panel/componentFactory'; diff --git a/packages/dockview-core/src/overlayRenderContainer.ts b/packages/dockview-core/src/overlayRenderContainer.ts index 5fea8cee0..62095e595 100644 --- a/packages/dockview-core/src/overlayRenderContainer.ts +++ b/packages/dockview-core/src/overlayRenderContainer.ts @@ -93,7 +93,7 @@ export class OverlayRenderContainer extends CompositeDisposable { toggleClass( focusContainer, 'dv-render-overlay-float', - panel.group.api.location === 'floating' + panel.group.api.location.type === 'floating' ); }; diff --git a/packages/dockview-core/src/popoutWindow.ts b/packages/dockview-core/src/popoutWindow.ts index c73334549..672a6d4e5 100644 --- a/packages/dockview-core/src/popoutWindow.ts +++ b/packages/dockview-core/src/popoutWindow.ts @@ -8,19 +8,26 @@ export type PopoutWindowOptions = { } & Box; export class PopoutWindow extends CompositeDisposable { + private readonly _onWillClose = new Emitter(); + readonly onWillClose = this._onWillClose.event; + private readonly _onDidClose = new Emitter(); readonly onDidClose = this._onDidClose.event; private _window: { value: Window; disposable: IDisposable } | null = null; + get window(): Window | null { + return this._window?.value ?? null; + } + constructor( - private readonly id: string, + private readonly target: string, private readonly className: string, private readonly options: PopoutWindowOptions ) { super(); - this.addDisposables(this._onDidClose, { + this.addDisposables(this._onWillClose, this._onDidClose, { dispose: () => { this.close(); }, @@ -42,9 +49,13 @@ export class PopoutWindow extends CompositeDisposable { close(): void { if (this._window) { + this._onWillClose.fire(); + this._window.disposable.dispose(); this._window.value.close(); this._window = null; + + this._onDidClose.fire(); } } @@ -64,8 +75,10 @@ export class PopoutWindow extends CompositeDisposable { .map(([key, value]) => `${key}=${value}`) .join(','); - // https://developer.mozilla.org/en-US/docs/Web/API/Window/open - const externalWindow = window.open(url, this.id, features); + /** + * @see https://developer.mozilla.org/en-US/docs/Web/API/Window/open + */ + const externalWindow = window.open(url, this.target, features); if (!externalWindow) { return; @@ -75,44 +88,55 @@ export class PopoutWindow extends CompositeDisposable { this._window = { value: externalWindow, disposable }; - const cleanUp = () => { - this._onDidClose.fire(); - this._window = null; - }; - - // prevent any default content from loading - // externalWindow.document.body.replaceWith(document.createElement('div')); - disposable.addDisposables( addDisposableWindowListener(window, 'beforeunload', () => { - cleanUp(); + /** + * before the main window closes we should close this popup too + * to be good citizens + * + * @see https://developer.mozilla.org/en-US/docs/Web/API/Window/beforeunload_event + */ this.close(); }) ); externalWindow.addEventListener('load', () => { + /** + * @see https://developer.mozilla.org/en-US/docs/Web/API/Window/load_event + */ + const externalDocument = externalWindow.document; externalDocument.title = document.title; - const div = document.createElement('div'); - div.classList.add('dv-popout-window'); - div.style.position = 'absolute'; - div.style.width = '100%'; - div.style.height = '100%'; - div.style.top = '0px'; - div.style.left = '0px'; - div.classList.add(this.className); - div.appendChild(content); + const container = this.createPopoutWindowContainer(); + container.classList.add(this.className); + container.appendChild(content); - externalDocument.body.replaceChildren(div); + // externalDocument.body.replaceChildren(container); + externalDocument.body.appendChild(container); externalDocument.body.classList.add(this.className); addStyles(externalDocument, window.document.styleSheets); externalWindow.addEventListener('beforeunload', () => { - // TODO: indicate external window is closing - cleanUp(); + /** + * @see https://developer.mozilla.org/en-US/docs/Web/API/Window/beforeunload_event + */ + this.close(); }); }); } + + private createPopoutWindowContainer(): HTMLElement { + const el = document.createElement('div'); + el.classList.add('dv-popout-window'); + el.id = 'dv-popout-window'; + el.style.position = 'absolute'; + el.style.width = '100%'; + el.style.height = '100%'; + el.style.top = '0px'; + el.style.left = '0px'; + + return el; + } } diff --git a/yarn.lock b/yarn.lock index 7844d2be6..448c87069 100644 --- a/yarn.lock +++ b/yarn.lock @@ -13882,6 +13882,13 @@ react-json-view-lite@^1.2.0: resolved "https://registry.yarnpkg.com/react-json-view-lite/-/react-json-view-lite-1.2.1.tgz#c59a0bea4ede394db331d482ee02e293d38f8218" integrity sha512-Itc0g86fytOmKZoIoJyGgvNqohWSbh3NXIKNgH6W6FT9PC1ck4xas1tT3Rr/b3UlFXyA9Jjaw9QSXdZy2JwGMQ== +react-laag@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/react-laag/-/react-laag-2.0.5.tgz#549f1035b761b9ba09ac98fd128ccad63464c877" + integrity sha512-RCvublJhdcgGRHU1wMYJ8kRtnYsKUgYusLvVhMuftg65POnnOB4+fwXvnETm6adc0cMnc1spujlrK6bGIz6aug== + dependencies: + tiny-warning "^1.0.3" + react-loadable-ssr-addon-v5-slorber@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/react-loadable-ssr-addon-v5-slorber/-/react-loadable-ssr-addon-v5-slorber-1.0.1.tgz#2cdc91e8a744ffdf9e3556caabeb6e4278689883" @@ -15678,7 +15685,7 @@ tiny-invariant@^1.0.2: resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.3.1.tgz#8560808c916ef02ecfd55e66090df23a4b7aa642" integrity sha512-AD5ih2NlSssTCwsMznbvwMZpJ1cbhkGd2uueNxzv2jDlEeZdU04JQfRnggJQ8DrcVBGjAsCKwFBbDlVNtEMlzw== -tiny-warning@^1.0.0: +tiny-warning@^1.0.0, tiny-warning@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/tiny-warning/-/tiny-warning-1.0.3.tgz#94a30db453df4c643d0fd566060d60a875d84754" integrity sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA== From 0fd3a669c7ea27dffef622e125be73208c9a1353 Mon Sep 17 00:00:00 2001 From: mathuo <6710312+mathuo@users.noreply.github.com> Date: Sat, 27 Jan 2024 19:21:09 +0000 Subject: [PATCH 16/20] test --- .../docs/sandboxes/demo-dockview/src/app.tsx | 4 +- .../floatinggroup-dockview/src/app.tsx | 4 +- .../popoutgroup-dockview/package.json | 5 +- .../popoutgroup-dockview/src/app.tsx | 99 ++++++++++++++----- .../popoutgroup-dockview/src/popover.tsx | 61 ++++++++++++ 5 files changed, 144 insertions(+), 29 deletions(-) create mode 100644 packages/docs/sandboxes/popoutgroup-dockview/src/popover.tsx diff --git a/packages/docs/sandboxes/demo-dockview/src/app.tsx b/packages/docs/sandboxes/demo-dockview/src/app.tsx index 79885e0bf..975f3f6be 100644 --- a/packages/docs/sandboxes/demo-dockview/src/app.tsx +++ b/packages/docs/sandboxes/demo-dockview/src/app.tsx @@ -87,7 +87,7 @@ const RightControls = (props: IDockviewHeaderActionsProps) => { ); const [isPopout, setIsPopout] = React.useState( - props.api.location === 'popout' + props.api.location.type === 'popout' ); React.useEffect(() => { @@ -96,7 +96,7 @@ const RightControls = (props: IDockviewHeaderActionsProps) => { }); const disposable2 = props.api.onDidLocationChange(() => { - setIsPopout(props.api.location === 'popout'); + setIsPopout(props.api.location.type === 'popout'); }); return () => { diff --git a/packages/docs/sandboxes/floatinggroup-dockview/src/app.tsx b/packages/docs/sandboxes/floatinggroup-dockview/src/app.tsx index e3c066bbd..c90e32cec 100644 --- a/packages/docs/sandboxes/floatinggroup-dockview/src/app.tsx +++ b/packages/docs/sandboxes/floatinggroup-dockview/src/app.tsx @@ -255,13 +255,13 @@ const LeftComponent = (props: IDockviewHeaderActionsProps) => { const RightComponent = (props: IDockviewHeaderActionsProps) => { const [floating, setFloating] = React.useState( - props.api.location === 'floating' + props.api.location.type === 'floating' ); React.useEffect(() => { const disposable = props.group.api.onDidLocationChange( (event) => { - setFloating(event.location === 'floating'); + setFloating(event.location.type === 'floating'); } ); diff --git a/packages/docs/sandboxes/popoutgroup-dockview/package.json b/packages/docs/sandboxes/popoutgroup-dockview/package.json index 9a533bff7..9b8601416 100644 --- a/packages/docs/sandboxes/popoutgroup-dockview/package.json +++ b/packages/docs/sandboxes/popoutgroup-dockview/package.json @@ -9,7 +9,8 @@ "dependencies": { "dockview": "*", "react": "^18.2.0", - "react-dom": "^18.2.0" + "react-dom": "^18.2.0", + "react-laag": "^2.0.5" }, "devDependencies": { "@types/react": "^18.0.28", @@ -29,4 +30,4 @@ "not ie <= 11", "not op_mini all" ] -} +} \ No newline at end of file diff --git a/packages/docs/sandboxes/popoutgroup-dockview/src/app.tsx b/packages/docs/sandboxes/popoutgroup-dockview/src/app.tsx index df732b7c0..57fe7e8be 100644 --- a/packages/docs/sandboxes/popoutgroup-dockview/src/app.tsx +++ b/packages/docs/sandboxes/popoutgroup-dockview/src/app.tsx @@ -5,12 +5,53 @@ import { IDockviewHeaderActionsProps, IDockviewPanelProps, SerializedDockview, + DockviewPanelApi, + DockviewGroupLocation, } from 'dockview'; import * as React from 'react'; +import * as ReactDOM from 'react-dom'; import { Icon } from './utils'; +import { PopoverMenu } from './popover'; + +function usePopoutWindowContext(api: DockviewPanelApi): Window { + const [location, setLocation] = React.useState( + api.location + ); + + React.useEffect(() => { + const disposable = api.onDidLocationChange((event) => { + setLocation(event.location); + }); + + return () => { + disposable.dispose(); + }; + }); + + const windowContext = React.useMemo(() => { + if (location.type === 'popout') { + return location.getWindow(); + } + return window; + }, [location]); + + return windowContext; +} const components = { default: (props: IDockviewPanelProps<{ title: string }>) => { + const windowContext = usePopoutWindowContext(props.api); + + React.useEffect(() => { + setTimeout(() => { + const a = windowContext.document.createElement('div'); + a.className = 'aaa'; + windowContext.document.body.appendChild(a); + }, 5000); + }, [windowContext]); + + const [reset, setReset] = React.useState(false); + return (
- {props.params.title} + + {!reset && } + {props.api.title}
); }, @@ -31,31 +84,31 @@ function loadDefaultLayout(api: DockviewApi) { component: 'default', }); - api.addPanel({ - id: 'panel_2', - component: 'default', - }); + // api.addPanel({ + // id: 'panel_2', + // component: 'default', + // }); - api.addPanel({ - id: 'panel_3', - component: 'default', - }); + // api.addPanel({ + // id: 'panel_3', + // component: 'default', + // }); - api.addPanel({ - id: 'panel_4', - component: 'default', - }); + // api.addPanel({ + // id: 'panel_4', + // component: 'default', + // }); - api.addPanel({ - id: 'panel_5', - component: 'default', - position: { direction: 'right' }, - }); + // api.addPanel({ + // id: 'panel_5', + // component: 'default', + // position: { direction: 'right' }, + // }); - api.addPanel({ - id: 'panel_6', - component: 'default', - }); + // api.addPanel({ + // id: 'panel_6', + // component: 'default', + // }); } let panelCount = 0; @@ -223,7 +276,7 @@ const RightComponent = (props: IDockviewHeaderActionsProps) => { const group = props.containerApi.addGroup(); props.group.api.moveTo({ group }); } else { - props.containerApi.addPopoutGroup(props.group, { + const window = props.containerApi.addPopoutGroup(props.group, { popoutUrl: '/popout/index.html', }); } diff --git a/packages/docs/sandboxes/popoutgroup-dockview/src/popover.tsx b/packages/docs/sandboxes/popoutgroup-dockview/src/popover.tsx new file mode 100644 index 000000000..3b0b8d1f4 --- /dev/null +++ b/packages/docs/sandboxes/popoutgroup-dockview/src/popover.tsx @@ -0,0 +1,61 @@ +import { useLayer, Arrow } from 'react-laag'; +import { motion, AnimatePresence } from 'framer-motion'; +import * as React from 'react'; +import { DockviewPanelApi } from 'dockview'; + +export function PopoverMenu(props: { api: DockviewPanelApi }) { + const [isOpen, setOpen] = React.useState(false); + + // helper function to close the menu + function close() { + setOpen(false); + } + + const _window = + props.api.location.type === 'popout' + ? props.api.location.getWindow() + : undefined; + + const { renderLayer, triggerProps, layerProps, arrowProps } = useLayer({ + isOpen, + onOutsideClick: close, // close the menu when the user clicks outside + onDisappear: close, // close the menu when the menu gets scrolled out of sight + overflowContainer: false, // keep the menu positioned inside the container + auto: true, // automatically find the best placement + placement: 'top-end', // we prefer to place the menu "top-end" + triggerOffset: 12, // keep some distance to the trigger + containerOffset: 16, // give the menu some room to breath relative to the container + arrowOffset: 16, // let the arrow have some room to breath also, + environment: _window, + container: _window + ? () => { + const el = _window.document.body; + Object.setPrototypeOf(el, HTMLElement.prototype); + return el; + } + : undefined, + // container: props.window.document.body + }); + + // Again, we're using framer-motion for the transition effect + return ( + <> + + {renderLayer( + + {isOpen && ( + +
  • Item 1
  • +
  • Item 2
  • +
  • Item 3
  • +
  • Item 4
  • + +
    + )} +
    + )} + + ); +} From 6274708acb8981dce02da39c16f4545676348c6d Mon Sep 17 00:00:00 2001 From: mathuo <6710312+mathuo@users.noreply.github.com> Date: Sun, 28 Jan 2024 13:43:09 +0000 Subject: [PATCH 17/20] feat: align event names --- packages/dockview-core/src/api/component.api.ts | 4 ++-- .../src/dockview/dockviewComponent.ts | 12 ++++++------ .../src/dockview/dockviewPopoutGroupPanel.ts | 8 ++++---- packages/dockview-core/src/popoutWindow.ts | 15 +++++++-------- 4 files changed, 19 insertions(+), 20 deletions(-) diff --git a/packages/dockview-core/src/api/component.api.ts b/packages/dockview-core/src/api/component.api.ts index 02f4292a7..f57fa9fa4 100644 --- a/packages/dockview-core/src/api/component.api.ts +++ b/packages/dockview-core/src/api/component.api.ts @@ -830,8 +830,8 @@ export class DockviewApi implements CommonApi { options?: { position?: Box; popoutUrl?: string; - onOpened?: (id: string, window: Window) => void; - onClosing?: (id: string, window: Window) => void; + onDidOpen?: (event: { id: string; window: Window }) => void; + onWillClose?: (event: { id: string; window: Window }) => void; } ): void { this.component.addPopoutGroup(item, options); diff --git a/packages/dockview-core/src/dockview/dockviewComponent.ts b/packages/dockview-core/src/dockview/dockviewComponent.ts index b28dde011..e808bdb79 100644 --- a/packages/dockview-core/src/dockview/dockviewComponent.ts +++ b/packages/dockview-core/src/dockview/dockviewComponent.ts @@ -287,8 +287,8 @@ export interface IDockviewComponent extends IBaseGrid { options?: { position?: Box; popoutUrl?: string; - onOpened?: (id: string, window: Window) => void; - onClosing?: (id: string, window: Window) => void; + onDidOpen?: (event: { id: string; window: Window }) => void; + onWillClose?: (event: { id: string; window: Window }) => void; } ): void; } @@ -516,8 +516,8 @@ export class DockviewComponent skipRemoveGroup?: boolean; position?: Box; popoutUrl?: string; - onOpened?: (id: string, window: Window) => void; - onClosing?: (id: string, window: Window) => void; + onDidOpen?: (event: { id: string; window: Window }) => void; + onWillClose?: (event: { id: string; window: Window }) => void; } ): void { let group: DockviewGroupPanel; @@ -566,8 +566,8 @@ export class DockviewComponent width: box.width, height: box.height, }, - onOpened: options?.onOpened, - onClosing: options?.onClosing + onDidOpen: options?.onDidOpen, + onWillClose: options?.onWillClose, } ); diff --git a/packages/dockview-core/src/dockview/dockviewPopoutGroupPanel.ts b/packages/dockview-core/src/dockview/dockviewPopoutGroupPanel.ts index 371fda793..858178280 100644 --- a/packages/dockview-core/src/dockview/dockviewPopoutGroupPanel.ts +++ b/packages/dockview-core/src/dockview/dockviewPopoutGroupPanel.ts @@ -13,8 +13,8 @@ export class DockviewPopoutGroupPanel extends CompositeDisposable { className: string; popoutUrl: string; box: Box; - onOpened?: (id: string, window: Window) => void; - onClosing?: (id: string, window: Window) => void; + onDidOpen?: (event: { id: string; window: Window }) => void; + onWillClose?: (event: { id: string; window: Window }) => void; } ) { super(); @@ -25,8 +25,8 @@ export class DockviewPopoutGroupPanel extends CompositeDisposable { top: this.options.box.top, width: this.options.box.width, height: this.options.box.height, - onOpened: this.options.onOpened, - onClosing: this.options.onClosing, + onDidOpen: this.options.onDidOpen, + onWillClose: this.options.onWillClose, }); group.model.location = { diff --git a/packages/dockview-core/src/popoutWindow.ts b/packages/dockview-core/src/popoutWindow.ts index 68dfc6736..3f1531cac 100644 --- a/packages/dockview-core/src/popoutWindow.ts +++ b/packages/dockview-core/src/popoutWindow.ts @@ -5,8 +5,8 @@ import { Box } from './types'; export type PopoutWindowOptions = { url: string; - onOpened?: (id: string, window: Window) => void; - onClosing?: (id: string, window: Window) => void; + onDidOpen?: (event: { id: string; window: Window }) => void; + onWillClose?: (event: { id: string; window: Window }) => void; } & Box; export class PopoutWindow extends CompositeDisposable { @@ -53,9 +53,10 @@ export class PopoutWindow extends CompositeDisposable { if (this._window) { this._onWillClose.fire(); - if (this.options.onClosing) { - this.options.onClosing(this.target, this._window.value); - } + this.options.onWillClose?.({ + id: this.target, + window: this._window.value, + }); this._window.disposable.dispose(); this._window.value.close(); @@ -132,9 +133,7 @@ export class PopoutWindow extends CompositeDisposable { }); }); - if (this.options.onOpened) { - this.options.onOpened(this.target, externalWindow); - } + this.options.onDidOpen?.({ id: this.target, window: externalWindow }); } private createPopoutWindowContainer(): HTMLElement { From 0127857544e458c3168f19d9f22926939925cd77 Mon Sep 17 00:00:00 2001 From: mathuo <6710312+mathuo@users.noreply.github.com> Date: Wed, 17 Jan 2024 22:09:30 +0000 Subject: [PATCH 18/20] feat: group gaps --- .../__tests__/api/dockviewPanelApi.spec.ts | 6 ++-- .../components/titlebar/tabsContainer.spec.ts | 34 +++++++++---------- .../dockview/dockviewGroupPanelModel.spec.ts | 7 +++- .../dockview/dockviewPanelModel.spec.ts | 7 ++++ .../src/dockview/dockviewComponent.scss | 26 ++++++++++++++ .../dockview-core/src/dockview/options.ts | 2 +- .../src/gridview/baseComponentGridview.scss | 4 +++ .../src/gridview/baseComponentGridview.ts | 11 ++++-- .../dockview-core/src/gridview/options.ts | 2 +- .../dockview-core/src/paneview/options.ts | 2 +- packages/dockview-core/src/resizable.ts | 11 ++---- .../dockview-core/src/splitview/options.ts | 2 +- packages/dockview-core/src/theme.scss | 9 ++--- .../resizecontainer-dockview/src/app.tsx | 4 +-- 14 files changed, 81 insertions(+), 46 deletions(-) create mode 100644 packages/dockview-core/src/gridview/baseComponentGridview.scss diff --git a/packages/dockview-core/src/__tests__/api/dockviewPanelApi.spec.ts b/packages/dockview-core/src/__tests__/api/dockviewPanelApi.spec.ts index 1d610ba99..e2474bc63 100644 --- a/packages/dockview-core/src/__tests__/api/dockviewPanelApi.spec.ts +++ b/packages/dockview-core/src/__tests__/api/dockviewPanelApi.spec.ts @@ -8,7 +8,7 @@ describe('groupPanelApi', () => { const accessor: Partial = { onDidAddPanel: jest.fn(), onDidRemovePanel: jest.fn(), - options: {}, + options: { parentElement: document.createElement('div') }, }; const panelMock = jest.fn(() => { @@ -44,7 +44,7 @@ describe('groupPanelApi', () => { const accessor: Partial = { onDidAddPanel: jest.fn(), onDidRemovePanel: jest.fn(), - options: {}, + options: { parentElement: document.createElement('div') }, }; const groupViewPanel = new DockviewGroupPanel( accessor, @@ -74,7 +74,7 @@ describe('groupPanelApi', () => { const accessor: Partial = { onDidAddPanel: jest.fn(), onDidRemovePanel: jest.fn(), - options: {}, + options: { parentElement: document.createElement('div') }, }; const groupViewPanel = new DockviewGroupPanel( accessor, diff --git a/packages/dockview-core/src/__tests__/dockview/components/titlebar/tabsContainer.spec.ts b/packages/dockview-core/src/__tests__/dockview/components/titlebar/tabsContainer.spec.ts index 58a007393..8d3a16b19 100644 --- a/packages/dockview-core/src/__tests__/dockview/components/titlebar/tabsContainer.spec.ts +++ b/packages/dockview-core/src/__tests__/dockview/components/titlebar/tabsContainer.spec.ts @@ -16,7 +16,7 @@ describe('tabsContainer', () => { return { onDidAddPanel: jest.fn(), onDidRemovePanel: jest.fn(), - options: {}, + options: { parentElement: document.createElement('div') }, }; }); const groupviewMock = jest.fn, []>( @@ -71,7 +71,7 @@ describe('tabsContainer', () => { id: 'testcomponentid', onDidAddPanel: jest.fn(), onDidRemovePanel: jest.fn(), - options: {}, + options: { parentElement: document.createElement('div') }, }; }); const groupviewMock = jest.fn, []>( @@ -139,7 +139,7 @@ describe('tabsContainer', () => { id: 'testcomponentid', onDidAddPanel: jest.fn(), onDidRemovePanel: jest.fn(), - options: {}, + options: { parentElement: document.createElement('div') }, }; }); const groupviewMock = jest.fn, []>( @@ -204,7 +204,7 @@ describe('tabsContainer', () => { id: 'testcomponentid', onDidAddPanel: jest.fn(), onDidRemovePanel: jest.fn(), - options: {}, + options: { parentElement: document.createElement('div') }, }; }); const groupviewMock = jest.fn, []>( @@ -269,7 +269,7 @@ describe('tabsContainer', () => { id: 'testcomponentid', onDidAddPanel: jest.fn(), onDidRemovePanel: jest.fn(), - options: {}, + options: { parentElement: document.createElement('div') }, }; }); const groupviewMock = jest.fn, []>( @@ -336,7 +336,7 @@ describe('tabsContainer', () => { test('left actions', () => { const accessorMock = jest.fn(() => { return (>{ - options: {}, + options: { parentElement: document.createElement('div') }, onDidAddPanel: jest.fn(), onDidRemovePanel: jest.fn(), }) as DockviewComponent; @@ -402,7 +402,7 @@ describe('tabsContainer', () => { test('right actions', () => { const accessorMock = jest.fn(() => { return (>{ - options: {}, + options: { parentElement: document.createElement('div') }, onDidAddPanel: jest.fn(), onDidRemovePanel: jest.fn(), }) as DockviewComponent; @@ -468,7 +468,7 @@ describe('tabsContainer', () => { test('that a tab will become floating when clicked if not floating and shift is selected', () => { const accessorMock = jest.fn(() => { return (>{ - options: {}, + options: { parentElement: document.createElement('div') }, onDidAddPanel: jest.fn(), onDidRemovePanel: jest.fn(), element: document.createElement('div'), @@ -514,21 +514,21 @@ describe('tabsContainer', () => { }, { inDragMode: true } ); - expect(accessor.addFloatingGroup).toBeCalledTimes(1); - expect(eventPreventDefaultSpy).toBeCalledTimes(1); + expect(accessor.addFloatingGroup).toHaveBeenCalledTimes(1); + expect(eventPreventDefaultSpy).toHaveBeenCalledTimes(1); const event2 = new KeyboardEvent('mousedown', { shiftKey: false }); const eventPreventDefaultSpy2 = jest.spyOn(event2, 'preventDefault'); fireEvent(container, event2); - expect(accessor.addFloatingGroup).toBeCalledTimes(1); - expect(eventPreventDefaultSpy2).toBeCalledTimes(0); + expect(accessor.addFloatingGroup).toHaveBeenCalledTimes(1); + expect(eventPreventDefaultSpy2).toHaveBeenCalledTimes(0); }); test('that a tab that is already floating cannot be floated again', () => { const accessorMock = jest.fn(() => { return (>{ - options: {}, + options: { parentElement: document.createElement('div') }, onDidAddPanel: jest.fn(), onDidRemovePanel: jest.fn(), element: document.createElement('div'), @@ -580,7 +580,7 @@ describe('tabsContainer', () => { test('that selecting a tab with shift down will move that tab into a new floating group', () => { const accessorMock = jest.fn(() => { return (>{ - options: {}, + options: { parentElement: document.createElement('div') }, onDidAddPanel: jest.fn(), onDidRemovePanel: jest.fn(), element: document.createElement('div'), @@ -642,7 +642,7 @@ describe('tabsContainer', () => { test('pre header actions', () => { const accessorMock = jest.fn(() => { return (>{ - options: {}, + options: { parentElement: document.createElement('div') }, onDidAddPanel: jest.fn(), onDidRemovePanel: jest.fn(), element: document.createElement('div'), @@ -712,7 +712,7 @@ describe('tabsContainer', () => { test('left header actions', () => { const accessorMock = jest.fn(() => { return (>{ - options: {}, + options: { parentElement: document.createElement('div') }, onDidAddPanel: jest.fn(), onDidRemovePanel: jest.fn(), element: document.createElement('div'), @@ -782,7 +782,7 @@ describe('tabsContainer', () => { test('right header actions', () => { const accessorMock = jest.fn(() => { return (>{ - options: {}, + options: { parentElement: document.createElement('div') }, onDidAddPanel: jest.fn(), onDidRemovePanel: jest.fn(), element: document.createElement('div'), diff --git a/packages/dockview-core/src/__tests__/dockview/dockviewGroupPanelModel.spec.ts b/packages/dockview-core/src/__tests__/dockview/dockviewGroupPanelModel.spec.ts index 6c58659dd..704a26009 100644 --- a/packages/dockview-core/src/__tests__/dockview/dockviewGroupPanelModel.spec.ts +++ b/packages/dockview-core/src/__tests__/dockview/dockviewGroupPanelModel.spec.ts @@ -243,7 +243,7 @@ describe('dockviewGroupPanelModel', () => { options = {}; dockview = (>{ - options: {}, + options: { parentElement: document.createElement('div') }, createWatermarkComponent: () => new Watermark(), doSetGroupActive: jest.fn(), id: 'dockview-1', @@ -639,6 +639,7 @@ describe('dockviewGroupPanelModel', () => { id: 'testcomponentid', options: { showDndOverlay: jest.fn(), + parentElement: document.createElement('div'), }, getPanel: jest.fn(), onDidAddPanel: jest.fn(), @@ -699,6 +700,7 @@ describe('dockviewGroupPanelModel', () => { id: 'testcomponentid', options: { showDndOverlay: () => true, + parentElement: document.createElement('div'), }, getPanel: jest.fn(), onDidAddPanel: jest.fn(), @@ -790,6 +792,7 @@ describe('dockviewGroupPanelModel', () => { id: 'testcomponentid', options: { showDndOverlay: jest.fn(), + parentElement: document.createElement('div'), }, getPanel: jest.fn(), doSetGroupActive: jest.fn(), @@ -863,6 +866,7 @@ describe('dockviewGroupPanelModel', () => { id: 'testcomponentid', options: { showDndOverlay: jest.fn(), + parentElement: document.createElement('div'), }, getPanel: jest.fn(), doSetGroupActive: jest.fn(), @@ -941,6 +945,7 @@ describe('dockviewGroupPanelModel', () => { id: 'testcomponentid', options: { showDndOverlay: jest.fn(), + parentElement: document.createElement('div'), }, getPanel: jest.fn(), doSetGroupActive: jest.fn(), diff --git a/packages/dockview-core/src/__tests__/dockview/dockviewPanelModel.spec.ts b/packages/dockview-core/src/__tests__/dockview/dockviewPanelModel.spec.ts index aacf0ca93..01fb2813f 100644 --- a/packages/dockview-core/src/__tests__/dockview/dockviewPanelModel.spec.ts +++ b/packages/dockview-core/src/__tests__/dockview/dockviewPanelModel.spec.ts @@ -38,6 +38,7 @@ describe('dockviewGroupPanel', () => { accessorMock = jest.fn(() => { const partial: Partial = { options: { + parentElement: document.createElement('div'), components: { contentComponent: contentMock, }, @@ -131,6 +132,7 @@ describe('dockviewGroupPanel', () => { accessorMock = jest.fn(() => { const partial: Partial = { options: { + parentElement: document.createElement('div'), components: { contentComponent: contentMock, }, @@ -159,6 +161,7 @@ describe('dockviewGroupPanel', () => { accessorMock = jest.fn(() => { const partial: Partial = { options: { + parentElement: document.createElement('div'), components: { contentComponent: contentMock, }, @@ -190,6 +193,7 @@ describe('dockviewGroupPanel', () => { accessorMock = jest.fn(() => { const partial: Partial = { options: { + parentElement: document.createElement('div'), components: { contentComponent: contentMock, }, @@ -222,6 +226,7 @@ describe('dockviewGroupPanel', () => { accessorMock = jest.fn(() => { const partial: Partial = { options: { + parentElement: document.createElement('div'), components: { contentComponent: contentMock, }, @@ -244,6 +249,7 @@ describe('dockviewGroupPanel', () => { accessorMock = jest.fn(() => { const partial: Partial = { options: { + parentElement: document.createElement('div'), components: { contentComponent: jest.fn().mockImplementation(() => { return contentMock; @@ -271,6 +277,7 @@ describe('dockviewGroupPanel', () => { accessorMock = jest.fn(() => { const partial: Partial = { options: { + parentElement: document.createElement('div'), frameworkComponents: { contentComponent: contentMock, }, diff --git a/packages/dockview-core/src/dockview/dockviewComponent.scss b/packages/dockview-core/src/dockview/dockviewComponent.scss index 54a0e290d..22261c1f4 100644 --- a/packages/dockview-core/src/dockview/dockviewComponent.scss +++ b/packages/dockview-core/src/dockview/dockviewComponent.scss @@ -14,6 +14,32 @@ .dv-overlay-render-container { position: relative; } + + .split-view-container { + &.horizontal { + > .view-container > .view { + &:not(:last-child) { + border-right: var(--dv-group-gap-size) solid transparent; + } + + &:not(:first-child) { + border-left: var(--dv-group-gap-size) solid transparent; + } + } + } + + &.vertical { + > .view-container > .view { + &:not(:last-child) { + border-bottom: var(--dv-group-gap-size) solid transparent; + } + + &:not(:first-child) { + border-top: var(--dv-group-gap-size) solid transparent; + } + } + } + } } .groupview { diff --git a/packages/dockview-core/src/dockview/options.ts b/packages/dockview-core/src/dockview/options.ts index 96493354b..4bfc8da06 100644 --- a/packages/dockview-core/src/dockview/options.ts +++ b/packages/dockview-core/src/dockview/options.ts @@ -89,7 +89,7 @@ export interface DockviewComponentOptions extends DockviewRenderFunctions { group: DockviewGroupPanel ) => IHeaderActionsRenderer; singleTabMode?: 'fullwidth' | 'default'; - parentElement?: HTMLElement; + parentElement: HTMLElement; disableFloatingGroups?: boolean; floatingGroupBounds?: | 'boundedWithinViewport' diff --git a/packages/dockview-core/src/gridview/baseComponentGridview.scss b/packages/dockview-core/src/gridview/baseComponentGridview.scss new file mode 100644 index 000000000..4b12e2437 --- /dev/null +++ b/packages/dockview-core/src/gridview/baseComponentGridview.scss @@ -0,0 +1,4 @@ +.dv-root-wrapper { + height: 100%; + width: 100%; +} diff --git a/packages/dockview-core/src/gridview/baseComponentGridview.ts b/packages/dockview-core/src/gridview/baseComponentGridview.ts index 2e4159a9b..12e877d2f 100644 --- a/packages/dockview-core/src/gridview/baseComponentGridview.ts +++ b/packages/dockview-core/src/gridview/baseComponentGridview.ts @@ -1,7 +1,7 @@ import { Emitter, Event, TickDelayedEvent } from '../events'; import { getGridLocation, Gridview, IGridView } from './gridview'; import { Position } from '../dnd/droptarget'; -import { IValueDisposable } from '../lifecycle'; +import { Disposable, IValueDisposable } from '../lifecycle'; import { sequentialNumberGenerator } from '../math'; import { ISplitviewStyles, Orientation, Sizing } from '../splitview/splitview'; import { IPanel } from '../panel/types'; @@ -32,7 +32,7 @@ export interface BaseGridOptions { readonly proportionalLayout: boolean; readonly orientation: Orientation; readonly styles?: ISplitviewStyles; - readonly parentElement?: HTMLElement; + readonly parentElement: HTMLElement; readonly disableAutoResizing?: boolean; } @@ -134,7 +134,9 @@ export abstract class BaseGrid } constructor(options: BaseGridOptions) { - super(options.parentElement, options.disableAutoResizing); + super(document.createElement('div'), options.disableAutoResizing); + + options.parentElement.appendChild(this.element); this.gridview = new Gridview( !!options.proportionalLayout, @@ -147,6 +149,9 @@ export abstract class BaseGrid this.layout(0, 0, true); // set some elements height/widths this.addDisposables( + Disposable.from(() => { + this.element.parentElement?.removeChild(this.element); + }), this.gridview.onDidChange(() => { this._bufferOnDidLayoutChange.fire(); }), diff --git a/packages/dockview-core/src/gridview/options.ts b/packages/dockview-core/src/gridview/options.ts index be37ce4d2..c40429358 100644 --- a/packages/dockview-core/src/gridview/options.ts +++ b/packages/dockview-core/src/gridview/options.ts @@ -17,5 +17,5 @@ export interface GridviewComponentOptions { }; frameworkComponentFactory?: FrameworkFactory; styles?: ISplitviewStyles; - parentElement?: HTMLElement; + parentElement: HTMLElement; } diff --git a/packages/dockview-core/src/paneview/options.ts b/packages/dockview-core/src/paneview/options.ts index 3523528d1..1d784e4f7 100644 --- a/packages/dockview-core/src/paneview/options.ts +++ b/packages/dockview-core/src/paneview/options.ts @@ -25,5 +25,5 @@ export interface PaneviewComponentOptions { }; disableDnd?: boolean; showDndOverlay?: (event: PaneviewDndOverlayEvent) => boolean; - parentElement?: HTMLElement; + parentElement: HTMLElement; } diff --git a/packages/dockview-core/src/resizable.ts b/packages/dockview-core/src/resizable.ts index d06418528..a40b76990 100644 --- a/packages/dockview-core/src/resizable.ts +++ b/packages/dockview-core/src/resizable.ts @@ -17,19 +17,12 @@ export abstract class Resizable extends CompositeDisposable { this._disableResizing = value; } - constructor(parentElement?: HTMLElement, disableResizing = false) { + constructor(parentElement: HTMLElement, disableResizing = false) { super(); this._disableResizing = disableResizing; - if (parentElement) { - this._element = parentElement; - } else { - this._element = document.createElement('div'); - this._element.style.height = '100%'; - this._element.style.width = '100%'; - this._element.className = 'dv-resizable-container'; - } + this._element = parentElement; this.addDisposables( watchElementResize(this._element, (entry) => { diff --git a/packages/dockview-core/src/splitview/options.ts b/packages/dockview-core/src/splitview/options.ts index a0c179e97..823f5a688 100644 --- a/packages/dockview-core/src/splitview/options.ts +++ b/packages/dockview-core/src/splitview/options.ts @@ -28,5 +28,5 @@ export interface SplitviewComponentOptions extends SplitViewOptions { [componentName: string]: any; }; frameworkWrapper?: FrameworkFactory; - parentElement?: HTMLElement; + parentElement: HTMLElement; } diff --git a/packages/dockview-core/src/theme.scss b/packages/dockview-core/src/theme.scss index 6a10a1a35..007fc426e 100644 --- a/packages/dockview-core/src/theme.scss +++ b/packages/dockview-core/src/theme.scss @@ -12,6 +12,7 @@ @mixin dockview-theme-dark-mixin { @include dockview-theme-core-mixin(); + // --dv-group-view-background-color: #1e1e1e; // @@ -228,13 +229,7 @@ } @mixin dockview-design-replit-mixin { - &.dv-dockview { - padding: 3px; - } - - .view:has(> .groupview) { - padding: 3px; - } + --dv-group-gap-size: 3px; .dv-resize-container:has(> .groupview) { border-radius: 8px; diff --git a/packages/docs/sandboxes/resizecontainer-dockview/src/app.tsx b/packages/docs/sandboxes/resizecontainer-dockview/src/app.tsx index 95fb87945..25c397bc2 100644 --- a/packages/docs/sandboxes/resizecontainer-dockview/src/app.tsx +++ b/packages/docs/sandboxes/resizecontainer-dockview/src/app.tsx @@ -93,7 +93,7 @@ export const App: React.FC = (props: { theme?: string }) => { ); }; -const Container = () => { +const Container = (props: any) => { const [value, setValue] = React.useState('50'); return ( @@ -108,7 +108,7 @@ const Container = () => { value={value} />
    - +
    ); From 8f9d225c61b1d325ae555e285d4c4f76e553d2cf Mon Sep 17 00:00:00 2001 From: mathuo <6710312+mathuo@users.noreply.github.com> Date: Sun, 28 Jan 2024 14:23:22 +0000 Subject: [PATCH 19/20] feat: window popout enhancements --- .../dockview/dockviewComponent.spec.ts | 16 ++ .../dockview-core/src/api/component.api.ts | 4 +- .../src/api/dockviewGroupPanelApi.ts | 13 +- .../dockview-core/src/api/dockviewPanelApi.ts | 11 +- .../src/dockview/dockviewComponent.ts | 167 +++++++++++------- .../src/dockview/dockviewPopoutGroupPanel.ts | 18 +- packages/dockview-core/src/popoutWindow.ts | 61 ++++--- packages/docs/package.json | 1 + .../popoutgroup-dockview/src/app.tsx | 39 ++-- .../popoutgroup-dockview/src/popover.tsx | 14 +- 10 files changed, 190 insertions(+), 154 deletions(-) diff --git a/packages/dockview-core/src/__tests__/dockview/dockviewComponent.spec.ts b/packages/dockview-core/src/__tests__/dockview/dockviewComponent.spec.ts index 15e38dd61..af7b18617 100644 --- a/packages/dockview-core/src/__tests__/dockview/dockviewComponent.spec.ts +++ b/packages/dockview-core/src/__tests__/dockview/dockviewComponent.spec.ts @@ -111,6 +111,13 @@ describe('dockviewComponent', () => { }); describe('memory leakage', () => { + beforeEach(() => { + window.open = () => fromPartial({ + addEventListener: jest.fn(), + close: jest.fn(), + }); + }); + test('event leakage', () => { Emitter.setLeakageMonitorEnabled(true); @@ -4415,6 +4422,15 @@ describe('dockviewComponent', () => { }); describe('popout group', () => { + beforeEach(() => { + jest.spyOn(window, 'open').mockReturnValue( + fromPartial({ + addEventListener: jest.fn(), + close: jest.fn(), + }) + ); + }); + test('that can remove a popout group', () => { const container = document.createElement('div'); diff --git a/packages/dockview-core/src/api/component.api.ts b/packages/dockview-core/src/api/component.api.ts index f57fa9fa4..49d82f98f 100644 --- a/packages/dockview-core/src/api/component.api.ts +++ b/packages/dockview-core/src/api/component.api.ts @@ -833,7 +833,7 @@ export class DockviewApi implements CommonApi { onDidOpen?: (event: { id: string; window: Window }) => void; onWillClose?: (event: { id: string; window: Window }) => void; } - ): void { - this.component.addPopoutGroup(item, options); + ): Promise { + return this.component.addPopoutGroup(item, options); } } diff --git a/packages/dockview-core/src/api/dockviewGroupPanelApi.ts b/packages/dockview-core/src/api/dockviewGroupPanelApi.ts index 1a999301d..a5a8bb371 100644 --- a/packages/dockview-core/src/api/dockviewGroupPanelApi.ts +++ b/packages/dockview-core/src/api/dockviewGroupPanelApi.ts @@ -9,12 +9,9 @@ export interface DockviewGroupPanelApi extends GridviewPanelApi { readonly onDidLocationChange: Event; readonly location: DockviewGroupLocation; /** - * - * If you require the documents Window object you can call `document.defaultView`. - * - * @see https://developer.mozilla.org/en-US/docs/Web/API/Document/defaultView + * If you require the Window object */ - getDocument(): Document; + getWindow(): Window; moveTo(options: { group?: DockviewGroupPanel; position?: Position }): void; maximize(): void; isMaximized(): boolean; @@ -49,10 +46,10 @@ export class DockviewGroupPanelApiImpl extends GridviewPanelApiImpl { this.addDisposables(this._onDidLocationChange); } - getDocument(): Document { + getWindow(): Window { return this.location.type === 'popout' - ? this.location.getWindow().document - : window.document; + ? this.location.getWindow() + : window; } moveTo(options: { group?: DockviewGroupPanel; position?: Position }): void { diff --git a/packages/dockview-core/src/api/dockviewPanelApi.ts b/packages/dockview-core/src/api/dockviewPanelApi.ts index dffca9297..66772bd1d 100644 --- a/packages/dockview-core/src/api/dockviewPanelApi.ts +++ b/packages/dockview-core/src/api/dockviewPanelApi.ts @@ -44,12 +44,9 @@ export interface DockviewPanelApi isMaximized(): boolean; exitMaximized(): void; /** - * - * If you require the documents Window object you can call `document.defaultView`. - * - * @see https://developer.mozilla.org/en-US/docs/Web/API/Document/defaultView + * If you require the Window object */ - getDocument(): Document; + getWindow(): Window; } export class DockviewPanelApiImpl @@ -145,8 +142,8 @@ export class DockviewPanelApiImpl ); } - getDocument(): Document { - return this.group.api.getDocument(); + getWindow(): Window { + return this.group.api.getWindow(); } moveTo(options: { diff --git a/packages/dockview-core/src/dockview/dockviewComponent.ts b/packages/dockview-core/src/dockview/dockviewComponent.ts index e808bdb79..0f6ef22c0 100644 --- a/packages/dockview-core/src/dockview/dockviewComponent.ts +++ b/packages/dockview-core/src/dockview/dockviewComponent.ts @@ -12,7 +12,7 @@ import { } from '../dnd/droptarget'; import { tail, sequenceEquals, remove } from '../array'; import { DockviewPanel, IDockviewPanel } from './dockviewPanel'; -import { CompositeDisposable, Disposable } from '../lifecycle'; +import { CompositeDisposable, Disposable, IDisposable } from '../lifecycle'; import { Event, Emitter } from '../events'; import { Watermark } from './components/watermark/watermark'; import { @@ -74,7 +74,7 @@ const DEFAULT_ROOT_OVERLAY_MODEL: DroptargetOverlayModel = { size: { type: 'pixels', value: 20 }, }; -function getTheme(element: HTMLElement): string | undefined { +function getDockviewTheme(element: HTMLElement): string | undefined { function toClassList(element: HTMLElement) { const list: string[] = []; @@ -290,7 +290,7 @@ export interface IDockviewComponent extends IBaseGrid { onDidOpen?: (event: { id: string; window: Window }) => void; onWillClose?: (event: { id: string; window: Window }) => void; } - ): void; + ): Promise; } export class DockviewComponent @@ -332,7 +332,11 @@ export class DockviewComponent this._onDidActivePanelChange.event; private readonly _floatingGroups: DockviewFloatingGroupPanel[] = []; - private readonly _popoutGroups: DockviewPopoutGroupPanel[] = []; + private readonly _popoutGroups: { + window: PopoutWindow; + group: DockviewGroupPanel; + disposable: IDisposable; + }[] = []; private readonly _rootDropTarget: Droptarget; get orientation(): Orientation { @@ -413,7 +417,7 @@ export class DockviewComponent // iterate over a copy of the array since .dispose() mutates the original array for (const group of [...this._popoutGroups]) { - group.dispose(); + group.disposable.dispose(); } }) ); @@ -510,7 +514,7 @@ export class DockviewComponent this.updateWatermark(); } - addPopoutGroup( + async addPopoutGroup( item: DockviewPanel | DockviewGroupPanel, options?: { skipRemoveGroup?: boolean; @@ -519,72 +523,108 @@ export class DockviewComponent onDidOpen?: (event: { id: string; window: Window }) => void; onWillClose?: (event: { id: string; window: Window }) => void; } - ): void { - let group: DockviewGroupPanel; - let box: Box | undefined = options?.position; + ): Promise { + const theme = getDockviewTheme(this.gridview.element); - if (item instanceof DockviewPanel) { - group = this.createGroup(); - - this.removePanel(item, { - removeEmptyGroup: true, - skipDispose: true, - }); - - group.model.openPanel(item); - - if (!box) { - box = this.element.getBoundingClientRect(); - } - } else { - group = item; - - if (!box) { - box = group.element.getBoundingClientRect(); + const getBox: () => Box = () => { + if (options?.position) { + return options.position; } - const skip = - typeof options?.skipRemoveGroup === 'boolean' && - options.skipRemoveGroup; - - if (!skip) { - this.doRemoveGroup(item, { skipDispose: true }); + if (item instanceof DockviewGroupPanel) { + return item.element.getBoundingClientRect(); } - } - const theme = getTheme(this.gridview.element); + if (item.group) { + return item.group.element.getBoundingClientRect(); + } + return this.element.getBoundingClientRect(); + }; - const popoutWindow = new DockviewPopoutGroupPanel( - `${this.id}-${group.id}`, // globally unique within dockview - group, + const box: Box = getBox(); + + const groupId = + item instanceof DockviewGroupPanel + ? item.id + : this.getNextGroupId(); + + const _window = new PopoutWindow( + `${this.id}-${groupId}`, // globally unique within dockview + theme ?? '', { - className: theme ?? '', - popoutUrl: options?.popoutUrl ?? '/popout.html', - box: { - left: window.screenX + box.left, - top: window.screenY + box.top, - width: box.width, - height: box.height, - }, + url: options?.popoutUrl ?? '/popout.html', + left: window.screenX + box.left, + top: window.screenY + box.top, + width: box.width, + height: box.height, onDidOpen: options?.onDidOpen, onWillClose: options?.onWillClose, } ); - popoutWindow.addDisposables( - { - dispose: () => { - remove(this._popoutGroups, popoutWindow); - this.updateWatermark(); - }, - }, - popoutWindow.window.onDidClose(() => { - this.doAddGroup(group, [0]); + const disposables = new CompositeDisposable( + _window, + _window.onDidClose(() => { + disposables.dispose(); }) ); - this._popoutGroups.push(popoutWindow); - this.updateWatermark(); + const popoutContainer = await _window.open(); + + if (popoutContainer) { + let group: DockviewGroupPanel; + + if (item instanceof DockviewPanel) { + group = this.createGroup({ id: groupId }); + + this.removePanel(item, { + removeEmptyGroup: true, + skipDispose: true, + }); + + group.model.openPanel(item); + } else { + group = item; + + const skip = + typeof options?.skipRemoveGroup === 'boolean' && + options.skipRemoveGroup; + + if (!skip) { + this.doRemoveGroup(item, { skipDispose: true }); + } + } + + popoutContainer.appendChild(group.element); + + group.model.location = { + type: 'popout', + getWindow: () => _window.window!, + }; + + const value = { window: _window, group, disposable: disposables }; + + disposables.addDisposables( + { + dispose: () => { + group.model.location = { type: 'grid' }; + + remove(this._popoutGroups, value); + this.updateWatermark(); + }, + }, + _window.onDidClose(() => { + this.doAddGroup(group, [0]); + }) + ); + + this._popoutGroups.push(value); + this.updateWatermark(); + return true; + } else { + disposables.dispose(); + return false; + } } addFloatingGroup( @@ -1428,7 +1468,7 @@ export class DockviewComponent this._onDidRemoveGroup.fire(group); } - selectedGroup.dispose(); + selectedGroup.disposable.dispose(); if (!options?.skipActive && this._activeGroup === group) { const groups = Array.from(this._groups.values()); @@ -1595,7 +1635,7 @@ export class DockviewComponent if (!selectedPopoutGroup) { throw new Error('failed to find popout group'); } - selectedPopoutGroup.dispose(); + selectedPopoutGroup.disposable.dispose(); } } @@ -1629,6 +1669,15 @@ export class DockviewComponent } } + private getNextGroupId(): string { + let id = this.nextGroupId.next(); + while (this._groups.has(id)) { + id = this.nextGroupId.next(); + } + + return id; + } + createGroup(options?: GroupOptions): DockviewGroupPanel { if (!options) { options = {}; diff --git a/packages/dockview-core/src/dockview/dockviewPopoutGroupPanel.ts b/packages/dockview-core/src/dockview/dockviewPopoutGroupPanel.ts index 858178280..3116b56d9 100644 --- a/packages/dockview-core/src/dockview/dockviewPopoutGroupPanel.ts +++ b/packages/dockview-core/src/dockview/dockviewPopoutGroupPanel.ts @@ -1,14 +1,12 @@ import { CompositeDisposable } from '../lifecycle'; import { PopoutWindow } from '../popoutWindow'; import { Box } from '../types'; -import { DockviewGroupPanel } from './dockviewGroupPanel'; export class DockviewPopoutGroupPanel extends CompositeDisposable { readonly window: PopoutWindow; constructor( readonly id: string, - readonly group: DockviewGroupPanel, private readonly options: { className: string; popoutUrl: string; @@ -29,23 +27,17 @@ export class DockviewPopoutGroupPanel extends CompositeDisposable { onWillClose: this.options.onWillClose, }); - group.model.location = { - type: 'popout', - getWindow: () => this.window.window!, - }; - this.addDisposables( this.window, - { - dispose: () => { - group.model.location = { type: 'grid' }; - }, - }, this.window.onDidClose(() => { this.dispose(); }) ); + } - this.window.open(group.element); + open(): Promise { + const didOpen = this.window.open(); + + return didOpen; } } diff --git a/packages/dockview-core/src/popoutWindow.ts b/packages/dockview-core/src/popoutWindow.ts index 3f1531cac..33d3a1434 100644 --- a/packages/dockview-core/src/popoutWindow.ts +++ b/packages/dockview-core/src/popoutWindow.ts @@ -66,7 +66,7 @@ export class PopoutWindow extends CompositeDisposable { } } - open(content: HTMLElement): void { + async open(): Promise { if (this._window) { throw new Error('instance of popout window is already open'); } @@ -88,9 +88,13 @@ export class PopoutWindow extends CompositeDisposable { const externalWindow = window.open(url, this.target, features); if (!externalWindow) { - return; + /** + * Popup blocked + */ + return null; } + const disposable = new CompositeDisposable(); this._window = { value: externalWindow, disposable }; @@ -104,36 +108,41 @@ export class PopoutWindow extends CompositeDisposable { * @see https://developer.mozilla.org/en-US/docs/Web/API/Window/beforeunload_event */ this.close(); - }) - ); - - externalWindow.addEventListener('load', () => { - /** - * @see https://developer.mozilla.org/en-US/docs/Web/API/Window/load_event - */ - - const externalDocument = externalWindow.document; - externalDocument.title = document.title; - - const container = this.createPopoutWindowContainer(); - container.classList.add(this.className); - container.appendChild(content); - - // externalDocument.body.replaceChildren(container); - externalDocument.body.appendChild(container); - externalDocument.body.classList.add(this.className); - - addStyles(externalDocument, window.document.styleSheets); - - externalWindow.addEventListener('beforeunload', () => { + }), + addDisposableWindowListener(externalWindow, 'beforeunload', () => { /** * @see https://developer.mozilla.org/en-US/docs/Web/API/Window/beforeunload_event */ this.close(); - }); + }) + ); + + const container = this.createPopoutWindowContainer(); + container.classList.add(this.className); + + this.options.onDidOpen?.({ + id: this.target, + window: externalWindow, }); - this.options.onDidOpen?.({ id: this.target, window: externalWindow }); + return new Promise((resolve) => { + externalWindow.addEventListener('load', () => { + /** + * @see https://developer.mozilla.org/en-US/docs/Web/API/Window/load_event + */ + + const externalDocument = externalWindow.document; + externalDocument.title = document.title; + + // externalDocument.body.replaceChildren(container); + externalDocument.body.appendChild(container); + externalDocument.body.classList.add(this.className); + + addStyles(externalDocument, window.document.styleSheets); + + resolve(container); + }); + }); } private createPopoutWindowContainer(): HTMLElement { diff --git a/packages/docs/package.json b/packages/docs/package.json index efc9d4128..6f40f715e 100644 --- a/packages/docs/package.json +++ b/packages/docs/package.json @@ -34,6 +34,7 @@ "dockview": "^1.9.2", "prism-react-renderer": "^2.3.1", "react-dnd": "^16.0.1", + "react-laag": "^2.0.5", "recoil": "^0.7.7", "source-map-loader": "^4.0.2", "uuid": "^9.0.1" diff --git a/packages/docs/sandboxes/popoutgroup-dockview/src/app.tsx b/packages/docs/sandboxes/popoutgroup-dockview/src/app.tsx index 57fe7e8be..fbca7560d 100644 --- a/packages/docs/sandboxes/popoutgroup-dockview/src/app.tsx +++ b/packages/docs/sandboxes/popoutgroup-dockview/src/app.tsx @@ -6,49 +6,30 @@ import { IDockviewPanelProps, SerializedDockview, DockviewPanelApi, - DockviewGroupLocation, } from 'dockview'; import * as React from 'react'; -import * as ReactDOM from 'react-dom'; import { Icon } from './utils'; import { PopoverMenu } from './popover'; -function usePopoutWindowContext(api: DockviewPanelApi): Window { - const [location, setLocation] = React.useState( - api.location - ); +function usePanelWindowObject(api: DockviewPanelApi): Window { + const [document, setDocument] = React.useState(api.getWindow()); React.useEffect(() => { const disposable = api.onDidLocationChange((event) => { - setLocation(event.location); + setDocument(api.getWindow()); }); return () => { disposable.dispose(); }; - }); + }, [api]); - const windowContext = React.useMemo(() => { - if (location.type === 'popout') { - return location.getWindow(); - } - return window; - }, [location]); - - return windowContext; + return document; } const components = { default: (props: IDockviewPanelProps<{ title: string }>) => { - const windowContext = usePopoutWindowContext(props.api); - - React.useEffect(() => { - setTimeout(() => { - const a = windowContext.document.createElement('div'); - a.className = 'aaa'; - windowContext.document.body.appendChild(a); - }, 5000); - }, [windowContext]); + const _window = usePanelWindowObject(props.api); const [reset, setReset] = React.useState(false); @@ -62,7 +43,7 @@ const components = { > - {!reset && } + {!reset && } {props.api.title} ); @@ -258,12 +239,12 @@ const LeftComponent = (props: IDockviewHeaderActionsProps) => { const RightComponent = (props: IDockviewHeaderActionsProps) => { const [popout, setPopout] = React.useState( - props.api.location === 'popout' + props.api.location.type === 'popout' ); React.useEffect(() => { const disposable = props.group.api.onDidLocationChange((event) => [ - setPopout(event.location === 'popout'), + setPopout(event.location.type === 'popout'), ]); return () => { diff --git a/packages/docs/sandboxes/popoutgroup-dockview/src/popover.tsx b/packages/docs/sandboxes/popoutgroup-dockview/src/popover.tsx index 3b0b8d1f4..9d9663443 100644 --- a/packages/docs/sandboxes/popoutgroup-dockview/src/popover.tsx +++ b/packages/docs/sandboxes/popoutgroup-dockview/src/popover.tsx @@ -3,7 +3,7 @@ import { motion, AnimatePresence } from 'framer-motion'; import * as React from 'react'; import { DockviewPanelApi } from 'dockview'; -export function PopoverMenu(props: { api: DockviewPanelApi }) { +export function PopoverMenu(props: { window: Window }) { const [isOpen, setOpen] = React.useState(false); // helper function to close the menu @@ -11,11 +11,6 @@ export function PopoverMenu(props: { api: DockviewPanelApi }) { setOpen(false); } - const _window = - props.api.location.type === 'popout' - ? props.api.location.getWindow() - : undefined; - const { renderLayer, triggerProps, layerProps, arrowProps } = useLayer({ isOpen, onOutsideClick: close, // close the menu when the user clicks outside @@ -26,15 +21,14 @@ export function PopoverMenu(props: { api: DockviewPanelApi }) { triggerOffset: 12, // keep some distance to the trigger containerOffset: 16, // give the menu some room to breath relative to the container arrowOffset: 16, // let the arrow have some room to breath also, - environment: _window, - container: _window + environment: props.window, + container: props.window ? () => { - const el = _window.document.body; + const el = props.window.document.body; Object.setPrototypeOf(el, HTMLElement.prototype); return el; } : undefined, - // container: props.window.document.body }); // Again, we're using framer-motion for the transition effect From 20c1a66d205855e0b85d2fd457a1047e6d6a77b6 Mon Sep 17 00:00:00 2001 From: mathuo <6710312+mathuo@users.noreply.github.com> Date: Mon, 29 Jan 2024 20:57:15 +0000 Subject: [PATCH 20/20] feat: popout group enhancements --- .../dockview/dockviewComponent.spec.ts | 214 ++++++++++-------- .../gridview/gridviewComponent.spec.ts | 2 +- .../dockview-core/src/api/component.api.ts | 2 +- packages/dockview-core/src/api/panelApi.ts | 59 +++-- .../components/titlebar/tabsContainer.ts | 3 +- .../src/dockview/dockviewComponent.ts | 186 +++++++++------ .../src/dockview/dockviewGroupPanelModel.ts | 1 + .../src/dockview/dockviewPopoutGroupPanel.ts | 43 ---- .../dockview-core/src/gridview/gridview.ts | 28 ++- .../src/gridview/gridviewPanel.ts | 13 +- packages/dockview-core/src/index.ts | 2 - packages/dockview-core/src/lifecycle.ts | 10 +- packages/dockview-core/src/popoutWindow.ts | 34 ++- .../src/splitview/splitviewPanel.ts | 6 +- packages/dockview/src/gridview/view.ts | 5 +- 15 files changed, 335 insertions(+), 273 deletions(-) delete mode 100644 packages/dockview-core/src/dockview/dockviewPopoutGroupPanel.ts diff --git a/packages/dockview-core/src/__tests__/dockview/dockviewComponent.spec.ts b/packages/dockview-core/src/__tests__/dockview/dockviewComponent.spec.ts index af7b18617..bdf032b03 100644 --- a/packages/dockview-core/src/__tests__/dockview/dockviewComponent.spec.ts +++ b/packages/dockview-core/src/__tests__/dockview/dockviewComponent.spec.ts @@ -110,109 +110,109 @@ describe('dockviewComponent', () => { window.open = jest.fn(); // not implemented by jest }); - describe('memory leakage', () => { - beforeEach(() => { - window.open = () => fromPartial({ - addEventListener: jest.fn(), - close: jest.fn(), - }); - }); + // describe('memory leakage', () => { + // beforeEach(() => { + // window.open = () => fromPartial({ + // addEventListener: jest.fn(), + // close: jest.fn(), + // }); + // }); - test('event leakage', () => { - Emitter.setLeakageMonitorEnabled(true); + // test('event leakage', () => { + // Emitter.setLeakageMonitorEnabled(true); - dockview = new DockviewComponent({ - parentElement: container, - components: { - default: PanelContentPartTest, - }, - }); + // dockview = new DockviewComponent({ + // parentElement: container, + // components: { + // default: PanelContentPartTest, + // }, + // }); - dockview.layout(500, 1000); + // dockview.layout(500, 1000); - const panel1 = dockview.addPanel({ - id: 'panel1', - component: 'default', - }); + // const panel1 = dockview.addPanel({ + // id: 'panel1', + // component: 'default', + // }); - const panel2 = dockview.addPanel({ - id: 'panel2', - component: 'default', - }); + // const panel2 = dockview.addPanel({ + // id: 'panel2', + // component: 'default', + // }); - dockview.removePanel(panel2); + // dockview.removePanel(panel2); - const panel3 = dockview.addPanel({ - id: 'panel3', - component: 'default', - position: { - direction: 'right', - referencePanel: 'panel1', - }, - }); + // const panel3 = dockview.addPanel({ + // id: 'panel3', + // component: 'default', + // position: { + // direction: 'right', + // referencePanel: 'panel1', + // }, + // }); - const panel4 = dockview.addPanel({ - id: 'panel4', - component: 'default', - position: { - direction: 'above', - }, - }); + // const panel4 = dockview.addPanel({ + // id: 'panel4', + // component: 'default', + // position: { + // direction: 'above', + // }, + // }); - dockview.moveGroupOrPanel( - panel4.group, - panel3.group.id, - panel3.id, - 'center' - ); + // dockview.moveGroupOrPanel( + // panel4.group, + // panel3.group.id, + // panel3.id, + // 'center' + // ); - dockview.addPanel({ - id: 'panel5', - component: 'default', - floating: true, - }); + // dockview.addPanel({ + // id: 'panel5', + // component: 'default', + // floating: true, + // }); - const panel6 = dockview.addPanel({ - id: 'panel6', - component: 'default', - position: { - referencePanel: 'panel5', - direction: 'within', - }, - }); + // const panel6 = dockview.addPanel({ + // id: 'panel6', + // component: 'default', + // position: { + // referencePanel: 'panel5', + // direction: 'within', + // }, + // }); - dockview.addFloatingGroup(panel4.api.group); + // dockview.addFloatingGroup(panel4.api.group); - dockview.addPopoutGroup(panel6); + // dockview.addPopoutGroup(panel6); - dockview.moveGroupOrPanel( - panel1.group, - panel6.group.id, - panel6.id, - 'center' - ); + // dockview.moveGroupOrPanel( + // panel1.group, + // panel6.group.id, + // panel6.id, + // 'center' + // ); - dockview.moveGroupOrPanel( - panel4.group, - panel6.group.id, - panel6.id, - 'center' - ); + // dockview.moveGroupOrPanel( + // panel4.group, + // panel6.group.id, + // panel6.id, + // 'center' + // ); - dockview.dispose(); + // dockview.dispose(); - if (Emitter.MEMORY_LEAK_WATCHER.size > 0) { - for (const entry of Array.from( - Emitter.MEMORY_LEAK_WATCHER.events - )) { - console.log('disposal', entry[1]); - } - throw new Error('not all listeners disposed'); - } + // if (Emitter.MEMORY_LEAK_WATCHER.size > 0) { + // for (const entry of Array.from( + // Emitter.MEMORY_LEAK_WATCHER.events + // )) { + // console.log('disposal', entry[1]); + // } + // throw new Error('not all listeners disposed'); + // } - Emitter.setLeakageMonitorEnabled(false); - }); - }); + // Emitter.setLeakageMonitorEnabled(false); + // }); + // }); test('duplicate panel', () => { dockview.layout(500, 1000); @@ -4425,13 +4425,22 @@ describe('dockviewComponent', () => { beforeEach(() => { jest.spyOn(window, 'open').mockReturnValue( fromPartial({ - addEventListener: jest.fn(), + document: fromPartial({ + body: document.createElement('body'), + }), + addEventListener: jest + .fn() + .mockImplementation((name, cb) => { + if (name === 'load') { + cb(); + } + }), close: jest.fn(), }) ); }); - test('that can remove a popout group', () => { + test('that can remove a popout group', async () => { const container = document.createElement('div'); const dockview = new DockviewComponent({ @@ -4452,10 +4461,10 @@ describe('dockviewComponent', () => { component: 'default', }); - dockview.addPopoutGroup(panel1); + await dockview.addPopoutGroup(panel1); expect(dockview.panels.length).toBe(1); - expect(dockview.groups.length).toBe(1); + expect(dockview.groups.length).toBe(2); expect(panel1.api.group.api.location.type).toBe('popout'); dockview.removePanel(panel1); @@ -4464,7 +4473,7 @@ describe('dockviewComponent', () => { expect(dockview.groups.length).toBe(0); }); - test('add a popout group', () => { + test('add a popout group', async () => { const container = document.createElement('div'); const dockview = new DockviewComponent({ @@ -4495,15 +4504,15 @@ describe('dockviewComponent', () => { expect(dockview.groups.length).toBe(1); expect(dockview.panels.length).toBe(2); - dockview.addPopoutGroup(panel2.group); + await dockview.addPopoutGroup(panel2.group); expect(panel1.group.api.location.type).toBe('popout'); expect(panel2.group.api.location.type).toBe('popout'); - expect(dockview.groups.length).toBe(1); + expect(dockview.groups.length).toBe(2); expect(dockview.panels.length).toBe(2); }); - test('move from fixed to popout group and back', () => { + test('move from fixed to popout group and back', async () => { const container = document.createElement('div'); const dockview = new DockviewComponent({ @@ -4543,12 +4552,12 @@ describe('dockviewComponent', () => { expect(dockview.groups.length).toBe(2); expect(dockview.panels.length).toBe(3); - dockview.addPopoutGroup(panel2.group); + await dockview.addPopoutGroup(panel2.group); expect(panel1.group.api.location.type).toBe('popout'); expect(panel2.group.api.location.type).toBe('popout'); expect(panel3.group.api.location.type).toBe('grid'); - expect(dockview.groups.length).toBe(2); + expect(dockview.groups.length).toBe(3); expect(dockview.panels.length).toBe(3); dockview.moveGroupOrPanel( @@ -4561,7 +4570,20 @@ describe('dockviewComponent', () => { expect(panel1.group.api.location.type).toBe('popout'); expect(panel2.group.api.location.type).toBe('grid'); expect(panel3.group.api.location.type).toBe('grid'); - expect(dockview.groups.length).toBe(3); + expect(dockview.groups.length).toBe(4); + expect(dockview.panels.length).toBe(3); + + dockview.moveGroupOrPanel( + panel3.api.group, + panel1.api.group.id, + panel1.api.id, + 'center' + ); + + expect(panel1.group.api.location.type).toBe('grid'); + expect(panel2.group.api.location.type).toBe('grid'); + expect(panel3.group.api.location.type).toBe('grid'); + expect(dockview.groups.length).toBe(2); expect(dockview.panels.length).toBe(3); }); }); diff --git a/packages/dockview-core/src/__tests__/gridview/gridviewComponent.spec.ts b/packages/dockview-core/src/__tests__/gridview/gridviewComponent.spec.ts index c54341cc0..d0873eba0 100644 --- a/packages/dockview-core/src/__tests__/gridview/gridviewComponent.spec.ts +++ b/packages/dockview-core/src/__tests__/gridview/gridviewComponent.spec.ts @@ -268,7 +268,7 @@ describe('gridview', () => { ], }, }, - activePanel: 'panel_1', + activePanel: 'panel_2', }); }); diff --git a/packages/dockview-core/src/api/component.api.ts b/packages/dockview-core/src/api/component.api.ts index 49d82f98f..e5faa8cd5 100644 --- a/packages/dockview-core/src/api/component.api.ts +++ b/packages/dockview-core/src/api/component.api.ts @@ -833,7 +833,7 @@ export class DockviewApi implements CommonApi { onDidOpen?: (event: { id: string; window: Window }) => void; onWillClose?: (event: { id: string; window: Window }) => void; } - ): Promise { + ): Promise { return this.component.addPopoutGroup(item, options); } } diff --git a/packages/dockview-core/src/api/panelApi.ts b/packages/dockview-core/src/api/panelApi.ts index 95d35a3b8..57e80f214 100644 --- a/packages/dockview-core/src/api/panelApi.ts +++ b/packages/dockview-core/src/api/panelApi.ts @@ -14,6 +14,10 @@ export interface VisibilityEvent { readonly isVisible: boolean; } +export interface HiddenEvent { + readonly isHidden: boolean; +} + export interface ActiveEvent { readonly isActive: boolean; } @@ -24,7 +28,7 @@ export interface PanelApi { readonly onDidFocusChange: Event; readonly onDidVisibilityChange: Event; readonly onDidActiveChange: Event; - setVisible(isVisible: boolean): void; + readonly onDidHiddenChange: Event; setActive(): void; updateParameters(parameters: Parameters): void; /** @@ -43,6 +47,10 @@ export interface PanelApi { * Whether the panel is visible */ readonly isVisible: boolean; + /** + * Whether the panel is hidden + */ + readonly isHidden: boolean; /** * The panel width in pixels */ @@ -60,6 +68,7 @@ export class PanelApiImpl extends CompositeDisposable implements PanelApi { private _isFocused = false; private _isActive = false; private _isVisible = true; + private _isHidden = false; private _width = 0; private _height = 0; @@ -69,56 +78,59 @@ export class PanelApiImpl extends CompositeDisposable implements PanelApi { replay: true, }); readonly onDidDimensionsChange = this._onDidDimensionChange.event; - // + readonly _onDidChangeFocus = new Emitter({ replay: true, }); readonly onDidFocusChange: Event = this._onDidChangeFocus.event; - // + readonly _onFocusEvent = new Emitter(); readonly onFocusEvent: Event = this._onFocusEvent.event; - // + readonly _onDidVisibilityChange = new Emitter({ replay: true, }); readonly onDidVisibilityChange: Event = this._onDidVisibilityChange.event; - // - readonly _onVisibilityChange = new Emitter(); - readonly onVisibilityChange: Event = - this._onVisibilityChange.event; - // + readonly _onDidHiddenChange = new Emitter(); + readonly onDidHiddenChange: Event = + this._onDidHiddenChange.event; + readonly _onDidActiveChange = new Emitter({ replay: true, }); readonly onDidActiveChange: Event = this._onDidActiveChange.event; - // + readonly _onActiveChange = new Emitter(); readonly onActiveChange: Event = this._onActiveChange.event; - // + readonly _onUpdateParameters = new Emitter(); readonly onUpdateParameters: Event = this._onUpdateParameters.event; - // - get isFocused() { + get isFocused(): boolean { return this._isFocused; } - get isActive() { + get isActive(): boolean { return this._isActive; } - get isVisible() { + + get isVisible(): boolean { return this._isVisible; } - get width() { + get isHidden(): boolean { + return this._isHidden; + } + + get width(): number { return this._width; } - get height() { + get height(): number { return this._height; } @@ -135,6 +147,9 @@ export class PanelApiImpl extends CompositeDisposable implements PanelApi { this.onDidVisibilityChange((event) => { this._isVisible = event.isVisible; }), + this.onDidHiddenChange((event) => { + this._isHidden = event.isHidden; + }), this.onDidDimensionsChange((event) => { this._width = event.width; this._height = event.height; @@ -146,7 +161,7 @@ export class PanelApiImpl extends CompositeDisposable implements PanelApi { this._onDidActiveChange, this._onFocusEvent, this._onActiveChange, - this._onVisibilityChange, + this._onDidHiddenChange, this._onUpdateParameters ); } @@ -161,8 +176,8 @@ export class PanelApiImpl extends CompositeDisposable implements PanelApi { ); } - setVisible(isVisible: boolean) { - this._onVisibilityChange.fire({ isVisible }); + setHidden(isHidden: boolean): void { + this._onDidHiddenChange.fire({ isHidden }); } setActive(): void { @@ -172,8 +187,4 @@ export class PanelApiImpl extends CompositeDisposable implements PanelApi { updateParameters(parameters: Parameters): void { this._onUpdateParameters.fire(parameters); } - - dispose() { - super.dispose(); - } } diff --git a/packages/dockview-core/src/dockview/components/titlebar/tabsContainer.ts b/packages/dockview-core/src/dockview/components/titlebar/tabsContainer.ts index 6a7caac29..60ae6ed23 100644 --- a/packages/dockview-core/src/dockview/components/titlebar/tabsContainer.ts +++ b/packages/dockview-core/src/dockview/components/titlebar/tabsContainer.ts @@ -350,7 +350,8 @@ export class TabsContainer !this.accessor.options.disableFloatingGroups; const isFloatingWithOnePanel = - this.group.api.location.type === 'floating' && this.size === 1; + this.group.api.location.type === 'floating' && + this.size === 1; if ( isFloatingGroupsEnabled && diff --git a/packages/dockview-core/src/dockview/dockviewComponent.ts b/packages/dockview-core/src/dockview/dockviewComponent.ts index 0f6ef22c0..3366da217 100644 --- a/packages/dockview-core/src/dockview/dockviewComponent.ts +++ b/packages/dockview-core/src/dockview/dockviewComponent.ts @@ -58,7 +58,6 @@ import { TabDragEvent, } from './components/titlebar/tabsContainer'; import { Box } from '../types'; -import { DockviewPopoutGroupPanel } from './dockviewPopoutGroupPanel'; import { DEFAULT_FLOATING_GROUP_OVERFLOW_SIZE, DEFAULT_FLOATING_GROUP_POSITION, @@ -290,7 +289,7 @@ export interface IDockviewComponent extends IBaseGrid { onDidOpen?: (event: { id: string; window: Window }) => void; onWillClose?: (event: { id: string; window: Window }) => void; } - ): Promise; + ): Promise; } export class DockviewComponent @@ -334,7 +333,8 @@ export class DockviewComponent private readonly _floatingGroups: DockviewFloatingGroupPanel[] = []; private readonly _popoutGroups: { window: PopoutWindow; - group: DockviewGroupPanel; + popoutGroup: DockviewGroupPanel; + referenceGroup: DockviewGroupPanel; disposable: IDisposable; }[] = []; private readonly _rootDropTarget: Droptarget; @@ -514,7 +514,7 @@ export class DockviewComponent this.updateWatermark(); } - async addPopoutGroup( + addPopoutGroup( item: DockviewPanel | DockviewGroupPanel, options?: { skipRemoveGroup?: boolean; @@ -523,10 +523,28 @@ export class DockviewComponent onDidOpen?: (event: { id: string; window: Window }) => void; onWillClose?: (event: { id: string; window: Window }) => void; } - ): Promise { - const theme = getDockviewTheme(this.gridview.element); + ): Promise { + if (item instanceof DockviewPanel && item.group.size === 1) { + return this.addPopoutGroup(item.group); + } - const getBox: () => Box = () => { + const theme = getDockviewTheme(this.gridview.element); + const element = this.element; + + function moveGroupWithoutDestroying(options: { + from: DockviewGroupPanel; + to: DockviewGroupPanel; + }) { + const panels = [...options.from.panels].map((panel) => + options.from.model.removePanel(panel) + ); + + panels.forEach((panel) => { + options.to.model.openPanel(panel); + }); + } + + function getBox(): Box { if (options?.position) { return options.position; } @@ -538,18 +556,17 @@ export class DockviewComponent if (item.group) { return item.group.element.getBoundingClientRect(); } - return this.element.getBoundingClientRect(); - }; + return element.getBoundingClientRect(); + } const box: Box = getBox(); - const groupId = - item instanceof DockviewGroupPanel - ? item.id - : this.getNextGroupId(); + const groupId = this.getNextGroupId(); //item.id; + + item.api.setHidden(true); const _window = new PopoutWindow( - `${this.id}-${groupId}`, // globally unique within dockview + `${this.id}-${groupId}`, // unique id theme ?? '', { url: options?.popoutUrl ?? '/popout.html', @@ -562,69 +579,85 @@ export class DockviewComponent } ); - const disposables = new CompositeDisposable( + const popoutWindowDisposable = new CompositeDisposable( _window, _window.onDidClose(() => { - disposables.dispose(); + popoutWindowDisposable.dispose(); }) ); - const popoutContainer = await _window.open(); - - if (popoutContainer) { - let group: DockviewGroupPanel; - - if (item instanceof DockviewPanel) { - group = this.createGroup({ id: groupId }); - - this.removePanel(item, { - removeEmptyGroup: true, - skipDispose: true, - }); - - group.model.openPanel(item); - } else { - group = item; - - const skip = - typeof options?.skipRemoveGroup === 'boolean' && - options.skipRemoveGroup; - - if (!skip) { - this.doRemoveGroup(item, { skipDispose: true }); + return _window + .open() + .then((popoutContainer) => { + if (_window.isDisposed) { + return; } - } - popoutContainer.appendChild(group.element); + if (popoutContainer === null) { + popoutWindowDisposable.dispose(); + return; + } - group.model.location = { - type: 'popout', - getWindow: () => _window.window!, - }; + const referenceGroup = + item instanceof DockviewPanel ? item.group : item; - const value = { window: _window, group, disposable: disposables }; + const group = this.createGroup({ id: groupId }); - disposables.addDisposables( - { - dispose: () => { - group.model.location = { type: 'grid' }; + if (item instanceof DockviewPanel) { + const panel = referenceGroup.model.removePanel(item); + group.model.openPanel(panel); + } else { + moveGroupWithoutDestroying({ + from: referenceGroup, + to: group, + }); + referenceGroup.api.setHidden(false); + } - remove(this._popoutGroups, value); - this.updateWatermark(); - }, - }, - _window.onDidClose(() => { - this.doAddGroup(group, [0]); - }) - ); + popoutContainer.appendChild(group.element); - this._popoutGroups.push(value); - this.updateWatermark(); - return true; - } else { - disposables.dispose(); - return false; - } + group.model.location = { + type: 'popout', + getWindow: () => _window.window!, + }; + + const value = { + window: _window, + popoutGroup: group, + referenceGroup, + disposable: popoutWindowDisposable, + }; + + popoutWindowDisposable.addDisposables( + Disposable.from(() => { + if (this.getPanel(referenceGroup.id)) { + moveGroupWithoutDestroying({ + from: group, + to: referenceGroup, + }); + + if (referenceGroup.api.isHidden) { + referenceGroup.api.setHidden(false); + } + + this.doRemoveGroup(group); + } else { + const removedGroup = this.doRemoveGroup(group, { + skipDispose: true, + skipActive: true, + }); + removedGroup.model.location = { type: 'grid' }; + this.doAddGroup(removedGroup, [0]); + } + }) + ); + + this._popoutGroups.push(value); + this.updateWatermark(); + }) + .catch((err) => { + console.error(err); + }); } addFloatingGroup( @@ -923,7 +956,7 @@ export class DockviewComponent const popoutGroups: SerializedPopoutGroup[] = this._popoutGroups.map( (group) => { return { - data: group.group.toJSON() as GroupPanelViewState, + data: group.popoutGroup.toJSON() as GroupPanelViewState, position: group.window.dimensions(), }; } @@ -1307,8 +1340,9 @@ export class DockviewComponent private updateWatermark(): void { if ( - this.groups.filter((x) => x.api.location.type === 'grid').length === - 0 + this.groups.filter( + (x) => x.api.location.type === 'grid' && !x.api.isHidden + ).length === 0 ) { if (!this.watermark) { this.watermark = this.createWatermarkComponent(); @@ -1458,12 +1492,14 @@ export class DockviewComponent if (group.api.location.type === 'popout') { const selectedGroup = this._popoutGroups.find( - (_) => _.group === group + (_) => _.popoutGroup === group ); if (selectedGroup) { if (!options?.skipDispose) { - selectedGroup.group.dispose(); + this.doRemoveGroup(selectedGroup.referenceGroup); + + selectedGroup.popoutGroup.dispose(); this._groups.delete(group.id); this._onDidRemoveGroup.fire(group); } @@ -1478,7 +1514,8 @@ export class DockviewComponent ); } - return selectedGroup.group; + this.updateWatermark(); + return selectedGroup.popoutGroup; } throw new Error('failed to find popout group'); @@ -1630,7 +1667,7 @@ export class DockviewComponent } case 'popout': { const selectedPopoutGroup = this._popoutGroups.find( - (x) => x.group === sourceGroup + (x) => x.popoutGroup === sourceGroup ); if (!selectedPopoutGroup) { throw new Error('failed to find popout group'); @@ -1700,7 +1737,7 @@ export class DockviewComponent } const view = new DockviewGroupPanel(this, id, options); - view.init({ params: {}, accessor: null }); // required to initialized .part and allow for correct disposal of group + view.init({ params: {}, accessor: this }); if (!this._groups.has(view.id)) { const disposable = new CompositeDisposable( @@ -1735,8 +1772,7 @@ export class DockviewComponent this._groups.set(view.id, { value: view, disposable }); } - // TODO: must be called after the above listeners have been setup, - // not an ideal pattern + // TODO: must be called after the above listeners have been setup, not an ideal pattern view.initialize(); return view; diff --git a/packages/dockview-core/src/dockview/dockviewGroupPanelModel.ts b/packages/dockview-core/src/dockview/dockviewGroupPanelModel.ts index a80a7baa0..120bc1b87 100644 --- a/packages/dockview-core/src/dockview/dockviewGroupPanelModel.ts +++ b/packages/dockview-core/src/dockview/dockviewGroupPanelModel.ts @@ -838,6 +838,7 @@ export class DockviewGroupPanelModel this.watermark?.element.remove(); this.watermark?.dispose?.(); + this.watermark = undefined; for (const panel of this.panels) { panel.dispose(); diff --git a/packages/dockview-core/src/dockview/dockviewPopoutGroupPanel.ts b/packages/dockview-core/src/dockview/dockviewPopoutGroupPanel.ts deleted file mode 100644 index 3116b56d9..000000000 --- a/packages/dockview-core/src/dockview/dockviewPopoutGroupPanel.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { CompositeDisposable } from '../lifecycle'; -import { PopoutWindow } from '../popoutWindow'; -import { Box } from '../types'; - -export class DockviewPopoutGroupPanel extends CompositeDisposable { - readonly window: PopoutWindow; - - constructor( - readonly id: string, - private readonly options: { - className: string; - popoutUrl: string; - box: Box; - onDidOpen?: (event: { id: string; window: Window }) => void; - onWillClose?: (event: { id: string; window: Window }) => void; - } - ) { - super(); - - this.window = new PopoutWindow(id, options.className ?? '', { - url: this.options.popoutUrl, - left: this.options.box.left, - top: this.options.box.top, - width: this.options.box.width, - height: this.options.box.height, - onDidOpen: this.options.onDidOpen, - onWillClose: this.options.onWillClose, - }); - - this.addDisposables( - this.window, - this.window.onDidClose(() => { - this.dispose(); - }) - ); - } - - open(): Promise { - const didOpen = this.window.open(); - - return didOpen; - } -} diff --git a/packages/dockview-core/src/gridview/gridview.ts b/packages/dockview-core/src/gridview/gridview.ts index f34fe2672..48138d1e8 100644 --- a/packages/dockview-core/src/gridview/gridview.ts +++ b/packages/dockview-core/src/gridview/gridview.ts @@ -273,7 +273,9 @@ export class Gridview implements IDisposable { readonly element: HTMLElement; private _root: BranchNode | undefined; - private _maximizedNode: LeafNode | undefined = undefined; + private _maximizedNode: + | { leaf: LeafNode; hiddenOnMaximize: LeafNode[] } + | undefined = undefined; private readonly disposable: MutableDisposable = new MutableDisposable(); private readonly _onDidChange = new Emitter<{ @@ -329,7 +331,7 @@ export class Gridview implements IDisposable { } maximizedView(): IGridView | undefined { - return this._maximizedNode?.view; + return this._maximizedNode?.leaf.view; } hasMaximizedView(): boolean { @@ -344,7 +346,7 @@ export class Gridview implements IDisposable { return; } - if (this._maximizedNode === node) { + if (this._maximizedNode?.leaf === node) { return; } @@ -352,12 +354,18 @@ export class Gridview implements IDisposable { this.exitMaximizedView(); } + const hiddenOnMaximize: LeafNode[] = []; + function hideAllViewsBut(parent: BranchNode, exclude: LeafNode): void { for (let i = 0; i < parent.children.length; i++) { const child = parent.children[i]; if (child instanceof LeafNode) { if (child !== exclude) { - parent.setChildVisible(i, false); + if (parent.isChildVisible(i)) { + parent.setChildVisible(i, false); + } else { + hiddenOnMaximize.push(child); + } } } else { hideAllViewsBut(child, exclude); @@ -366,7 +374,7 @@ export class Gridview implements IDisposable { } hideAllViewsBut(this.root, node); - this._maximizedNode = node; + this._maximizedNode = { leaf: node, hiddenOnMaximize }; this._onDidMaxmizedNodeChange.fire(); } @@ -375,11 +383,15 @@ export class Gridview implements IDisposable { return; } + const hiddenOnMaximize = this._maximizedNode.hiddenOnMaximize; + function showViewsInReverseOrder(parent: BranchNode): void { for (let index = parent.children.length - 1; index >= 0; index--) { const child = parent.children[index]; if (child instanceof LeafNode) { - parent.setChildVisible(index, true); + if (!hiddenOnMaximize.includes(child)) { + parent.setChildVisible(index, true); + } } else { showViewsInReverseOrder(child); } @@ -395,8 +407,8 @@ export class Gridview implements IDisposable { public serialize(): SerializedGridview { if (this.hasMaximizedView()) { /** - * do not persist maximized view state but we must first exit any maximized views - * before serialization to ensure the correct dimensions are persisted + * do not persist maximized view state + * firstly exit any maximized views to ensure the correct dimensions are persisted */ this.exitMaximizedView(); } diff --git a/packages/dockview-core/src/gridview/gridviewPanel.ts b/packages/dockview-core/src/gridview/gridviewPanel.ts index b35758287..c2573bde8 100644 --- a/packages/dockview-core/src/gridview/gridviewPanel.ts +++ b/packages/dockview-core/src/gridview/gridviewPanel.ts @@ -16,6 +16,7 @@ import { import { LayoutPriority } from '../splitview/splitview'; import { Emitter, Event } from '../events'; import { IViewSize } from './gridview'; +import { BaseGrid, IGridPanelView } from './baseComponentGridview'; export interface GridviewInitParameters extends PanelInitParameters { minimumWidth?: number; @@ -24,7 +25,7 @@ export interface GridviewInitParameters extends PanelInitParameters { maximumHeight?: number; priority?: LayoutPriority; snap?: boolean; - accessor: GridviewComponent; + accessor: BaseGrid; isVisible?: boolean; } @@ -157,14 +158,16 @@ export abstract class GridviewPanel< this.api.initialize(this); // TODO: required to by-pass 'super before this' requirement this.addDisposables( - this.api.onVisibilityChange((event) => { - const { isVisible } = event; + this.api.onDidHiddenChange((event) => { + const { isHidden } = event; const { accessor } = this._params as GridviewInitParameters; - accessor.setVisible(this, isVisible); + + accessor.setVisible(this, !isHidden); }), this.api.onActiveChange(() => { const { accessor } = this._params as GridviewInitParameters; - accessor.setActive(this); + + accessor.doSetGroupActive(this); }), this.api.onDidConstraintsChangeInternal((event) => { if ( diff --git a/packages/dockview-core/src/index.ts b/packages/dockview-core/src/index.ts index 3f5c8bf70..62d415174 100644 --- a/packages/dockview-core/src/index.ts +++ b/packages/dockview-core/src/index.ts @@ -11,8 +11,6 @@ export { CompositeDisposable as DockviewCompositeDisposable, } from './lifecycle'; -export { PopoutWindow } from './popoutWindow'; - export * from './panel/types'; export * from './panel/componentFactory'; diff --git a/packages/dockview-core/src/lifecycle.ts b/packages/dockview-core/src/lifecycle.ts index 69936fff2..439b181c1 100644 --- a/packages/dockview-core/src/lifecycle.ts +++ b/packages/dockview-core/src/lifecycle.ts @@ -24,10 +24,10 @@ export namespace Disposable { } export class CompositeDisposable { - private readonly _disposables: IDisposable[]; + private _disposables: IDisposable[]; private _isDisposed = false; - protected get isDisposed(): boolean { + get isDisposed(): boolean { return this._isDisposed; } @@ -40,9 +40,13 @@ export class CompositeDisposable { } public dispose(): void { - this._disposables.forEach((arg) => arg.dispose()); + if (this._isDisposed) { + return; + } this._isDisposed = true; + this._disposables.forEach((arg) => arg.dispose()); + this._disposables = []; } } diff --git a/packages/dockview-core/src/popoutWindow.ts b/packages/dockview-core/src/popoutWindow.ts index 33d3a1434..b289ba645 100644 --- a/packages/dockview-core/src/popoutWindow.ts +++ b/packages/dockview-core/src/popoutWindow.ts @@ -94,7 +94,6 @@ export class PopoutWindow extends CompositeDisposable { return null; } - const disposable = new CompositeDisposable(); this._window = { value: externalWindow, disposable }; @@ -108,17 +107,14 @@ export class PopoutWindow extends CompositeDisposable { * @see https://developer.mozilla.org/en-US/docs/Web/API/Window/beforeunload_event */ this.close(); - }), - addDisposableWindowListener(externalWindow, 'beforeunload', () => { - /** - * @see https://developer.mozilla.org/en-US/docs/Web/API/Window/beforeunload_event - */ - this.close(); }) ); const container = this.createPopoutWindowContainer(); - container.classList.add(this.className); + + if (this.className) { + container.classList.add(this.className); + } this.options.onDidOpen?.({ id: this.target, @@ -126,6 +122,11 @@ export class PopoutWindow extends CompositeDisposable { }); return new Promise((resolve) => { + externalWindow.addEventListener('unload', (e) => { + // if page fails to load before unloading + // this.close(); + }); + externalWindow.addEventListener('load', () => { /** * @see https://developer.mozilla.org/en-US/docs/Web/API/Window/load_event @@ -134,12 +135,25 @@ export class PopoutWindow extends CompositeDisposable { const externalDocument = externalWindow.document; externalDocument.title = document.title; - // externalDocument.body.replaceChildren(container); externalDocument.body.appendChild(container); - externalDocument.body.classList.add(this.className); addStyles(externalDocument, window.document.styleSheets); + /** + * beforeunload must be registered after load for reasons I could not determine + * otherwise the beforeunload event will not fire when the window is closed + */ + addDisposableWindowListener( + externalWindow, + 'beforeunload', + () => { + /** + * @see https://developer.mozilla.org/en-US/docs/Web/API/Window/beforeunload_event + */ + this.close(); + } + ); + resolve(container); }); }); diff --git a/packages/dockview-core/src/splitview/splitviewPanel.ts b/packages/dockview-core/src/splitview/splitviewPanel.ts index 4782e8c30..9cafdb801 100644 --- a/packages/dockview-core/src/splitview/splitviewPanel.ts +++ b/packages/dockview-core/src/splitview/splitviewPanel.ts @@ -89,10 +89,10 @@ export abstract class SplitviewPanel this.addDisposables( this._onDidChange, - this.api.onVisibilityChange((event) => { - const { isVisible } = event; + this.api.onDidHiddenChange((event) => { + const { isHidden } = event; const { accessor } = this._params as PanelViewInitParameters; - accessor.setVisible(this, isVisible); + accessor.setVisible(this, !isHidden); }), this.api.onActiveChange(() => { const { accessor } = this._params as PanelViewInitParameters; diff --git a/packages/dockview/src/gridview/view.ts b/packages/dockview/src/gridview/view.ts index fce6f0690..35ed132df 100644 --- a/packages/dockview/src/gridview/view.ts +++ b/packages/dockview/src/gridview/view.ts @@ -3,6 +3,7 @@ import { GridviewPanel, GridviewInitParameters, IFrameworkPart, + GridviewComponent, } from 'dockview-core'; import { ReactPart, ReactPortalStore } from '../react'; import { IGridviewPanelProps } from './gridview'; @@ -25,8 +26,10 @@ export class ReactGridPanelView extends GridviewPanel { { params: this._params?.params ?? {}, api: this.api, + // TODO: fix casting hack containerApi: new GridviewApi( - (this._params as GridviewInitParameters).accessor + (this._params as GridviewInitParameters) + .accessor as GridviewComponent ), } );