Add initial Nebula Core packages and docs

Import initial monorepo structure for Nebula Core: add packages (@nebula/core, core-glyphs, core-input, core-navigation, core-theme, core-ui, core-utils) with source, dist, tests and assets. Expand README with overview, quick start and API snippets, and add package-level documentation files. Add jsconfig.json for path mapping, package.json and lockfiles to bootstrap the repo. This commit sets up the project layout, docs, and local package links for further development.
This commit is contained in:
2026-01-31 22:57:16 +13:00
parent 40dcfc1853
commit 987ff560f5
309 changed files with 2611 additions and 1 deletions
+128
View File
@@ -0,0 +1,128 @@
# @nebula/core-utils
Small, dependency-free utilities shared across Nebula Core packages. No DOM or platform assumptions.
## Why it exists
Controller-first apps often duplicate tiny helpers (math, events, clamping). This package centralizes those primitives without imposing any UI or input model.
## Installation
```bash
npm install @nebula/core-utils
```
## Usage
```js
import { clamp, createEmitter, lerp, roundTo } from "@nebula/core-utils";
const value = clamp(12, 0, 10); // 10
const blended = lerp(0, 100, 0.25); // 25
const rounded = roundTo(3.14159, 2); // 3.14
const inputEvents = createEmitter();
const unsubscribe = inputEvents.on((payload) => {
console.log("input", payload);
});
inputEvents.emit({ action: "confirm" });
unsubscribe();
```
## API
### clamp(value, min, max)
Clamp a number between a minimum and maximum.
**Parameters**
- `value` (`number`): The value to clamp.
- `min` (`number`): Lower bound.
- `max` (`number`): Upper bound.
**Returns**
- `number`: The clamped value.
**Examples**
```js
clamp(5, 0, 10); // 5
clamp(-2, 0, 10); // 0
clamp(99, 0, 10); // 10
```
### lerp(from, to, t)
Linear interpolation between two numbers.
**Parameters**
- `from` (`number`): Start value.
- `to` (`number`): End value.
- `t` (`number`): Interpolation factor in the range 0..1.
**Returns**
- `number`: Interpolated value.
**Examples**
```js
lerp(0, 10, 0); // 0
lerp(0, 10, 1); // 10
lerp(0, 10, 0.5); // 5
```
### roundTo(value, decimals)
Round a number to a specific decimal precision.
**Parameters**
- `value` (`number`): The value to round.
- `decimals` (`number`): Number of decimal places.
**Returns**
- `number`: Rounded value.
**Examples**
```js
roundTo(1.005, 2); // 1.01
roundTo(123.4567, 1); // 123.5
```
### createEmitter()
Create a tiny event emitter for a single payload type. The emitter provides three methods: `on`, `emit`, and `clear`.
**Returns**
An object with the shape:
```ts
{
on: (listener: (payload: T) => void) => () => void;
emit: (payload: T) => void;
clear: () => void;
}
```
**Methods**
- `on(listener)`: Registers a listener. Returns an `unsubscribe()` function.
- `emit(payload)`: Calls all listeners with the payload.
- `clear()`: Removes all listeners.
**Examples**
```js
const events = createEmitter();
const off = events.on((payload) => {
console.log(payload);
});
events.emit({ type: "focus", id: "button-1" });
off();
events.clear();
```
## TypeScript / IntelliSense
This package ships as ES Modules with JSDoc annotations, so you get editor IntelliSense out of the box in TypeScript-aware editors.
## SteamOS / Steam Deck notes
This package is platform-agnostic and safe to use in any runtime (browser, Electron, Node).
## License
MIT
+18
View File
@@ -0,0 +1,18 @@
{
"name": "@nebulaproject/core-utils",
"private": true,
"version": "0.1.0",
"description": "Shared small utilities for controller-first apps.",
"type": "module",
"exports": "./src/index.js",
"main": "./src/index.js",
"files": [
"src"
],
"engines": {
"node": ">=18"
},
"scripts": {
"test": "node --test"
}
}
+57
View File
@@ -0,0 +1,57 @@
/**
* Clamp a number between a minimum and maximum.
* @param {number} value
* @param {number} min
* @param {number} max
* @returns {number}
*/
export function clamp(value, min, max) {
return Math.min(max, Math.max(min, value));
}
/**
* Linear interpolation between two numbers.
* @param {number} from
* @param {number} to
* @param {number} t - 0..1
* @returns {number}
*/
export function lerp(from, to, t) {
return from + (to - from) * t;
}
/**
* Round a number to a specific decimal precision.
* @param {number} value
* @param {number} decimals
* @returns {number}
*/
export function roundTo(value, decimals) {
const factor = 10 ** decimals;
return Math.round(value * factor) / factor;
}
/**
* Create a tiny event emitter.
* @template T
* @returns {{ on: (listener: (payload: T) => void) => () => void, emit: (payload: T) => void, clear: () => void }}
*/
export function createEmitter() {
/** @type {Set<(payload: T) => void>} */
const listeners = new Set();
return {
on(listener) {
listeners.add(listener);
return () => listeners.delete(listener);
},
emit(payload) {
for (const listener of listeners) {
listener(payload);
}
},
clear() {
listeners.clear();
}
};
}
+22
View File
@@ -0,0 +1,22 @@
import test from "node:test";
import assert from "node:assert/strict";
import { clamp, createEmitter } from "../src/index.js";
test("clamp keeps value within bounds", () => {
assert.equal(clamp(5, 0, 10), 5);
assert.equal(clamp(-1, 0, 10), 0);
assert.equal(clamp(99, 0, 10), 10);
});
test("createEmitter registers and emits", () => {
const emitter = createEmitter();
/** @type {Array<string>} */
const events = [];
const off = emitter.on((payload) => events.push(payload));
emitter.emit("confirm");
off();
emitter.emit("cancel");
assert.deepEqual(events, ["confirm"]);
});