Introduction

WakaPAC is a lightweight JavaScript toolkit for building interactive applications in plain HTML. It combines a Win32-style message system, reactive data binding, and declarative HTML templates in a single script with no build step. The name combines Waka (inspired by Pac-Man) with PAC, the Presentation–Abstraction–Control architecture pattern it implements.

explanation

Core Concept

WakaPAC applications are driven by messages. Browser input events, component communication, and system events all flow through a single handler called msgProc. This creates a predictable event pipeline similar to Win32 applications, where input handling, messaging, and state updates follow a consistent model. Instead of scattering listeners across components, you reason about all interaction in one place.

Quick Example

A complete interactive to-do application:

<!DOCTYPE html>
<html>
<head>
    <script src="https://cdn.jsdelivr.net/gh/quellabs/wakapac@main/wakapac.min.js"></script>
</head>
<body>
    <div id="app">
        <input data-pac-bind="value: task" placeholder="New task">
        <button data-pac-bind="click: add, enable: task.length > 0">Add</button>
        <ul data-pac-bind="foreach: tasks" data-pac-item="task">
            <li>{{ task.name }}</li>
        </ul>
    </div>

    <script>
        wakaPAC('#app', {
            task: '',
            tasks: [],

            add() {
                this.tasks.push({ name: this.task });
                this.task = '';
            },
            msgProc(event) {
                if (event.message === wakaPAC.MSG_KEYDOWN &&
                    event.wParam === wakaPAC.VK_RETURN
                ) {
                    this.add();
                }
            }
        });
    </script>
</body>
</html>

The button stays disabled until there is input, the list updates automatically when an item is added, and msgProc handles Enter via VK_RETURN — the same mental model as a Win32 window procedure. No compilation, no virtual DOM, no JSX.

How WakaPAC Works

  1. Create a component with wakaPAC(selector, abstraction, options?)
  2. The abstraction object is exposed to the template via reactive bindings
  3. DOM bindings update automatically when state changes
  4. All input — keyboard, mouse, scroll, etc. — flows through msgProc
  5. Components communicate using sendMessage() and postMessage()

Comparison with Component Frameworks

Scenario Vue / React / Alpine WakaPAC
Components talking to each other Props, event emitters, or external state libraries (Pinia, Redux) sendMessage() and postMessage() — the same model as Win32
Handling keyboard and mouse input Per-component event listeners wired manually Central msgProc handler — one place for all input, like a window procedure
Reacting to state changes Framework-specific reactivity APIs with varying edge cases Declare your data, bind it in HTML — changes propagate automatically
Mental model Component tree with lifecycle hooks and framework conventions Message loop, centralized input handling, reactive state — a coherent application model
Getting started Build tooling, a CLI, or at minimum a module bundler One script tag

Key Features

Desktop Subsystem

All browser input flows through a unified message-driven interface. Mouse coordinates, modifier keys, and virtual key codes follow Win32 conventions.

wakaPAC('#app', {
    msgProc(event) {
        switch (event.message) {
            case wakaPAC.MSG_LBUTTONDOWN:
                // wParam contains modifier flags: MK_SHIFT, MK_CONTROL, etc.
                // lParam contains mouse coordinates
                const x = wakaPAC.LOWORD(event.lParam);
                const y = wakaPAC.HIWORD(event.lParam);
                console.log(`Left click at ${x},${y}`);
                break;

            case wakaPAC.MSG_KEYDOWN:
                // wParam is the virtual key code
                if (event.wParam === wakaPAC.VK_RETURN) {
                    console.log('Enter pressed');
                }

                // Check modifiers in lParam
                if (event.lParam & wakaPAC.KM_CONTROL) {
                    console.log('Ctrl was held');
                }

                break;
        }
    }
});

Inter-Component Messaging

Any component can send messages to any other using synchronous sendMessage() or queued postMessage(). The receiving component handles them in msgProc alongside all other events.

// Sender
wakaPAC('#toolbar', {
    notifyContent() {
        wakaPAC.sendMessage('content', wakaPAC.MSG_USER + 1, 0, 0, { action: 'refresh' });
    }
});

// Receiver
wakaPAC('#content', {
    msgProc(event) {
        switch (event.message) {
            case wakaPAC.MSG_USER + 1:
                console.log('Refresh requested:', event.detail.action);
                break;
        }
    }
});

Reactive Data Binding

Updating state automatically updates the DOM. User interaction updates state without manual event wiring.

<input data-pac-bind="value: email">
<p>You entered: {{ email }}</p>

Declarative Templates

Describe how data maps to the DOM directly in HTML, without imperative DOM manipulation.

<button data-pac-bind="click: save, enable: !saving">
    {{ saving ? 'Saving...' : 'Save' }}
</button>

<ul data-pac-bind="foreach: items" data-pac-item="item">
    <li>{{ item.name }}: {{ item.price }}</li>
</ul>

Computed Properties

Derived values that recalculate automatically when their dependencies change.

wakaPAC('#app', {
    firstName: 'John',
    lastName: 'Doe',

    computed: {
        fullName() {
            return `${this.firstName} ${this.lastName}`;
        }
    }
});

Multiple Element Binding

A single wakaPAC() call on a class selector creates independent reactive instances for each matched element.

<div class="counter">Count: {{count}} <button data-pac-bind="click: increment">+</button></div>
<div class="counter">Count: {{count}} <button data-pac-bind="click: increment">+</button></div>

<script>
    wakaPAC('.counter', { count: 0, increment() { this.count++; } });
</script>

Browser & Container Properties

Scroll position, visibility, dimensions, and connectivity are exposed as reactive properties — no manual listeners required.

<button data-pac-bind="visible: browserScrollY > 300">
    ↑ Back to Top
</button>

<div data-pac-bind="visible: !browserOnline" class="offline-banner">
    You're offline - changes will sync when reconnected
</div>

Expression Language

Template bindings support conditional logic, arithmetic, method calls, and common data operations. Expressions are evaluated through a dedicated parser — not JavaScript's eval — for sandboxed, predictable execution.

<p>Total: ${{ total }}</p>
<span>{{ count > 0 ? count + ' items' : 'Empty' }}</span>
<div data-pac-bind="class: { active: isSelected, disabled: !enabled }"></div>
<p>{{ formatDate(createdAt) }}</p>
<span>{{ tags.join(', ') }}</span>
Note: Array methods that require callbacks, such as filter and map, are not supported in expressions. That logic belongs in a computed property, where it is cached and only recalculates when dependencies change.

Size

WakaPAC is a single JavaScript file with no dependencies.

  • Source: ~460KB (with extensive comments)
  • Minified: ~95KB
  • Minified + gzipped: ~28KB