feat: restructure project and update documentation

This commit is contained in:
2026-03-22 01:10:30 +01:00
parent 07d1fb0060
commit b95af97596
148 changed files with 3707 additions and 20260 deletions

View File

@@ -0,0 +1,107 @@
# Development Tool: `_debug`
The **Debug Plugin** is a lightweight reactive listener. Once attached to a signal or a computed function, it automatically monitors changes, compares values, and formats the output in the browser console.
## 1. Core Features
* **Reactive Tracking:** Automatically logs whenever the tracked signal updates.
* **Visual Grouping:** Uses styled console groups to keep your dev tools organized.
* **Object Inspection:** Automatically uses `console.table()` when the signal contains an object or array.
* **Efficient Comparison:** Uses `Object.is` to prevent redundant logging if the value hasn't actually changed.
---
## 2. Installation
To use `_debug`, you only need the SigPro core. Register the plugin in your `main.js`. You can conditionally load it so it only runs during development.
```javascript
import { $ } from 'sigpro';
import { Debug } from 'sigpro/plugins';
// Only load Debug in development mode
const plugins = [];
if (import.meta.env.DEV) plugins.push(Debug);
$.plugin(plugins).then(() => {
import('./App.js').then(app => $.mount(app.default));
});
```
::: code-group
```bash [NPM]
npm install sigpro
```
```bash [PNPM]
pnpm add sigpro
```
```bash [Yarn]
yarn add sigpro
```
```bash [Bun]
bun add sigpro
```
:::
---
## 3. Basic Usage
Call `_debug` anywhere in your component. It stays active in the background, watching the signal's lifecycle.
```javascript
export default () => {
const $count = $(0);
const $user = $({ name: "Guest", role: "Viewer" });
// Start tracking
_debug($count, "Main Counter");
_debug($user, "User Session");
return div([
button({ onclick: () => $count(c => c + 1) }, "Increment"),
button({ onclick: () => $user({ name: "Admin", role: "Super" }) }, "Promote")
]);
};
```
---
## 4. Console Output Breakdown
When a signal changes, the console displays a structured block:
1. **Header:** A styled badge with the name (e.g., `SigPro Debug: Main Counter`).
2. **Previous Value:** The value before the update (in red).
3. **Current Value:** The new value (in green).
4. **Table View:** If the value is an object, a formatted table appears automatically.
---
## 5. Debugging Computed Values
You can also debug **computed functions** to see exactly when derived state is recalculated.
```javascript
const $price = $(100);
const $tax = $(0.21);
const $total = $(() => $price() * (1 + $tax()));
// Monitor the result of the calculation
_debug($total, "Final Invoice Total");
```
---
## 6. Why use `_debug`?
1. **Clean Logic:** No need to scatter `console.log` inside your reactive functions.
2. **State History:** Instantly see the "Before" and "After" of any user action.
3. **No-Noise:** It only logs when a real change occurs, keeping the console clean.
4. **Deep Inspection:** The automatic `console.table` makes debugging large API responses much faster.

View File

@@ -0,0 +1,80 @@
# Data Fetching: `_fetch`
The **Fetch Plugin** provides a reactive wrapper around the native browser Fetch API. Instead of managing complex `async/await` flows within your UI, `_fetch` returns a "Reactive Tripod" (Data, Loading, and Error) that your components can listen to automatically.
## 1. Core Concept
When you call `_fetch`, it returns three signals immediately. Your UI declares how to react to these signals as they change from their initial state to the final response.
* **`$data`**: Initialized as `null`. Automatically holds the JSON response on success.
* **`$loading`**: Initialized as `true`. Flips to `false` once the request settles.
* **`$error`**: Initialized as `null`. Holds the error message if the request fails.
---
## 2. Installation
Register the `Fetch` plugin in your `main.js`. By convention, we load it alongside the UI and Router to have the full SigPro ecosystem ready.
```javascript
import { $ } from 'sigpro';
import { Fetch } from 'sigpro/plugins';
$.plugin([Fetch]).then(() => {
// Now _fetch() is available globally
import('./App.js').then(app => $.mount(app.default));
});
```
---
## 3. Basic Usage
Use `_fetch` inside your component to get live updates. The UI updates surgically whenever a signal changes.
```javascript
export default () => {
const { $data, $loading, $error } = _fetch('https://api.github.com/users/octocat');
return div({ class: 'p-6 flex flex-col gap-4' }, [
h1("Profile Details"),
// 1. Loading State (using SigPro UI button)
() => $loading() && _button({ $loading: true }, "Fetching..."),
// 2. Error State
() => $error() && div({ class: 'alert alert-error' }, $error()),
// 3. Success State
() => $data() && div({ class: 'card bg-base-200 p-4' }, [
img({ src: $data().avatar_url, class: 'w-16 rounded-full' }),
h2($data().name),
p($data().bio)
])
]);
};
```
---
## 4. Advanced Configuration
`_fetch` accepts the same `RequestInit` options as the standard `fetch()` (methods, headers, body, etc.).
```javascript
const { $data, $loading } = _fetch('/api/v1/update', {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ status: 'active' })
});
```
---
## 5. Why use `_fetch` instead of native Fetch?
1. **Declarative UI**: You define the "Loading", "Error", and "Success" templates once, and they swap automatically.
2. **No `useEffect` required**: Since SigPro is natively reactive, you don't need lifecycle hooks to trigger re-renders; the signals handle it.
3. **Consistency**: It follows the same `_prefix` pattern as the rest of the official plugin ecosystem.
4. **Automatic JSON Parsing**: It assumes JSON by default and handles 404/500 errors by populating the `$error` signal.

View File

@@ -0,0 +1,110 @@
# Navigation Plugin: `Router`
The SigPro Router handles URL changes via hashes (`#`) and maps them to components. It supports dynamic parameters (like `:id`) and asynchronous loading for heavy pages.
## 1. Core Features
* **Hash-based:** Works everywhere without special server configuration.
* **Lazy Loading:** Pages are only downloaded when the user visits the route.
* **Reactive:** The view updates automatically when the hash changes.
* **Dynamic Routes:** Supports paths like `/user/:id`.
---
## 2. Installation
The Router is usually included in the official plugins package.
::: code-group
```bash [NPM]
npm install -D tailwindcss @tailwindcss/vite daisyui@next
```
```bash [PNPM]
pnpm add -D tailwindcss @tailwindcss/vite daisyui@next
```
```bash [Yarn]
yarn add -D tailwindcss @tailwindcss/vite daisyui@next
```
```bash [Bun]
bun add -d tailwindcss @tailwindcss/vite daisyui@next
```
:::
---
## 3. Setting Up Routes
In your `App.js` (or a dedicated routes file), define your navigation map.
```javascript
const routes = [
{ path: '/', component: () => h1("Home Page") },
{
path: '/admin',
// Lazy Loading: This file is only fetched when needed
component: () => import('./pages/Admin.js')
},
{ path: '/user/:id', component: (params) => h2(`User ID: ${params.id}`) },
{ path: '*', component: () => div("404 - Page Not Found") }
];
export default () => div([
_navbar({ title: "My App" }),
_router(routes) // The router is now a global tag
]);
```
---
## 4. Navigation (`_router.go`)
To move between pages programmatically (e.g., inside an `onclick` event), use the global `_router.go` helper.
```javascript
_button({
onclick: () => _router.go('/admin')
}, "Go to Admin")
```
---
## 5. How it Works (Under the Hood)
The router tracks the `window.location.hash` and uses a reactive signal to trigger a re-render of the specific area where `_router(routes)` is placed.
1. **Match:** It filters your route array to find the best fit.
2. **Resolve:** * If it's a standard function, it executes it immediately.
* If it's a **Promise** (via `import()`), it shows a loading state and swaps the content once the module arrives.
3. **Inject:** It replaces the previous DOM node with the new page content surgically.
---
## 6. Integration with UI Components
Since you are using the **UI Plugin**, you can easily create active states in your navigation menus by checking the current hash.
```javascript
// Example of a reactive sidebar menu
_menu({
items: [
{
label: 'Dashboard',
active: () => window.location.hash === '#/',
onclick: () => _router.go('/')
},
{
label: 'Settings',
active: () => window.location.hash === '#/settings',
onclick: () => _router.go('/settings')
}
]
})
```

View File

@@ -0,0 +1,106 @@
# Persistence Tool: `_storage`
The Storage plugin synchronizes a signal with a specific key in your browser's `localStorage`. It handles both the **initial hydration** (loading data when the app starts) and **automatic saving** whenever the signal's value changes.
## 1. Core Concept
When you "attach" a signal to `_storage`, two things happen:
1. **Hydration:** The plugin checks if the key already exists in `localStorage`. If it does, it parses the JSON and updates the signal immediately.
2. **Reactive Sync:** It creates a reactive watcher that stringifies and saves the signal's value to the disk every time it is updated.
---
## 2. Installation
Register the `Storage` plugin in your `main.js`. Since this is a logic-only plugin, it doesn't require any CSS or UI dependencies.
```javascript
import { $ } from 'sigpro';
import { Storage } from 'sigpro/plugins';
$.plugin(Storage).then(() => {
import('./App.js').then(app => $.mount(app.default));
});
```
::: code-group
```bash [NPM]
npm install sigpro
```
```bash [PNPM]
pnpm add sigpro
```
```bash [Yarn]
yarn add sigpro
```
```bash [Bun]
bun add sigpro
```
:::
---
## 3. Basic Usage
You can wrap any signal with `_storage`. It is common practice to do this right after creating the signal.
```javascript
export default () => {
// 1. Create a signal with a default value
const $theme = $( 'light' );
// 2. Persist it. If 'user_theme' exists in localStorage,
// $theme will be updated to that value instantly.
_storage($theme, 'user_theme');
return div({ class: () => `app-${$theme()}` }, [
h1(`Current Theme: ${$theme()}`),
button({
onclick: () => $theme(t => t === 'light' ? 'dark' : 'light')
}, "Toggle Theme")
]);
};
```
---
## 4. Complex Data (Objects & Arrays)
Since the plugin uses `JSON.parse` and `JSON.stringify` internally, it works perfectly with complex state structures.
```javascript
const $settings = $({
notifications: true,
fontSize: 16
});
// Automatically saves the whole object whenever any property changes
_storage($settings, 'app_settings');
```
---
## 5. Why use `_storage`?
1. **Zero Boilerplate:** You don't need to manually write `localStorage.getItem` or `setItem` logic inside your components.
2. **Chaining:** Because `_storage` returns the signal, you can persist it inline.
3. **Error Resilience:** It includes a built-in `try/catch` block to prevent your app from crashing if the stored JSON is corrupted.
4. **Surgical Persistence:** Only the signals you explicitly mark for storage are saved, keeping your `localStorage` clean.
---
## 6. Pro Tip: Combining with Debug
You can chain plugins to create a fully monitored and persistent state:
```javascript
const $score = _storage($(0), 'high_score');
// Now it's saved to disk AND logged to console on every change
_debug($score, "Game Score");
```

147
src/docs/plugins/core.ui.md Normal file
View File

@@ -0,0 +1,147 @@
# Official UI Plugin: `UI`
The **SigPro UI** plugin is a high-level component library built on top of the reactive core. It leverages **Tailwind CSS v4** for utility styling and **daisyUI v5** for semantic components.
## 1. Prerequisites & Installation
To use these components, you must install the styling engine. SigPro UI provides the logic, but Tailwind and daisyUI provide the visuals.
::: code-group
```bash [NPM]
npm install -D tailwindcss @tailwindcss/vite daisyui@next
```
```bash [PNPM]
pnpm add -D tailwindcss @tailwindcss/vite daisyui@next
```
```bash [Yarn]
yarn add -D tailwindcss @tailwindcss/vite daisyui@next
```
```bash [Bun]
bun add -d tailwindcss @tailwindcss/vite daisyui@next
```
:::
Would you like to continue with the **Router.md** documentation now?
### CSS Configuration (`app.css`)
In Tailwind v4, configuration is handled directly in your CSS. Create a `src/app.css` file:
```css
/* src/app.css */
@import "tailwindcss";
/* Import daisyUI v5 as a Tailwind v4 plugin */
@plugin "daisyui";
/* Optional: Configure themes */
@custom-variant dark (&:where(.dark, [data-theme="dark"], [data-theme="dark"] *)));
```
---
## 2. Initialization
You must import your CSS and register the `UI` plugin in your entry point. This populates the global scope with reactive component helpers (prefixed with `_`).
```javascript
// main.js
import './app.css';
import { $ } from 'sigpro';
import { UI } from 'sigpro/plugins';
$.plugin(UI).then(() => {
// Global components like _button and _input are now ready
import('./App.js').then(app => $.mount(app.default));
});
```
---
## 3. Core Component Tags (`_tags`)
SigPro UI components are more than just HTML; they are **Reactive Functional Components** that manage complex states (loading, errors, accessibility) automatically.
### A. Action Components (`_button`)
The `_button` automatically handles spinners and disabled states based on signals.
| Property | Type | Description |
| :--- | :--- | :--- |
| **`$loading`** | `signal` | If true, shows a spinner and disables the button. |
| **`$disabled`**| `signal` | Manually disables the button (logic-bound). |
| **`icon`** | `node/str`| Prepends an icon to the text. |
| **`badge`** | `string` | Appends a small badge to the button. |
```javascript
_button({
$loading: $isSaving,
icon: '💾',
class: 'btn-primary'
}, "Save Data")
```
### B. High-Density Forms (`_input`, `_select`, `_checkbox`)
These components wrap the raw input in a `fieldset` with integrated labels and tooltips.
* **`label`**: Field title displayed above the input.
* **`tip`**: Displays a `?` badge that shows a tooltip on hover.
* **`$error`**: A signal that, when populated, turns the input red and displays the message.
* **`$value`**: **Two-way binding**. Updates the signal on input and the input on signal change.
```javascript
_input({
label: "Username",
tip: "Choose a unique name",
$value: $name,
$error: $nameError
})
```
---
## 4. Complex UI Patterns
### Reactive Modals (`_modal`)
The `_modal` is surgically mounted. If the `$open` signal is `false`, the component is completely removed from the DOM, optimizing performance.
```javascript
const $showModal = $(false);
_modal({ $open: $showModal, title: "Alert" }, [
p("Are you sure you want to proceed?"),
_button({ onclick: () => doAction() }, "Confirm")
])
```
### Navigation & Layout (`_tabs`, `_drawer`, `_navbar`)
Designed to work seamlessly with the **Router**.
| Component | Key Logic |
| :--- | :--- |
| **`_tabs`** | Accepts an `active` property (signal or function) to highlight the current tab. |
| **`_drawer`** | A responsive sidebar that toggles via an ID or an `$open` signal. |
| **`_navbar`** | Standard top bar with shadow and glass effect support. |
| **`_menu`** | Vertical navigation list with active state support. |
---
## 5. Summary Table: UI Globals
Once `$.plugin(UI)` is active, these tags are available project-wide:
| Tag | Category | Use Case |
| :--- | :--- | :--- |
| `_fieldset` | Layout | Grouping related inputs with a `legend`. |
| `_accordion`| Content | Collapsible sections (FAQs). |
| `_badge` | Feedback | Status indicators (Success, Warning). |
| `_tooltip` | Feedback | Descriptive text on hover. |
| `_range` | Input | Reactive slider for numerical values. |
---
### What's next?
With the UI ready and styled via **Tailwind v4**, we can move to the **Router.md**. We will explain how to link `_tabs` and `_menu` to different URL paths for a full SPA experience.
**Would you like to start with the Router configuration?**

123
src/docs/plugins/custom.md Normal file
View File

@@ -0,0 +1,123 @@
# Creating Custom Plugins
There are two main ways to expose a plugin's functionality: **Static/Manual Imports** (cleaner for large projects) or **Global/Automatic Window Injection** (easier for quick scripts and global helpers).
## 1. The Anatomy of a Plugin
A plugin is a standard JavaScript function. By convention, if a plugin adds a global helper or component, it should be prefixed with an underscore (`_`).
```javascript
// plugins/my-utils.js
export const MyUtils = ($) => {
// 1. Attach to the SigPro instance
$.capitalize = (str) => str.charAt(0).toUpperCase() + str.slice(1);
// 2. Attach to the Window (Global access)
window._hello = (name) => div(`Hello, ${$.capitalize(name)}!`);
// 3. You can also return values if needed
return { version: '1.0.0' };
};
```
---
## 2. Integration Strategies
### Option A: Manual Import (Recommended)
This approach keeps your global namespace clean. You import the logic only where you need it, but the plugin still initializes the core `$` extensions.
```javascript
// main.js
import { $ } from 'sigpro';
import { MyUtils } from './plugins/my-utils.js';
$.plugin(MyUtils);
// App.js
export default () => {
const name = "sigpro";
// $.capitalize was added by the plugin
return h1($.capitalize(name));
};
```
### Option B: Automatic Window Injection
If your plugin defines global tags (like `_button` or `_hello`), you should attach them to the `window` object inside the plugin function. This makes them available everywhere without imports.
```javascript
// plugins/theme.js
export const Theme = ($) => {
const $dark = $(false);
window._themeToggle = () => button({
onclick: () => $dark(v => !v),
class: () => $dark() ? 'bg-black text-white' : 'bg-white text-black'
}, "Toggle Mode");
};
// main.js
$.plugin(Theme).then(() => {
// _themeToggle is now a global function
$.mount(App);
});
```
---
## 3. Asynchronous Plugins
If your plugin needs to load external data or scripts before the app starts, make it `async`. SigPro will wait for it.
```javascript
export const ConfigLoader = async ($) => {
const res = await fetch('/config.json');
const config = await res.json();
$.config = config; // Attach loaded config to SigPro
};
// Usage
$.plugin(ConfigLoader).then(() => {
console.log("Config loaded:", $.config);
$.mount(App);
});
```
---
## 4. Best Practices for Plugin Authors
| Rule | Description |
| :--- | :--- |
| **Prefixing** | Use `_` for UI components (`_modal`) and `$.` for logic (`$.fetch`). |
| **Idempotency** | Ensure calling `$.plugin(MyPlugin)` twice doesn't break the app. |
| **Encapsulation** | Use the `$` instance passed as an argument rather than importing it again inside the plugin. |
| **Reactivity** | Always use `$(...)` for internal state so the app stays reactive. |
---
## 5. Installation
Custom plugins don't require extra packages, but ensure your build tool (Vite/Bun) is configured to handle the module imports.
::: code-group
```bash [NPM]
npm install sigpro
```
```bash [PNPM]
pnpm add sigpro
```
```bash [Yarn]
yarn add sigpro
```
```bash [Bun]
bun add sigpro
```
:::

101
src/docs/plugins/quick.md Normal file
View File

@@ -0,0 +1,101 @@
# Extending SigPro: `$.plugin`
The plugin system is the engine's way of growing. It allows you to inject new functionality directly into the `$` object or load external resources.
## 1. How Plugins Work
A plugin in **SigPro** is simply a function that receives the core instance. When you run `$.plugin(MyPlugin)`, the engine hands over the `$` object so the plugin can attach new methods or register global tags (like `div()`, `span()`, etc.).
### Functional Plugin Example
```javascript
// A plugin that adds a simple logger to any signal
const Logger = ($) => {
$.watch = (target, label = "Log") => {
$(() => console.log(`[${label}]:`, target()));
};
};
// Activation
$.plugin(Logger);
const $count = $(0);
$.watch($count, "Counter"); // Now available globally via $
```
---
## 2. Initialization Patterns
Since plugins often set up global variables (like the HTML tags), the order of initialization is critical. Here are the two ways to start your app:
### Option A: The "Safe" Async Start (Recommended)
This is the most robust way. It ensures all global tags (`div`, `button`, etc.) are created **before** your App code is even read by the browser.
```javascript
// main.js
import { $ } from 'sigpro';
import { UI, Router } from 'sigpro/plugins';
// 1. Load plugins first
$.plugin([UI, Router]).then(() => {
// 2. Import your app only after the environment is ready
import('./App.js').then(appFile => {
const MyApp = appFile.default;
$.mount(MyApp, '#app');
});
});
```
### Option B: Static Start (No Global Tags)
Use this only if you prefer **not** to use global tags and want to use `$.html` directly in your components. This allows for standard static imports.
```javascript
// main.js
import { $ } from 'sigpro';
import { UI } from 'sigpro/plugins';
import MyApp from './App.js'; // Static import works here
$.plugin(UI);
$.mount(MyApp, '#app');
```
> **Warning:** In this mode, if `App.js` uses `div()` instead of `$.html('div')`, it will throw a `ReferenceError`.
---
## 3. Resource Plugins (External Scripts)
You can pass a **URL** or an **Array of URLs**. SigPro will inject them as `<script>` tags and return a Promise that resolves when the scripts are fully loaded and executed.
```javascript
// Loading external libraries as plugins
await $.plugin([
'https://cdn.jsdelivr.net/npm/chart.js',
'https://cdn.example.com/custom-ui-lib.js'
]);
console.log("External resources are ready to use!");
```
---
## 4. Polymorphic Loading Reference
The `$.plugin` method adapts to whatever you throw at it:
| Input Type | Action | Behavior |
| :--- | :--- | :--- |
| **Function** | Executes `fn($)` | Synchronous / Immediate |
| **String (URL)** | Injects `<script src="...">` | Asynchronous (Returns Promise) |
| **Array** | Processes each item in the list | Returns Promise if any item is Async |
---
## 💡 Pro Tip: Why the `.then()`?
Using `$.plugin([...]).then(...)` is like giving your app a "Pre-flight Check". It guarantees that:
1. All reactive methods are attached.
2. Global HTML tags are defined.
3. External libraries (like Chart.js) are loaded.
4. **The result:** Your components are cleaner, smaller, and error-free.