# Pascal Editor Event Bus for Inter-Component Communication: Typed Events with Mitt

> Discover how the Pascal Editor uses a strongly-typed event bus with Mitt for efficient inter-component communication across packages. Simplify your application architecture.

- Repository: [Pascal/editor](https://github.com/pascalorg/editor)
- Tags: internals
- Published: 2026-03-25

---

**The Pascal Editor implements a centralized, strongly-typed event bus using the lightweight mitt library to decouple UI components, tools, and systems across the `@pascal-app/core`, viewer, and editor packages.**

The pascalorg/editor repository relies on a single shared `emitter` instance to coordinate complex interactions between the Three.js-based viewer, property grids, and keyboard-controlled editing tools. This article examines how the Pascal Editor event bus for inter-component communication enables maintainable architecture through strongly-typed payloads, consistent event suffixes, and package-agnostic consumption patterns.

## Core Architecture in [`packages/core/src/events/bus.ts`](https://github.com/pascalorg/editor/blob/main/packages/core/src/events/bus.ts)

The foundation of the system resides in [`packages/core/src/events/bus.ts`](https://github.com/pascalorg/editor/blob/main/packages/core/src/events/bus.ts), which exports a configured mitt instance and comprehensive TypeScript definitions.

### The Mitt-Based Emitter

The bus instantiates a single global emitter typed with a composite `EditorEvents` union:

```typescript
export const emitter = mitt<EditorEvents>()
export const eventSuffixes = [...] as const

```

This design ensures that every package—`@pascal-app/core`, `@pascal-app/viewer`, and `@pascal-app/editor`—references the same event bus instance imported from the core package's public API.

### Typed Event Payloads and Suffixes

The architecture enforces strict typing through the `NodeEvent<T>` interface, which describes event payloads containing the node reference, world/local positions, optional surface normals, and a `stopPropagation` helper. The system reuses event suffixes (`click`, `move`, `enter`, `leave`) across all node types, enabling a concise naming scheme such as `wall:click` or `item:pointerdown`. This approach generates a strongly-typed `EditorEvents` union that covers grid interactions, camera controls, tool actions, and preset operations.

## Consuming Events Across Packages

Different subsystems consume the Pascal Editor event bus through specialized hooks that translate raw inputs into typed emissions.

### Node Interactions via `useNodeEvents`

Located in [`packages/viewer/src/hooks/use-node-events.ts`](https://github.com/pascalorg/editor/blob/main/packages/viewer/src/hooks/use-node-events.ts), this hook intercepts React Three Fiber (R3F) pointer events on mesh components and converts them into typed node events (e.g., `wall:pointerdown`, `wall:click`). It automatically wires callbacks like `onPointerDown` and `onPointerEnter` to the global emitter, keeping mesh components declarative and free of direct event handling logic.

### Grid Raycasting with `useGridEvents`

The [`packages/editor/src/hooks/use-grid-events.ts`](https://github.com/pascalorg/editor/blob/main/packages/editor/src/hooks/use-grid-events.ts) module performs manual raycasting on the HTML5 canvas to detect interactions with the custom grid plane. It constructs `GridEvent` payloads and emits events such as `grid:click` and `grid:pointermove`, enabling tools to snap to cursor positions without relying on Three.js object intersection.

### Keyboard Shortcuts and Tool Actions

In [`packages/editor/src/hooks/use-keyboard.ts`](https://github.com/pascalorg/editor/blob/main/packages/editor/src/hooks/use-keyboard.ts), keyboard listeners emit high-level tool actions like `tool:cancel` when users press **Esc**, or `tool:commit` on **Enter**. This abstraction allows tool logic to remain keyboard-agnostic while reacting to semantic event names.

## Practical Implementation Examples

### Subscribing to Wall Click Events

Components subscribe to specific node events using the shared emitter and must cleanup listeners in effect hooks:

```tsx
import { useEffect } from 'react'
import { emitter } from '@pascal-app/core'

export function WallClickListener() {
  useEffect(() => {
    const handler = (e: WallEvent) => {
      console.log('Wall clicked:', e.node.id, e.position)
      // Custom logic – e.g., open a property panel
    }

    emitter.on('wall:click', handler)
    return () => emitter.off('wall:click', handler)
  }, [])

  return null
}

```

The `WallEvent` type is defined in [`packages/core/src/events/bus.ts`](https://github.com/pascalorg/editor/blob/main/packages/core/src/events/bus.ts) at line 34, ensuring full IntelliSense support for the payload structure.

### Emitting Custom Preset Events

Arbitrary systems can emit events to trigger cross-component workflows:

```typescript
import { emitter } from '@pascal-app/core'

function generateThumbnail(presetId: string, nodeId: string) {
  emitter.emit('preset:generate-thumbnail', { presetId, nodeId })
}

```

Corresponding listeners update UI state when asynchronous operations complete:

```typescript
emitter.on('preset:thumbnail-updated', ({ presetId, thumbnailUrl }) => {
  // Update preview image, cache, etc.
})

```

### Declarative Event Binding with Hooks

The `useNodeEvents` hook abstracts event registration for Three.js meshes:

```tsx
import { useNodeEvents } from '@pascal-app/viewer'
import { WallNode } from '@pascal-app/core'

type Props = { wall: WallNode }

export function WallMesh({ wall }: Props) {
  const nodeEvents = useNodeEvents(wall, 'wall')

  return (
    <mesh
      {...nodeEvents}
      // ...geometry, material
    />
  )
}

```

This pattern automatically spreads pointer event handlers onto the mesh, emitting typed bus events without imperative code.

### Tracking Grid Movement in Tools

Tools requiring cursor position tracking subscribe to grid-specific events:

```tsx
import { useEffect } from 'react'
import { emitter, type GridEvent } from '@pascal-app/core'

export function GridTracker() {
  useEffect(() => {
    const onMove = (e: GridEvent) => {
      console.log('Cursor over grid at', e.position)
    }

    emitter.on('grid:move', onMove)
    return () => emitter.off('grid:move', onMove)
  }, [])

  return null
}

```

## Key Source Files Reference

The Pascal Editor event bus implementation spans several critical files:

- **[`packages/core/src/events/bus.ts`](https://github.com/pascalorg/editor/blob/main/packages/core/src/events/bus.ts)** – Central definition of the `mitt` instance, `EditorEvents` types, `NodeEvent<T>` interfaces, and `eventSuffixes` constants.
- **[`packages/core/src/index.ts`](https://github.com/pascalorg/editor/blob/main/packages/core/src/index.ts)** – Public API surface that re-exports `emitter` and `eventSuffixes` for cross-package consumption.
- **[`packages/viewer/src/hooks/use-node-events.ts`](https://github.com/pascalorg/editor/blob/main/packages/viewer/src/hooks/use-node-events.ts)** – Hook translating R3F pointer events into typed node bus events.
- **[`packages/editor/src/hooks/use-grid-events.ts`](https://github.com/pascalorg/editor/blob/main/packages/editor/src/hooks/use-grid-events.ts)** – Canvas raycasting implementation emitting `grid:*` events.
- **[`packages/editor/src/hooks/use-keyboard.ts`](https://github.com/pascalorg/editor/blob/main/packages/editor/src/hooks/use-keyboard.ts)** – Keyboard shortcut handler emitting high-level tool commands.
- **[`packages/editor/src/lib/sfx-bus.ts`](https://github.com/pascalorg/editor/blob/main/packages/editor/src/lib/sfx-bus.ts)** – Demonstrates coexistence of multiple mitt instances by implementing a separate sound-effects bus.

## Summary

- The Pascal Editor uses a **single typed mitt instance** exported from [`packages/core/src/events/bus.ts`](https://github.com/pascalorg/editor/blob/main/packages/core/src/events/bus.ts) to coordinate all inter-component communication.
- **Strongly-typed payloads** via `NodeEvent<T>` and composite `EditorEvents` unions eliminate runtime type errors across the monorepo.
- **Consistent suffix patterns** (`:click`, `:move`, `:enter`) enable predictable event naming across node types like walls, items, and zones.
- **Specialized hooks** (`useNodeEvents`, `useGridEvents`, `useKeyboard`) abstract input sources while publishing to the centralized bus, maintaining loose coupling between the viewer, editor, and tool systems.

## Frequently Asked Questions

### What event library powers the Pascal Editor event bus?

The Pascal Editor uses **mitt**, a lightweight 200-byte event emitter. The source code in [`packages/core/src/events/bus.ts`](https://github.com/pascalorg/editor/blob/main/packages/core/src/events/bus.ts) imports mitt and creates a single strongly-typed instance: `export const emitter = mitt<EditorEvents>()`. This tiny footprint keeps the core package size minimal while supporting typed event payloads.

### How does the Pascal Editor maintain type safety across package boundaries?

Type safety is enforced through TypeScript's generic constraints. The `EditorEvents` interface in [`bus.ts`](https://github.com/pascalorg/editor/blob/main/bus.ts) defines all possible event keys and payload types. When packages like `@pascal-app/viewer` or `@pascal-app/editor` import the `emitter`, they receive the same typed instance, ensuring that `emitter.on('wall:click')` automatically infers the `WallEvent` payload structure without additional runtime checks.

### Can multiple event buses coexist in the Pascal Editor architecture?

Yes. While the primary inter-component communication uses the shared `emitter`, the codebase demonstrates isolated buses for specific concerns. The [`packages/editor/src/lib/sfx-bus.ts`](https://github.com/pascalorg/editor/blob/main/packages/editor/src/lib/sfx-bus.ts) file creates a separate mitt instance dedicated to sound effects, proving that multiple typed buses can operate independently without event leakage between subsystems.

### How do grid events differ from node events in the implementation?

Grid events originate from manual raycasting logic in `useGridEvents` that queries the HTML5 canvas directly, emitting events like `grid:click` with custom `GridEvent` payloads containing world positions. Node events, handled by `useNodeEvents`, derive from React Three Fiber's pointer event system on Three.js meshes, automatically calculating intersections and surface normals before emitting typed events like `wall:pointerdown` or `item:click`.