Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

199 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

haxball-ui-framework / HaxUI

πŸ‡ΊπŸ‡Έ English | πŸ‡ͺπŸ‡Έ EspaΓ±ol

npm version License Architecture Vanilla JS Version

HaxBall UI Framework is a minimal, stable UI layer for building overlay windows on top of the HaxBall client.

It is not a full framework like React. It is not a game engine. It is a small, well-designed UI core that gives you full DOM control without fighting HaxBall.


πŸ“¦ Installation

npm install haxball-ui-framework
// ES Module (Vite, Webpack, Rollup)
import HaxUI from 'haxball-ui-framework';

// CommonJS (Node.js)
const HaxUI = require('haxball-ui-framework');

Or inject the IIFE bundle directly into HaxBall (no npm needed):

// Paste dist/haxball-ui-framework.iife.js into the DevTools console
// or @require it from a Tampermonkey userscript
HaxUI.diagnostics(); // β†’ { initialized: false, version: 'v1', ... }

🧠 Core Idea

HaxBall UI Framework introduces a thin overlay layer over the HaxBall DOM that enables:

  • window creation and management
  • dynamic content updates
  • clean lifecycle and destruction
  • full CSS isolation via Shadow DOM
  • safe event handling that doesn't leak into the game
  • drag & resize, native-styled buttons, and a theme that matches HaxBall's own UI

Everything is built around a single principle:

One API. One namespace. Full control.


βš™οΈ Architecture

Layer Module Responsibility
Bootstrap RootMount Detects execution context, anchors #haxui-root to document.body, re-anchors if HaxBall clears the DOM
Registry WindowManager Window registry, lifecycle, and z-index stack
Instance Window Builds the DOM tree for one window, owns its Shadow Root
Safety EventGuard Per-event-type policy so window events never leak into the game
Safety EventRegistry Tracks every listener so destroy() cleans up with zero leaks
Style StyleManager Injects base styles per theme into each Shadow Root
Content Sanitize DOMParser-based sanitization β€” strings never hit innerHTML raw
Interaction DragManager Drag windows by their header, clamped to the viewport
Interaction ResizeManager Resize from any of the 4 corners, with minimum dimensions
Integration ButtonInjector Injects native-styled buttons into HaxBall's own button bar
Public API HaxUI window.HaxUI β€” the only global name exposed
Handle WindowHandle Lightweight object returned per window β€” no internals exposed

πŸ“¦ Project Structure

haxball-ui-framework/
β”‚
β”œβ”€β”€ src/                        # ES Module source (import/export)
β”‚   β”œβ”€β”€ index.js
β”‚   β”œβ”€β”€ constants/config.js
β”‚   β”œβ”€β”€ utils/sanitize.js
β”‚   └── core/
β”‚       β”œβ”€β”€ HaxUI.js
β”‚       β”œβ”€β”€ WindowManager.js
β”‚       β”œβ”€β”€ Window.js
β”‚       β”œβ”€β”€ RootMount.js
β”‚       β”œβ”€β”€ EventGuard.js
β”‚       β”œβ”€β”€ EventRegistry.js
β”‚       β”œβ”€β”€ StyleManager.js
β”‚       β”œβ”€β”€ DragManager.js
β”‚       β”œβ”€β”€ ResizeManager.js
β”‚       └── ButtonInjector.js
β”‚
β”œβ”€β”€ dist/                       # Built outputs (generated by npm run build)
β”‚   β”œβ”€β”€ haxball-ui-framework.esm.js     # ES Module
β”‚   β”œβ”€β”€ haxball-ui-framework.cjs.js     # CommonJS
β”‚   └── haxball-ui-framework.iife.js    # IIFE β€” paste into HaxBall console
β”‚
β”œβ”€β”€ core/                       # Legacy IIFE source (kept for build:bundle)
β”œβ”€β”€ constants/
β”œβ”€β”€ utils/
β”‚
β”œβ”€β”€ dev/
β”‚   β”œβ”€β”€ playground.js           # 13 test groups, 70+ assertions
β”‚   └── examples.js             # 7 worked examples
β”‚
β”œβ”€β”€ build-esm.cjs               # Builds dist/ (ESM + CJS + IIFE)
β”œβ”€β”€ build.cjs                   # Builds legacy haxball-ui.bundle.js
└── package.json

πŸš€ Getting Started

Option A β€” npm (recommended for projects)

npm install haxball-ui-framework
import HaxUI from 'haxball-ui-framework';

HaxUI.init();

const win = HaxUI.createWindow({
  id: 'stats',
  title: 'Statistics',
  theme: 'haxball',
  width: 260,
  height: 180,
  x: 16,
  y: 16,
  content: '<p>Loading...</p>'
});

Option B β€” IIFE bundle (inject directly into HaxBall)

npm run build
# β†’ dist/haxball-ui-framework.iife.js

Paste into the HaxBall DevTools console or add to a Tampermonkey userscript with @require file:///path/to/haxball-ui-framework.iife.js.

Option C β€” Build from source

git clone https://github.com/mcvn2wrgx2-cpu/haxball-ui-framework
cd haxball-ui-framework
npm run build        # β†’ dist/ (ESM + CJS + IIFE)
npm run build:bundle # β†’ haxball-ui.bundle.js (legacy IIFE)
npm run build:all    # β†’ both

🧩 Public API

Initialize

HaxUI.init({ baseZ: 9000 }); // optional β€” auto-called on first createWindow()

Create a window

const win = HaxUI.createWindow({
  id:           'my-window',
  title:        'My Window',
  width:        260,
  height:       180,
  x:            16,
  y:            16,
  content:      '<p>Hello HaxBall</p>',   // string or DOM Node
  theme:        'default',                 // 'default' | 'haxball'
  draggable:    true,                      // drag from header
  resizable:    true,                      // resize from corners
  titleVisible: true,                      // show/hide header on creation
  closable:     true                       // show close button (βœ•)
});

Update content

// Safe: pass a Node for external data (player names, chat β€” avoids XSS)
const node = document.createElement('div');
node.textContent = 'Goals: ' + data.goals;
win.setContent(node);

// Static markup
win.setContent('<p>Match ended</p>');

Show / hide / title

win.show();
win.hide();
win.hideTitle();   // collapses the header
win.showTitle();

Destroy

win.destroy();
HaxUI.destroyWindow('my-window');
HaxUI.destroyAll();   // use on script unload

Get an existing window

const existing = HaxUI.getWindow('my-window'); // returns null if not found β€” never throws
if (existing) existing.setContent('<p>Updated</p>');

Native-styled buttons

HaxUI.createButton({
  id:    'stats-btn',
  label: 'πŸ“Š Stats',
  onClick: () => win.show()
});

// Or open a window directly on click
HaxUI.createButton({
  id:    'panel-btn',
  label: 'πŸ›‘οΈ Admin',
  onOpenWindow: { id: 'admin', title: 'Admin Panel', theme: 'haxball', width: 280, height: 400, x: 20, y: 60 }
});

HaxUI.destroyButton('stats-btn');

Diagnostics

HaxUI.diagnostics();
// β†’ { initialized: true, mode: 'shadow', rootPresent: true, windowCount: 2, baseZ: 9000, version: 'v1' }

🎨 The haxball Theme

theme: 'haxball' replicates HaxBall's native .dialog style using values measured directly from the live DOM via getComputedStyle():

HaxUI.createWindow({
  id:    'confirm',
  title: 'Leave room?',
  theme: 'haxball',
  width: 300, height: 150, x: 100, y: 100,
  content: '<p>Are you sure?</p><button>Leave</button>'
});
Property Value
Background rgb(26, 33, 37)
Border radius 5px
Header accent 3px solid rgb(193, 53, 53)
Font "Open Sans", sans-serif
Button padding 5px 15px
Button height 26px
Button border none

πŸ”’ Design Decisions

Decision Risk mitigated
Shadow DOM per window (CSS namespace fallback) HaxBall's global CSS bleeding into overlay elements
Single window.HaxUI namespace Collisions with HaxBall's own globals or other scripts
EventGuard per-event-type policy Keyboard/mouse events leaking into the game
BASE_Z = 9000, configurable Overlay windows rendering behind HaxBall's own menus
MutationObserver in RootMount HaxBall clearing the DOM on room transitions
DOMParser in setContent() XSS when rendering external strings (player names, chat)
WindowHandle._destroyed flag Safe post-destroy calls β€” no errors inside game callbacks
Drag/resize listeners in capture phase EventGuard's bubble-phase stopPropagation was blocking drag release
ButtonInjector polling for .header-btns Unreliable timing for a single MutationObserver
Button styles measured via getComputedStyle() Visual mismatch with HaxBall's real native buttons

🎯 Example: Live Stats Overlay

import HaxUI from 'haxball-ui-framework';

const stats = HaxUI.createWindow({
  id: 'stats', title: 'Stats', theme: 'haxball',
  width: 260, height: 180, x: 16, y: 16
});

function onGameData(data) {
  const node = document.createElement('div');
  node.innerHTML = [
    '<div>Team 1: ' + data.score[0] + '</div>',
    '<div>Team 2: ' + data.score[1] + '</div>',
    '<div>Time: '   + data.time     + '</div>'
  ].join('');
  stats.setContent(node);
}

function onLeaveRoom() { HaxUI.destroyAll(); }

More examples in dev/examples.js.


πŸ—ΊοΈ Roadmap

  • v0 β€” Core: windows, content updates, Shadow DOM, event isolation, DOM re-anchor
  • v1 β€” Interaction: drag & drop, resize, haxball theme, native button injection, hideTitle, close button
  • v1.0.1 β€” npm: import HaxUI from 'haxball-ui-framework', ESM + CJS + IIFE outputs
  • v2 β€” Components: component system for setContent(), base components
  • v3 β€” Plugins: HaxUI.use(plugin), window lifecycle hooks

⚠️ Status

v1.0.1 β€” stable. Published on npm. The v0/v1 API contract is frozen and won't break.


🧠 Design Philosophy

  • minimal surface area, maximum control
  • one global name, zero internals exposed
  • every decision traceable to a real HaxBall environment risk
  • extensible to v2–v3 without breaking existing code

πŸ“„ License

MIT


❀️ DEVLOG

HappyCat
made with love :)

About

πŸͺŸ Lightweight and extensible UI framework for creating modern HaxBall interfaces with a clean developer experience.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

Watchers

Forks

Releases

Used by

Contributors

Languages