New 1.1.3
This commit is contained in:
@@ -40,7 +40,10 @@ export default defineConfig({
|
||||
text: 'API Reference',
|
||||
items: [
|
||||
{ text: 'Quick Start', link: '/api/quick' },
|
||||
{ text: '$', link: '/api/$' },
|
||||
{ text: '$', link: '/api/signal' },
|
||||
{ text: '$.effect', link: '/api/effect' },
|
||||
{ text: '$.ignore', link: '/api/ignore' },
|
||||
{ text: '$.view', link: '/api/view' },
|
||||
{ text: '$.html', link: '/api/html' },
|
||||
{ text: '$.router', link: '/api/router' },
|
||||
{ text: '$.mount', link: '/api/mount' },
|
||||
@@ -48,18 +51,11 @@ export default defineConfig({
|
||||
]
|
||||
},
|
||||
{
|
||||
text: 'Plugins',
|
||||
text: 'UI Components',
|
||||
items: [
|
||||
{ text: 'Quick Start', link: '/plugins/quick' },
|
||||
{ text: 'Custom', link: '/plugins/custom' },
|
||||
{ text: 'Quick Start', link: '/ui/quick' }
|
||||
]
|
||||
},
|
||||
{
|
||||
text: 'Examples',
|
||||
items: [
|
||||
{ text: 'Demo Core', link: '/examples' }
|
||||
]
|
||||
}
|
||||
],
|
||||
socialLinks: [
|
||||
{ icon: 'github', link: 'https://github.com/natxocc/sigpro' }
|
||||
|
||||
@@ -1,142 +0,0 @@
|
||||
# The Reactive Core: `$( )`
|
||||
|
||||
The `$` function is a **Unified Reactive Constructor**. It detects the type of input you provide and returns the appropriate reactive primitive.
|
||||
|
||||
---
|
||||
|
||||
## 1. Signals (Atomic State)
|
||||
A **Signal** is the simplest form of reactivity. It holds a single value (string, number, boolean, null).
|
||||
|
||||
### **Option A: Standard Signal (RAM)**
|
||||
Ideal for volatile state that shouldn't persist after a page refresh.
|
||||
```javascript
|
||||
const $count = $(0);
|
||||
|
||||
// Usage:
|
||||
$count(); // Getter: returns 0
|
||||
$count(10); // Setter: updates to 10
|
||||
$count(c => c + 1); // Functional update: updates to 11
|
||||
```
|
||||
|
||||
### **Option B: Persistent Signal (Disk)**
|
||||
By adding a `key`, SigPro links the signal to `localStorage`.
|
||||
```javascript
|
||||
// Syntax: $(initialValue, "storage-key")
|
||||
const $theme = $("light", "app-theme");
|
||||
|
||||
// It restores the value from disk automatically on load.
|
||||
// When you update it, it saves to disk instantly:
|
||||
$theme("dark"); // localStorage.getItem("app-theme") is now "dark"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. Stores (Reactive Objects)
|
||||
A **Store** is a proxy that wraps an **Object**. SigPro makes every property reactive recursively. You access and set properties as if they were individual signals.
|
||||
|
||||
### **Option A: Standard Store (RAM)**
|
||||
```javascript
|
||||
const user = $({
|
||||
name: "Alice",
|
||||
profile: { bio: "Developer" }
|
||||
});
|
||||
|
||||
// Getter: Call the property as a function
|
||||
console.log(user.name()); // "Alice"
|
||||
|
||||
// Setter: Pass the value to the property function
|
||||
user.name("Bob");
|
||||
|
||||
// Nested updates work exactly the same:
|
||||
user.profile.bio("Architect");
|
||||
```
|
||||
|
||||
### **Option B: Persistent Store (Disk)**
|
||||
The most powerful way to save complex state. The **entire object tree** is serialized to JSON and kept in sync with the disk.
|
||||
```javascript
|
||||
const settings = $({
|
||||
volume: 50,
|
||||
notifications: true
|
||||
}, "user-settings");
|
||||
|
||||
// Any change in the object triggers a disk sync:
|
||||
settings.volume(100); // The whole JSON is updated in localStorage
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Stores (Reactive Arrays)
|
||||
When you pass an **Array**, SigPro tracks changes to the list. You can use standard methods or access indexes as reactive getters.
|
||||
|
||||
```javascript
|
||||
const $list = $(["Item 1", "Item 2"]);
|
||||
|
||||
// Get by index
|
||||
console.log($list[0]()); // "Item 1"
|
||||
|
||||
// Update by index
|
||||
$list[0]("Updated Item");
|
||||
|
||||
// Note: For adding/removing items, use standard array methods
|
||||
// which SigPro makes reactive (push, pop, splice, etc.)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Computed (Derived Logic)
|
||||
A **Computed Signal** is a read-only value that depends on other signals. It is defined by passing a **function that returns a value**.
|
||||
|
||||
```javascript
|
||||
const $price = $(100);
|
||||
const $tax = $(0.21);
|
||||
|
||||
// This function HAS a return statement
|
||||
const $total = $(() => {
|
||||
return $price() * (1 + $tax());
|
||||
});
|
||||
|
||||
// Usage (Read-only):
|
||||
console.log($total()); // 121
|
||||
|
||||
$price(200);
|
||||
console.log($total()); // 242 (Auto-updated)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. Effects (Reactive Actions)
|
||||
An **Effect** is used for side-effects. It is defined by passing a **function that does NOT return a value**. It runs once immediately and then re-runs whenever its dependencies change.
|
||||
|
||||
```javascript
|
||||
const $name = $("Alice");
|
||||
|
||||
// This function has NO return statement (Side-effect)
|
||||
$(() => {
|
||||
console.log("The name changed to:", $name());
|
||||
document.title = `Profile: ${$name()}`;
|
||||
});
|
||||
|
||||
$name("Bob"); // Triggers the console.log and updates document.title
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. Summary: Input Mapping
|
||||
|
||||
| If you pass... | SigPro creates a... | Access Method |
|
||||
| :--- | :--- | :--- |
|
||||
| **A Value** | **Signal** | `$var()` / `$var(val)` |
|
||||
| **An Object** | **Store** | `obj.prop()` / `obj.prop(val)` |
|
||||
| **An Array** | **Array Store** | `arr[i]()` / `arr.push()` |
|
||||
| **Function (returns)** | **Computed** | `$comp()` (Read-only) |
|
||||
| **Function (no return)** | **Effect** | Automatically executed |
|
||||
|
||||
---
|
||||
|
||||
## 💡 Naming Convention: The `$` Prefix
|
||||
To keep your code clean, always prefix your reactive variables with `$`. This tells you at a glance that you need to call it as a function to get its value.
|
||||
|
||||
```javascript
|
||||
const name = "Static"; // Just a string
|
||||
const $name = $("Alice"); // A Reactive Signal
|
||||
```
|
||||
90
src/docs/api/effect.md
Normal file
90
src/docs/api/effect.md
Normal file
@@ -0,0 +1,90 @@
|
||||
# ⚡ Side Effects: `$.effect( )`
|
||||
|
||||
The `$.effect` function allows you to run a piece of code whenever the signals it depends on are updated. It automatically tracks any signal called within its body.
|
||||
|
||||
## 🛠 Function Signature
|
||||
|
||||
```typescript
|
||||
$.effect(callback: Function): StopFunction
|
||||
```
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
| **`callback`** | `Function` | Yes | The code to run. It will execute immediately and then re-run on dependency changes. |
|
||||
|
||||
**Returns:** A `StopFunction` that, when called, cancels the effect and prevents further executions.
|
||||
|
||||
---
|
||||
|
||||
## 📖 Usage Patterns
|
||||
|
||||
### 1. Basic Tracking
|
||||
Any signal you "touch" inside the effect becomes a dependency.
|
||||
|
||||
```javascript
|
||||
const count = $(0);
|
||||
|
||||
$.effect(() => {
|
||||
// This runs every time 'count' changes
|
||||
console.log(`The count is now: ${count()}`);
|
||||
});
|
||||
|
||||
count(1); // Console: "The count is now: 1"
|
||||
```
|
||||
|
||||
### 2. Manual Cleanup
|
||||
If your effect creates something that needs to be destroyed (like a timer or a global event listener), you can return a cleanup function.
|
||||
|
||||
```javascript
|
||||
$.effect(() => {
|
||||
const timer = setInterval(() => console.log("Tick"), 1000);
|
||||
|
||||
// SigPro will run this BEFORE the next effect execution
|
||||
// or when the effect is stopped.
|
||||
return () => clearInterval(timer);
|
||||
});
|
||||
```
|
||||
|
||||
### 3. Nesting & Automatic Cleanup
|
||||
If you create a signal or another effect inside an effect, SigPro tracks them as "children". When the parent effect re-runs or stops, all children are automatically cleaned up to prevent memory leaks.
|
||||
|
||||
```javascript
|
||||
$.effect(() => {
|
||||
if (isLoggedIn()) {
|
||||
// This sub-effect is only active while 'isLoggedIn' is true
|
||||
$.effect(() => {
|
||||
console.log("Fetching user data...");
|
||||
});
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🛑 Stopping an Effect
|
||||
You can stop an effect manually by calling the function it returns. This is useful for one-time operations or complex logic.
|
||||
|
||||
```javascript
|
||||
const stop = $.effect(() => {
|
||||
console.log(count());
|
||||
});
|
||||
|
||||
// Later...
|
||||
stop(); // The effect is destroyed and will never run again.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 💡 Pro Tip: Batching
|
||||
SigPro uses a **Microtask Queue** to handle updates. If you update multiple signals at once, the effect will only run **once** at the end of the current task.
|
||||
|
||||
```javascript
|
||||
const a = $(0);
|
||||
const b = $(0);
|
||||
|
||||
$.effect(() => console.log(a(), b()));
|
||||
|
||||
// This triggers only ONE re-run, not two.
|
||||
a(1);
|
||||
b(2);
|
||||
```
|
||||
@@ -1,103 +1,93 @@
|
||||
# Rendering Engine: `$.html`
|
||||
# 🏗️ The DOM Factory: `$.html( )`
|
||||
|
||||
The `$.html` function is the architect of your UI. It creates standard HTML elements and wires them directly to your signals without the need for a Virtual DOM.
|
||||
`$.html` is the internal engine that creates, attributes, and attaches reactivity to DOM elements. It is the foundation for all Tag Constructors in SigPro.
|
||||
|
||||
## 1. Syntax: `$.html(tag, [props], [content])`
|
||||
## 🛠 Function Signature
|
||||
|
||||
```typescript
|
||||
$.html(tagName: string, props?: Object, children?: any[] | any): HTMLElement
|
||||
```
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
| **tag** | `string` | **Yes** | Any valid HTML5 tag (e.g., `'div'`, `'button'`, `'input'`). |
|
||||
| **props** | `Object` | No | Attributes, event listeners, and reactive bindings. |
|
||||
| **content** | `any` | No | Text, Nodes, Arrays, or Reactive Functions. |
|
||||
|
||||
### Example:
|
||||
```javascript
|
||||
const myButton = $.html('button', { class: 'btn-primary' }, 'Click me');
|
||||
```
|
||||
| **`tagName`** | `string` | Yes | Valid HTML tag name (e.g., `"div"`, `"button"`). |
|
||||
| **`props`** | `Object` | No | HTML attributes, event listeners, and reactive bindings. |
|
||||
| **`children`** | `any` | No | Nested elements, text strings, or reactive functions. |
|
||||
|
||||
---
|
||||
|
||||
## 2. Global Tag Helpers
|
||||
## 📖 Key Features
|
||||
|
||||
To avoid repetitive `$.html` calls, SigPro automatically exposes common tags to the global `window` object. This allows for a clean, declarative syntax.
|
||||
### 1. Attribute Handling
|
||||
SigPro intelligently decides how to apply each property:
|
||||
* **Standard Props**: Applied via `setAttribute` or direct property assignment.
|
||||
* **Boolean Props**: Uses `toggleAttribute` (e.g., `checked`, `disabled`, `hidden`).
|
||||
* **Class Names**: Supports `class` or `className` interchangeably.
|
||||
|
||||
### 2. Event Listeners & Modifiers
|
||||
Events are defined by the `on` prefix. SigPro supports **Dot Notation** for common event operations:
|
||||
|
||||
```javascript
|
||||
// Instead of $.html('div', ...), just use:
|
||||
div({ id: 'wrapper' }, [
|
||||
h1("Welcome"),
|
||||
p("This is SigPro.")
|
||||
$.html("button", {
|
||||
// e.preventDefault() is called automatically
|
||||
"onsubmit.prevent": (e) => save(e),
|
||||
|
||||
// e.stopPropagation() is called automatically
|
||||
"onclick.stop": () => console.log("No bubbling"),
|
||||
|
||||
// { once: true } listener option
|
||||
"onclick.once": () => console.log("Runs only once")
|
||||
}, "Click Me");
|
||||
```
|
||||
|
||||
### 3. Reactive Attributes
|
||||
If an attribute value is a **function** (like a Signal), `$.html` creates an internal `$.effect` to keep the DOM in sync with the state.
|
||||
|
||||
```javascript
|
||||
$.html("div", {
|
||||
// Updates the class whenever 'theme()' changes
|
||||
class: () => theme() === "dark" ? "bg-black" : "bg-white"
|
||||
});
|
||||
```
|
||||
|
||||
### 4. Reactive Children
|
||||
Children can be static or dynamic. When a child is a function, SigPro creates a reactive boundary for that specific part of the DOM.
|
||||
|
||||
```javascript
|
||||
$.html("div", {}, [
|
||||
H1("Static Title"),
|
||||
// Only this text node re-renders when 'count' changes
|
||||
() => `Current count: ${count()}`
|
||||
]);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Handling Properties & Attributes
|
||||
## 🔄 Two-Way Binding Operator (`$`)
|
||||
|
||||
SigPro distinguishes between static attributes and reactive bindings using the **`$` prefix**.
|
||||
|
||||
### Static vs. Reactive Attributes
|
||||
* **Static:** Applied once during creation.
|
||||
* **Reactive (`$`):** Automatically updates the DOM when the signal changes.
|
||||
|
||||
| Property | Syntax | Result |
|
||||
| :--- | :--- | :--- |
|
||||
| **Attribute** | `{ id: 'main' }` | `id="main"` |
|
||||
| **Event** | `{ onclick: fn }` | Adds an event listener. |
|
||||
| **Reactive Attr** | `{ $class: $theme }` | Updates `class` whenever `$theme()` changes. |
|
||||
| **Boolean Attr** | `{ $disabled: $isBusy }` | Toggles the `disabled` attribute automatically. |
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
## 4. Two-Way Data Binding
|
||||
|
||||
For form inputs, SigPro provides a powerful shortcut using `$value` or `$checked`. It automatically handles the event listening and the value synchronization.
|
||||
When a property starts with `$`, `$.html` enables bidirectional synchronization. This is primarily used for form inputs.
|
||||
|
||||
```javascript
|
||||
const $text = $("Type here...");
|
||||
|
||||
input({
|
||||
type: 'text',
|
||||
$value: $text // Syncs input -> signal and signal -> input
|
||||
});
|
||||
|
||||
p(["You typed: ", $text]);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. Reactive Content (Dynamic Children)
|
||||
|
||||
The `content` argument is incredibly flexible. If you pass a **function**, SigPro treats it as a reactive "portal" that re-renders only that specific part of the DOM.
|
||||
|
||||
### Text & Nodes
|
||||
```javascript
|
||||
const $count = $(0);
|
||||
|
||||
// Text node updates surgically
|
||||
div(["Count: ", $count]);
|
||||
|
||||
// Conditional rendering with a function
|
||||
div(() => {
|
||||
return $count() > 10
|
||||
? h1("High Score!")
|
||||
: p("Keep going...");
|
||||
$.html("input", {
|
||||
type: "text",
|
||||
$value: username // Syncs input value <-> signal
|
||||
});
|
||||
```
|
||||
|
||||
### The "Guillotine" (Performance Tip)
|
||||
When a reactive function in the content returns a **new Node**, SigPro uses `replaceWith()` to swap the old node for the new one. This ensures that:
|
||||
1. The update is nearly instantaneous.
|
||||
2. The old node is correctly garbage-collected.
|
||||
## 🧹 Automatic Cleanup
|
||||
Every element created with `$.html` gets a hidden `._cleanups` property (a `Set`).
|
||||
* When SigPro removes an element via `$.view` or `$.router`, it automatically executes all functions stored in this Set (stopping effects, removing listeners, etc.).
|
||||
|
||||
---
|
||||
|
||||
## 6. Summary: Content Types
|
||||
## 💡 Tag Constructors (The Shortcuts)
|
||||
|
||||
| Input | Behavior |
|
||||
| :--- | :--- |
|
||||
| **String / Number** | Appended as a TextNode. |
|
||||
| **HTMLElement** | Appended directly to the parent. |
|
||||
| **Array** | Each item is processed and appended in order. |
|
||||
| **Function `() => ...`** | Creates a **live reactive zone** that updates automatically. |
|
||||
Instead of writing `$.html("div", ...)` every time, SigPro provides PascalCase global functions:
|
||||
|
||||
```javascript
|
||||
// This:
|
||||
Div({ class: "wrapper" }, [ Span("Hello") ])
|
||||
|
||||
// Is exactly equivalent to:
|
||||
$.html("div", { class: "wrapper" }, [ $.html("span", {}, "Hello") ])
|
||||
```
|
||||
|
||||
75
src/docs/api/ignore.md
Normal file
75
src/docs/api/ignore.md
Normal file
@@ -0,0 +1,75 @@
|
||||
# 🛑 Untracking: `$.ignore( )`
|
||||
|
||||
The `$.ignore` function allows you to read a signal's value inside an effect or a computed signal **without** creating a dependency.
|
||||
|
||||
## 🛠 Function Signature
|
||||
|
||||
```typescript
|
||||
$.ignore(callback: Function): any
|
||||
```
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
| **`callback`** | `Function` | Yes | A function where signals can be read "silently". |
|
||||
|
||||
**Returns:** Whatever the callback function returns.
|
||||
|
||||
---
|
||||
|
||||
## 📖 Usage Patterns
|
||||
|
||||
### 1. Preventing Dependency Tracking
|
||||
Normally, reading a signal inside `$.effect` makes the effect re-run when that signal changes. `$.ignore` breaks this link.
|
||||
|
||||
```javascript
|
||||
const count = $(0);
|
||||
const logLabel = $("System Log");
|
||||
|
||||
$.effect(() => {
|
||||
// This effect tracks 'count'...
|
||||
const currentCount = count();
|
||||
|
||||
// ...but NOT 'logLabel'.
|
||||
// Changing 'logLabel' will NOT re-run this effect.
|
||||
const label = $.ignore(() => logLabel());
|
||||
|
||||
console.log(`${label}: ${currentCount}`);
|
||||
});
|
||||
|
||||
count(1); // Console: "System Log: 1" (Triggers re-run)
|
||||
logLabel("UI"); // Nothing happens in console (Ignored)
|
||||
```
|
||||
|
||||
### 2. Reading State in Event Handlers
|
||||
Inside complex UI logic, you might want to take a "snapshot" of a signal without triggering a reactive chain.
|
||||
|
||||
```javascript
|
||||
const handleClick = () => {
|
||||
// Accessing state without letting the caller know we touched it
|
||||
const data = $.ignore(() => mySignal());
|
||||
process(data);
|
||||
};
|
||||
```
|
||||
|
||||
### 3. Avoiding Infinite Loops
|
||||
If you need to **write** to a signal based on its own value inside an effect (and you aren't using the functional updater), `$.ignore` prevents the effect from triggering itself.
|
||||
|
||||
```javascript
|
||||
$.effect(() => {
|
||||
const value = someSignal();
|
||||
|
||||
if (value > 100) {
|
||||
// We update the signal, but we ignore the read to avoid a loop
|
||||
$.ignore(() => someSignal(0));
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 💡 Why use it?
|
||||
|
||||
* **Performance:** Prevents expensive effects from running when non-essential data changes.
|
||||
* **Logic Control:** Allows "sampling" a signal at a specific point in time.
|
||||
* **Safety:** Essential for complex state orchestrations where circular dependencies might occur.
|
||||
|
||||
@@ -1,107 +1,112 @@
|
||||
# Application Mounter: `$.mount`
|
||||
# 🔌 Application Mounter: `$.mount( )`
|
||||
|
||||
The `$.mount` function is the entry point of your reactive world. It takes a **SigPro component** (or a plain DOM node) and injects it into the real document, bridging the gap between your logic and the browser.
|
||||
The `$.mount` function is the entry point of your reactive world. It bridges the gap between your SigPro logic and the browser's Real DOM by injecting a component into the document.
|
||||
|
||||
## 1. Syntax: `$.mount(node, [target])`
|
||||
## 1. Function Signature
|
||||
|
||||
```typescript
|
||||
$.mount(node: Function | HTMLElement, target?: string | HTMLElement): RuntimeObject
|
||||
```
|
||||
|
||||
| Parameter | Type | Default | Description |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
| **node** | `HTMLElement` or `Function` | **Required** | The component or element to render. |
|
||||
| **target** | `string` or `HTMLElement` | `document.body` | Where to mount the app (CSS selector or Element). |
|
||||
| **`node`** | `Function` or `Node` | **Required** | The component function or DOM element to render. |
|
||||
| **`target`** | `string` or `Node` | `document.body` | CSS selector or DOM element where the app will live. |
|
||||
|
||||
**Returns:** A `Runtime` object containing the `container` and a `destroy()` method.
|
||||
|
||||
---
|
||||
|
||||
## 2. Usage Scenarios
|
||||
## 2. Common Usage Scenarios
|
||||
|
||||
### A. The "Clean Slate" (Main Entry)
|
||||
In a modern app, you usually want to control the entire page. By default, `$.mount` clears the target's existing HTML before mounting your application.
|
||||
### A. The "Clean Slate" (SPA Entry)
|
||||
In a modern Single Page Application, you typically want SigPro to manage the entire view. By default, if no target is provided, it mounts to `document.body`.
|
||||
|
||||
```javascript
|
||||
// src/main.js
|
||||
import { $ } from 'sigpro';
|
||||
import { $ } from './sigpro.js';
|
||||
import App from './App.js';
|
||||
|
||||
// SigPro: No .then() needed, global tags are ready immediately
|
||||
// Mounts your main App component directly to the body
|
||||
$.mount(App);
|
||||
```
|
||||
|
||||
### B. Targeting a Specific Container
|
||||
If you have an existing HTML structure and want **SigPro** to manage only a specific section (like a `#root` div), pass a CSS selector or a reference.
|
||||
If your HTML has a predefined structure, you can tell SigPro exactly where to render by passing a CSS selector or a direct reference.
|
||||
|
||||
```html
|
||||
<div id="sidebar"></div>
|
||||
<div id="app-root"></div>
|
||||
<main id="app-root"></main>
|
||||
```
|
||||
|
||||
```javascript
|
||||
// Mount to a specific ID
|
||||
// Mount using a CSS selector
|
||||
$.mount(MyComponent, '#app-root');
|
||||
|
||||
// Or using a direct DOM reference
|
||||
const sidebar = document.getElementById('sidebar');
|
||||
// Mount using a direct DOM reference
|
||||
const sidebar = document.querySelector('#sidebar');
|
||||
$.mount(SidebarComponent, sidebar);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Creating "Reactive Islands"
|
||||
One of SigPro's strengths is its ability to work alongside "Old School" static HTML. You can inject a reactive widget into any part of a legacy page.
|
||||
### C. Creating "Reactive Islands"
|
||||
SigPro is excellent for "sprinkling" reactivity onto legacy or static pages. You can inject small reactive widgets into any part of an existing HTML layout.
|
||||
|
||||
```javascript
|
||||
// A small reactive widget
|
||||
const CounterWidget = () => {
|
||||
const $c = $(0);
|
||||
return button({ onclick: () => $c(v => v + 1) }, [
|
||||
"Clicks: ", $c
|
||||
const count = $(0);
|
||||
return Button({ onclick: () => count(c => c + 1) }, [
|
||||
"Clicks: ", count
|
||||
]);
|
||||
};
|
||||
|
||||
// Mount it into an existing div in your static HTML
|
||||
// Mount it into a specific div in your static HTML
|
||||
$.mount(CounterWidget, '#counter-container');
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. How it Works (Lifecycle)
|
||||
When `$.mount` is called, it performs three critical steps:
|
||||
## 3. How it Works (Lifecycle)
|
||||
|
||||
1. **Resolution:** If you passed a **Function**, it executes it once to generate the initial DOM node.
|
||||
2. **Clearance:** It sets `target.innerHTML = ''`. This prevents "zombie" HTML or static placeholders from interfering with your app.
|
||||
3. **Injection:** It appends the resulting node to the target.
|
||||
When `$.mount` is executed, it performs these critical steps:
|
||||
|
||||
1. **Resolution & Wrapping**: If you pass a **Function**, SigPro wraps it in a `$.view()`. This starts tracking all internal signals and effects.
|
||||
2. **Target Clearance**: It uses `target.replaceChildren()`. This efficiently wipes any existing HTML or "zombie" nodes inside the target before mounting.
|
||||
3. **Injection**: The component's container is appended to the target.
|
||||
4. **Memory Management**: It stores the `Runtime` instance associated with that DOM element. If you call `$.mount` again on the same target, SigPro automatically **destroys the previous app** to prevent memory leaks.
|
||||
|
||||
---
|
||||
|
||||
## 5. Global vs. Local Scope
|
||||
## 4. Global vs. Local Scope
|
||||
|
||||
### Global (The "Framework" Way)
|
||||
In a standard Vite project, you initialize SigPro in your entry file. This makes `$` and the tag helpers (`div`, `button`, etc.) available globally for a clean, declarative developer experience.
|
||||
### The "Framework" Way (Global)
|
||||
By importing your core in your entry file, SigPro automatically initializes global Tag Constructors (`Div`, `Span`, `H1`, etc.). This allows for a clean, declarative DX across your entire project.
|
||||
|
||||
```javascript
|
||||
// src/main.js
|
||||
import { $ } from 'sigpro';
|
||||
// main.js
|
||||
import './sigpro.js';
|
||||
|
||||
// Any component in any file can now use:
|
||||
$.mount(() => h1("Global App"));
|
||||
// Now any file can simply do:
|
||||
$.mount(() => H1("Global SigPro App"));
|
||||
```
|
||||
|
||||
### Local (The "Library" Way)
|
||||
If you prefer to avoid polluting the `window` object, you can import and use SigPro locally within specific modules.
|
||||
### The "Library" Way (Local)
|
||||
If you prefer to avoid global variables, you can use the low-level `$.html` factory to create elements locally.
|
||||
|
||||
```javascript
|
||||
// widget.js
|
||||
import { $ } from 'sigpro';
|
||||
import { $ } from './sigpro.js';
|
||||
|
||||
const myNode = $.html('div', 'Local Widget');
|
||||
const myNode = $.html('div', { class: 'widget' }, 'Local Instance');
|
||||
$.mount(myNode, '#widget-target');
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. Summary Cheat Sheet
|
||||
## 5. Summary Cheat Sheet
|
||||
|
||||
| Goal | Code |
|
||||
| Goal | Code Pattern |
|
||||
| :--- | :--- |
|
||||
| **Mount to body** | `$.mount(App)` |
|
||||
| **Mount to ID** | `$.mount(App, '#id')` |
|
||||
| **Mount to ID** | `$.mount(App, '#root')` |
|
||||
| **Mount to Element** | `$.mount(App, myElement)` |
|
||||
| **Direct Function** | `$.mount(() => div("Hi"), '#widget')` |
|
||||
| **Mount raw Node** | `$.mount(Div("Hello"), '#id')` |
|
||||
| **Unmount/Destroy** | `const app = $.mount(App); app.destroy();` |
|
||||
|
||||
@@ -1,176 +1,38 @@
|
||||
# Quick API Reference
|
||||
# ⚡ Quick API Reference
|
||||
|
||||
SigPro is a minimal yet powerful engine. Here is a complete overview of its capabilities.
|
||||
SigPro is a high-performance micro-framework that updates the **Real DOM** surgically. No Virtual DOM, no unnecessary re-renders.
|
||||
|
||||
## 1. Core API Summary
|
||||
## 🟢 Core Functions
|
||||
|
||||
| Function | Description | Example |
|
||||
| Function | Signature | Description |
|
||||
| :--- | :--- | :--- |
|
||||
| **`$(val, key?)`** | Creates a Signal, Computed, or Store (with optional persistence). | `const $n = $(0)` |
|
||||
| **`$.html()`** | The base engine to create reactive HTMLElements. | `$.html('div', {}, 'Hi')` |
|
||||
| **`Tags`** | Global helpers (div, span, button, etc.) built on top of `$.html`. | `div("Hello SigPro")` |
|
||||
| **`$.mount()`** | Mounts a component into a target element (clears target first). | `$.mount(App, '#app')` |
|
||||
| **`$.router()`** | Hash-based router with dynamic params and lazy loading. | `$.router(routes)` |
|
||||
| **`$.plugin()`** | Extends SigPro or loads external scripts/plugins. | `$.plugin(MyPlugin)` |
|
||||
| `$(val, key?)` | `(any, string?) => Signal` | Creates a **Signal**. If `key` is provided, it persists in `localStorage`. |
|
||||
| `$(fn)` | `(function) => Computed` | Creates a **Computed Signal** that auto-updates when its dependencies change. |
|
||||
| `$.effect(fn)` | `(function) => stopFn` | Runs a side-effect that tracks signals. Returns a function to manually stop it. |
|
||||
| `$.html(tag, props, children)` | `(string, object, any) => HTMLElement` | The low-level DOM factory powering all tag constructors. |
|
||||
| `$.router(routes)` | `(Array) => HTMLElement` | Initializes the hash-based router for SPAs. |
|
||||
| `$.go(path)` | `(string) => void` | Programmatic navigation (e.g., `$.go('/home')`). |
|
||||
| `$.mount(comp, target)` | `(any, string\|Node) => Runtime` | Mounts the application into the specified DOM element. |
|
||||
| `$.ignore(fn)` | `(function) => any` | Executes code without tracking any signals inside it. |
|
||||
|
||||
---
|
||||
|
||||
## 2. The Power of `$` (Reactivity)
|
||||
## 🏗️ Element Constructors (Tags)
|
||||
|
||||
The `$` function adapts to whatever you pass to it:
|
||||
SigPro provides PascalCase wrappers for all standard HTML5 tags (e.g., `Div`, `Span`, `Button`).
|
||||
|
||||
### **Signals & Persistent State**
|
||||
Reactive values in RAM or synced with `localStorage`.
|
||||
### Syntax Pattern
|
||||
```javascript
|
||||
const $count = $(0); // Simple Signal
|
||||
const $theme = $('dark', 'app-theme'); // Persistent Signal (Disk)
|
||||
|
||||
$count(10); // Update value
|
||||
console.log($count()); // Get value: 10
|
||||
Tag({ attributes }, [children])
|
||||
```
|
||||
|
||||
### **Computed Signals**
|
||||
Read-only signals that update automatically when their dependencies change.
|
||||
```javascript
|
||||
const $double = $(() => $count() * 2);
|
||||
```
|
||||
### Attribute & Content Handling
|
||||
|
||||
### **Reactive Stores (Objects + Disk)**
|
||||
Transforms an object into a reactive tree. If a `key` is provided, the **entire structure** persists.
|
||||
```javascript
|
||||
// Store in RAM + Disk (Auto-syncs nested properties)
|
||||
const state = $({
|
||||
user: { name: 'Natxo' },
|
||||
settings: { dark: true }
|
||||
}, 'my-app-state');
|
||||
| Pattern | Code Example | Behavior |
|
||||
| :--- | :--- | :--- |
|
||||
| **Static** | `class: "text-red"` | Standard HTML attribute string. |
|
||||
| **Reactive** | `disabled: isLoading` | Updates automatically when `isLoading()` changes. |
|
||||
| **Two-way** | `$value: username` | Syncs input with signal **both ways** (Binding Operator). |
|
||||
| **Text** | `P({}, () => count())` | Updates text node whenever `count` changes. |
|
||||
| **Boolean** | `hidden: isHidden` | Toggles the attribute based on signal truthiness. |
|
||||
|
||||
// Accessing properties (they become signals)
|
||||
state.user.name(); // Get: 'Natxo'
|
||||
state.user.name('Guest'); // Set & Sync to Disk: 'my-app-state_user_name'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### **3. UI Creation: Constructor vs. Direct Tags**
|
||||
|
||||
SigPro provides the `$.html` engine for defining any element and global "Sugar Tags" for rapid development.
|
||||
|
||||
::: code-group
|
||||
|
||||
```javascript [Engine Constructor ($.html)]
|
||||
// 1. DEFINE: Create a custom piece of UI
|
||||
// This returns a real DOM element ready to be used.
|
||||
const MyHero = $.html('section', { class: 'hero' }, [
|
||||
h1("Internal Title")
|
||||
]);
|
||||
|
||||
// 2. USE: Nest it inside other elements like a standard tag
|
||||
const Page = () => div([
|
||||
MyHero, // We just drop the variable here
|
||||
p("This paragraph is outside the Hero section.")
|
||||
]);
|
||||
|
||||
$.mount(Page, '#app');
|
||||
```
|
||||
|
||||
```javascript [Global Helpers (Tags)]
|
||||
// Use pre-defined global tags to compose layouts instantly.
|
||||
// No need to define them, just call them.
|
||||
|
||||
const Page = () => div({ id: 'main' }, [
|
||||
section({ class: 'hero' }, [
|
||||
h1("Direct Global Tag"),
|
||||
p("Building UI without boilerplate.")
|
||||
]),
|
||||
button({ onclick: () => alert('Hi!') }, "Click Me")
|
||||
]);
|
||||
|
||||
$.mount(Page, '#app');
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
### **Technical Breakdown**
|
||||
|
||||
* **`$.html(tag, props, children)`**: This is the core factory. Use it when you need to create an element dynamically or when working with **Custom Elements / Web Components**.
|
||||
* **`Global Tags (div, p, etc.)`**: These are shortcut functions that SigPro injects into the `window` object. They internally call `$.html` for you, making your component code much cleaner and easier to read.
|
||||
|
||||
---
|
||||
|
||||
### **Key Difference**
|
||||
* **`$.html`**: Acts as a **constructor**. Use it when you want to "bake" a specific structure (like a Section that *always* contains an H1) into a single variable.
|
||||
* **`Global Tags`**: Act as **scaffolding**. Use them to wrap different contents dynamically as you build your views.
|
||||
|
||||
### **Global Tags (Standard Syntax)**
|
||||
SigPro declares standard tags in the global scope so you don't have to import them.
|
||||
```javascript
|
||||
const Card = (title, $val) => div({ class: 'card' }, [
|
||||
h2(title),
|
||||
p("Reactive content below:"),
|
||||
input({
|
||||
type: 'number',
|
||||
$value: $val, // Automatic Two-way binding
|
||||
$style: () => $val() > 10 ? 'color: red' : 'color: green'
|
||||
})
|
||||
]);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Mounting: `$.mount`
|
||||
|
||||
The entry point of your application. It links your JavaScript logic to a specific DOM element.
|
||||
|
||||
```html
|
||||
<div id="app"></div>
|
||||
```
|
||||
|
||||
```javascript
|
||||
// In your main.js
|
||||
const App = () => main([
|
||||
h1("Welcome to SigPro"),
|
||||
p("Everything here is reactive.")
|
||||
]);
|
||||
|
||||
// Usage: $.mount(component, selectorOrElement)
|
||||
$.mount(App, '#app');
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. Navigation: `$.router`
|
||||
|
||||
A robust hash-based router (`#/path`) that handles view switching automatically.
|
||||
|
||||
```javascript
|
||||
const routes = [
|
||||
{ path: '/', component: Home },
|
||||
{ path: '/user/:id', component: (params) => h1(`User ID: ${params.id}`) },
|
||||
{ path: '/admin', component: () => import('./Admin.js') }, // Native Lazy Loading
|
||||
{ path: '*', component: () => p("404 - Not Found") }
|
||||
];
|
||||
|
||||
// Initialize and mount the router
|
||||
$.mount($.router(routes), '#app');
|
||||
|
||||
// Programmatic navigation
|
||||
$.router.go('/user/42');
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. Plugins: `$.plugin`
|
||||
|
||||
Extend the engine or load external dependencies.
|
||||
|
||||
```javascript
|
||||
// 1. Function-based plugin
|
||||
$.plugin(($) => {
|
||||
$.myHelper = () => console.log("Plugin active!");
|
||||
});
|
||||
|
||||
// 2. Load external scripts
|
||||
await $.plugin('https://cdn.example.com/library.js');
|
||||
```
|
||||
|
||||
@@ -1,106 +1,87 @@
|
||||
# Routing Engine: `$.router`
|
||||
# 🚦 Routing: `$.router( )` & `$.go( )`
|
||||
|
||||
The `$.router` is SigPro's high-performance, hash-based navigation system. It connects the browser's URL directly to your reactive signals, enabling seamless page transitions without full reloads.
|
||||
SigPro includes a built-in, lightweight **Hash Router** to create Single Page Applications (SPA). It manages the URL state, matches components to paths, and handles the lifecycle of your pages automatically.
|
||||
|
||||
## 1. Core Features
|
||||
## 🛠 Router Signature
|
||||
|
||||
* **Hash-based:** Works everywhere without special server configuration (using `#/path`).
|
||||
* **Lazy Loading:** Pages are only downloaded when the user visits the route, keeping the initial bundle under 2KB.
|
||||
* **Reactive:** The view updates automatically and surgically when the hash changes.
|
||||
* **Dynamic Routes:** Built-in support for parameters like `/user/:id`.
|
||||
```typescript
|
||||
$.router(routes: Route[]): HTMLElement
|
||||
```
|
||||
|
||||
### Route Object
|
||||
| Property | Type | Description |
|
||||
| :--- | :--- | :--- |
|
||||
| **`path`** | `string` | The URL fragment (e.g., `"/home"`, `"/user/:id"`, or `"*"`). |
|
||||
| **`component`** | `Function` | A function that returns a Tag or a `$.view`. |
|
||||
|
||||
---
|
||||
|
||||
## 2. Syntax: `$.router(routes)`
|
||||
## 📖 Usage Patterns
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
| **routes** | `Array<Object>` | **Yes** | An array of route definitions `{ path, component }`. |
|
||||
|
||||
---
|
||||
|
||||
## 3. Setting Up Routes
|
||||
|
||||
In your `App.js` (or a dedicated routes file), define your navigation map and inject it into your layout.
|
||||
### 1. Defining Routes
|
||||
The router returns a `div` element with the class `.router-outlet`. When the hash changes, the router destroys the previous view and mounts the new one inside this container.
|
||||
|
||||
```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([
|
||||
header([
|
||||
h1("SigPro App"),
|
||||
nav([
|
||||
button({ onclick: () => $.router.go('/') }, "Home"),
|
||||
button({ onclick: () => $.router.go('/admin') }, "Admin")
|
||||
])
|
||||
]),
|
||||
// The router returns a reactive div that swaps content
|
||||
main($.router(routes))
|
||||
const App = () => Div({ class: "app-layout" }, [
|
||||
Navbar(),
|
||||
// The router outlet is placed here
|
||||
$.router([
|
||||
{ path: "/", component: Home },
|
||||
{ path: "/profile/:id", component: UserProfile },
|
||||
{ path: "*", component: NotFound }
|
||||
])
|
||||
]);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Navigation (`$.router.go`)
|
||||
|
||||
To move between pages programmatically (e.g., inside an `onclick` event or after a successful fetch), use the `$.router.go` helper.
|
||||
### 2. Dynamic Segments (`:id`)
|
||||
When a path contains a colon (e.g., `:id`), the router parses that segment and passes it as an object to the component function.
|
||||
|
||||
```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, handling dynamic segments (`:id`) and fallbacks (`*`).
|
||||
2. **Resolve:** * If it's a standard function, it executes it immediately.
|
||||
* If it's a **Promise** (via `import()`), it renders a temporary `Loading...` state and swaps the content once the module arrives.
|
||||
3. **Inject:** It replaces the previous DOM node with the new page content surgically using `replaceWith()`.
|
||||
|
||||
---
|
||||
|
||||
## 6. Integration with UI Components
|
||||
|
||||
Since the router is reactive, you can easily create "active" states in your navigation menus by checking the current hash.
|
||||
|
||||
```javascript
|
||||
// Example of a reactive navigation link
|
||||
const NavLink = (path, label) => {
|
||||
const $active = $(() => window.location.hash === `#${path}`);
|
||||
|
||||
return button({
|
||||
$class: () => $active() ? 'nav-active' : 'nav-link',
|
||||
onclick: () => $.router.go(path)
|
||||
}, label);
|
||||
// If the URL is #/profile/42
|
||||
const UserProfile = (params) => {
|
||||
return H1(`User ID is: ${params.id}`); // Displays "User ID is: 42"
|
||||
};
|
||||
|
||||
nav([
|
||||
NavLink('/', 'Home'),
|
||||
NavLink('/settings', 'Settings')
|
||||
]);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. Summary: Route Component Types
|
||||
## 🏎 Programmatic Navigation: `$.go( )`
|
||||
|
||||
| Component Type | Behavior |
|
||||
| :--- | :--- |
|
||||
| **HTMLElement** | Rendered immediately. |
|
||||
| **Function `(params) => ...`** | Executed with URL parameters and rendered. |
|
||||
| **Promise / `import()`** | Triggers **Lazy Loading** with a loading state. |
|
||||
| **String / Number** | Rendered as simple text inside a span. |
|
||||
To navigate between pages without using an `<a>` tag, use `$.go`. This function updates the browser's hash, which in turn triggers the router to swap components.
|
||||
|
||||
### Signature
|
||||
```typescript
|
||||
$.go(path: string): void
|
||||
```
|
||||
|
||||
### Examples
|
||||
```javascript
|
||||
// Navigate to a static path
|
||||
Button({ onclick: () => $.go("/") }, "Home")
|
||||
|
||||
// Navigate to a dynamic path
|
||||
Button({
|
||||
onclick: () => $.go(`/profile/${user.id}`)
|
||||
}, "View Profile")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ⚡ Technical Behavior
|
||||
|
||||
* **Automatic Cleanup**: Every time you navigate, the router calls `.destroy()` on the previous `$.view`. This ensures that all **signals, effects, and event listeners** from the old page are purged from memory.
|
||||
* **Hash-Based**: By using `window.location.hash`, SigPro works out-of-the-box on any static hosting (like GitHub Pages or Vercel) without needing server-side redirects.
|
||||
* **Initial Load**: On the first execution, `$.router` automatically reads the current hash or defaults to `/` if empty.
|
||||
|
||||
---
|
||||
|
||||
## 🎨 Styling the Outlet
|
||||
Since the router returns a standard DOM element, you can style the transition or the container easily:
|
||||
|
||||
```css
|
||||
.router-outlet {
|
||||
flex: 1;
|
||||
padding: 2rem;
|
||||
animation: fadeIn 0.2s ease-in;
|
||||
}
|
||||
```
|
||||
|
||||
68
src/docs/api/signal.md
Normal file
68
src/docs/api/signal.md
Normal file
@@ -0,0 +1,68 @@
|
||||
# 💎 The Signal Function: `$( )`
|
||||
|
||||
The `$( )` function is the core constructor of SigPro. It defines how data is stored, computed, and persisted.
|
||||
|
||||
## 🛠 Function Signature
|
||||
|
||||
```typescript
|
||||
$(initialValue: any, key?: string): Signal
|
||||
$(computation: Function): ComputedSignal
|
||||
```
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
| **`initialValue`** | `any` | Yes* | The starting value of your signal. |
|
||||
| **`computation`** | `Function` | Yes* | A function that returns a value based on other signals. |
|
||||
| **`key`** | `string` | No | A unique name to persist the signal in `localStorage`. |
|
||||
|
||||
*\*Either an initial value or a computation function must be provided.*
|
||||
|
||||
---
|
||||
|
||||
## 📖 Usage Patterns
|
||||
|
||||
### 1. Simple State
|
||||
**`$(value)`**
|
||||
Creates a writable signal. It returns a function that acts as both **getter** and **setter**.
|
||||
|
||||
```javascript
|
||||
const count = $(0);
|
||||
|
||||
count(); // Read (0)
|
||||
count(10); // Write (10)
|
||||
```
|
||||
|
||||
### 2. Persistent State
|
||||
**`$(value, key)`**
|
||||
Creates a writable signal that syncs with the browser's storage.
|
||||
|
||||
```javascript
|
||||
const theme = $("light", "app-theme");
|
||||
|
||||
theme("dark"); // Automatically calls localStorage.setItem("app-theme", '"dark"')
|
||||
```
|
||||
*Note: On page load, SigPro will prioritize the value found in `localStorage` over the `initialValue`.*
|
||||
|
||||
### 3. Computed State (Derived)
|
||||
**`$(function)`**
|
||||
Creates a read-only signal that updates automatically when any signal used inside it changes.
|
||||
|
||||
```javascript
|
||||
const price = $(100);
|
||||
const tax = $(0.21);
|
||||
|
||||
// This tracks both 'price' and 'tax' automatically
|
||||
const total = $(() => price() * (1 + tax()));
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔄 Updating with Logic
|
||||
When calling the setter, you can pass an **updater function** to access the current value safely.
|
||||
|
||||
```javascript
|
||||
const list = $(["A", "B"]);
|
||||
|
||||
// Adds "C" using the previous state
|
||||
list(prev => [...prev, "C"]);
|
||||
```
|
||||
@@ -1,128 +1,136 @@
|
||||
# Global Tag Helpers
|
||||
# 🎨 Global Tag Helpers
|
||||
|
||||
In **SigPro**, you don't need to write `$.html('div', ...)` every time. To keep your code clean and readable, the engine automatically generates global helper functions for all standard HTML tags upon initialization.
|
||||
In **SigPro**, you don't need to manually type `$.html('div', ...)` for every element. To keep your code declarative and readable, the engine automatically generates **Global Helper Functions** for all standard HTML5 tags upon initialization.
|
||||
|
||||
## 1. How it Works
|
||||
|
||||
SigPro iterates through an internal manifest of standard HTML tags and attaches a wrapper function for each one directly to the `window` object. This creates a native "DSL" (Domain Specific Language) that looks like a template engine but is **100% standard JavaScript**.
|
||||
SigPro iterates through a manifest of standard HTML tags and attaches a wrapper function for each one directly to the `window` object. This creates a specialized **DSL** (Domain Specific Language) that looks like a template engine but is **100% standard JavaScript**.
|
||||
|
||||
* **Under the hood:** `$.html('button', { onclick: ... }, 'Click')`
|
||||
* **SigPro Style:** `button({ onclick: ... }, 'Click')`
|
||||
* **SigPro Style:** `Button({ onclick: ... }, 'Click')`
|
||||
|
||||
---
|
||||
|
||||
## 2. The Complete Global Registry
|
||||
|
||||
The following tags are injected into the global scope and are ready to use as soon as SigPro loads:
|
||||
The following functions are injected into the global scope (using **PascalCase** to prevent naming collisions with common JS variables) and are ready to use:
|
||||
|
||||
| Category | Available Global Functions |
|
||||
| :--- | :--- |
|
||||
| **Structure** | `div`, `span`, `p`, `section`, `nav`, `main`, `header`, `footer`, `article`, `aside` |
|
||||
| **Typography** | `h1`, `h2`, `h3`, `h4`, `h5`, `h6`, `ul`, `ol`, `li`, `dl`, `dt`, `dd`, `strong`, `em`, `code`, `pre`, `small`, `i`, `b`, `u`, `mark` |
|
||||
| **Interactive** | `button`, `a`, `label`, `br`, `hr`, `details`, `summary` |
|
||||
| **Forms** | `form`, `input`, `select`, `option`, `textarea`, `fieldset`, `legend` |
|
||||
| **Tables** | `table`, `thead`, `tbody`, `tr`, `th`, `td`, `tfoot`, `caption` |
|
||||
| **Media & Graphics** | `img`, `canvas`, `video`, `audio`, `svg`, `path`, `iframe` |
|
||||
| **Structure** | `Div`, `Span`, `P`, `Section`, `Nav`, `Main`, `Header`, `Footer`, `Article`, `Aside` |
|
||||
| **Typography** | `H1` to `H6`, `Ul`, `Ol`, `Li`, `Dl`, `Dt`, `Dd`, `Strong`, `Em`, `Code`, `Pre`, `Small`, `B`, `U`, `Mark` |
|
||||
| **Interactive** | `Button`, `A`, `Label`, `Br`, `Hr`, `Details`, `Summary`, `Dialog` |
|
||||
| **Forms** | `Form`, `Input`, `Select`, `Option`, `Textarea`, `Fieldset`, `Legend` |
|
||||
| **Tables** | `Table`, `Thead`, `Tbody`, `Tr`, `Th`, `Td`, `Tfoot`, `Caption` |
|
||||
| **Media** | `Img`, `Canvas`, `Video`, `Audio`, `Svg`, `Iframe`, `Picture`, `Source` |
|
||||
|
||||
> "In SigPro, tags are not 'magic' strings handled by a compiler. They are **functional imitations** of HTML elements. Every time you call `div()`, you are executing a standard JavaScript function that returns a real DOM element. This gives you the speed of a specialized DSL with the transparency of pure JS."
|
||||
|
||||
::: danger WARNING: GLOBAL NAMING COLLISIONS
|
||||
Since **SigPro** injects these helpers directly into the `window` object, they are regular JavaScript functions. This means **they can be overwritten**.
|
||||
|
||||
If you declare a variable, constant, or function with the same name as an HTML tag (e.g., `const div = ...` or `function p()`), you will **nullify or shadow** the built-in SigPro helper for that tag in your current scope.
|
||||
|
||||
**Best Practice:** To avoid conflicts, always use **PascalCase** for your custom components (e.g., `UserCard`, `AppHeader`) to distinguish them from the **lowercase** global HTML helpers.
|
||||
:::
|
||||
> **The SigPro Philosophy:** Tags are not "magic strings" handled by a compiler. They are **functional constructors**. Every time you call `Div()`, you execute a pure JS function that returns a real, reactive DOM element.
|
||||
|
||||
---
|
||||
|
||||
## 3. Usage Patterns (Argument Flexibility)
|
||||
## 3. Usage Patterns (Smart Arguments)
|
||||
|
||||
The tag functions are "smart". They detect whether you are passing attributes, content, or both.
|
||||
SigPro tag helpers are flexible. They automatically detect if you are passing attributes, children, or both.
|
||||
|
||||
### A. Attributes + Content
|
||||
The standard way to build complex nodes.
|
||||
### A. Attributes + Children
|
||||
The standard way to build structured UI.
|
||||
```javascript
|
||||
div({ class: 'container', id: 'main-wrapper' }, [
|
||||
h1("Welcome"),
|
||||
p("This is SigPro.")
|
||||
Div({ class: 'container', id: 'main' }, [
|
||||
H1("Welcome to SigPro"),
|
||||
P("The zero-VDOM framework.")
|
||||
]);
|
||||
```
|
||||
|
||||
### B. Content Only (The "Skipper")
|
||||
If you don't need attributes, you can pass the content (string, array, or function) as the **first and only** argument.
|
||||
### B. Children Only (The "Skipper")
|
||||
If you don't need attributes, you can skip the object and pass the content (string, array, or function) directly as the first argument.
|
||||
```javascript
|
||||
section([
|
||||
h2("No Attributes Needed"),
|
||||
button("Click Me")
|
||||
Section([
|
||||
H2("Clean Syntax"),
|
||||
Button("I have no props!")
|
||||
]);
|
||||
```
|
||||
|
||||
### C. Primitive Content
|
||||
For simple tags, you can just pass a string or a number.
|
||||
For simple tags, just pass a string or a number.
|
||||
```javascript
|
||||
h1("Hello World");
|
||||
span(42);
|
||||
H1("Hello World");
|
||||
Span(42);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Reactive Attributes & Content
|
||||
## 4. Reactive Power Examples
|
||||
|
||||
These helpers fully support SigPro's reactivity. Attributes starting with `$` are automatically tracked.
|
||||
These helpers are natively wired into SigPro's reactivity. No manual `useEffect` or `watch` calls are needed.
|
||||
|
||||
### Reactive Attributes
|
||||
Simply pass a Signal (function) to any attribute. SigPro handles the rest.
|
||||
```javascript
|
||||
const $count = $(0);
|
||||
const theme = $("light");
|
||||
|
||||
div({ class: 'counter-app' }, [
|
||||
h2(["Current Count: ", $count]), // Auto-unwrapping text content
|
||||
|
||||
button({
|
||||
onclick: () => $count(c => c + 1),
|
||||
$style: () => $count() > 5 ? "color: red" : "color: green" // Reactive style
|
||||
}, "Increment")
|
||||
Div({
|
||||
// Updates 'class' automatically when theme() changes
|
||||
class: () => `app-box ${theme()}`
|
||||
}, "Themeable Box");
|
||||
```
|
||||
|
||||
### The Binding Operator (`$`)
|
||||
Use the `$` prefix for **Two-Way Binding** on inputs.
|
||||
```javascript
|
||||
const search = $("");
|
||||
|
||||
Input({
|
||||
type: "text",
|
||||
placeholder: "Search...",
|
||||
$value: search // UI updates Signal AND Signal updates UI
|
||||
});
|
||||
```
|
||||
|
||||
### Dynamic Lists
|
||||
Combine tags with `ui.For` for high-performance list rendering.
|
||||
```javascript
|
||||
const items = $(["Apple", "Banana", "Cherry"]);
|
||||
|
||||
Ul({ class: "list-disc" }, [
|
||||
ui.For(items, (item) => Li(item), (item) => item)
|
||||
]);
|
||||
```
|
||||
|
||||
---
|
||||
::: danger
|
||||
## ⚠️ Important: Naming Conventions
|
||||
|
||||
## 5. Technical Implementation
|
||||
|
||||
As seen in the SigPro core, the engine registers these tags dynamically. This means **zero imports** are needed for UI creation in your component files.
|
||||
|
||||
```javascript
|
||||
// Internal SigPro loop
|
||||
tags.forEach(t => window[t] = (p, c) => $.html(t, p, c));
|
||||
```
|
||||
|
||||
Because they are real functions, you get full IDE autocompletion and valid JS syntax highlighting without needing special plugins like JSX.
|
||||
Since SigPro injects these helpers into the global `window` object, follow these rules to avoid bugs:
|
||||
|
||||
1. **Avoid Shadowing**: Don't name your local variables like the tags (e.g., `const Div = ...`). This will "hide" the SigPro helper.
|
||||
2. **Custom Components**: Always use **PascalCase**, **UPPERCASE**, or **Underscore** prefixes for your own component functions (e.g., `UserCard`, `INPUT`, or `_Input`) to distinguish them from the built-in Tag Helpers and avoid naming collisions.
|
||||
:::
|
||||
---
|
||||
|
||||
## 6. Comparison: Logic to UI
|
||||
## 6. Logic to UI Comparison
|
||||
|
||||
Here is how a dynamic **Task Item** component translates from SigPro logic to the final DOM structure.
|
||||
Here is how a dynamic **User Status** component translates from SigPro logic to the final DOM structure.
|
||||
|
||||
::: code-group
|
||||
```javascript [SigPro Component]
|
||||
const Task = (title, $done) => (
|
||||
li({ class: 'task-item' }, [
|
||||
input({
|
||||
type: 'checkbox',
|
||||
$checked: $done // Two-way reactive binding
|
||||
```javascript
|
||||
// SigPro Component
|
||||
const UserStatus = (name, $online) => (
|
||||
Div({ class: 'flex items-center gap-2' }, [
|
||||
Span({
|
||||
// Boolean toggle for 'hidden' attribute
|
||||
hidden: () => !$online(),
|
||||
class: 'w-3 h-3 bg-green-500 rounded-full'
|
||||
}),
|
||||
span({
|
||||
$style: () => $done() ? "text-decoration: line-through" : ""
|
||||
}, title)
|
||||
P({
|
||||
// Reactive text content
|
||||
class: () => $online() ? "text-bold" : "text-gray-400"
|
||||
}, name)
|
||||
])
|
||||
);
|
||||
```
|
||||
|
||||
```html [Rendered HTML]
|
||||
<li class="task-item">
|
||||
<input type="checkbox" checked>
|
||||
<style="text-decoration: line-through">Buy milk</span>
|
||||
</li>
|
||||
```
|
||||
:::
|
||||
| State (`$online`) | Rendered HTML |
|
||||
| :--- | :--- |
|
||||
| **`true`** | `<div class="flex..."><span class="w-3..."></span><p class="text-bold">John</p></div>` |
|
||||
| **`false`** | `<div class="flex..."><span hidden class="w-3..."></span><p class="text-gray-400">John</p></div>` |
|
||||
|
||||
|
||||
|
||||
78
src/docs/api/view.md
Normal file
78
src/docs/api/view.md
Normal file
@@ -0,0 +1,78 @@
|
||||
# 🖼️ Component Lifecycle: `$.view( )`
|
||||
|
||||
The `$.view` function is a specialized wrapper used to manage the lifecycle of a UI component. It tracks all signals, effects, and DOM elements created within it, providing a single point of destruction to prevent memory leaks.
|
||||
|
||||
## 🛠 Function Signature
|
||||
|
||||
```typescript
|
||||
$.view(renderFn: Function): RuntimeObject
|
||||
```
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
| **`renderFn`** | `Function` | Yes | A function that returns a DOM Node or an array of Nodes. |
|
||||
|
||||
**Returns:** A `Runtime` object containing:
|
||||
* `container`: A `div` (with `display: contents`) holding the rendered content.
|
||||
* `destroy()`: A function that unmounts the view and cleans up all internal effects/listeners.
|
||||
|
||||
---
|
||||
|
||||
## 📖 Usage Patterns
|
||||
|
||||
### 1. Basic Component Wrapper
|
||||
When you wrap logic in `$.view`, SigPro creates a "boundary".
|
||||
|
||||
```javascript
|
||||
const myView = $.view(() => {
|
||||
const count = $(0);
|
||||
|
||||
// This effect is "owned" by this view
|
||||
$.effect(() => console.log(count()));
|
||||
|
||||
return Div([
|
||||
H1("Internal View"),
|
||||
Button({ onclick: () => count(c => c + 1) }, "Add")
|
||||
]);
|
||||
});
|
||||
|
||||
// To show it:
|
||||
document.body.appendChild(myView.container);
|
||||
|
||||
// To kill it (removes from DOM and stops all internal effects):
|
||||
myView.destroy();
|
||||
```
|
||||
|
||||
### 2. Manual Cleanups with `onCleanup`
|
||||
The `renderFn` receives a helper object that allows you to register custom cleanup logic (like closing a WebSocket or a third-party library instance).
|
||||
|
||||
```javascript
|
||||
const ChartComponent = () => $.view(({ onCleanup }) => {
|
||||
const socket = new WebSocket("ws://...");
|
||||
|
||||
onCleanup(() => {
|
||||
socket.close();
|
||||
console.log("Cleanup: WebSocket closed");
|
||||
});
|
||||
|
||||
return Div({ class: "chart-container" });
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🏗️ Internal Architecture
|
||||
|
||||
When `$.view` is called, it performs the following:
|
||||
1. **Isolation**: It creates a new `Set` of cleanups.
|
||||
2. **Execution**: It runs your function and captures any `$.effect` or child `$.view` created during execution.
|
||||
3. **Container**: It wraps the result in a `display: contents` element (which doesn't affect your CSS layout).
|
||||
4. **Disposal**: When `destroy()` is called, it recursively calls all cleanup functions and removes the container from the DOM.
|
||||
|
||||
---
|
||||
|
||||
## 💡 Why use `$.view`?
|
||||
|
||||
* **Automatic Cleanup**: You don't have to manually stop every effect when a user navigates away.
|
||||
* **Router Integration**: The SigPro Router (`$.router`) uses `$.view` internally to swap pages and clean up the previous one automatically.
|
||||
* **Performance**: It ensures that background processes (like intervals or observers) stop as soon as the element is no longer visible.
|
||||
@@ -10,7 +10,7 @@ Choose the method that best fits your workflow:
|
||||
|
||||
```bash [npm]
|
||||
npm install sigpro
|
||||
````
|
||||
```
|
||||
|
||||
```bash [pnpm]
|
||||
pnpm add sigpro
|
||||
@@ -26,7 +26,12 @@ bun add sigpro
|
||||
|
||||
```html [CDN (ESM)]
|
||||
<script type="module">
|
||||
// Import the core and UI components
|
||||
import { $ } from 'https://cdn.jsdelivr.net/npm/sigpro@latest/+esm';
|
||||
import { UI } from 'https://cdn.jsdelivr.net/npm/sigpro@latest/ui/+esm';
|
||||
|
||||
// Initialize UI components globally
|
||||
UI($);
|
||||
</script>
|
||||
```
|
||||
|
||||
@@ -34,9 +39,9 @@ bun add sigpro
|
||||
|
||||
-----
|
||||
|
||||
## 2\. Quick Start Examples
|
||||
## 2. Quick Start Examples
|
||||
|
||||
Depending on your installation method, here is how you can get SigPro running in seconds.
|
||||
SigPro uses **PascalCase** for Tag Helpers (e.g., `Div`, `Button`) to provide a clean, component-like syntax without needing JSX.
|
||||
|
||||
::: code-group
|
||||
|
||||
@@ -45,13 +50,15 @@ Depending on your installation method, here is how you can get SigPro running in
|
||||
import { $ } from 'sigpro';
|
||||
|
||||
export const App = () => {
|
||||
// $ is global, but we import it for better IDE intellisense
|
||||
const $count = $(0);
|
||||
|
||||
// Tags like div, h1, button are available globally
|
||||
return div({ class: 'card' }, [
|
||||
h1(["Count is: ", $count]),
|
||||
button({ onclick: () => $count(c => c + 1) }, "Increment")
|
||||
// Tag Helpers like Div, H1, Button are available globally
|
||||
return Div({ class: 'card p-4' }, [
|
||||
H1(["Count is: ", $count]),
|
||||
Button({
|
||||
class: 'btn btn-primary',
|
||||
onclick: () => $count(c => c + 1)
|
||||
}, "Increment")
|
||||
]);
|
||||
};
|
||||
|
||||
@@ -69,16 +76,16 @@ $.mount(App, '#app');
|
||||
<div id="app"></div>
|
||||
|
||||
<script type="module">
|
||||
// Import directly from CDN
|
||||
import { $ } from 'https://cdn.jsdelivr.net/npm/sigpro@latest/+esm';
|
||||
|
||||
const $name = $("Developer");
|
||||
|
||||
// No need to import div, section, h2, input... they are global!
|
||||
const App = () => section([
|
||||
h2(["Welcome, ", $name]),
|
||||
input({
|
||||
// No need to import Div, Section, H2, Input... they are global!
|
||||
const App = () => Section({ class: 'container' }, [
|
||||
H2(["Welcome, ", $name]),
|
||||
Input({
|
||||
type: 'text',
|
||||
class: 'input input-bordered',
|
||||
$value: $name, // Automatic two-way binding
|
||||
placeholder: 'Type your name...'
|
||||
})
|
||||
@@ -94,17 +101,46 @@ $.mount(App, '#app');
|
||||
|
||||
-----
|
||||
|
||||
## 3\. Global by Design
|
||||
## 3. Global by Design
|
||||
|
||||
One of SigPro's core strengths is its **Global API**.
|
||||
One of SigPro's core strengths is its **Global API**, which eliminates "Import Hell".
|
||||
|
||||
* **The `$` Function:** Once loaded, it attaches itself to `window.$`. While you can use `import` for better IDE support and type checking, it is accessible everywhere.
|
||||
* **HTML Tags:** Common tags (`div`, `span`, `button`, etc.) are automatically registered in the global scope. This eliminates "Import Hell" and keeps your components clean and readable.
|
||||
* **The `$` Function:** Once loaded, it attaches itself to `window.$`. It handles state, effects, and DOM mounting.
|
||||
* **Tag Helpers (PascalCase):** Common HTML tags (`Div`, `Span`, `Button`, `Input`, etc.) are automatically registered in the global scope.
|
||||
* **Custom Components:** We recommend using **PascalCase** (e.g., `UserCard`) or prefixes like `_Input` to keep your code organized and distinguish your logic from standard tags.
|
||||
|
||||
## 4\. Why no build step?
|
||||
## 4. Why no build step?
|
||||
|
||||
Because SigPro uses **native ES Modules** and standard JavaScript functions to generate the DOM, you don't actually *need* a compiler like Babel or a loader for JSX.
|
||||
Because SigPro uses **native ES Modules** and standard JavaScript functions to generate the DOM, you don't actually *need* a compiler like Babel or a transformer for JSX.
|
||||
|
||||
* **Development:** Just save and refresh. No complex HMR issues.
|
||||
* **Production:** Use any bundler (Vite, esbuild, Rollup) to tree-shake and minify your final code for maximum performance.
|
||||
* **Development:** Just save and refresh. Pure JS, no "transpilation" required.
|
||||
* **Performance:** Extremely lightweight. Use any modern bundler (Vite, esbuild) only when you are ready to minify and tree-shake for production.
|
||||
|
||||
## 5. Why SigPro? (The Competitive Edge)
|
||||
|
||||
SigPro stands out by removing the "Build Step" tax and the "Virtual DOM" overhead. It is the closest you can get to writing raw HTML/JS while maintaining modern reactivity.
|
||||
|
||||
| Feature | **SigPro** | **SolidJS** | **Svelte** | **React** | **Vue** |
|
||||
| :--- | :--- | :--- | :--- | :--- | :--- |
|
||||
| **Bundle Size** | **~2KB** | ~7KB | ~4KB | ~40KB+ | ~30KB |
|
||||
| **DOM Strategy** | **Direct DOM** | Direct DOM | Compiled DOM | Virtual DOM | Virtual DOM |
|
||||
| **Reactivity** | **Fine-grained** | Fine-grained | Compiled | Re-renders | Proxies |
|
||||
| **Build Step** | **Optional** | Required | Required | Required | Optional |
|
||||
| **Learning Curve**| **Minimal** | Medium | Low | High | Medium |
|
||||
| **Initialization**| **Ultra-Fast** | Very Fast | Fast | Slow | Medium |
|
||||
|
||||
---
|
||||
|
||||
## 6. Key Advantages
|
||||
|
||||
* **Extreme Performance**: No Virtual DOM reconciliation. SigPro updates the specific node or attribute instantly when a Signal changes.
|
||||
* **Fine-Grained Reactivity**: State changes only trigger updates where the data is actually used, not on the entire component.
|
||||
* **Native Web Standards**: Everything is a standard JS function. No custom template syntax to learn.
|
||||
* **Zero Magic**: No hidden compilers. What you write is what runs in the browser.
|
||||
* **Global by Design**: Tag Helpers and the `$` function are available globally to eliminate "Import Hell" and keep your code clean.
|
||||
|
||||
## 7. Summary
|
||||
|
||||
SigPro isn't just another framework; it's a bridge to the native web. By using standard ES Modules and functional DOM generation, you gain the benefits of a modern library with the weight of a utility script.
|
||||
|
||||
**Because, in the end... why fight the web when we can embrace it?**
|
||||
|
||||
@@ -1,97 +0,0 @@
|
||||
# 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. |
|
||||
|
||||
@@ -1,94 +0,0 @@
|
||||
# Extending SigPro: `$.plugin`
|
||||
|
||||
The plugin system is the engine's modular backbone. It allows you to inject new functionality directly into the `$` object, register custom global tags, or load external libraries seamlessly.
|
||||
|
||||
## 1. How Plugins Work
|
||||
|
||||
A plugin in **SigPro** is a function that receives the core instance. When you call `$.plugin(MyPlugin)`, the engine hands over the `$` object so the plugin can attach new methods or extend the reactive system.
|
||||
|
||||
### Functional Plugin Example
|
||||
```javascript
|
||||
// A plugin that adds a simple watcher 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 (SigPro)
|
||||
|
||||
Thanks to the **Synchronous Tag Engine**, you no longer need complex `import()` nesting. Global tags like `div()`, `span()`, and `button()` are ready the moment you import the Core.
|
||||
|
||||
### The "Natural" Start (Recommended)
|
||||
This is the standard way to build apps. It's clean, readable, and supports standard ESM imports.
|
||||
|
||||
```javascript
|
||||
// main.js
|
||||
import { $ } from 'sigpro';
|
||||
import { Fetch } from 'sigpro/plugins';
|
||||
import App from './App.js'; // Static import works perfectly!
|
||||
|
||||
// 1. Register plugins
|
||||
$.plugin(Fetch);
|
||||
|
||||
// 2. Mount your app directly
|
||||
$.mount(App, '#app');
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 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. This is perfect for integrating heavy third-party libraries only when needed.
|
||||
|
||||
```javascript
|
||||
// Loading external libraries as plugins
|
||||
$.plugin([
|
||||
'https://cdn.jsdelivr.net/npm/chart.js',
|
||||
'https://cdn.example.com/custom-ui-lib.js'
|
||||
]).then(() => {
|
||||
console.log("External resources are ready to use!");
|
||||
$.mount(DashboardApp);
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Polymorphic Loading Reference
|
||||
|
||||
The `$.plugin` method is smart; it adapts its behavior based on the input type:
|
||||
|
||||
| Input Type | Action | Behavior |
|
||||
| :--- | :--- | :--- |
|
||||
| **Function** | Executes `fn($)` | **Synchronous**: Immediate availability. |
|
||||
| **String (URL)** | Injects `<script src="...">` | **Asynchronous**: Returns a Promise. |
|
||||
| **Array** | Processes each item in the list | Returns a Promise if any item is a URL. |
|
||||
|
||||
---
|
||||
|
||||
## 💡 Pro Tip: When to use `.then()`?
|
||||
|
||||
In **SigPro**, you only need `.then()` in two specific cases:
|
||||
1. **External Assets:** When loading a plugin via a URL (CDN).
|
||||
2. **Strict Dependency:** If your `App.js` requires a variable that is strictly defined inside an asynchronous external script (like `window.Chart`).
|
||||
|
||||
For everything else (UI components, Router, Local State), just call `$.plugin()` and continue with your code. It's that simple.
|
||||
|
||||
---
|
||||
|
||||
### Summary Cheat Sheet
|
||||
|
||||
| Goal | Code |
|
||||
| :--- | :--- |
|
||||
| **Local Plugin** | `$.plugin(myPlugin)` |
|
||||
| **Multiple Plugins** | `$.plugin([UI, Router])` |
|
||||
| **External Library** | `$.plugin('https://...').then(...)` |
|
||||
| **Hybrid Load** | `$.plugin([UI, 'https://...']).then(...)` |
|
||||
@@ -1,114 +0,0 @@
|
||||
# Button Component
|
||||
|
||||
The `_button` component creates reactive buttons with built-in support for loading states, icons, badges, and disabled states.
|
||||
|
||||
## Basic Usage
|
||||
|
||||
<div id="basic-button-demo"></div>
|
||||
|
||||
```javascript
|
||||
_button({ onclick: () => alert('Clicked!') }, 'Click Me')
|
||||
```
|
||||
|
||||
## Loading State
|
||||
|
||||
The `$loading` signal automatically shows a spinner and disables the button.
|
||||
|
||||
<div id="loading-button-demo"></div>
|
||||
|
||||
```javascript
|
||||
const $loading = $(false)
|
||||
|
||||
_button({
|
||||
$loading: $loading,
|
||||
onclick: async () => {
|
||||
$loading(true)
|
||||
await saveData()
|
||||
$loading(false)
|
||||
}
|
||||
}, 'Save')
|
||||
```
|
||||
|
||||
## Icons
|
||||
|
||||
Add icons to buttons using the `icon` prop.
|
||||
|
||||
<div id="icon-button-demo"></div>
|
||||
|
||||
```javascript
|
||||
_button({ icon: '⭐' }, 'Favorite')
|
||||
_button({ icon: '💾' }, 'Save')
|
||||
_button({ icon: '🗑️', class: 'btn-error' }, 'Delete')
|
||||
```
|
||||
|
||||
## Badges
|
||||
|
||||
Add badges to buttons for notifications or status indicators.
|
||||
|
||||
<div id="badge-button-demo"></div>
|
||||
|
||||
```javascript
|
||||
_button({ badge: '3' }, 'Notifications')
|
||||
_button({ badge: 'New', badgeClass: 'badge-secondary' }, 'Update Available')
|
||||
```
|
||||
|
||||
## Button Variants
|
||||
|
||||
Use daisyUI classes to style your buttons.
|
||||
|
||||
<div id="variant-button-demo"></div>
|
||||
|
||||
```javascript
|
||||
_button({ class: 'btn-primary' }, 'Primary')
|
||||
_button({ class: 'btn-secondary' }, 'Secondary')
|
||||
_button({ class: 'btn-outline' }, 'Outline')
|
||||
_button({ class: 'btn-sm' }, 'Small')
|
||||
```
|
||||
|
||||
## Counter Example
|
||||
|
||||
<div id="counter-demo"></div>
|
||||
|
||||
```javascript
|
||||
const $count = $(0)
|
||||
|
||||
_button({
|
||||
onclick: () => $count($count() + 1),
|
||||
icon: '🔢'
|
||||
}, () => `Count: ${$count()}`)
|
||||
```
|
||||
|
||||
## Async Action Example
|
||||
|
||||
<div id="async-demo"></div>
|
||||
|
||||
```javascript
|
||||
const $saving = $(false)
|
||||
const $success = $(false)
|
||||
|
||||
_button({
|
||||
$loading: $saving,
|
||||
icon: '💾',
|
||||
onclick: async () => {
|
||||
$saving(true)
|
||||
await saveToDatabase()
|
||||
$saving(false)
|
||||
$success(true)
|
||||
setTimeout(() => $success(false), 2000)
|
||||
}
|
||||
}, 'Save')
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
| Prop | Type | Description |
|
||||
|------|------|-------------|
|
||||
| `$loading` | `Signal<boolean>` | Shows spinner and disables button |
|
||||
| `$disabled` | `Signal<boolean>` | Disables the button |
|
||||
| `icon` | `string \| Node` | Icon to display before text |
|
||||
| `badge` | `string` | Badge text to display |
|
||||
| `badgeClass` | `string` | Additional CSS classes for badge |
|
||||
| `class` | `string \| function` | Additional CSS classes |
|
||||
| `onclick` | `function` | Click event handler |
|
||||
| `type` | `string` | Button type ('button', 'submit', etc.) |
|
||||
|
||||
@@ -1,112 +0,0 @@
|
||||
# Form Components
|
||||
|
||||
SigPro UI provides a complete set of reactive form components including select dropdowns, checkboxes, radio buttons, and range sliders.
|
||||
|
||||
## Select Dropdown (`_select`)
|
||||
|
||||
Creates a reactive dropdown select with options.
|
||||
|
||||
<div id="select-demo"></div>
|
||||
|
||||
```javascript
|
||||
const $role = $('user')
|
||||
|
||||
_select({
|
||||
label: 'User Role',
|
||||
options: [
|
||||
{ value: 'admin', label: 'Administrator' },
|
||||
{ value: 'user', label: 'Standard User' },
|
||||
{ value: 'guest', label: 'Guest' }
|
||||
],
|
||||
$value: $role
|
||||
})
|
||||
```
|
||||
|
||||
## Checkbox (`_checkbox`)
|
||||
|
||||
Reactive checkbox with label.
|
||||
|
||||
<div id="checkbox-demo"></div>
|
||||
|
||||
|
||||
```javascript
|
||||
const $agreed = $(false)
|
||||
|
||||
_checkbox({
|
||||
label: 'I agree to the terms and conditions',
|
||||
$value: $agreed
|
||||
})
|
||||
```
|
||||
|
||||
## Radio Button (`_radio`)
|
||||
|
||||
Radio buttons for selecting one option from a group.
|
||||
|
||||
<div id="radio-demo"></div>
|
||||
|
||||
```javascript
|
||||
const $payment = $('credit')
|
||||
|
||||
_radio({ name: 'payment', label: 'Credit Card', value: 'credit', $value: $payment })
|
||||
_radio({ name: 'payment', label: 'PayPal', value: 'paypal', $value: $payment })
|
||||
_radio({ name: 'payment', label: 'Crypto', value: 'crypto', $value: $payment })
|
||||
```
|
||||
|
||||
## Range Slider (`_range`)
|
||||
|
||||
Reactive range slider for numeric values.
|
||||
|
||||
<div id="range-demo"></div>
|
||||
|
||||
```javascript
|
||||
const $volume = $(50)
|
||||
|
||||
_range({
|
||||
label: 'Volume',
|
||||
min: 0,
|
||||
max: 100,
|
||||
step: 1,
|
||||
$value: $volume
|
||||
})
|
||||
```
|
||||
|
||||
## Complete Form Example
|
||||
|
||||
<div id="complete-form-demo"></div>
|
||||
|
||||
|
||||
## API Reference
|
||||
|
||||
### `_select`
|
||||
|
||||
| Prop | Type | Description |
|
||||
|------|------|-------------|
|
||||
| `label` | `string` | Field label |
|
||||
| `options` | `Array<{value: any, label: string}>` | Select options |
|
||||
| `$value` | `Signal<any>` | Selected value signal |
|
||||
|
||||
### `_checkbox`
|
||||
|
||||
| Prop | Type | Description |
|
||||
|------|------|-------------|
|
||||
| `label` | `string` | Checkbox label |
|
||||
| `$value` | `Signal<boolean>` | Checked state signal |
|
||||
|
||||
### `_radio`
|
||||
|
||||
| Prop | Type | Description |
|
||||
|------|------|-------------|
|
||||
| `name` | `string` | Radio group name |
|
||||
| `label` | `string` | Radio option label |
|
||||
| `value` | `any` | Value for this option |
|
||||
| `$value` | `Signal<any>` | Group selected value signal |
|
||||
|
||||
### `_range`
|
||||
|
||||
| Prop | Type | Description |
|
||||
|------|------|-------------|
|
||||
| `label` | `string` | Slider label |
|
||||
| `min` | `number` | Minimum value |
|
||||
| `max` | `number` | Maximum value |
|
||||
| `step` | `number` | Step increment |
|
||||
| `$value` | `Signal<number>` | Current value signal |
|
||||
@@ -1,137 +0,0 @@
|
||||
# Input Component
|
||||
|
||||
The `_input` component creates reactive form inputs with built-in support for labels, tooltips, error messages, and two-way binding.
|
||||
|
||||
## Basic Usage
|
||||
|
||||
<div id="basic-input-demo"></div>
|
||||
|
||||
|
||||
```javascript
|
||||
const $name = $('')
|
||||
|
||||
_input({
|
||||
label: 'Name',
|
||||
placeholder: 'Enter your name',
|
||||
$value: $name
|
||||
})
|
||||
```
|
||||
|
||||
## With Tooltip
|
||||
|
||||
The `tip` prop adds an info badge with a tooltip.
|
||||
|
||||
<div id="tooltip-input-demo"></div>
|
||||
|
||||
|
||||
```javascript
|
||||
_input({
|
||||
label: 'Username',
|
||||
tip: 'Choose a unique username (min. 3 characters)',
|
||||
placeholder: 'johndoe123',
|
||||
$value: $username
|
||||
})
|
||||
```
|
||||
|
||||
## With Error Handling
|
||||
|
||||
The `$error` signal displays an error message and styles the input accordingly.
|
||||
|
||||
<div id="error-input-demo"></div>
|
||||
|
||||
```javascript
|
||||
const $email = $('')
|
||||
const $error = $(null)
|
||||
|
||||
const validate = (value) => {
|
||||
if (value && !value.includes('@')) {
|
||||
$error('Please enter a valid email address')
|
||||
} else {
|
||||
$error(null)
|
||||
}
|
||||
}
|
||||
|
||||
_input({
|
||||
label: 'Email',
|
||||
type: 'email',
|
||||
placeholder: 'user@example.com',
|
||||
$value: $email,
|
||||
$error: $error,
|
||||
oninput: (e) => validate(e.target.value)
|
||||
})
|
||||
```
|
||||
|
||||
## Input Types
|
||||
|
||||
The component supports all standard HTML input types.
|
||||
|
||||
<div id="types-input-demo"></div>
|
||||
|
||||
```javascript
|
||||
_input({ label: 'Text', placeholder: 'Text input', $value: $text })
|
||||
_input({ label: 'Password', type: 'password', placeholder: '••••••••', $value: $password })
|
||||
_input({ label: 'Number', type: 'number', placeholder: '0', $value: $number })
|
||||
```
|
||||
|
||||
## Two-Way Binding
|
||||
|
||||
The `$value` prop creates two-way binding between the input and the signal.
|
||||
|
||||
<div id="binding-input-demo"></div>
|
||||
|
||||
```javascript
|
||||
const $message = $('Hello World')
|
||||
|
||||
_input({
|
||||
label: 'Message',
|
||||
$value: $message
|
||||
})
|
||||
|
||||
// The input updates when signal changes, and vice versa
|
||||
_button({ onclick: () => $message('Reset!') }, 'Reset Signal')
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
| Prop | Type | Description |
|
||||
|------|------|-------------|
|
||||
| `label` | `string` | Field label text |
|
||||
| `tip` | `string` | Tooltip text shown on hover |
|
||||
| `$value` | `Signal<any>` | Two-way bound value signal |
|
||||
| `$error` | `Signal<string\|null>` | Error message signal |
|
||||
| `type` | `string` | Input type (text, email, password, number, etc.) |
|
||||
| `placeholder` | `string` | Placeholder text |
|
||||
| `class` | `string \| function` | Additional CSS classes |
|
||||
| `oninput` | `function` | Input event handler |
|
||||
| `onchange` | `function` | Change event handler |
|
||||
| `disabled` | `boolean` | Disabled state |
|
||||
|
||||
## Examples
|
||||
|
||||
### Registration Form Field
|
||||
|
||||
<div id="register-demo"></div>
|
||||
|
||||
```javascript
|
||||
const $username = $('')
|
||||
const $usernameError = $(null)
|
||||
const $email = $('')
|
||||
const $emailError = $(null)
|
||||
|
||||
_input({
|
||||
label: 'Username',
|
||||
placeholder: 'johndoe',
|
||||
$value: $username,
|
||||
$error: $usernameError,
|
||||
oninput: (e) => validateUsername(e.target.value)
|
||||
})
|
||||
|
||||
_input({
|
||||
label: 'Email',
|
||||
type: 'email',
|
||||
placeholder: 'john@example.com',
|
||||
$value: $email,
|
||||
$error: $emailError,
|
||||
oninput: (e) => validateEmail(e.target.value)
|
||||
})
|
||||
```
|
||||
@@ -1,53 +0,0 @@
|
||||
# Installation
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Node.js 18 or higher
|
||||
- A project with SigPro already installed
|
||||
|
||||
## Step 1: Install Dependencies
|
||||
|
||||
```bash
|
||||
npm install -D tailwindcss @tailwindcss/vite daisyui@next
|
||||
```
|
||||
|
||||
## Step 2: Configure Tailwind CSS v4
|
||||
|
||||
Create a CSS file (e.g., `src/app.css`):
|
||||
|
||||
```css
|
||||
@import "tailwindcss";
|
||||
@plugin "daisyui";
|
||||
```
|
||||
|
||||
## Step 3: Import CSS in Your Entry Point
|
||||
|
||||
```javascript
|
||||
// main.js
|
||||
import './app.css';
|
||||
import { $ } from 'sigpro';
|
||||
import { UI } from 'sigpro/plugins';
|
||||
|
||||
$.plugin(UI).then(() => {
|
||||
console.log('✅ UI Components ready');
|
||||
import('./App.js').then(app => $.mount(app.default));
|
||||
});
|
||||
```
|
||||
|
||||
## Step 4: Verify Installation
|
||||
|
||||
<div id="test-install"></div>
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Styles not applying?
|
||||
- Make sure `app.css` is imported before any other code
|
||||
- Check that Tailwind is properly configured in your build tool
|
||||
|
||||
### Components not found?
|
||||
- Ensure `$.plugin(UI)` has completed before using components
|
||||
- Check browser console for any loading errors
|
||||
|
||||
### Reactive updates not working?
|
||||
- Make sure you're passing signals, not primitive values
|
||||
- Use `$value` prop for two-way binding
|
||||
@@ -1,33 +0,0 @@
|
||||
# UI Components
|
||||
|
||||
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, themeable components.
|
||||
|
||||
## Features
|
||||
|
||||
- 🚀 **Fully Reactive**: Every component automatically updates with signals
|
||||
- 🎨 **Themeable**: Supports all daisyUI themes out of the box
|
||||
- 📱 **Responsive**: Designed to work on all devices
|
||||
- 🔧 **Zero Dependencies**: Pure SigPro with no framework overhead
|
||||
|
||||
## Quick Demo
|
||||
|
||||
<div id="quick-demo"></div>
|
||||
|
||||
## What's Included
|
||||
|
||||
The UI plugin provides a comprehensive set of reactive components:
|
||||
|
||||
| Category | Components |
|
||||
|----------|------------|
|
||||
| **Actions** | `_button` |
|
||||
| **Forms** | `_input`, `_select`, `_checkbox`, `_radio`, `_range` |
|
||||
| **Layout** | `_fieldset`, `_accordion`, `_drawer` |
|
||||
| **Navigation** | `_navbar`, `_menu`, `_tabs` |
|
||||
| **Overlays** | `_modal`, `_dropdown` |
|
||||
| **Feedback** | `_badge`, `_tooltip` |
|
||||
|
||||
## Next Steps
|
||||
|
||||
- [Installation Guide](/ui/installation) - Set up Tailwind and daisyUI
|
||||
- [Button Component](/ui/button) - Explore the button component
|
||||
- [Form Components](/ui/form) - Build reactive forms with validation
|
||||
@@ -1,117 +0,0 @@
|
||||
# Layout Components
|
||||
|
||||
Layout components for structuring your application with containers, sections, and collapsible panels.
|
||||
|
||||
## Fieldset (`_fieldset`)
|
||||
|
||||
Groups related form fields with a legend.
|
||||
|
||||
<div id="fieldset-demo"></div>
|
||||
|
||||
|
||||
```javascript
|
||||
_fieldset({ legend: 'Personal Information' }, [
|
||||
_input({ label: 'Full Name', $value: $name }),
|
||||
_input({ label: 'Email Address', type: 'email', $value: $email }),
|
||||
_select({ label: 'Role', options: [...], $value: $role })
|
||||
])
|
||||
```
|
||||
|
||||
## Accordion (`_accordion`)
|
||||
|
||||
Collapsible content panels. Can be used as standalone or grouped.
|
||||
|
||||
### Single Accordion
|
||||
|
||||
<div id="single-accordion-demo"></div>
|
||||
|
||||
```javascript
|
||||
_accordion({ title: 'What is SigPro UI?' }, [
|
||||
$.html('p', {}, 'SigPro UI is a reactive component library...')
|
||||
])
|
||||
```
|
||||
|
||||
### Grouped Accordions (Radio Behavior)
|
||||
|
||||
When multiple accordions share the same `name`, only one can be open at a time.
|
||||
|
||||
<div id="grouped-accordion-demo"></div>
|
||||
|
||||
```javascript
|
||||
// Grouped accordions - only one open at a time
|
||||
_accordion({ title: 'Getting Started', name: 'faq' }, content1)
|
||||
_accordion({ title: 'Installation', name: 'faq' }, content2)
|
||||
_accordion({ title: 'Customization', name: 'faq' }, content3)
|
||||
```
|
||||
|
||||
### Accordion with Open State
|
||||
|
||||
Control the initial open state with the `open` prop.
|
||||
|
||||
<div id="open-accordion-demo"></div>
|
||||
|
||||
```javascript
|
||||
_accordion({ title: 'Open by Default', open: true }, [
|
||||
$.html('p', {}, 'This accordion starts open.')
|
||||
])
|
||||
```
|
||||
|
||||
## Complete Layout Example
|
||||
|
||||
<div id="complete-layout-demo"></div>
|
||||
|
||||
## API Reference
|
||||
|
||||
### `_fieldset`
|
||||
|
||||
| Prop | Type | Description |
|
||||
|------|------|-------------|
|
||||
| `legend` | `string` | Fieldset title/legend text |
|
||||
| `class` | `string \| function` | Additional CSS classes |
|
||||
|
||||
### `_accordion`
|
||||
|
||||
| Prop | Type | Description |
|
||||
|------|------|-------------|
|
||||
| `title` | `string` | Accordion header text |
|
||||
| `name` | `string` | Group name for radio behavior (optional) |
|
||||
| `open` | `boolean` | Initially open state (default: false) |
|
||||
|
||||
## Styling Tips
|
||||
|
||||
### Custom Fieldset Styling
|
||||
|
||||
```javascript
|
||||
_fieldset({
|
||||
legend: 'Custom Styled',
|
||||
class: 'bg-primary/10 border-primary'
|
||||
}, [
|
||||
// content
|
||||
])
|
||||
```
|
||||
|
||||
### Custom Accordion Styling
|
||||
|
||||
```javascript
|
||||
_accordion({
|
||||
title: 'Styled Accordion',
|
||||
class: 'bg-base-200'
|
||||
}, [
|
||||
// content
|
||||
])
|
||||
```
|
||||
|
||||
### Nested Layouts
|
||||
|
||||
Layout components can be nested to create complex structures:
|
||||
|
||||
```javascript
|
||||
_fieldset({ legend: 'Main Section' }, [
|
||||
_accordion({ title: 'Subsection 1' }, [
|
||||
_input({ label: 'Field 1', $value: $field1 })
|
||||
]),
|
||||
_accordion({ title: 'Subsection 2' }, [
|
||||
_input({ label: 'Field 2', $value: $field2 })
|
||||
])
|
||||
])
|
||||
```
|
||||
@@ -1,90 +0,0 @@
|
||||
# Modal & Drawer Components
|
||||
|
||||
Overlay components for dialogs, side panels, and popups with reactive control.
|
||||
|
||||
## Modal (`_modal`)
|
||||
|
||||
A dialog component that appears on top of the page. The modal is completely removed from the DOM when closed, optimizing performance.
|
||||
|
||||
### Basic Modal
|
||||
|
||||
<div id="basic-modal-demo"></div>
|
||||
|
||||
```javascript
|
||||
const $open = $(false)
|
||||
|
||||
_button({ onclick: () => $open(true) }, 'Open Modal')
|
||||
|
||||
_modal({ $open: $open, title: 'Welcome' }, [
|
||||
$.html('p', {}, 'This is a simple modal dialog.'),
|
||||
_button({ onclick: () => $open(false) }, 'Close')
|
||||
])
|
||||
```
|
||||
|
||||
### Modal with Actions
|
||||
|
||||
<div id="action-modal-demo"></div>
|
||||
|
||||
|
||||
```javascript
|
||||
const $open = $(false)
|
||||
const $result = $(null)
|
||||
|
||||
_modal({ $open: $open, title: 'Confirm Delete' }, [
|
||||
$.html('p', {}, 'Are you sure you want to delete this item?'),
|
||||
_button({ class: 'btn-error', onclick: () => {
|
||||
$result('Item deleted')
|
||||
$open(false)
|
||||
} }, 'Delete')
|
||||
])
|
||||
```
|
||||
|
||||
### Modal with Form
|
||||
|
||||
<div id="form-modal-demo"></div>
|
||||
|
||||
## Drawer (`_drawer`)
|
||||
|
||||
A sidebar panel that slides in from the side.
|
||||
|
||||
### Basic Drawer
|
||||
|
||||
<div id="basic-drawer-demo"></div>
|
||||
|
||||
```javascript
|
||||
const $open = $(false)
|
||||
|
||||
_drawer({
|
||||
id: 'my-drawer',
|
||||
$open: $open,
|
||||
content: $.html('div', {}, 'Main content'),
|
||||
side: $.html('div', { class: 'p-4' }, [
|
||||
$.html('h3', {}, 'Menu'),
|
||||
$.html('ul', { class: 'menu' }, [
|
||||
$.html('li', {}, [$.html('a', { onclick: () => $open(false) }, 'Close')])
|
||||
])
|
||||
])
|
||||
})
|
||||
```
|
||||
|
||||
### Drawer with Navigation Menu
|
||||
|
||||
<div id="nav-drawer-demo"></div>
|
||||
|
||||
## API Reference
|
||||
|
||||
### `_modal`
|
||||
|
||||
| Prop | Type | Description |
|
||||
|------|------|-------------|
|
||||
| `$open` | `Signal<boolean>` | Controls modal visibility |
|
||||
| `title` | `string` | Modal title text |
|
||||
|
||||
### `_drawer`
|
||||
|
||||
| Prop | Type | Description |
|
||||
|------|------|-------------|
|
||||
| `id` | `string` | Unique identifier for the drawer |
|
||||
| `$open` | `Signal<boolean>` | Controls drawer visibility |
|
||||
| `content` | `HTMLElement` | Main content area |
|
||||
| `side` | `HTMLElement` | Sidebar content |
|
||||
@@ -1,124 +0,0 @@
|
||||
# Navigation Components
|
||||
|
||||
Navigation components for building menus, navbars, and tabs with reactive active states.
|
||||
|
||||
## Navbar (`_navbar`)
|
||||
|
||||
A responsive navigation bar with built-in styling.
|
||||
|
||||
<div id="navbar-demo"></div>
|
||||
|
||||
```javascript
|
||||
const $active = $('Home')
|
||||
|
||||
_navbar({ class: 'shadow-md' }, [
|
||||
div({ class: 'flex-1' }, [
|
||||
a({ class: 'text-xl font-bold' }, 'MyApp')
|
||||
]),
|
||||
div({ class: 'flex-none gap-2' }, [
|
||||
_button({
|
||||
class: () => $active() === 'Home' ? 'btn-primary btn-sm' : 'btn-ghost btn-sm',
|
||||
onclick: () => $active('Home')
|
||||
}, 'Home'),
|
||||
_button({
|
||||
class: () => $active() === 'About' ? 'btn-primary btn-sm' : 'btn-ghost btn-sm',
|
||||
onclick: () => $active('About')
|
||||
}, 'About')
|
||||
])
|
||||
])
|
||||
```
|
||||
|
||||
## Menu (`_menu`)
|
||||
|
||||
Vertical navigation menu with active state highlighting.
|
||||
|
||||
<div id="menu-demo"></div>
|
||||
|
||||
```javascript
|
||||
const $selected = $('dashboard')
|
||||
|
||||
_menu({ items: [
|
||||
{
|
||||
label: 'Dashboard',
|
||||
icon: '📊',
|
||||
active: () => $selected() === 'dashboard',
|
||||
onclick: () => $selected('dashboard')
|
||||
},
|
||||
{
|
||||
label: 'Analytics',
|
||||
icon: '📈',
|
||||
active: () => $selected() === 'analytics',
|
||||
onclick: () => $selected('analytics')
|
||||
}
|
||||
]})
|
||||
```
|
||||
|
||||
## Tabs (`_tabs`)
|
||||
|
||||
Horizontal tabs with lifted styling and active state.
|
||||
|
||||
<div id="tabs-demo"></div>
|
||||
|
||||
```javascript
|
||||
const $activeTab = $('profile')
|
||||
|
||||
_tabs({ items: [
|
||||
{
|
||||
label: 'Profile',
|
||||
active: () => $activeTab() === 'profile',
|
||||
onclick: () => $activeTab('profile')
|
||||
},
|
||||
{
|
||||
label: 'Settings',
|
||||
active: () => $activeTab() === 'settings',
|
||||
onclick: () => $activeTab('settings')
|
||||
}
|
||||
]})
|
||||
```
|
||||
|
||||
## Dropdown (`_dropdown`)
|
||||
|
||||
A dropdown menu that appears on click.
|
||||
|
||||
<div id="dropdown-demo"></div>
|
||||
|
||||
```javascript
|
||||
const $selected = $(null)
|
||||
|
||||
_dropdown({ label: 'Options' }, [
|
||||
li([a({ onclick: () => $selected('Edit') }, '✏️ Edit')]),
|
||||
li([a({ onclick: () => $selected('Duplicate') }, '📋 Duplicate')]),
|
||||
li([a({ onclick: () => $selected('Delete') }, '🗑️ Delete')])
|
||||
])
|
||||
```
|
||||
|
||||
## Complete Navigation Example
|
||||
|
||||
<div id="complete-nav-demo"></div>
|
||||
|
||||
## API Reference
|
||||
|
||||
### `_navbar`
|
||||
|
||||
| Prop | Type | Description |
|
||||
|------|------|-------------|
|
||||
| `class` | `string \| function` | Additional CSS classes |
|
||||
|
||||
### `_menu`
|
||||
|
||||
| Prop | Type | Description |
|
||||
|------|------|-------------|
|
||||
| `items` | `Array<{label: string, icon?: any, active?: boolean\|function, onclick: function}>` | Menu items |
|
||||
|
||||
### `_tabs`
|
||||
|
||||
| Prop | Type | Description |
|
||||
|------|------|-------------|
|
||||
| `items` | `Array<{label: string, active: boolean\|function, onclick: function}>` | Tab items |
|
||||
|
||||
### `_dropdown`
|
||||
|
||||
| Prop | Type | Description |
|
||||
|------|------|-------------|
|
||||
| `label` | `string` | Dropdown trigger text |
|
||||
| `class` | `string \| function` | Additional CSS classes |
|
||||
118
src/docs/ui/quick.md
Normal file
118
src/docs/ui/quick.md
Normal file
@@ -0,0 +1,118 @@
|
||||
# 🧩 UI Components `(WIP)`
|
||||
|
||||
> **Status: Work In Progress.** > These are high-level, complex visual components designed to speed up development. They often replace native HTML elements with "superpowered" versions that handle their own internal logic, reactivity, and accessibility.
|
||||
|
||||
## 1. What are UI Components?
|
||||
|
||||
Unlike **Tag Helpers** (which are just functional mirrors of HTML tags), SigPro UI Components are smart abstractions.
|
||||
|
||||
* **Stateful**: They manage complex internal states (like date ranges, search filtering, or API lifecycles).
|
||||
* **Reactive**: Attributes prefixed with `$` are automatically tracked.
|
||||
* **Self-Cleaning**: They automatically use `_cleanups` to destroy observers or event listeners when removed from the DOM.
|
||||
* **Themed**: Fully compatible with Tailwind CSS and DaisyUI themes.
|
||||
|
||||
---
|
||||
|
||||
## 2. The UI Registry (Available Now)
|
||||
|
||||
| Category | Components |
|
||||
| :--- | :--- |
|
||||
| **Logic & Flow** | `If`, `For`, `Json` |
|
||||
| **Forms & Inputs** | `Button`, `Input`, `Select`, `Autocomplete`, `Datepicker`, `Colorpicker`, `CheckBox`, `Radio`, `Range`, `Rating`, `Swap` |
|
||||
| **Feedback** | `Alert`, `Toast`, `Modal`, `Loading`, `Badge`, `Tooltip`, `Indicator` |
|
||||
| **Navigation** | `Navbar`, `Menu`, `Drawer`, `Tabs`, `Accordion`, `Dropdown` |
|
||||
| **Data & Layout** | `Request`, `Response`, `Grid` (AG-Grid), `List`, `Stack`, `Timeline`, `Stat`, `Fieldset`, `Fab` |
|
||||
|
||||
---
|
||||
|
||||
## 3. Examples with "Superpowers"
|
||||
|
||||
### A. The Declarative API Flow (`Request` & `Response`)
|
||||
Instead of manually managing `loading` and `error` flags, use these two together to handle data fetching elegantly.
|
||||
|
||||
```javascript
|
||||
// 1. Define the request (it tracks dependencies automatically)
|
||||
const userProfile = Request(
|
||||
() => `https://api.example.com/user/${userId()}`
|
||||
);
|
||||
|
||||
// 2. Render the UI based on the request state
|
||||
Div({ class: "p-4" }, [
|
||||
Response(userProfile, (data) =>
|
||||
Div([
|
||||
H1(data.name),
|
||||
P(data.email)
|
||||
])
|
||||
)
|
||||
]);
|
||||
```
|
||||
|
||||
### B. Smart Inputs & Autocomplete
|
||||
Native inputs are boring. SigPro UI inputs handle labels, icons, password toggles, and validation states out of the box.
|
||||
|
||||
```javascript
|
||||
const searchQuery = $("");
|
||||
|
||||
Autocomplete({
|
||||
label: "Find a Country",
|
||||
placeholder: "Start typing...",
|
||||
options: ["Spain", "France", "Germany", "Italy", "Portugal"],
|
||||
$value: searchQuery,
|
||||
onSelect: (val) => console.log("Selected:", val)
|
||||
});
|
||||
```
|
||||
|
||||
### C. The Reactive Datepicker
|
||||
Handles single dates or ranges with a clean, reactive interface.
|
||||
|
||||
```javascript
|
||||
const myDate = $(""); // or { start: "", end: "" } for range
|
||||
|
||||
Datepicker({
|
||||
label: "Select Expiry Date",
|
||||
$value: myDate,
|
||||
range: false // Set to true for range selection
|
||||
});
|
||||
```
|
||||
|
||||
### D. Imperative Toasts & Modals
|
||||
Sometimes you just need to trigger a message without cluttering your template.
|
||||
|
||||
```javascript
|
||||
// Show a notification from anywhere in your logic
|
||||
Toast("Settings saved successfully!", "alert-success", 3000);
|
||||
|
||||
// Control a modal with a simple signal
|
||||
const isModalOpen = $(false);
|
||||
|
||||
Modal({
|
||||
$open: isModalOpen,
|
||||
title: "Delete Account",
|
||||
buttons: [
|
||||
Button({ class: "btn-error", onclick: doDelete }, "Confirm")
|
||||
]
|
||||
}, "This action cannot be undone.");
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Internationalization (i18n)
|
||||
|
||||
The UI library comes with a built-in locale system. It currently supports `es` and `en`.
|
||||
|
||||
```javascript
|
||||
// Set the global UI language
|
||||
SetLocale("en");
|
||||
|
||||
// Access translated strings in your own components
|
||||
const t = tt("confirm"); // Returns a signal that tracks the current locale
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. Best Practices
|
||||
|
||||
* **Use `$` for Reactivity**: If a property starts with `$`, it expects a Signal (e.g., `$value: mySignal`).
|
||||
* **Key your Lists**: When using `For`, always provide a `keyFn` to ensure high-performance DOM reconciliation.
|
||||
* **Cleanups**: If you build custom components that use `setInterval` or `observers`, add them to the element's `_cleanups` Set.
|
||||
|
||||
Reference in New Issue
Block a user