npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

vue-win-mgr

v0.9.2

Published

A drag/dock window manager for Vue 3 components.

Readme

vue-window-manager

This provides a Blender / QT / Unity, etc like Window Manager.

HOMEPAGE - https://orokro.github.io/Vue-Window-Manager/

Project Setup

npm install

Compile and Hot-Reload for Development

npm run dev

Compile and Minify for Production

npm run build

▶️ TUTORIAL

ThreeQuery Full Tutorial!

🎛️ DOCS

A powerful, highly customizable, Vue 3-based window manager for building creative, Blender-like, QT-style applications with floating, dockable, tabbed, and split-window interfaces.


📚 Table of Contents

  • 🚀 Introduction
  • 🧱 The <WindowManager /> Component
  • 🧩 Providing availableWindows
  • 🗺 Defining a Layout
  • 📐 Top Bar & Status Bar
  • 🎨 Theming System
  • ⚙️ Remaining Props
  • 🪓 Splitting, Empty Frames & Merging
  • 🖼 MWI Enhancements (Task Bar, Start Menu, Minimize)
  • 🧠 JavaScript API: Contexts
    • 🔧 WindowManagerContext
    • 🪟 WindowFrameContext
    • 📦 WindowContext
  • 📦 Saving & Restoring Layouts
    • 💾 Window State Serialization
  • 🧼 Wrap Up
  • 🔮 Coming Soon

🚀 Introduction

vue-win-mgr is a Vue 3-based layout and window manager designed for building creative applications that demand powerful, flexible, and dynamic interfaces.

Inspired by tools like Blender, Qt Creator, and other professional-grade apps, it allows you to create sophisticated layout systems with minimal boilerplate.

📐 Core Concepts

  • WindowManager: Defines the screen space.
  • Window Frames: Regions of the screen that can be resized, split, or merged.
  • Windows: Individual components you provide, hosted inside frames.
  • Frame Modes:
    • SINGLE: One window at a time (swap out with menu).
    • TABBED: Like a browser — multiple window tabs.
    • MWI: Free-floating window chaos (in a good way).

👉 You bring your Vue components. The window manager handles layout, snapping, tabs, drag-and-drop, and more.


🧱 The <WindowManager /> Component

This is the heart of the system. Drop it into your Vue template, and it takes care of rendering everything according to the availableWindows and layout you give it.

<WindowManager
	ref="windowManagerEl"

	:availableWindows="availableWindows"
	:defaultLayout="layout"

	:showTopBar="true"
	:showStatusBar="true"
	:topBarComponent="MyHeaderBar"
	:statusBarComponent="MyStatusBar"

	:splitMergeHandles="true"
	mwiBGPattern="/bg_pattern.png"

	:theme="{
		frameBGColor: '#002244',
		tabTextColor: '#ccc',
	}"
/>

You can also use named slots for the top and status bars:

<WindowManager :showTopBar="true" :showStatusBar="true">
	<template #topBar>
		<!-- custom header content -->
	</template>
	<template #statusBar>
		<!-- custom footer content -->
	</template>
</WindowManager>

Yes, you can mix and match: use a component for one bar and a slot for the other.


🧩 Providing availableWindows

This array tells the WindowManager what Vue components are valid windows. There are two ways to define them:

✅ Preferred: Object Form (Verbose)

const availableWindows = [
	{
		window: Viewport,
		title: "Scene Viewport",
		slug: "viewport",
		icon: 'icons/viewport.png'
	},
	{
		window: Notes,
		title: "Notes",
		slug: "notes",
		icon: 'icons/notes.png'
	}
];

⚡ Shorthand: Constructors Only

const availableWindows = [Viewport, Notes, About, Settings];

Slugs will be auto-generated from the component name. The slug generator looks for:

  1. component.name
  2. component.__name
  3. filename from component.__file

⚠️ TIP:

Always prefer the object format for full control. Use the constructor-only form for quick prototypes.


🗺 Defining a Layout

Layouts describe how your screen is broken into regions (frames), and which windows live in each region.

const layout = [
	{
		name: "window",
		top: 0,
		left: 0,
		bottom: 1080,
		right: 1920
	},
	{
		name: "MainView",
		windows: ['viewport'],
		style: FRAME_STYLE.TABBED,
		left: 0,
		right: ["ref", "window.right-460"],
		top: 0,
		bottom: ["ref", "window.bottom-300"]
	},
	// more frames...
];

🧮 Frame Boundaries

Each frame defines top, left, right, and bottom using one of:

| Format | Meaning | |--------|---------| | 100 | Absolute pixel value | | ['val', 50] | Same as the raw number, above - absolute pixel value | | ['val', 50, '%'] | Adding '%' item, 50% of parent frame | | ['ref', 'MainView.right'] | Reference to another frame edge | | ['ref', 'MainView.right-460'] | Reference to another frame edge with math. Only + or - supported |

🧩 Window Entries

windows: [
	"notes",
	{ kind: "viewport", props: { someProp: true } }
]

You can mix plain slugs and objects with props.


📐 Top Bar & Status Bar

To show bars at the top or bottom of the WindowManager:

<WindowManager :showTopBar="true" :showStatusBar="true" />

Then, provide either:

  • :topBarComponent / :statusBarComponent
  • or named slots #topBar, #statusBar
  • or both!

🎨 Theming System

Pass a theme object to control look & feel.

theme: {
	frameBGColor: '#444',
	tabTextColor: '#ccc',
	windowBGColor: '#fff'
}

Full list of available theme keys:

const defaultThemeColors = {

	// background colors
	systemBGColor: '#000',
	topBarBGColor: '#31313B',
	statusBarBGColor: '#31313B',
	frameBGColor: '#737378',
	windowBGColor: '#EFEFEF',
	mwiBGColor: '#39393E',
	menuBGColor: 'rgba(0, 0, 0, 0.7)',
	menuActiveBGColor: 'rgba(255, 255, 255, 0.8)',

	// header colors for windows & tabs
	frameHeaderColor: '#5C5C60',
	frameTabsHeaderColor: '#2E2E30',
	frameTabsColor: '#4A4A4E',
	frameTabsActiveColor: '#737378',

	// text colors
	windowTitleTextColor: 'rgb(209, 209, 209)',
	tabTextColor: 'rgb(150, 149, 149)',
	activeTabTextColor: 'rgb(209, 209, 209)',
	menuTextColor: '#EFEFEF',
	menuActiveTextColor: '#000',
	menuDisabledTextColor: '#999',

	// blur for the window menus
	menuBlur: '2px',

	// hamburger theme
	hamburgerIconColor: 'rgba(255, 255, 255, 0.5)',
	hamburgerIconColorHover: '#FFF',
	hamburgerCircleColor: 'none',
	hamburgerCircleColorHover: 'rgba(255, 255, 255, 0.25)',

	// close buttons for floating windows & tabs
	closeButtonCircle: 'none',
	closeButtonCircleHover: 'rgba(255, 0, 0, 0.3)',
	closeButtonXColor: 'rgba(0, 0, 0, 0.5)',
	closeButtonXColorHover: 'rgba(255, 255, 255, 1)',
}

All values are reactive and hot-swappable at runtime.


⚙️ Remaining Props

| Prop | Type | Description | |------|------|-------------| | availableWindows | Array | Required list of window definitions | | defaultLayout | Array | Required layout definition | | showTopBar / showStatusBar | Boolean | Optional toggle bars | | topBarComponent / statusBarComponent | Component | Optional bar components | | splitMergeHandles | Boolean | Toggles split/merge handle visibility | | mwiBGPattern | String | Background pattern for MWI frames | | theme | Object | Theme overrides | | splitFillMode | String | How a freshly-split frame is filled: 'clone' (copy the source frame's active window, the default) or 'picker' (leave empty and show a window-picker grid). | | keepEmptyFrames | Boolean | When true, a TABBED/SINGLE frame that loses its last window is not auto-merged away — it stays put and shows the picker / merge helpers. Default false. | | showMergeButtons | Boolean | When true, an empty (non-MWI) frame shows merge-arrow buttons along each edge that has a valid adjacent neighbor, so it can be collapsed. Default false. | | mwiTaskBar | Boolean | When true, MWI frames render a Windows-style task bar along the bottom (this also enables per-window minimize buttons). Default false. | | mwiStartMenu | Boolean | When true, MWI frames get a "start" add-window affordance (a task-bar button, or a floating button when the task bar is off) plus right-click-without-drag on the background. Default false. | | mwiPanFromWindowBody | Boolean | When true, right-click-drag anywhere over an MWI window body pans the desktop. When false (default), the window body keeps its own right-click and only the empty background pans. |

All of these props are also settable at runtime via the WindowManagerContext (see below).


🪓 Splitting, Empty Frames & Merging

When you split a frame (via the corner handles, the hamburger menu, or the right-click edge menu), the new region is populated according to splitFillMode:

  • 'clone' (default) — the new frame inherits a copy of the source frame's active window, Blender-style: split a 3D view and you get another 3D view. You never end up staring at an empty frame.
  • 'picker' — the new frame is left empty and shows a picker grid of every available window kind (icon + name); click one to fill the space.

Dragging-and-dropping a tab to a frame edge always places the dragged window there (this is unaffected by splitFillMode).

Empty frames as a "start menu"

By default, closing the last window in a TABBED/SINGLE frame auto-merges that frame away. Set keepEmptyFrames to true to keep the empty frame around instead — it will display the same picker grid so the user can drop a new window back in.

Merge buttons

Blender newcomers often ask "how do I close a window?" — the answer in a no-overlap system is to merge a neighbor over it. Set showMergeButtons to true and any empty (non-MWI) frame shows merge-arrow buttons on each edge that has a perfectly-adjacent neighbor. Clicking one collapses the empty frame by expanding that neighbor over it.

Empty MWI frames are valid (an empty floating-window desktop is fine), so the picker / merge helpers only apply to TABBED/SINGLE frames. Use the MWI start menu (below) to add windows to an empty MWI frame.


🖼 MWI Enhancements (Task Bar, Start Menu, Minimize)

MWI (floating-window) frames have a few optional, OS-like conveniences:

  • Task bar (mwiTaskBar) — a strip along the bottom of the frame with one button per floating window. Buttons show the window's icon/title and reflect its state (focused / visible / minimized). Clicking a button focuses & raises the window; clicking the button of the already-focused window minimizes it (just like Windows); clicking a minimized window restores it. The task bar stays fixed while you pan the desktop.
  • Start menu (mwiStartMenu) — an add-window menu listing every available window kind. It appears as a button at the left of the task bar, or as a floating button in the bottom-left when the task bar is off. You can also right-click the MWI background (without dragging) to open it.
  • Minimize — when the task bar is enabled, each floating window's title bar gets a minimize button. Minimized windows are hidden but remain recoverable from their task-bar button.

Panning: right-click-drag the MWI background to pan all windows at once. By default the background is the only thing that pans on right-click — set mwiPanFromWindowBody to true to allow panning from over a window body as well (off by default so your window-components keep their own right-click).


🧠 JavaScript API: Contexts

The vue-win-mgr system provides three JavaScript context objects you can get, to access some programmatic functionality.

  • WindowManagerContext - control top-level component features
  • WindowFrameContext - control a window frame in JS
  • WindowContext - control a window in JS

🔧 WindowManagerContext

const ctx = windowManagerEl.value.getContext();
ctx.showTopBar(false);
ctx.loadLayout(savedLayout);

All Methods

| Method | Description | |--------|-------------| | showTopBar(bool) | Show/hide the top bar | | showStatusBar(bool) | Show/hide the status bar | | showSplitMergeHandles(bool) | Enable/disable the corner handles | | loadLayout(layoutObj) | Load a new layout definition | | resetLayout() | Revert to default layout | | getLayoutDetails() | Get the current layout structure | | setSplitFillMode(mode) | Set how splits are filled: 'clone' or 'picker' | | setKeepEmptyFrames(bool) | Keep empty (non-MWI) frames instead of auto-merging them | | setShowMergeButtons(bool) | Show/hide merge-arrow buttons on empty frames | | setMwiTaskBar(bool) | Show/hide the MWI task bar (also enables minimize) | | setMwiStartMenu(bool) | Show/hide the MWI start-menu affordance | | setMwiPanFromWindowBody(bool) | Allow/disallow right-click-drag panning from a window body |

🪟 WindowFrameContext

Inside a window component:

const frameCtx = inject("frameCtx");
frameCtx.setFrameStyle(FRAME_STYLE.MWI);
frameCtx.addWindow("notes");

All Methods

| Method | Description | |--------|-------------| | addWindow(slug, props = {}) | Add a window to the current frame | | getAvailableWindowKinds() | List all windows allowed in this frame | | getFrameDimensions() | Returns { top, left, bottom, right, width, height } | | getWindows() | Returns WindowContext[] for windows in the frame | | closeAllWindows() | Close all windows in the frame | | setFrameStyle(newType) | Change frame style (SINGLE, TABBED, MWI) | | getFrameStyle() | Returns { styleName, styleValue } |

The injected frameCtx now stays live. It is a thin proxy that always resolves the window's current frame, so it keeps working after the window is dragged/docked into another frame (e.g. frameCtx.addWindow(...) always targets the frame the window is actually in). This used to be a limitation — it no longer is.

📦 WindowContext

Also inside window components:

const windowCtx = inject("windowCtx");
windowCtx.setTitle("Custom Title");
windowCtx.close();

All Methods

| Method | Description | |--------|-------------| | getTitle() | Get the window’s title | | setTitle(title) | Set the window’s title | | close() | Close the current window | | setKind(slug) | Change window kind to another slug | | getFrame() | Get the live WindowFrameContext for the frame this window is currently in (or null) | | onFrameChange(cb) | Subscribe to re-dock events; cb(newFrameCtx, oldFrameCtx) fires whenever the window moves to a different frame. Returns a stop handle. | | onSerialize(cb) | Register a callback returning JSON-safe state to persist with the layout (see below) | | onLayoutLoad(cb) | Register a callback that receives that state back when a saved layout is restored (see below) |


📦 Saving & Restoring Layouts

You can persist layouts using getContext().getLayoutDetails() and reapply them later with loadLayout():

const layout = windowManagerEl.value.getContext().getLayoutDetails();
localStorage.setItem("myLayout", JSON.stringify(layout));

// later...
const saved = JSON.parse(localStorage.getItem("myLayout"));
windowManagerEl.value.getContext().loadLayout(saved);

💾 Window State Serialization

A saved layout stores each window's slug so the right component is recreated on load. But if two windows are the same kind showing different data (e.g. image A vs. image B), the slug alone isn't enough to restore them.

Your window-components can attach their own JSON-safe "rider" data using two composables exported from the library:

import { onSerialize, onLayoutLoad } from 'vue-win-mgr';

// return JSON-safe data to persist alongside this window in the layout
onSerialize(() => ({ imageId: currentImage.value }));

// receive that exact data back when a saved layout is restored
onLayoutLoad((saved) => { currentImage.value = saved.imageId; });

Notes:

  • Whatever onSerialize returns must be JSON-safe (no circular references, functions, etc.). It's validated at save time, so a bad payload fails loudly rather than silently corrupting the layout.
  • onLayoutLoad fires the moment your component registers it (on mount) with any pending restore data — so it doesn't matter that the window manager mounts before the rest of your app's state is ready. If you need to wait on external state, defer inside the callback.
  • Under the hood, windows that carry rider data (or props) are serialized in the richer object form { kind, props, state }; plain windows stay as a simple slug string, so old layouts keep loading.

These composables are also available as methods on the WindowContext (windowCtx.onSerialize(...) / windowCtx.onLayoutLoad(...)).


🧼 Wrap Up

vue-win-mgr is flexible, powerful, and built to scale alongside serious creative applications. Whether you're building an editor, dashboard, IDE, or a strange new thing that defies classification — it’s got you covered.

Let your components do their thing, and let the window manager handle the rest.


🔮 Coming Soon

  • Configurable SNAP_SIZE for docking precision
  • Customizable split/merge drag sensitivity
  • Support for temporary/non-persistent window managers

MIT Licensed · Made with ❤️ by [Greg Miller]