JavaScript • Win32 Application Model • Browser Native

Reactive UIs with desktop-style event control

Win32's window model — message loop, msgProc, sendMessage — running natively in the browser. Reactive bindings included, no build step required.

<script src="https://cdn.jsdelivr.net/gh/quellabs/wakapac@2.5.0/wakapac.min.js"></script>
Live Demo — Issue Triage
Issue triage
Choose an issue and resolve it.
Ctrl+Z to undo
INBOX{{issues.length}} open
All caught up.
MESSAGE LOG
{{item}}
ISSUE DETAIL
{{title}}
{{description}}
Severity: {{severity}}
Select an issue to see its details.
Message flow excerpt · full implementation in the page source
const MSG_SELECTED = wakaPAC.MSG_USER + 1;
const MSG_RESOLVE  = wakaPAC.MSG_USER + 2;
const MSG_UNDO     = wakaPAC.MSG_USER + 3;

// The inbox owns the issues and sends the selected one.
wakaPAC('#hero-queue', {
  issues: [/* the three issues shown above */],
  selectedId: 0,
  selectIssue(id) {
    const issue = this.issues.find(item => item.id === id);
    if (!issue) return;
    this.selectedId = id;
    wakaPAC.sendMessage('hero-detail', MSG_SELECTED, id, 0, issue);
  },
  msgProc(event) {
    if (event.message === MSG_RESOLVE) this.resolve(event.wParam);
    if (event.message === MSG_UNDO) this.undo();
  }
});

// The detail pane sends an action back to the inbox.
wakaPAC('#hero-detail', {
  selectedId: 0, title: '', description: '', severity: '',
  resolveIssue() {
    wakaPAC.sendMessage('hero-queue', MSG_RESOLVE, this.selectedId, 0);
  },
  msgProc(event) {
    if (event.message === MSG_SELECTED) {
      this.selectedId = event.wParam;
      this.title = event.detail.title;
      this.description = event.detail.description;
      this.severity = event.detail.severity;
    }
  }
});

// Ctrl+Z is registered as an accelerator in both components.

One mental model.

Win32's window architecture, directly in the browser. If you know WndProc, you know msgProc.

Message-driven.

Components communicate via sendMessage and postMessage. One data path. Predictable by construction.

Reactive included.

State, computed properties, two-way bindings — all there, zero build step, zero dependencies.

Get Started in 60 Seconds

Copy, paste, done. No installation. No configuration.

<!DOCTYPE html>
<html>
<head>
    <script src="https://cdn.jsdelivr.net/gh/quellabs/wakapac@2.5.0/wakapac.min.js"></script>
</head>
<body>

<div id="app">
    <h1>Hello {{name}}!</h1>
    <input data-pac-bind="value: name">
</div>

<script>
    wakaPAC('#app', {
        name: 'World'
    });
</script>

</body>
</html>

That's a complete WakaPAC component • ~98KB minified • MIT licensed

The full Win32 model. In one file.

Not just reactive bindings — a complete application architecture.

msgProc

One function. Every message. Mouse, keyboard, resize, focus, clipboard — all in a single switch. WakaPAC's WndProc.

sendMessage & postMessage

Components talk exactly as Win32 windows do. Synchronous or queued delivery. Custom messages offset from MSG_USER.

Accelerator Tables

Map "Ctrl+S" to a command ID. MSG_ACCEL delivers it to msgProc. Child tables override parents; parent tables cover all descendants.

Beyond DOM Events

Mouse gestures, drag-and-drop with file metadata, synchronous clipboard interception — all arriving through the same msgProc switch.

Canvas Drawing

getDC() on a canvas component returns its drawing context with Win32-style acquire/release. 2D and 3D through WebGL. HiDPI handled internally.

Reactivity Included

State, computed properties, two-way bindings, list rendering. Write this.items.push(...) and the DOM updates. No build step, no dependencies.

Why WakaPAC Exists

Win32's window model is a complete application architecture — message loop, WndProc, SendMessage, device contexts. It works. I wanted the same model in the browser, with reactivity built in. Not another binding library. An application model.

— Floris, Quellabs

One architecture, extensible everywhere

Plugins don't bolt on a second application model. They expose reactive state and send/receive messages through the same msgProc pipeline WakaPAC itself uses — so storage, networking, forms, routing, and rendering all stay one mental model deep.

See It In Action

Real code, real patterns, ready to copy

msgProc — Drag with Mouse Capture
wakaPAC('#box', {
  msgProc(event) {
    switch (event.message) {
      case wakaPAC.MSG_LBUTTONDOWN: {
        wakaPAC.setCapture(this.pacId);
        this._dragging = true;
        break;
      }

      case wakaPAC.MSG_MOUSEMOVE:
        if (this._dragging) {
          ...
        }
        break;

      case wakaPAC.MSG_LBUTTONUP:
        wakaPAC.releaseCapture();
        this._dragging = false;
        break;

      case wakaPAC.MSG_CAPTURECHANGED:
        this._dragging = false;
        break;
    }
  }
});

setCapture() routes all mouse events to this component — even when the cursor leaves it

sendMessage — Component Communication
const MSG_REFRESH = wakaPAC.MSG_USER + 1;
const MSG_RESET   = wakaPAC.MSG_USER + 2;

// Sender
wakaPAC('#toolbar', {
  refresh() {
    wakaPAC.sendMessage('content', MSG_REFRESH, 0, 0);
  },
  reset() {
    wakaPAC.postMessage('content', MSG_RESET, 0, 0);
  }
});

// Receiver
wakaPAC('#content', {
  msgProc(event) {
    switch (event.message) {
      case MSG_REFRESH: this.reload(); break;
      case MSG_RESET:   this.clear();  break;
    }
  }
});

sendMessage() waits for the receiver to return. postMessage() queues and returns immediately.

Computed Properties
wakaPAC('#app', {
  firstName: 'John',
  lastName: 'Doe',
  items: [
    { name: 'Apple', price: 10 },
    { name: 'Banana', price: 20 }
  ],

  computed: {
    fullName() {
      return `${this.firstName} ${this.lastName}`;
    },
    totalPrice() {
      return this.items.reduce(
        (sum, item) => sum + item.price, 0
      );
    }
  }
});

Recalculates automatically when dependencies change

Reactive Browser Properties
<div id="app">
  <p data-pac-bind="visible: !browserOnline">
    ⚠️ You are offline
  </p>

  <p>
    Viewport: {{browserViewportWidth}} × {{browserViewportHeight}}
  </p>

  <p data-pac-bind="visible: !browserVisible">
    Tab hidden — updates paused
  </p>

  <p>Scrolled: {{browserScrollY}}px</p>

  <p>Connection: {{browserNetworkQuality}}</p>
</div>

Network, viewport, scroll, visibility — all reactive, zero setup

Built on PAC Architecture

The same architecture Win32 uses — and the reason WakaPAC behaves like a desktop application framework rather than a binding library.

Presentation

Your HTML template. Owns its visual area and renders state — but never reads or writes state directly. The equivalent of a Win32 window surface.

Abstraction

Your data and business logic. Knows nothing about the DOM. Never triggers a UI update directly — it only holds state. The equivalent of application data in a Win32 program.

Control

WakaPAC itself, and your msgProc. Mediates all communication between Presentation and Abstraction — state changes flow here, and the DOM follows.

Why this matters: A template names a handler — click: save — but Control is what resolves that name and invokes it; the template never holds a reference to your data or calls into it directly. And state never touches the DOM on its own: every change flows back through Control, which decides what re-renders. Presentation and Abstraction only ever interact indirectly, through that one mediator — the same discipline that makes Win32 applications predictable.

The Quellabs Ecosystem

A family of open-source tools built around the same philosophy.

The Win32 model. In the browser.

Download one file and write working code in the next 60 seconds

Open source • MIT licensed • ~98KB minified • Zero dependencies