The XE TLDraw Window Manager provides a comprehensive API for building desktop-like window management experiences on top of tldraw. This document defines the complete API surface, design principles, and usage patterns.
- Developer First: APIs should be intuitive, well-typed, and provide excellent IntelliSense support
- Composable: Features should be modular and work well together
- Extensible: Core functionality can be extended through plugins and custom implementations
- Performance: APIs should encourage performant patterns and avoid unnecessary re-renders
- Type Safe: Full TypeScript support with strict typing
interface WindowManager {
// Window lifecycle
create(options: WindowCreateOptions): WindowHandle
destroy(windowId: string): void
destroyAll(): void
// Window state
getWindow(windowId: string): WindowHandle | null
getWindows(): WindowHandle[]
getActiveWindow(): WindowHandle | null
// Window operations
setActive(windowId: string): void
minimize(windowId: string): void
maximize(windowId: string): void
restore(windowId: string): void
close(windowId: string): void
// Window positioning
center(windowId: string): void
cascade(windowIds?: string[]): void
tile(windowIds?: string[], direction?: 'horizontal' | 'vertical'): void
// Z-order management
bringToFront(windowId: string): void
sendToBack(windowId: string): void
setZIndex(windowId: string, zIndex: number): void
// Content registration
registerContent(registration: ContentRegistration): void
unregisterContent(contentId: string): void
getContentTypes(): ContentRegistration[]
// Event handling
on(event: WindowEvent, handler: WindowEventHandler): () => void
off(event: WindowEvent, handler: WindowEventHandler): void
// State management
getState(): WindowManagerState
setState(state: Partial<WindowManagerState>): void
subscribe(listener: StateListener): () => void
}interface WindowCreateOptions {
// Identity
id?: string
title?: string
icon?: string | React.ReactNode
// Content
content?: WindowContent
contentType?: string
contentProps?: Record<string, any>
// Position & Size
x?: number
y?: number
width?: number
height?: number
position?: 'center' | 'cascade' | 'random' | { x: number, y: number }
// Constraints
minWidth?: number
minHeight?: number
maxWidth?: number
maxHeight?: number
aspectRatio?: number
// Behavior
resizable?: boolean
movable?: boolean
closable?: boolean
minimizable?: boolean
maximizable?: boolean
focusable?: boolean
alwaysOnTop?: boolean
// Appearance
theme?: string | WindowTheme
opacity?: number
shadow?: boolean
borderless?: boolean
transparent?: boolean
// Advanced
parent?: string // Parent window ID for modal behavior
modal?: boolean
skipTaskbar?: boolean
metadata?: Record<string, any>
}interface WindowHandle {
// Identity
readonly id: string
readonly createdAt: Date
// Properties
title: string
icon?: string | React.ReactNode
theme: string
state: WindowState
// Dimensions
x: number
y: number
width: number
height: number
// Methods
setTitle(title: string): void
setIcon(icon: string | React.ReactNode): void
setTheme(theme: string | WindowTheme): void
// Position & Size
move(x: number, y: number): void
resize(width: number, height: number): void
setBounds(bounds: WindowBounds): void
center(): void
// State
minimize(): void
maximize(): void
restore(): void
close(): void
focus(): void
blur(): void
// Z-order
bringToFront(): void
sendToBack(): void
// Content
setContent(content: WindowContent): void
getContent(): WindowContent | null
// Events
on(event: WindowEvent, handler: WindowEventHandler): () => void
off(event: WindowEvent, handler: WindowEventHandler): void
// Metadata
setMetadata(key: string, value: any): void
getMetadata(key?: string): any
}// Content types
type WindowContent =
| { type: 'react', component: React.ComponentType<WindowContentProps> }
| { type: 'iframe', url: string, sandbox?: string[] }
| { type: 'html', html: string }
| { type: 'custom', render: ContentRenderer }
// Content registration
interface ContentRegistration {
id: string
name: string
description?: string
icon?: string | React.ReactNode
component: React.ComponentType<WindowContentProps>
defaultProps?: Record<string, any>
// Capabilities
capabilities?: {
resizable?: boolean
scrollable?: boolean
interactive?: boolean
}
// Lifecycle
onMount?: (props: WindowContentProps) => void
onUnmount?: (props: WindowContentProps) => void
}
// Props passed to content components
interface WindowContentProps {
// Window context
windowId: string
windowHandle: WindowHandle
isActive: boolean
isFocused: boolean
// Window actions
onRequestClose?: () => void
onRequestMinimize?: () => void
onRequestMaximize?: () => void
onRequestRestore?: () => void
// Custom props from registration
[key: string]: any
}type WindowEvent =
| 'create'
| 'destroy'
| 'beforeClose'
| 'close'
| 'minimize'
| 'maximize'
| 'restore'
| 'move'
| 'resize'
| 'focus'
| 'blur'
| 'stateChange'
| 'titleChange'
| 'contentChange'
interface WindowEventData {
windowId: string
window: WindowHandle
type: WindowEvent
timestamp: Date
// Event-specific data
data?: {
// For move events
position?: { x: number, y: number, prevX: number, prevY: number }
// For resize events
size?: { width: number, height: number, prevWidth: number, prevHeight: number }
// For state change
state?: { current: WindowState, previous: WindowState }
// For beforeClose
preventDefault?: () => void
}
}
type WindowEventHandler = (event: WindowEventData) => void// Main hook for window management
function useWindowManager(): WindowManager
// Hook for individual window
function useWindow(windowId: string): WindowHandle | null
// Hook for active window
function useActiveWindow(): WindowHandle | null
// Hook for window state
function useWindowState(windowId: string): WindowState | null
// Hook for window events
function useWindowEvent(
windowId: string | null,
event: WindowEvent,
handler: WindowEventHandler
): void
// Hook for content registration
function useContentRegistration(
registration: ContentRegistration,
deps?: React.DependencyList
): voidinterface WindowTheme {
id: string
name: string
description?: string
// Colors
colors: {
titleBar: ThemeColors
window: ThemeColors
buttons: {
close: ButtonColors
minimize: ButtonColors
maximize: ButtonColors
}
}
// Metrics
metrics: {
titleBarHeight: number
borderWidth: number
borderRadius: number
buttonSize: number
buttonSpacing: number
padding: number
shadowBlur: number
shadowOffset: { x: number, y: number }
}
// Typography
typography?: {
titleFont: string
titleSize: number
titleWeight: number
}
// Animations
animations?: {
duration: number
easing: string
}
// Custom rendering
customRender?: {
titleBar?: TitleBarRenderer
buttons?: ButtonRenderer
border?: BorderRenderer
}
}
interface ThemeColors {
background: string
backgroundHover?: string
backgroundActive?: string
backgroundInactive?: string
text: string
textHover?: string
textActive?: string
textInactive?: string
border?: string
borderHover?: string
borderActive?: string
borderInactive?: string
shadow?: string
shadowHover?: string
shadowActive?: string
shadowInactive?: string
}interface WindowManagerConfig {
// Behavior
defaultTheme?: string
defaultPosition?: 'center' | 'cascade' | 'random'
cascadeOffset?: { x: number, y: number }
animationsEnabled?: boolean
// Constraints
defaultConstraints?: {
minWidth?: number
minHeight?: number
maxWidth?: number
maxHeight?: number
}
// Features
features?: {
snap?: boolean | SnapConfig
animations?: boolean | AnimationConfig
persistence?: boolean | PersistenceConfig
accessibility?: boolean | AccessibilityConfig
}
// Advanced
maxWindows?: number
zIndexBase?: number
focusOnCreate?: boolean
destroyOnClose?: boolean
// Plugins
plugins?: WindowManagerPlugin[]
}interface WindowManagerPlugin {
name: string
version: string
// Lifecycle
install(manager: WindowManager, config: WindowManagerConfig): void
uninstall?(manager: WindowManager): void
// Hooks into window lifecycle
beforeCreate?(options: WindowCreateOptions): WindowCreateOptions | false
afterCreate?(window: WindowHandle): void
beforeDestroy?(window: WindowHandle): boolean
afterDestroy?(windowId: string): void
// Custom commands
commands?: Record<string, PluginCommand>
}
type PluginCommand = (manager: WindowManager, ...args: any[]) => anyinterface SnapConfig {
enabled: boolean
threshold: number
showGuides: boolean
targets: {
screen: boolean
windows: boolean
grid: boolean | { size: number }
}
modifiers?: {
temporary: 'shift' | 'ctrl' | 'alt'
disable: 'shift' | 'ctrl' | 'alt'
}
}
interface SnapResult {
snapped: boolean
edge?: 'top' | 'right' | 'bottom' | 'left' | 'center'
target?: 'screen' | 'window' | 'grid'
position: { x: number, y: number }
}interface AnimationConfig {
enabled: boolean
duration: number
easing: 'linear' | 'ease' | 'ease-in' | 'ease-out' | 'ease-in-out' | string
// Per-action animations
animations: {
create?: AnimationOptions
destroy?: AnimationOptions
minimize?: AnimationOptions
maximize?: AnimationOptions
restore?: AnimationOptions
move?: AnimationOptions
resize?: AnimationOptions
}
}
interface AnimationOptions {
duration?: number
easing?: string
delay?: number
keyframes?: Keyframe[]
}interface PersistenceConfig {
enabled: boolean
adapter: PersistenceAdapter
autoSave: boolean | number // Interval in ms
autoRestore: boolean
// What to persist
include: {
windows?: boolean
positions?: boolean
sizes?: boolean
states?: boolean
content?: boolean
theme?: boolean
}
}
interface PersistenceAdapter {
save(state: WindowManagerState): Promise<void>
load(): Promise<WindowManagerState | null>
clear(): Promise<void>
}
// Built-in adapters
class LocalStorageAdapter implements PersistenceAdapter {}
class IndexedDBAdapter implements PersistenceAdapter {}
class SessionStorageAdapter implements PersistenceAdapter {}interface AccessibilityConfig {
enabled: boolean
// Keyboard navigation
keyboard: {
enabled: boolean
shortcuts: Record<string, KeyboardShortcut>
}
// Screen reader
announcements: {
enabled: boolean
verbosity: 'minimal' | 'normal' | 'verbose'
}
// Visual
focusIndicator: boolean
highContrast: boolean
reducedMotion: boolean
}
interface KeyboardShortcut {
key: string
modifiers?: ('ctrl' | 'alt' | 'shift' | 'meta')[]
action: string | ((manager: WindowManager) => void)
description?: string
}import { useWindowManager } from 'xe-tldraw-window-manager'
function App() {
const wm = useWindowManager()
const openWindow = () => {
const window = wm.create({
title: 'My Window',
width: 600,
height: 400,
position: 'center'
})
window.on('close', () => {
console.log('Window closed')
})
}
return <button onClick={openWindow}>Open Window</button>
}// Register a custom component
wm.registerContent({
id: 'text-editor',
name: 'Text Editor',
component: TextEditorComponent,
defaultProps: {
fontSize: 14,
theme: 'dark'
}
})
// Create window with registered content
const window = wm.create({
title: 'Text Editor',
contentType: 'text-editor',
contentProps: {
initialText: 'Hello world'
}
})// Tile all windows
wm.tile()
// Cascade specific windows
const windows = wm.getWindows()
.filter(w => w.title.includes('Document'))
wm.cascade(windows.map(w => w.id))
// Custom layout
const arrangeInGrid = (columns = 3) => {
const windows = wm.getWindows()
const { width, height } = getScreenSize()
windows.forEach((window, i) => {
const row = Math.floor(i / columns)
const col = i % columns
window.setBounds({
x: col * (width / columns),
y: row * (height / 3),
width: width / columns,
height: height / 3
})
})
}const AutoSavePlugin: WindowManagerPlugin = {
name: 'auto-save',
version: '1.0.0',
install(manager, config) {
let saveTimer: NodeJS.Timeout
const save = () => {
const state = manager.getState()
localStorage.setItem('wm-state', JSON.stringify(state))
}
manager.on('stateChange', () => {
clearTimeout(saveTimer)
saveTimer = setTimeout(save, 1000)
})
// Restore on load
const saved = localStorage.getItem('wm-state')
if (saved) {
manager.setState(JSON.parse(saved))
}
}
}// Old API
const window = editor.createShape({
type: 'window',
props: { title: 'My Window' }
})
// New API
const window = wm.create({
title: 'My Window'
})- Always provide meaningful window titles
- Set appropriate size constraints for content
- Handle window events for cleanup
- Use content registration for reusable components
- Implement proper error boundaries in content
- Test with keyboard navigation enabled
- Optimize re-renders with React.memo
- Use the persistence API for user preferences
- Lazy load window content when possible
- Use virtual scrolling for large content
- Debounce resize/move events
- Minimize state updates during animations
- Use CSS transforms for smooth animations
- Batch window operations when possible
- Sanitize HTML content
- Use sandbox attributes for iframes
- Validate content registration
- Limit window creation rate
- Implement proper CSP headers