Skip to content

Comments

WIP: integrate fixed side panels into DockviewComponent#1122

Open
mathuo wants to merge 20 commits intomasterfrom
wip/fixed-panels-integration
Open

WIP: integrate fixed side panels into DockviewComponent#1122
mathuo wants to merge 20 commits intomasterfrom
wip/fixed-panels-integration

Conversation

@mathuo
Copy link
Owner

@mathuo mathuo commented Feb 17, 2026

Summary

Integrates fixed side panels (IDE shell layout) as a first-class feature of `DockviewComponent`. Fixed panels wrap the dockview grid in a `ShellManager` (nested splitviews) and expose fully-functional `DockviewGroupPanel` instances at each edge — complete with tabs, drag-and-drop, overflow, serialization, and the full group panel API.

Also adds a `location` prop to header action renderer components (React + Vue) so custom renderers can adapt their UI based on where a group lives (grid, floating, or a fixed position).


Changes

New: Fixed Side Panels (fixedPanels option)

Pass a fixedPanels config to DockviewOptions (or the React/Vue/Angular wrapper prop) to wrap the dockview grid with up to four fixed panels:

// Vanilla TypeScript
const dockview = new DockviewComponent(container, {
    fixedPanels: {
        top:    { id: 'top',    initialSize: 80,  minimumSize: 60 },
        bottom: { id: 'bottom', initialSize: 200, minimumSize: 100 },
        left:   { id: 'left',  initialSize: 220, minimumSize: 150 },
        right:  { id: 'right', initialSize: 220, minimumSize: 150 },
    },
    // ...other options
});
// React
<DockviewReact
    fixedPanels={{
        top:    { id: 'top',    initialSize: 80,  minimumSize: 60 },
        bottom: { id: 'bottom', initialSize: 200, minimumSize: 100 },
        left:   { id: 'left',  initialSize: 220, minimumSize: 150 },
        right:  { id: 'right', initialSize: 220, minimumSize: 150 },
    }}
    onReady={(event) => {
        // Add panels into a fixed group using getFixedPanel
        const leftApi = event.api.getFixedPanel('left');
        if (leftApi) {
            event.api.addPanel({
                id: 'explorer',
                component: 'Explorer',
                title: 'Explorer',
                position: { referenceGroup: leftApi.id },
            });
        }
    }}
    components={components}
/>

Each position accepts:

Option Description
id Required. Stable ID used for serialization and getFixedPanel() lookup.
initialSize Initial width (left/right) or height (top/bottom) in pixels. Default: 200.
minimumSize Minimum expanded size. Default: collapsedSize + 50.
maximumSize Maximum expanded size. Default: unlimited.
collapsedSize Height/width of the header when collapsed. Default: 35.

Fixed panels behave like regular dockview groups: they support tabs, the overflow dropdown, drag-and-drop (into the panel), and all tab/header customisation APIs.


New DockviewApi methods

// Get the group panel API for a fixed panel (returns undefined if not configured)
const leftGroup: DockviewGroupPanelApi | undefined = api.getFixedPanel('left');

// Show or hide a fixed panel
api.setFixedPanelVisible('bottom', false);

// Query visibility
const isVisible: boolean = api.isFixedPanelVisible('right');

New DockviewGroupPanelApi methods (fixed groups only)

const groupApi = api.getFixedPanel('left');

groupApi?.collapse();          // Collapse to header-only
groupApi?.expand();            // Restore to last expanded size
const collapsed = groupApi?.isCollapsed(); // true/false

Collapse/expand are no-ops on non-fixed groups. Clicking a non-active tab in a fixed panel switches to that tab without toggling the expansion; clicking the already-active tab toggles collapse/expand.


New location prop in header action renderers (React + Vue)

IDockviewHeaderActionsProps now includes a location: DockviewGroupLocation field. This is available in rightHeaderActionsComponent, leftHeaderActionsComponent, and prefixHeaderActionsComponent and updates reactively when a group moves (e.g. dragged into a floating window or a fixed panel).

// React example: show a collapse button only in fixed panels
const MyRightActions = (props: IDockviewGroupHeaderActionsProps) => {
    if (props.location.type !== 'fixed') return null;
    return (
        <button onClick={() => props.api.collapse()}>
            Collapse
        </button>
    );
};

DockviewGroupLocation is now exported from the public API (dockview-core, dockview, dockview-vue, dockview-angular).


Serialization

Fixed panel state (size, visibility, collapsed state, and group contents) is included in toJSON / fromJSON:

const state = api.toJSON();
// state.fixedPanels = {
//   left: { size: 220, visible: true, collapsed: false, group: { ... } },
//   bottom: { size: 200, visible: false, collapsed: false, group: { ... } },
//   ...
// }

api.fromJSON(state); // restores fixed panel layout

Implementation notes

  • ShellManager (dockviewShell.ts) — wraps the dockview element in two nested Splitview instances (vertical outer, horizontal inner) to position fixed panels at each edge. The center view has LayoutPriority.High so it absorbs all resize.
  • FixedPanelViewIView adapter for each fixed panel group. When collapsed, it locks minimumSize === maximumSize === collapsedSize to prevent sash dragging from reopening the panel.
  • Layout routingDockviewComponent.layout() is intercepted when a shell is active: external resize calls flow through ShellManager, and shell callbacks go directly to the grid via a re-entrancy guard (_inShellLayout).
  • Popup service root — moved to the shell element so overflow dropdowns in fixed panels position correctly relative to the shell container.
  • Theme propagation — the shell element receives the same theme class as the dockview element.

🤖 Generated with Claude Code

Integrate fixed side panels (IDE shell layout) as a first-class feature
of DockviewComponent, replacing the standalone DockviewShell wrapper.

- Refactor DockviewShell to internal ShellManager class
- Add fixedPanels option to DockviewOptions (auto-propagates to all
  framework wrappers via PROPERTY_KEYS_DOCKVIEW)
- Add layout routing: external calls go through shell splitviews,
  internal callbacks go directly to grid
- Extract forceRelayout() in BaseGrid for shell-aware override
- Add API methods: getFixedPanel, setFixedPanelVisible, isFixedPanelVisible
- Add serialization support (toJSON/fromJSON) for fixed panel state
- Update demo with shell toggle and panel visibility controls
- Fix splitview orientation bug (VERTICAL for rows, HORIZONTAL for columns)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@mathuo
Copy link
Owner Author

mathuo commented Feb 17, 2026

image

@codesandbox-ci
Copy link

codesandbox-ci bot commented Feb 17, 2026

This pull request is automatically built and testable in CodeSandbox.

To see build info of the built libraries, click here or the icon next to each commit SHA.

Latest deployment of this branch, based on commit 2b1f2ab:

Sandbox Source
dockview-app Configuration
editor-gridview Configuration
externaldnd-dockview Configuration
fullwidthtab-dockview Configuration
iframe-dockview Configuration
keyboard-dockview Configuration
nativeapp-dockview Configuration
rendering-dockview Configuration

- Add 'fixed' to DockviewGroupLocation union; fixed groups get center-only
  drop zones and the dv-groupview-fixed CSS class
- IFixedPanelGroup interface in dockviewShell.ts avoids circular imports
- FixedPanelView now hosts a real DockviewGroupPanel instead of a plain div
- DockviewComponent creates a DockviewGroupPanel per configured fixed position
  before constructing ShellManager; groups are guarded in clear() and
  doRemoveGroup() so they survive fromJSON round-trips
- getFixedPanel() returns DockviewGroupPanelApi; serialization persists fixed
  group panel state alongside shell size/visibility
- Apply theme class to shell element so fixed panels inherit CSS custom
  properties and theme-scoped rules (theme is set before shell exists, so
  class is applied immediately after ShellManager is created)
- Fix duplicate-groups in demo: reset panels/groups/active state at the start
  of useEffect([api]) so stale IDs from previous api instances don't accumulate

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@mathuo
Copy link
Owner Author

mathuo commented Feb 18, 2026

image

mathuo and others added 2 commits February 19, 2026 22:56
The collapsedSize+50 heuristic was being applied even when the caller
explicitly set minimumSize, causing min > max when a small maximumSize
was configured. This made the sash enter SashState.DISABLED.

Now the heuristic only applies as a fallback when minimumSize is not
provided. Also updates the demo top panel to a more useful initial/min size.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@mathuo
Copy link
Owner Author

mathuo commented Feb 19, 2026

image

mathuo and others added 10 commits February 20, 2026 20:55
Move the expand/collapse toggle for fixed groups from `pointerdown` to the
`click` event so that dragging a tab does not accidentally trigger a collapse
or expand. A genuine click fires `click`; a drag does not.

Add `onTabClick` emitter + DOM `click` listener to `Tab`, then wire the
toggle logic to that event in `Tabs.openPanel()`. The `onPointerDown` handler
for fixed groups is trimmed to only switch inactive tabs.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Export DockviewGroupLocation from dockview-core public API (index.ts)
- Add location field to IDockviewHeaderActionsProps so framework bindings
  can pass the current group location to header action components
- Bump default collapsedSize from 30 to 35 in FixedPanelView

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Subscribe to onDidLocationChange and forward the current group location
as a prop to header action components in both the React and Vue bindings.
Update tests accordingly.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- New dockviewShell.spec.ts: 41 tests covering FixedPanelView
  (construction, minimumSize/maximumSize getters, setCollapsed, layout)
  and ShellManager (hasFixedPanel, visibility, collapsed state,
  toJSON/fromJSON, dispose)
- New dockviewGroupPanelApi.spec.ts: 7 tests covering collapse(),
  expand(), and isCollapsed() including no-op behavior when uninitialized
- Extended dockviewComponent.spec.ts: 17 integration tests covering
  getFixedPanel, location type, setFixedPanelVisible, collapse/expand
  end-to-end, toJSON/fromJSON, and dispose

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… toggling expansion

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The PopupService anchor was rooted to the dockview component element (the
centre grid). Fixed panels live outside that element in the shell, so the
clientX/Y offset calculation produced wrong (often negative) values.

After the ShellManager is created, updateRoot() moves the anchor into the
shell element so the offset covers the full layout area.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Auto-collapse fixed panel when all tabs are removed; do not auto-expand on tab add (preserves drag behaviour)
- Add `initiallyCollapsed` to `FixedPanelViewOptions` so collapsed state can be declared in config rather than set imperatively after load
- Fix `fromJSON`: call `setCollapsed` before `resizeView` so min/max constraints are correct before resize (fixes double-load regression where expanded panels weren't restored)
- Fix `fromJSON`: unconditionally set collapsed state (true or false) to correctly override `initiallyCollapsed` on restore
- Demo: enable shell by default with left (1), right (2), bottom (3) fixed panels, all initially collapsed
- Demo: populate fixed panels in `loadLayout` (after `fromJSON`/`defaultConfig`) to survive `clear()` on layout load
- Demo: hide left, prefix, and right header action controls for fixed panels via `location.type === 'fixed'`

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…nds group

Add tests covering all four fixed-panel tab-click scenarios.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@sonarqubecloud
Copy link

mathuo and others added 6 commits February 21, 2026 19:55
ShellManager was accepting defaultCollapsedSize but not forwarding it to
FixedPanelView instances, and DockviewComponent wasn't passing the
theme's fixedPanelCollapsedSize to ShellManager at all. Per-panel
collapsedSize still takes precedence over the theme default.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
For left/right fixed panels the tab container is narrow
(width = --dv-tabs-and-actions-container-height = 44px). The space
mixin's 0px 10px padding consumed 20px of that, leaving only 24px for
tab content. Override to padding: 0 for .dv-groupview-header-vertical
so the full tab-bar width is available.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
With writing-mode: vertical-rl, physical margin-left/right become the
cross-axis margins that shrink the tab's width inside the 44px strip.
Override to margin: 0.25rem 0 for .dv-tabs-container-vertical so tabs
fill the full available width with only between-tab spacing retained.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
margin: 0.25rem 0 left tabs touching the strip edges. Use 2px side
margins so the tab pill fills most of the 44px strip (40px) while
keeping a small visual gap between the pill and the strip background.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
padding: 0 removed all breathing room. Vertical strips need padding at
top/bottom (not left/right) to match the feel of horizontal tab bars
which have 10px side padding. Use calc(--dv-border-radius / 2) 0.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2px was too small to produce a visible horizontal padding gap.
4px matches the visual weight of the 10px vertical container padding,
giving a 36px tab in the 44px strip with clear breathing room on both sides.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant