Skip to content
This repository was archived by the owner on Aug 7, 2025. It is now read-only.

Latest commit

 

History

History
707 lines (575 loc) · 14.8 KB

File metadata and controls

707 lines (575 loc) · 14.8 KB

XE TLDraw Window Manager - API Design & Interface Specification

Overview

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.

Design Principles

  1. Developer First: APIs should be intuitive, well-typed, and provide excellent IntelliSense support
  2. Composable: Features should be modular and work well together
  3. Extensible: Core functionality can be extended through plugins and custom implementations
  4. Performance: APIs should encourage performant patterns and avoid unnecessary re-renders
  5. Type Safe: Full TypeScript support with strict typing

Core API

WindowManager Instance

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
}

Window Creation Options

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>
}

Window Handle

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 System

// 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
}

Events

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

Hooks

// 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
): void

Themes

interface 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
}

Configuration

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[]
}

Plugins

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[]) => any

Advanced Features

Window Snapping

interface 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 }
}

Animations

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[]
}

Persistence

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 {}

Accessibility

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
}

Usage Examples

Basic Usage

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>
}

Content Registration

// 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'
  }
})

Advanced Window Management

// 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
    })
  })
}

Plugin Development

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))
    }
  }
}

Migration Guide

From v0.x to v1.0

// Old API
const window = editor.createShape({
  type: 'window',
  props: { title: 'My Window' }
})

// New API
const window = wm.create({
  title: 'My Window'
})

Best Practices

  1. Always provide meaningful window titles
  2. Set appropriate size constraints for content
  3. Handle window events for cleanup
  4. Use content registration for reusable components
  5. Implement proper error boundaries in content
  6. Test with keyboard navigation enabled
  7. Optimize re-renders with React.memo
  8. Use the persistence API for user preferences

Performance Guidelines

  1. Lazy load window content when possible
  2. Use virtual scrolling for large content
  3. Debounce resize/move events
  4. Minimize state updates during animations
  5. Use CSS transforms for smooth animations
  6. Batch window operations when possible

Security Considerations

  1. Sanitize HTML content
  2. Use sandbox attributes for iframes
  3. Validate content registration
  4. Limit window creation rate
  5. Implement proper CSP headers