Update Funcions
This commit is contained in:
@@ -81,7 +81,7 @@ const Counter = () => {
|
||||
]);
|
||||
};
|
||||
|
||||
$mount(Counter, "#app");
|
||||
Mount(Counter, "#app");
|
||||
```
|
||||
|
||||
-----
|
||||
@@ -96,7 +96,7 @@ $mount(Counter, "#app");
|
||||
| **Native Persistence** | **Included ($)** | Requires Plugins | Manual |
|
||||
| **Dependencies** | **Zero** | Many | Build Toolchain |
|
||||
| **Lifecycle Mgmt** | **Automatic (Cleanup Root)** | Manual / Hook-based | Manual / Hook-based |
|
||||
| **Routing** | **Reactive Hash ($router) + File-based (Vite)** | Virtual Router (External) | File-based / External |
|
||||
| **Routing** | **Reactive Hash (Router) + File-based (Vite)** | Virtual Router (External) | File-based / External |
|
||||
| **Learning Curve** | **Zero (Vanilla JS)** | Steep (JSX/Templates) | Medium (Directives) |
|
||||
|
||||
-----
|
||||
|
||||
18
dist/sigpro.esm.js
vendored
18
dist/sigpro.esm.js
vendored
@@ -337,15 +337,19 @@ var For = (source, renderFn, keyFn, tag = "div", props = { style: "display:conte
|
||||
for (let i = 0;i < items.length; i++) {
|
||||
const item = items[i];
|
||||
const key = keyFn ? keyFn(item, i) : i;
|
||||
let view = viewCache.get(key) || Render(() => renderFn(item, i));
|
||||
let view = viewCache.get(key);
|
||||
if (!view) {
|
||||
const result = renderFn(item, i);
|
||||
view = result instanceof Node ? { container: result, destroy: () => {
|
||||
cleanupNode(result);
|
||||
result.remove();
|
||||
} } : Render(() => result);
|
||||
}
|
||||
viewCache.delete(key);
|
||||
nextCache.set(key, view);
|
||||
order.push(key);
|
||||
}
|
||||
viewCache.forEach((view) => {
|
||||
view.destroy();
|
||||
view.container.remove();
|
||||
});
|
||||
viewCache.forEach((v) => v.destroy());
|
||||
let anchor = marker;
|
||||
for (let i = order.length - 1;i >= 0; i--) {
|
||||
const view = nextCache.get(order[i]);
|
||||
@@ -411,8 +415,7 @@ var Mount = (component, target) => {
|
||||
MOUNTED_NODES.set(targetEl, instance);
|
||||
return instance;
|
||||
};
|
||||
var Fragment = ({ children }) => children;
|
||||
var SigPro = { $, $$, Render, Watch, Tag, If, For, Router, Mount, Fragment };
|
||||
var SigPro = { $, $$, Render, Watch, Tag, If, For, Router, Mount };
|
||||
if (typeof window !== "undefined") {
|
||||
assign(window, SigPro);
|
||||
const tags = `div span p h1 h2 h3 h4 h5 h6 br hr section article aside nav main header footer address ul ol li dl dt dd a em strong small i b u mark time sub sup pre code blockquote details summary dialog form label input textarea select button option fieldset legend table thead tbody tfoot tr th td caption img video audio canvas svg iframe picture source progress meter`.split(" ");
|
||||
@@ -430,7 +433,6 @@ export {
|
||||
Render,
|
||||
Mount,
|
||||
If,
|
||||
Fragment,
|
||||
For,
|
||||
$$,
|
||||
$
|
||||
|
||||
2
dist/sigpro.esm.min.js
vendored
2
dist/sigpro.esm.min.js
vendored
File diff suppressed because one or more lines are too long
18
dist/sigpro.js
vendored
18
dist/sigpro.js
vendored
@@ -36,7 +36,6 @@
|
||||
Render: () => Render,
|
||||
Mount: () => Mount,
|
||||
If: () => If,
|
||||
Fragment: () => Fragment,
|
||||
For: () => For,
|
||||
$$: () => $$,
|
||||
$: () => $
|
||||
@@ -381,15 +380,19 @@
|
||||
for (let i = 0;i < items.length; i++) {
|
||||
const item = items[i];
|
||||
const key = keyFn ? keyFn(item, i) : i;
|
||||
let view = viewCache.get(key) || Render(() => renderFn(item, i));
|
||||
let view = viewCache.get(key);
|
||||
if (!view) {
|
||||
const result = renderFn(item, i);
|
||||
view = result instanceof Node ? { container: result, destroy: () => {
|
||||
cleanupNode(result);
|
||||
result.remove();
|
||||
} } : Render(() => result);
|
||||
}
|
||||
viewCache.delete(key);
|
||||
nextCache.set(key, view);
|
||||
order.push(key);
|
||||
}
|
||||
viewCache.forEach((view) => {
|
||||
view.destroy();
|
||||
view.container.remove();
|
||||
});
|
||||
viewCache.forEach((v) => v.destroy());
|
||||
let anchor = marker;
|
||||
for (let i = order.length - 1;i >= 0; i--) {
|
||||
const view = nextCache.get(order[i]);
|
||||
@@ -455,8 +458,7 @@
|
||||
MOUNTED_NODES.set(targetEl, instance);
|
||||
return instance;
|
||||
};
|
||||
var Fragment = ({ children }) => children;
|
||||
var SigPro = { $, $$, Render, Watch, Tag, If, For, Router, Mount, Fragment };
|
||||
var SigPro = { $, $$, Render, Watch, Tag, If, For, Router, Mount };
|
||||
if (typeof window !== "undefined") {
|
||||
assign(window, SigPro);
|
||||
const tags = `div span p h1 h2 h3 h4 h5 h6 br hr section article aside nav main header footer address ul ol li dl dt dd a em strong small i b u mark time sub sup pre code blockquote details summary dialog form label input textarea select button option fieldset legend table thead tbody tfoot tr th td caption img video audio canvas svg iframe picture source progress meter`.split(" ");
|
||||
|
||||
2
dist/sigpro.min.js
vendored
2
dist/sigpro.min.js
vendored
File diff suppressed because one or more lines are too long
@@ -7,13 +7,13 @@
|
||||
|
||||
* **API Reference**
|
||||
* [Quick Start](api/quick.md)
|
||||
* [$ signals](api/signal.md)
|
||||
* [$watch](api/watch.md)
|
||||
* [$if](api/if.md)
|
||||
* [$for](api/for.md)
|
||||
* [$router](api/router.md)
|
||||
* [$mount](api/mount.md)
|
||||
* [$html](api/html.md)
|
||||
* [Signals & Proxies](api/signal.md)
|
||||
* [Watch](api/watch.md)
|
||||
* [If](api/if.md)
|
||||
* [For](api/for.md)
|
||||
* [Router](api/router.md)
|
||||
* [Mount](api/mount.md)
|
||||
* [Tag](api/html.md)
|
||||
* [Tags](api/tags.md)
|
||||
* [Global Store](api/global.md)
|
||||
* [JSX Style](api/jsx.md)
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
# Reactive Lists: `$for( )`
|
||||
# Reactive Lists: `For( )`
|
||||
|
||||
The `$for` function is a high-performance list renderer. It maps an array (or a Signal containing an array) to DOM nodes. Unlike a simple `.map()`, `$for` is **keyed**, meaning it only updates, moves, or deletes the specific items that changed.
|
||||
The `For` function is a high-performance list renderer. It maps an array (or a Signal containing an array) to DOM nodes. Unlike a simple `.map()`, `For` is **keyed**, meaning it only updates, moves, or deletes the specific items that changed.
|
||||
|
||||
## Function Signature
|
||||
|
||||
```typescript
|
||||
$for(
|
||||
For(
|
||||
source: Signal<any[]> | Function | any[],
|
||||
render: (item: any, index: number) => HTMLElement,
|
||||
keyFn?: (item: any, index: number) => string | number
|
||||
@@ -34,7 +34,7 @@ const users = $([
|
||||
]);
|
||||
|
||||
Ul({ class: "list" }, [
|
||||
$for(users,
|
||||
For(users,
|
||||
(user) => Li({ class: "p-2" }, user.name),
|
||||
(user) => user.id // Stable and unique key
|
||||
)
|
||||
@@ -42,14 +42,14 @@ Ul({ class: "list" }, [
|
||||
```
|
||||
|
||||
### 2. Simplified Usage (Automatic Key)
|
||||
If you omit the third parameter, `$for` will automatically use the array index as the key. This is ideal for simple lists that don't change order frequently.
|
||||
If you omit the third parameter, `For` will automatically use the array index as the key. This is ideal for simple lists that don't change order frequently.
|
||||
|
||||
```javascript
|
||||
const tags = $(["Tech", "JS", "Web"]);
|
||||
|
||||
// No need to pass keyFn if the index is sufficient
|
||||
Div({ class: "flex gap-1" }, [
|
||||
$for(tags, (tag) => Badge(tag))
|
||||
For(tags, (tag) => Badge(tag))
|
||||
]);
|
||||
```
|
||||
|
||||
@@ -57,7 +57,7 @@ Div({ class: "flex gap-1" }, [
|
||||
|
||||
## How it Works (The Reconciliation)
|
||||
|
||||
When the `source` signal changes, `$for` performs the following steps:
|
||||
When the `source` signal changes, `For` performs the following steps:
|
||||
|
||||
1. **Key Diffing**: It compares the new keys with the previous ones stored in an internal `Map`.
|
||||
2. **Node Reuse**: If a key already exists, the DOM node is **reused** and moved to its new position. No new elements are created.
|
||||
@@ -68,13 +68,13 @@ When the `source` signal changes, `$for` performs the following steps:
|
||||
## Performance Tips
|
||||
|
||||
* **Stable Keys**: Never use `Math.random()` as a key. This will force SigPro to destroy and recreate the entire list on every update, killing performance.
|
||||
* **State Preservation**: If your list items have internal state (like an input with text), `$for` ensures that state is preserved even if the list is reordered, as long as the key (`id`) remains the same.
|
||||
* **State Preservation**: If your list items have internal state (like an input with text), `For` ensures that state is preserved even if the list is reordered, as long as the key (`id`) remains the same.
|
||||
|
||||
---
|
||||
|
||||
## Summary Comparison
|
||||
|
||||
| Feature | Standard `Array.map` | SigPro `$for` |
|
||||
| Feature | Standard `Array.map` | SigPro `For` |
|
||||
| :--- | :--- | :--- |
|
||||
| **Re-render** | Re-renders everything | Only updates changes |
|
||||
| **DOM Nodes** | Re-created every time | **Reused via Keys** |
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
# The DOM Factory: `$html( )`
|
||||
# The DOM Factory: `Tag( )`
|
||||
|
||||
`$html` is the internal engine that creates, attributes, and attaches reactivity to DOM elements. It uses `$watch` to maintain a live, high-performance link between your Signals and the Document Object Model.
|
||||
`Tag` is the internal engine that creates, attributes, and attaches reactivity to DOM elements. It uses `Watch` to maintain a live, high-performance link between your Signals and the Document Object Model.
|
||||
|
||||
## Function Signature
|
||||
|
||||
```typescript
|
||||
$html(tagName: string, props?: Object, children?: any[] | any): HTMLElement
|
||||
Tag(tagName: string, props?: Object, children?: any[] | any): HTMLElement
|
||||
```
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
@@ -50,7 +50,7 @@ Button({
|
||||
```
|
||||
|
||||
### 4. Reactive Attributes (One-Way)
|
||||
If an attribute value is a **function** (like a Signal), `$html` creates an internal **`$watch`** to keep the DOM in sync with the state.
|
||||
If an attribute value is a **function** (like a Signal), `Tag` creates an internal **`Watch`** to keep the DOM in sync with the state.
|
||||
|
||||
```javascript
|
||||
Div({
|
||||
@@ -72,7 +72,7 @@ Input({
|
||||
> **Note:** To use a Signal as **read-only** in an input, wrap it in an anonymous function: `value: () => username()`.
|
||||
|
||||
### 6. Reactive Children
|
||||
Children can be static or dynamic. When a child is a function, SigPro creates a reactive boundary using `$watch` for that specific part of the DOM.
|
||||
Children can be static or dynamic. When a child is a function, SigPro creates a reactive boundary using `Watch` for that specific part of the DOM.
|
||||
|
||||
```javascript
|
||||
Div({}, [
|
||||
@@ -85,20 +85,20 @@ Div({}, [
|
||||
---
|
||||
|
||||
## Memory Management (Internal)
|
||||
Every element created with `$html` is "self-aware" regarding its reactive dependencies.
|
||||
* **`._cleanups`**: A hidden `Set` attached to the element that stores all `stop()` functions from its internal `$watch` calls and event listeners.
|
||||
* **Lifecycle**: When an element is removed by a Controller (`$if`, `$for`, or `$router`), SigPro performs a recursive **"sweep"** to execute these cleanups, ensuring **zero memory leaks**.
|
||||
Every element created with `Tag` is "self-aware" regarding its reactive dependencies.
|
||||
* **`._cleanups`**: A hidden `Set` attached to the element that stores all `stop()` functions from its internal `Watch` calls and event listeners.
|
||||
* **Lifecycle**: When an element is removed by a Controller (`If`, `For`, or `Router`), SigPro performs a recursive **"sweep"** to execute these cleanups, ensuring **zero memory leaks**.
|
||||
|
||||
---
|
||||
|
||||
## Tag Constructors (The Shortcuts)
|
||||
|
||||
Instead of writing `$html("div", ...)` every time, SigPro provides PascalCase global functions for all standard HTML tags. These are direct mappings to the `$html` factory.
|
||||
Instead of writing `Tag("div", ...)` every time, SigPro provides PascalCase global functions for all standard HTML tags. These are direct mappings to the `Tag` factory.
|
||||
|
||||
```javascript
|
||||
// This:
|
||||
Div({ class: "wrapper" }, [ Span("Hello") ])
|
||||
|
||||
// Is exactly equivalent to:
|
||||
$html("div", { class: "wrapper" }, [ $html("span", {}, "Hello") ])
|
||||
Tag("div", { class: "wrapper" }, [ Tag("span", {}, "Hello") ])
|
||||
```
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
# Reactive Branching: `$if( )`
|
||||
# Reactive Branching: `If( )`
|
||||
|
||||
The `$if` function is a reactive control flow operator. It manages the conditional rendering of components with optional smooth transitions, ensuring that only the active branch exists in the DOM and in memory.
|
||||
The `If` function is a reactive control flow operator. It manages the conditional rendering of components with optional smooth transitions, ensuring that only the active branch exists in the DOM and in memory.
|
||||
|
||||
## Function Signature
|
||||
|
||||
```typescript
|
||||
$if(
|
||||
If(
|
||||
condition: Signal<boolean> | Function,
|
||||
thenVal: Component | Node,
|
||||
otherwiseVal?: Component | Node,
|
||||
@@ -53,7 +53,7 @@ const fade = {
|
||||
}
|
||||
};
|
||||
|
||||
$if(show, Modal, null, fade);
|
||||
If(show, Modal, null, fade);
|
||||
```
|
||||
|
||||
---
|
||||
@@ -68,7 +68,7 @@ const isVisible = $(false);
|
||||
Div([
|
||||
Button({ onclick: () => isVisible(!isVisible()) }, "Toggle Message"),
|
||||
|
||||
$if(isVisible,
|
||||
If(isVisible,
|
||||
P("Now you see me!"),
|
||||
P("Now you don't...")
|
||||
)
|
||||
@@ -83,7 +83,7 @@ const showModal = $(false);
|
||||
Div([
|
||||
Button({ onclick: () => showModal(true) }, "Open Modal"),
|
||||
|
||||
$if(showModal,
|
||||
If(showModal,
|
||||
() => Modal({ onClose: () => showModal(false) }),
|
||||
null,
|
||||
fade // ← Smooth enter/exit animation
|
||||
@@ -93,10 +93,10 @@ Div([
|
||||
|
||||
### 3. Lazy Component Loading
|
||||
|
||||
Unlike CSS `display: none`, `$if` is **lazy**. The inactive branch is never created, saving memory.
|
||||
Unlike CSS `display: none`, `If` is **lazy**. The inactive branch is never created, saving memory.
|
||||
|
||||
```javascript
|
||||
$if(() => user.isLogged(),
|
||||
If(() => user.isLogged(),
|
||||
() => Dashboard(), // Only executed if logged in
|
||||
() => LoginGate() // Only executed if guest
|
||||
)
|
||||
@@ -105,15 +105,15 @@ $if(() => user.isLogged(),
|
||||
### 4. Complex Conditions
|
||||
|
||||
```javascript
|
||||
$if(() => count() > 10 && status() === 'ready',
|
||||
If(() => count() > 10 && status() === 'ready',
|
||||
Span("Threshold reached!")
|
||||
)
|
||||
```
|
||||
|
||||
### 5. $if.not Helper
|
||||
### 5. If.not Helper
|
||||
|
||||
```javascript
|
||||
$if.not(loading,
|
||||
If.not(loading,
|
||||
() => Content(), // Shows when loading is FALSE
|
||||
() => Spinner() // Shows when loading is TRUE
|
||||
)
|
||||
@@ -123,10 +123,10 @@ $if.not(loading,
|
||||
|
||||
## Automatic Cleanup
|
||||
|
||||
One of the core strengths of `$if` is its integrated **Cleanup** logic. SigPro ensures that when a branch is swapped out, it is completely purged.
|
||||
One of the core strengths of `If` is its integrated **Cleanup** logic. SigPro ensures that when a branch is swapped out, it is completely purged.
|
||||
|
||||
1. **Stop Watchers**: All `$watch` calls inside the inactive branch are permanently stopped.
|
||||
2. **Unbind Events**: Event listeners attached via `$html` are removed.
|
||||
1. **Stop Watchers**: All `Watch` calls inside the inactive branch are permanently stopped.
|
||||
2. **Unbind Events**: Event listeners attached via `Tag` are removed.
|
||||
3. **Recursive Sweep**: SigPro performs a deep "sweep" of the removed branch.
|
||||
4. **Transition Respect**: When using transitions, destruction only happens AFTER the `out` animation completes.
|
||||
|
||||
@@ -142,7 +142,7 @@ One of the core strengths of `$if` is its integrated **Cleanup** logic. SigPro e
|
||||
|
||||
## Technical Comparison
|
||||
|
||||
| Feature | Standard CSS `hidden` | SigPro `$if` |
|
||||
| Feature | Standard CSS `hidden` | SigPro `If` |
|
||||
| :--- | :--- | :--- |
|
||||
| **DOM Presence** | Always present | Only if active |
|
||||
| **Reactivity** | Still processing | **Paused/Destroyed** |
|
||||
|
||||
@@ -11,7 +11,7 @@ SigPro works seamlessly with JSX.
|
||||
{
|
||||
"compilerOptions": {
|
||||
"jsx": "react",
|
||||
"jsxFactory": "$html",
|
||||
"jsxFactory": "Tag",
|
||||
"jsxFragmentFactory": "Fragment"
|
||||
}
|
||||
}
|
||||
@@ -25,7 +25,7 @@ import { defineConfig } from 'vite'
|
||||
|
||||
export default defineConfig({
|
||||
esbuild: {
|
||||
jsxFactory: '$html',
|
||||
jsxFactory: 'Tag',
|
||||
jsxFragmentFactory: 'Fragment'
|
||||
}
|
||||
})
|
||||
@@ -38,7 +38,7 @@ export default defineConfig({
|
||||
export default {
|
||||
plugins: [
|
||||
['@babel/plugin-transform-react-jsx', {
|
||||
pragma: '$html',
|
||||
pragma: 'Tag',
|
||||
pragmaFrag: 'Fragment'
|
||||
}]
|
||||
]
|
||||
@@ -49,7 +49,7 @@ export default {
|
||||
|
||||
```jsx
|
||||
// App.jsx
|
||||
import { $, $mount, Fragment } from 'sigpro';
|
||||
import { $, Mount, Fragment } from 'sigpro';
|
||||
|
||||
const Button = ({ onClick, children }) => (
|
||||
<button class="btn btn-primary" onclick={onClick}>
|
||||
@@ -76,7 +76,7 @@ const App = () => {
|
||||
);
|
||||
};
|
||||
|
||||
$mount(App, '#app');
|
||||
Mount(App, '#app');
|
||||
```
|
||||
|
||||
## What Gets Compiled
|
||||
@@ -90,8 +90,8 @@ Your JSX:
|
||||
|
||||
Compiles to:
|
||||
```javascript
|
||||
$html('div', { class: "container" },
|
||||
$html(Button, {}, "Click")
|
||||
Tag('div', { class: "container" },
|
||||
Tag(Button, {}, "Click")
|
||||
)
|
||||
```
|
||||
|
||||
@@ -109,7 +109,7 @@ SigPro automatically injects `Div()`, `Button()`, `Span()`, and all other HTML t
|
||||
<div id="app"></div>
|
||||
|
||||
<script>
|
||||
const { $, $mount, Fragment } = SigPro;
|
||||
const { $, Mount, Fragment } = SigPro;
|
||||
|
||||
const App = () => {
|
||||
const count = $(0);
|
||||
@@ -127,7 +127,7 @@ SigPro automatically injects `Div()`, `Button()`, `Span()`, and all other HTML t
|
||||
]);
|
||||
};
|
||||
|
||||
$mount(App, '#app');
|
||||
Mount(App, '#app');
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -138,10 +138,10 @@ SigPro automatically injects `Div()`, `Button()`, `Span()`, and all other HTML t
|
||||
For a JSX-like syntax without a build step, use `htm`:
|
||||
|
||||
```javascript
|
||||
import { $, $mount } from 'https://unpkg.com/sigpro';
|
||||
import { $, Mount } from 'https://unpkg.com/sigpro';
|
||||
import htm from 'https://esm.sh/htm';
|
||||
|
||||
const html = htm.bind($html);
|
||||
const html = htm.bind(Tag);
|
||||
|
||||
const App = () => {
|
||||
const count = $(0);
|
||||
@@ -156,7 +156,7 @@ const App = () => {
|
||||
`;
|
||||
};
|
||||
|
||||
$mount(App, '#app');
|
||||
Mount(App, '#app');
|
||||
```
|
||||
|
||||
## Summary
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
# Application Mounter: `$mount( )`
|
||||
# Application Mounter: `Mount( )`
|
||||
|
||||
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 and initializing its reactive lifecycle.
|
||||
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 and initializing its reactive lifecycle.
|
||||
|
||||
## Function Signature
|
||||
|
||||
```typescript
|
||||
$mount(node: Function | HTMLElement, target?: string | HTMLElement): RuntimeObject
|
||||
Mount(node: Function | HTMLElement, target?: string | HTMLElement): RuntimeObject
|
||||
```
|
||||
|
||||
| Parameter | Type | Default | Description |
|
||||
@@ -27,7 +27,7 @@ import SigPro from 'sigpro';
|
||||
import App from './App.js';
|
||||
|
||||
// Mounts your main App component
|
||||
$mount(App, '#app-root');
|
||||
Mount(App, '#app-root');
|
||||
```
|
||||
|
||||
### 2. Reactive "Islands"
|
||||
@@ -42,17 +42,17 @@ const Counter = () => {
|
||||
};
|
||||
|
||||
// Mount only the counter into a specific sidebar div
|
||||
$mount(Counter, '#sidebar-widget');
|
||||
Mount(Counter, '#sidebar-widget');
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## How it Works (Lifecycle & Cleanup)
|
||||
|
||||
When `$mount` is executed, it performs these critical steps to ensure a leak-free environment:
|
||||
When `Mount` is executed, it performs these critical steps to ensure a leak-free environment:
|
||||
|
||||
1. **Duplicate Detection**: If you call `$mount` on a target that already has a SigPro instance, it automatically calls `.destroy()` on the previous instance. This prevents "Zombie Effects" from stacking in memory.
|
||||
2. **Internal Scoping**: It executes the component function inside an internal **Reactive Owner**. This captures every `$watch` and event listener created during the render.
|
||||
1. **Duplicate Detection**: If you call `Mount` on a target that already has a SigPro instance, it automatically calls `.destroy()` on the previous instance. This prevents "Zombie Effects" from stacking in memory.
|
||||
2. **Internal Scoping**: It executes the component function inside an internal **Reactive Owner**. This captures every `Watch` and event listener created during the render.
|
||||
3. **Target Injection**: It clears the target using `replaceChildren()` and appends the new component.
|
||||
4. **Runtime Creation**: It returns a control object:
|
||||
* `container`: The actual DOM element created.
|
||||
@@ -62,10 +62,10 @@ When `$mount` is executed, it performs these critical steps to ensure a leak-fre
|
||||
|
||||
## Manual Unmounting
|
||||
|
||||
While SigPro handles most cleanups automatically (via `$if`, `$for`, and `$router`), you can manually destroy any mounted instance. This is vital for imperatively managed UI like **Toasts** or **Modals**.
|
||||
While SigPro handles most cleanups automatically (via `If`, `For`, and `Router`), you can manually destroy any mounted instance. This is vital for imperatively managed UI like **Toasts** or **Modals**.
|
||||
|
||||
```javascript
|
||||
const instance = $mount(MyToast, '#toast-container');
|
||||
const instance = Mount(MyToast, '#toast-container');
|
||||
|
||||
// Later, to remove the toast and kill its reactivity:
|
||||
instance.destroy();
|
||||
@@ -77,9 +77,9 @@ instance.destroy();
|
||||
|
||||
| Goal | Code Pattern |
|
||||
| :--- | :--- |
|
||||
| **Mount to body** | `$mount(App)` |
|
||||
| **Mount to CSS Selector** | `$mount(App, '#root')` |
|
||||
| **Mount to DOM Node** | `$mount(App, myElement)` |
|
||||
| **Clean & Re-mount** | Calling `$mount` again on the same target |
|
||||
| **Total Cleanup** | `const app = $mount(App); app.destroy();` |
|
||||
| **Mount to body** | `Mount(App)` |
|
||||
| **Mount to CSS Selector** | `Mount(App, '#root')` |
|
||||
| **Mount to DOM Node** | `Mount(App, myElement)` |
|
||||
| **Clean & Re-mount** | Calling `Mount` again on the same target |
|
||||
| **Total Cleanup** | `const app = Mount(App); app.destroy();` |
|
||||
|
||||
|
||||
@@ -5,17 +5,16 @@ SigPro is a high-performance micro-framework that updates the **Real DOM** surgi
|
||||
<div class="text-center my-8">
|
||||
<div class="flex justify-center gap-2 flex-wrap mb-4">
|
||||
<span class="badge badge-primary badge-lg font-mono text-lg">$-$$</span>
|
||||
<span class="badge badge-secondary badge-lg font-mono text-lg">$watch</span>
|
||||
<span class="badge badge-accent badge-lg font-mono text-lg">$html</span>
|
||||
<span class="badge badge-info badge-lg font-mono text-lg">$if</span>
|
||||
<span class="badge badge-success badge-lg font-mono text-lg">$for</span>
|
||||
<span class="badge badge-warning badge-lg font-mono text-lg">$router</span>
|
||||
<span class="badge badge-error badge-lg font-mono text-lg">$mount</span>
|
||||
<span class="badge badge-secondary badge-lg font-mono text-lg">Watch</span>
|
||||
<span class="badge badge-accent badge-lg font-mono text-lg">Tag</span>
|
||||
<span class="badge badge-info badge-lg font-mono text-lg">If</span>
|
||||
<span class="badge badge-success badge-lg font-mono text-lg">For</span>
|
||||
<span class="badge badge-warning badge-lg font-mono text-lg">Router</span>
|
||||
<span class="badge badge-error badge-lg font-mono text-lg">Mount</span>
|
||||
</div>
|
||||
<h1 class="text-5xl font-black bg-gradient-to-r from-primary via-secondary to-accent bg-clip-text text-transparent">
|
||||
⚡ All the power! ⚡
|
||||
</h1>
|
||||
<p class="text-xl opacity-70 mt-2">6 functions that will change the way you code</p>
|
||||
</div>
|
||||
|
||||
## Core Functions
|
||||
@@ -48,32 +47,32 @@ Explore the reactive building blocks of SigPro.
|
||||
<td>Creates a <b>Deep Reactive Proxy</b>. Track nested property access automatically. No need for manual signals.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code class="text-secondary font-bold">$watch(fn)</code></td>
|
||||
<td><code class="text-secondary font-bold">Watch(fn)</code></td>
|
||||
<td class="font-mono text-xs opacity-70">(function) => stopFn</td>
|
||||
<td><b>Auto Mode:</b> Tracks any signal touched inside. Returns a stop function.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code class="text-secondary font-bold">$watch(deps, fn)</code></td>
|
||||
<td><code class="text-secondary font-bold">Watch(deps, fn)</code></td>
|
||||
<td class="font-mono text-xs opacity-70">(Array, function) => stopFn</td>
|
||||
<td><b>Explicit Mode:</b> Only runs when signals in <code>deps</code> change.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code class="text-accent font-bold">$if(cond, then, else?)</code></td>
|
||||
<td><code class="text-accent font-bold">If(cond, then, else?)</code></td>
|
||||
<td class="font-mono text-xs opacity-70">(Signal|bool, fn, fn?) => Node</td>
|
||||
<td>Reactive conditional. Automatically destroys "else" branch memory.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code class="text-accent font-bold">$for(src, render, key)</code></td>
|
||||
<td><code class="text-accent font-bold">For(src, render, key)</code></td>
|
||||
<td class="font-mono text-xs opacity-70">(Signal, fn, fn) => Node</td>
|
||||
<td><b>Keyed Loop:</b> Optimized list renderer. Uses the key function for surgical DOM moves.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code class="text-info font-bold">$router(routes)</code></td>
|
||||
<td><code class="text-info font-bold">Router(routes)</code></td>
|
||||
<td class="font-mono text-xs opacity-70">(Array) => Node</td>
|
||||
<td><b>SPA Router:</b> Hash-based routing with dynamic params (<code>:id</code>) and auto-cleanup.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code class="font-bold">$mount(node, target)</code></td>
|
||||
<td><code class="font-bold">Mount(node, target)</code></td>
|
||||
<td class="font-mono text-xs opacity-70">(any, string|Node) => Runtime</td>
|
||||
<td>Entry point. Cleans the target and mounts the app with full lifecycle management.</td>
|
||||
</tr>
|
||||
@@ -107,8 +106,8 @@ SigPro provides **PascalCase** wrappers for all standard HTML5 tags (e.g., `Div`
|
||||
<div class="card bg-base-200 border border-base-300 shadow-sm">
|
||||
<div class="card-body p-5">
|
||||
<h3 class="text-xs font-black uppercase tracking-widest opacity-60">Dynamic Routing</h3>
|
||||
<code class="text-info font-bold text-sm bg-base-300/50 p-2 rounded-lg">$router.to('/user/1')</code>
|
||||
<p class="text-xs mt-3 leading-relaxed">Navigate programmatically. Access params via <code>$router.params().id</code>.</p>
|
||||
<code class="text-info font-bold text-sm bg-base-300/50 p-2 rounded-lg">Router.to('/user/1')</code>
|
||||
<p class="text-xs mt-3 leading-relaxed">Navigate programmatically. Access params via <code>Router.params().id</code>.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -131,14 +130,14 @@ SigPro provides **PascalCase** wrappers for all standard HTML5 tags (e.g., `Div`
|
||||
|
||||
---
|
||||
|
||||
## Custom API BYOS (Bring Your Own Syntax)
|
||||
## Custom API (Bring Your Own Syntax)
|
||||
|
||||
SigPro's core functions are intentionally simple and can be easily renamed in **one line** to match your preferred coding style.
|
||||
|
||||
### One-Line Renaming
|
||||
|
||||
```javascript
|
||||
import { $ as signal, $mount as render, $html as tag, $if as when, $for as each, $watch as effect } from 'sigpro';
|
||||
import { $ as signal, Mount as render, Tag as tag, If as when, For as each, Watch as effect } from 'sigpro';
|
||||
|
||||
// Now use your custom names
|
||||
const count = signal(0);
|
||||
@@ -166,21 +165,21 @@ const useState = (initial) => {
|
||||
};
|
||||
|
||||
const useEffect = (fn, deps) => {
|
||||
deps ? SigPro.$watch(deps, fn) : SigPro.$watch(fn);
|
||||
deps ? SigPro.Watch(deps, fn) : SigPro.Watch(fn);
|
||||
};
|
||||
|
||||
// Usage
|
||||
const Counter = () => {
|
||||
const [count, setCount] = useState(0);
|
||||
useEffect(() => console.log(count()), [count]);
|
||||
return SigPro.$html('button', { onClick: () => setCount(count() + 1) }, count);
|
||||
return SigPro.Tag('button', { onClick: () => setCount(count() + 1) }, count);
|
||||
};
|
||||
```
|
||||
|
||||
### Create Vue-like API
|
||||
|
||||
```javascript
|
||||
import { $ as ref, $watch as watch, $mount as mount } from 'sigpro';
|
||||
import { $ as ref, Watch as watch, Mount as mount } from 'sigpro';
|
||||
|
||||
const computed = (fn) => ref(fn);
|
||||
const createApp = (component) => ({ mount: (selector) => mount(component, selector) });
|
||||
@@ -197,7 +196,7 @@ Create a central configuration file to reuse your custom naming across the entir
|
||||
|
||||
```javascript
|
||||
// config/sigpro.config.js
|
||||
import { $ as signal, $mount as render, $html as tag, $if as when, $for as each, $watch as effect } from 'sigpro';
|
||||
import { $ as signal, Mount as render, Tag as tag, If as when, For as each, Watch as effect } from 'sigpro';
|
||||
|
||||
// Re-export everything with your custom names
|
||||
export { signal, render, tag, when, each, effect };
|
||||
@@ -229,7 +228,7 @@ render(App, '#app');
|
||||
> **Why rename?** Team preferences, framework migration, or just personal taste. SigPro adapts to you, not the other way around.
|
||||
|
||||
> [!IMPORTANT]
|
||||
> **Performance Hint:** For lists (`$for`), always provide a unique key function `(item) => item.id` to prevent unnecessary node creation and enable reordering.
|
||||
> **Performance Hint:** For lists (`For`), always provide a unique key function `(item) => item.id` to prevent unnecessary node creation and enable reordering.
|
||||
|
||||
> [!TIP]
|
||||
> **Pro Tip:** Use `$$()` for complex nested state objects instead of multiple `$()` signals. It's cleaner and automatically tracks deep properties.
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
# Routing: `$router()` & Utilities
|
||||
# Routing: `Router()` & Utilities
|
||||
|
||||
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.
|
||||
|
||||
## Router Signature
|
||||
|
||||
```typescript
|
||||
$router(routes: Route[]): HTMLElement
|
||||
Router(routes: Route[]): HTMLElement
|
||||
```
|
||||
|
||||
### Route Object
|
||||
@@ -19,13 +19,13 @@ $router(routes: Route[]): HTMLElement
|
||||
## Usage Patterns
|
||||
|
||||
### 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.
|
||||
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 App = () => Div({ class: "app-layout" }, [
|
||||
Navbar(),
|
||||
// The router outlet is placed here
|
||||
$router([
|
||||
Router([
|
||||
{ path: "/", component: Home },
|
||||
{ path: "/profile/:id", component: (params) => UserProfile(params.id) },
|
||||
{ path: "*", component: () => H1("404 Not Found") }
|
||||
@@ -34,7 +34,7 @@ const App = () => Div({ class: "app-layout" }, [
|
||||
```
|
||||
|
||||
### 2. Dynamic Segments (`:id`)
|
||||
The router automatically parses URL parameters (like `:id`) and passes them as an object to the component function. You can also access them globally via `$router.params()`.
|
||||
The router automatically parses URL parameters (like `:id`) and passes them as an object to the component function. You can also access them globally via `Router.params()`.
|
||||
|
||||
```javascript
|
||||
// If the URL is #/profile/42
|
||||
@@ -49,23 +49,23 @@ const UserProfile = (params) => {
|
||||
|
||||
SigPro provides a set of programmatic methods to control the history and read the state.
|
||||
|
||||
### `$router.to(path)`
|
||||
### `Router.to(path)`
|
||||
Navigates to a specific path. It automatically formats the hash (e.g., `/home` becomes `#/home`).
|
||||
```javascript
|
||||
Button({ onclick: () => $router.to("/dashboard") }, "Go to Dashboard")
|
||||
Button({ onclick: () => Router.to("/dashboard") }, "Go to Dashboard")
|
||||
```
|
||||
|
||||
### `$router.back()`
|
||||
### `Router.back()`
|
||||
Goes back to the previous page in the browser history.
|
||||
```javascript
|
||||
Button({ onclick: () => $router.back() }, "Back")
|
||||
Button({ onclick: () => Router.back() }, "Back")
|
||||
```
|
||||
|
||||
### `$router.path()`
|
||||
### `Router.path()`
|
||||
Returns the current path (without the `#`).
|
||||
```javascript
|
||||
$watch(() => {
|
||||
console.log("Current path is:", $router.path());
|
||||
Watch(() => {
|
||||
console.log("Current path is:", Router.path());
|
||||
});
|
||||
```
|
||||
|
||||
@@ -74,7 +74,7 @@ $watch(() => {
|
||||
## 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.
|
||||
* **Reactive Params**: `$router.params` is a signal (`$`). You can react to parameter changes without re-mounting the entire router outlet.
|
||||
* **Reactive Params**: `Router.params` is a signal (`$`). You can react to parameter changes without re-mounting the entire router outlet.
|
||||
* **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.
|
||||
|
||||
---
|
||||
|
||||
@@ -94,7 +94,7 @@ $$<T extends object>(obj: T): T
|
||||
```javascript
|
||||
const state = $$({ count: 0, name: "Juan" });
|
||||
|
||||
$watch(() => state.count, () => {
|
||||
Watch(() => state.count, () => {
|
||||
console.log(`Count is now ${state.count}`);
|
||||
});
|
||||
|
||||
@@ -118,7 +118,7 @@ const user = $$({
|
||||
});
|
||||
|
||||
// This works! Tracks deep property access
|
||||
$watch(() => user.profile.address.city, () => {
|
||||
Watch(() => user.profile.address.city, () => {
|
||||
console.log("City changed");
|
||||
});
|
||||
|
||||
@@ -135,7 +135,7 @@ const todos = $$([
|
||||
{ id: 2, text: "Build an app", done: false }
|
||||
]);
|
||||
|
||||
$watch(() => todos.length, () => {
|
||||
Watch(() => todos.length, () => {
|
||||
console.log(`You have ${todos.length} todos`);
|
||||
});
|
||||
|
||||
@@ -164,7 +164,7 @@ const canSubmit = $(() =>
|
||||
form.fields.password.length > 6
|
||||
);
|
||||
|
||||
$watch(canSubmit, (valid) => {
|
||||
Watch(canSubmit, (valid) => {
|
||||
form.isValid(valid); // Update signal inside reactive object
|
||||
});
|
||||
```
|
||||
@@ -224,8 +224,8 @@ state.user.name = "Ana";
|
||||
todos.push(newItem);
|
||||
|
||||
// Track in effects
|
||||
$watch(() => state.count, () => {});
|
||||
$watch(() => state.user.name, () => {});
|
||||
Watch(() => state.count, () => {});
|
||||
Watch(() => state.user.name, () => {});
|
||||
```
|
||||
|
||||
### ❌ DON'T:
|
||||
@@ -248,7 +248,7 @@ Like all SigPro reactive primitives, `$$()` integrates with the cleanup system:
|
||||
|
||||
- Effects tracking reactive properties are automatically disposed
|
||||
- No manual cleanup needed
|
||||
- Works with `$watch`, `$if`, and `$for`
|
||||
- Works with `Watch`, `If`, and `For`
|
||||
|
||||
---
|
||||
|
||||
@@ -302,7 +302,7 @@ const app = {
|
||||
// UI component
|
||||
const UserProfile = () => {
|
||||
return Div({}, [
|
||||
$if(() => app.isLoggedIn(),
|
||||
If(() => app.isLoggedIn(),
|
||||
() => Div({}, [
|
||||
H2(`Welcome ${app.user.name}`),
|
||||
P(`Email: ${app.user.email}`),
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
# Global Tag Helpers
|
||||
|
||||
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.
|
||||
In **SigPro**, you don't need to manually type `Tag('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 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')`
|
||||
* **Under the hood:** `Tag('button', { onclick: ... }, 'Click')`
|
||||
* **SigPro Style:** `Button({ onclick: ... }, 'Click')`
|
||||
|
||||
---
|
||||
@@ -51,10 +51,10 @@ Section([
|
||||
|
||||
## 4. Reactive Power
|
||||
|
||||
These helpers are natively wired into SigPro's **`$watch`** engine.
|
||||
These helpers are natively wired into SigPro's **`Watch`** engine.
|
||||
|
||||
### Reactive Attributes (One-Way)
|
||||
Simply pass a Signal (function) to any attribute. SigPro creates an internal `$watch` to keep the DOM in sync.
|
||||
Simply pass a Signal (function) to any attribute. SigPro creates an internal `Watch` to keep the DOM in sync.
|
||||
```javascript
|
||||
const theme = $("light");
|
||||
|
||||
@@ -80,12 +80,12 @@ Input({
|
||||
> **Pro Tip:** If you want an input to be **read-only** but still reactive, wrap the signal in an anonymous function: `value: () => search()`. This prevents the "backwards" synchronization.
|
||||
|
||||
### Dynamic Flow & Cleanup
|
||||
Combine tags with Core controllers. SigPro automatically cleans up the `$watch` instances and event listeners when nodes are removed from the DOM.
|
||||
Combine tags with Core controllers. SigPro automatically cleans up the `Watch` instances and event listeners when nodes are removed from the DOM.
|
||||
```javascript
|
||||
const items = $(["Apple", "Banana", "Cherry"]);
|
||||
|
||||
Ul({ class: "list-disc" }, [
|
||||
$for(items, (item) => Li(item), (item) => item)
|
||||
For(items, (item) => Li(item), (item) => item)
|
||||
]);
|
||||
```
|
||||
|
||||
@@ -128,15 +128,15 @@ const UserStatus = (name, online) => (
|
||||
|
||||
---
|
||||
|
||||
## 6. Custom Tags with `$html`
|
||||
## 6. Custom Tags with `Tag`
|
||||
|
||||
Create reusable components using `$html`. All reactivity auto-cleans itself.
|
||||
Create reusable components using `Tag`. All reactivity auto-cleans itself.
|
||||
|
||||
### Basic Example
|
||||
|
||||
```javascript
|
||||
const UserCard = (props, children) =>
|
||||
$html('div', { class: 'card p-4', 'data-id': props.id }, children);
|
||||
Tag('div', { class: 'card p-4', 'data-id': props.id }, children);
|
||||
|
||||
UserCard({ id: 123 }, [H3("John Doe"), P("john@example.com")]);
|
||||
```
|
||||
@@ -146,7 +146,7 @@ UserCard({ id: 123 }, [H3("John Doe"), P("john@example.com")]);
|
||||
```javascript
|
||||
const Counter = (initial = 0) => {
|
||||
const count = $(initial);
|
||||
return $html('div', { class: 'flex gap-2' }, [
|
||||
return Tag('div', { class: 'flex gap-2' }, [
|
||||
Button({ onclick: () => count(count() - 1) }, '-'),
|
||||
Span(() => count()),
|
||||
Button({ onclick: () => count(count() + 1) }, '+')
|
||||
@@ -161,7 +161,7 @@ Only for external resources (intervals, sockets, third-party libs):
|
||||
```javascript
|
||||
const Timer = () => {
|
||||
const time = $(new Date().toLocaleTimeString());
|
||||
const el = $html('span', {}, () => time());
|
||||
const el = Tag('span', {}, () => time());
|
||||
|
||||
const interval = setInterval(() => time(new Date().toLocaleTimeString()), 1000);
|
||||
el._cleanups.add(() => clearInterval(interval)); // Manual cleanup
|
||||
@@ -170,15 +170,15 @@ const Timer = () => {
|
||||
};
|
||||
```
|
||||
|
||||
### `$html` vs Tag Helpers
|
||||
### `Tag` vs Tag Helpers
|
||||
|
||||
| Use | Recommendation |
|
||||
|:---|:---|
|
||||
| Standard tags (`div`, `span`) | `Div()`, `Span()` helpers |
|
||||
| Reusable components | Function returning `$html` |
|
||||
| Dynamic tag names | `$html(tagName, props, children)` |
|
||||
| Reusable components | Function returning `Tag` |
|
||||
| Dynamic tag names | `Tag(tagName, props, children)` |
|
||||
|
||||
> **Auto-cleanup**: `$html` automatically destroys watchers, events, and nested components. Only add to `_cleanups` for external resources.
|
||||
> **Auto-cleanup**: `Tag` automatically destroys watchers, events, and nested components. Only add to `_cleanups` for external resources.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
# Reactivity Control: `$watch( )`
|
||||
# Reactivity Control: `Watch( )`
|
||||
|
||||
The `$watch` function is the reactive engine of SigPro. It allows you to execute code automatically when signals change. `$watch` is **polymorphic**: it can track dependencies automatically or follow an explicit list.
|
||||
The `Watch` function is the reactive engine of SigPro. It allows you to execute code automatically when signals change. `Watch` is **polymorphic**: it can track dependencies automatically or follow an explicit list.
|
||||
|
||||
## Function Signature
|
||||
|
||||
```typescript
|
||||
// Automatic Mode (Magic Tracking)
|
||||
$watch(callback: Function): StopFunction
|
||||
Watch(callback: Function): StopFunction
|
||||
|
||||
// Explicit Mode (Isolated Dependencies)
|
||||
$watch(deps: Signal[], callback: Function): StopFunction
|
||||
Watch(deps: Signal[], callback: Function): StopFunction
|
||||
```
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
@@ -29,7 +29,7 @@ Any signal you "touch" inside the callback becomes a dependency. SigPro tracks t
|
||||
```javascript
|
||||
const count = $(0);
|
||||
|
||||
$watch(() => {
|
||||
Watch(() => {
|
||||
// Re-runs every time 'count' changes
|
||||
console.log(`Count is: ${count()}`);
|
||||
});
|
||||
@@ -42,7 +42,7 @@ This mode **isolates** execution. The callback only triggers when the signals in
|
||||
const sPath = $("/home");
|
||||
const user = $("Admin");
|
||||
|
||||
$watch([sPath], () => {
|
||||
Watch([sPath], () => {
|
||||
// Only triggers when 'sPath' changes.
|
||||
// Changes to 'user' will NOT trigger this, preventing accidental re-renders.
|
||||
console.log(`Navigating to ${sPath()} as ${user()}`);
|
||||
@@ -53,11 +53,11 @@ $watch([sPath], () => {
|
||||
If your logic creates timers, event listeners, or other reactive effects, SigPro tracks them as "children" of the current watch. When the watcher re-runs or stops, it kills everything inside automatically.
|
||||
|
||||
```javascript
|
||||
$watch(() => {
|
||||
Watch(() => {
|
||||
const timer = setInterval(() => console.log("Tick"), 1000);
|
||||
|
||||
// Register a manual cleanup if needed
|
||||
// Or simply rely on SigPro to kill nested $watch() calls
|
||||
// Or simply rely on SigPro to kill nested Watch() calls
|
||||
return () => clearInterval(timer);
|
||||
});
|
||||
```
|
||||
@@ -68,7 +68,7 @@ $watch(() => {
|
||||
Call the returned function to manually kill the watcher. This is essential for manual DOM injections (like Toasts) or long-lived background processes.
|
||||
|
||||
```javascript
|
||||
const stop = $watch(() => console.log(count()));
|
||||
const stop = Watch(() => console.log(count()));
|
||||
|
||||
// Later...
|
||||
stop(); // The link between the signal and this code is physically severed.
|
||||
@@ -83,7 +83,7 @@ SigPro batches updates. If you update multiple signals in the same execution blo
|
||||
const a = $(0);
|
||||
const b = $(0);
|
||||
|
||||
$watch(() => console.log(a(), b()));
|
||||
Watch(() => console.log(a(), b()));
|
||||
|
||||
// This triggers only ONE re-run.
|
||||
a(1);
|
||||
|
||||
@@ -24,7 +24,7 @@ const Counter = () => {
|
||||
Button({ class: 'btn btn-circle btn-primary', onclick: () => $count(c => c + 1) }, "+")
|
||||
]);
|
||||
};
|
||||
$mount(Counter, '#demo-counter');
|
||||
Mount(Counter, '#demo-counter');
|
||||
```
|
||||
|
||||
---
|
||||
@@ -48,12 +48,12 @@ const ComputedDemo = () => {
|
||||
P({ class: 'text-center' }, ["Base: ", $count, " ⮕ ", Span({class: 'text-primary font-bold'}, ["Double: ", $double])])
|
||||
]);
|
||||
};
|
||||
$mount(ComputedDemo, '#demo-computed');
|
||||
Mount(ComputedDemo, '#demo-computed');
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Lists and Loops ($for)
|
||||
## 3. Lists and Loops (For)
|
||||
SigPro handles lists efficiently, only updating specific DOM nodes.
|
||||
|
||||
<div class="card bg-base-200 border border-base-300 shadow-sm my-6">
|
||||
@@ -79,16 +79,16 @@ const ListDemo = () => {
|
||||
Button({ class: 'btn btn-primary', onclick: addTodo }, "Add")
|
||||
]),
|
||||
Ul({ class: 'menu bg-base-200 rounded-box p-2' },
|
||||
$for($todos, (item) => Li([A(item)]), (item) => item)
|
||||
For($todos, (item) => Li([A(item)]), (item) => item)
|
||||
)
|
||||
]);
|
||||
};
|
||||
$mount(ListDemo, '#demo-list');
|
||||
Mount(ListDemo, '#demo-list');
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Conditional Rendering ($if)
|
||||
## 4. Conditional Rendering (If)
|
||||
Toggle visibility without re-rendering the entire parent.
|
||||
|
||||
<div class="card bg-base-200 border border-base-300 shadow-sm my-6">
|
||||
@@ -103,13 +103,13 @@ const ConditionalDemo = () => {
|
||||
const $isVisible = $(false);
|
||||
return Div({ class: 'text-center w-full' }, [
|
||||
Button({ class: 'btn btn-outline mb-4', onclick: () => $isVisible(! $isVisible()) }, "Toggle Secret"),
|
||||
$if($isVisible,
|
||||
If($isVisible,
|
||||
() => Div({ class: 'p-4 bg-warning text-warning-content rounded-lg shadow-inner' }, "🤫 SigPro is Awesome!"),
|
||||
() => Div({ class: 'p-4 opacity-50' }, "Nothing to see here...")
|
||||
)
|
||||
]);
|
||||
};
|
||||
$mount(ConditionalDemo, '#demo-if');
|
||||
Mount(ConditionalDemo, '#demo-if');
|
||||
```
|
||||
|
||||
---
|
||||
@@ -133,7 +133,7 @@ const PersistDemo = () => {
|
||||
P({ class: 'text-xs opacity-50' }, "Refresh the page to see magic!")
|
||||
]);
|
||||
};
|
||||
$mount(PersistDemo, '#demo-persist');
|
||||
Mount(PersistDemo, '#demo-persist');
|
||||
```
|
||||
|
||||
<script>
|
||||
@@ -150,7 +150,7 @@ $mount(PersistDemo, '#demo-persist');
|
||||
Button({ class: 'btn btn-circle btn-primary', onclick: () => $count(c => c + 1) }, "+")
|
||||
]);
|
||||
};
|
||||
$mount(Counter, counterTarget);
|
||||
Mount(Counter, counterTarget);
|
||||
}
|
||||
|
||||
// 2. Computed
|
||||
@@ -164,7 +164,7 @@ $mount(PersistDemo, '#demo-persist');
|
||||
P({ class: 'text-center' }, ["Base: ", $count, " ⮕ ", Span({class: 'text-primary font-bold'}, ["Double: ", $double])])
|
||||
]);
|
||||
};
|
||||
$mount(ComputedDemo, computedTarget);
|
||||
Mount(ComputedDemo, computedTarget);
|
||||
}
|
||||
|
||||
// 3. List
|
||||
@@ -179,10 +179,10 @@ $mount(PersistDemo, '#demo-persist');
|
||||
Input({ class: 'input input-bordered flex-1', value: $input, placeholder: 'New task...' }),
|
||||
Button({ class: 'btn btn-primary', onclick: addTodo }, "Add")
|
||||
]),
|
||||
Ul({ class: 'menu bg-base-200 rounded-box p-2' }, $for($todos, (item) => Li([A(item)]), (item) => item))
|
||||
Ul({ class: 'menu bg-base-200 rounded-box p-2' }, For($todos, (item) => Li([A(item)]), (item) => item))
|
||||
]);
|
||||
};
|
||||
$mount(ListDemo, listTarget);
|
||||
Mount(ListDemo, listTarget);
|
||||
}
|
||||
|
||||
// 4. If
|
||||
@@ -192,13 +192,13 @@ $mount(PersistDemo, '#demo-persist');
|
||||
const $isVisible = $(false);
|
||||
return Div({ class: 'text-center w-full' }, [
|
||||
Button({ class: 'btn btn-outline mb-4', onclick: () => $isVisible(! $isVisible()) }, "Toggle Secret"),
|
||||
$if($isVisible,
|
||||
If($isVisible,
|
||||
() => Div({ class: 'p-4 bg-warning text-warning-content rounded-lg' }, "🤫 SigPro is Awesome!"),
|
||||
() => Div({ class: 'p-4 opacity-50' }, "Nothing to see here...")
|
||||
)
|
||||
]);
|
||||
};
|
||||
$mount(ConditionalDemo, ifTarget);
|
||||
Mount(ConditionalDemo, ifTarget);
|
||||
}
|
||||
|
||||
// 5. Persist
|
||||
@@ -212,7 +212,7 @@ $mount(PersistDemo, '#demo-persist');
|
||||
P({ class: 'text-xs opacity-50' }, "Refresh the page!")
|
||||
]);
|
||||
};
|
||||
$mount(PersistDemo, persistTarget);
|
||||
Mount(PersistDemo, persistTarget);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
name: 'SigPro',
|
||||
repo: '',
|
||||
loadSidebar: true,
|
||||
subMaxLevel: 3,
|
||||
subMaxLevel: 0,
|
||||
sidebarDisplayLevel: 1,
|
||||
executeScript: true,
|
||||
copyCode: {
|
||||
@@ -31,9 +31,7 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<script type="module">
|
||||
import SigPro from 'https://cdn.jsdelivr.net/npm/sigpro@latest/+esm';
|
||||
</script>
|
||||
<script src="./sigpro.js"></script>
|
||||
<script src="//cdn.jsdelivr.net/npm/docsify/lib/docsify.min.js"></script>
|
||||
<script src="//cdn.jsdelivr.net/npm/docsify-copy-code/dist/docsify-copy-code.min.js"></script>
|
||||
</body>
|
||||
|
||||
@@ -106,7 +106,7 @@ export const App = () => {
|
||||
import "sigpro";
|
||||
import { App } from "./App.js";
|
||||
|
||||
$mount(App, "#app");
|
||||
Mount(App, "#app");
|
||||
```
|
||||
|
||||
</div>
|
||||
@@ -137,7 +137,7 @@ $mount(App, "#app");
|
||||
}),
|
||||
]);
|
||||
|
||||
$mount(App, "#app");
|
||||
Mount(App, "#app");
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -153,14 +153,14 @@ $mount(App, "#app");
|
||||
One of SigPro's core strengths is its **Global API**, which eliminates "Import Hell" while remaining ESM-compatible.
|
||||
|
||||
- **The "Zero-Config" Import:** By simply adding `import SigPro from "sigpro"`, the framework automatically "hydrates" the global `window` object.
|
||||
- **Core Functions:** You get immediate access to `$`, `$watch`, `$html`, `$if`, `$for`, and `$router` anywhere in your scripts without using the `SigPro.` prefix.
|
||||
- **Core Functions:** You get immediate access to `$`, `Watch`, `Tag`, `If`, `For`, and `Router` anywhere in your scripts without using the `SigPro.` prefix.
|
||||
- **Auto-Installation:** This happens instantly upon import thanks to its built-in `install()` routine, making it "Plug & Play" for both local projects and CDN usage.
|
||||
|
||||
- **PascalCase Tag Helpers:** Standard HTML tags are pre-registered as global functions (`Div`, `Span`, `Button`, `Section`, etc.).
|
||||
- **Clean UI Syntax:** This allows you to write UI structures that look like HTML but are pure, reactive JavaScript: `Div({ class: "card" }, [ H1("Title") ])`.
|
||||
|
||||
- **Hybrid Tree Shaking:** \* For **Maximum Speed**, use `import SigPro from "sigpro"`.
|
||||
- For **Maximum Optimization**, you can still use Named Imports: `import { $, $html } from "sigpro"`. This allows modern bundlers like Vite to prune unused code while keeping your core reactive.
|
||||
- For **Maximum Optimization**, you can still use Named Imports: `import { $, Tag } from "sigpro"`. This allows modern bundlers like Vite to prune unused code while keeping your core reactive.
|
||||
|
||||
- **Custom Components:** We recommend using **PascalCase** for your own components (e.g., `UserCard()`) to maintain visual consistency with the built-in Tag Helpers and distinguish them from standard logic.
|
||||
|
||||
|
||||
472
docs/sigpro.js
Normal file
472
docs/sigpro.js
Normal file
@@ -0,0 +1,472 @@
|
||||
(() => {
|
||||
var __defProp = Object.defineProperty;
|
||||
var __getOwnPropNames = Object.getOwnPropertyNames;
|
||||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
||||
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
||||
var __moduleCache = /* @__PURE__ */ new WeakMap;
|
||||
var __toCommonJS = (from) => {
|
||||
var entry = __moduleCache.get(from), desc;
|
||||
if (entry)
|
||||
return entry;
|
||||
entry = __defProp({}, "__esModule", { value: true });
|
||||
if (from && typeof from === "object" || typeof from === "function")
|
||||
__getOwnPropNames(from).map((key) => !__hasOwnProp.call(entry, key) && __defProp(entry, key, {
|
||||
get: () => from[key],
|
||||
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
||||
}));
|
||||
__moduleCache.set(from, entry);
|
||||
return entry;
|
||||
};
|
||||
var __export = (target, all) => {
|
||||
for (var name in all)
|
||||
__defProp(target, name, {
|
||||
get: all[name],
|
||||
enumerable: true,
|
||||
configurable: true,
|
||||
set: (newValue) => all[name] = () => newValue
|
||||
});
|
||||
};
|
||||
|
||||
// index.js
|
||||
var exports_sigpro = {};
|
||||
__export(exports_sigpro, {
|
||||
Watch: () => Watch,
|
||||
Tag: () => Tag,
|
||||
Router: () => Router,
|
||||
Render: () => Render,
|
||||
Mount: () => Mount,
|
||||
If: () => If,
|
||||
For: () => For,
|
||||
$$: () => $$,
|
||||
$: () => $
|
||||
});
|
||||
|
||||
// sigpro.js
|
||||
var activeEffect = null;
|
||||
var currentOwner = null;
|
||||
var effectQueue = new Set;
|
||||
var isFlushing = false;
|
||||
var MOUNTED_NODES = new WeakMap;
|
||||
var doc = document;
|
||||
var isArr = Array.isArray;
|
||||
var assign = Object.assign;
|
||||
var createEl = (t) => doc.createElement(t);
|
||||
var createText = (t) => doc.createTextNode(String(t ?? ""));
|
||||
var isFunc = (f) => typeof f === "function";
|
||||
var isObj = (o) => typeof o === "object" && o !== null;
|
||||
var runWithContext = (effect, callback) => {
|
||||
const previousEffect = activeEffect;
|
||||
activeEffect = effect;
|
||||
try {
|
||||
return callback();
|
||||
} finally {
|
||||
activeEffect = previousEffect;
|
||||
}
|
||||
};
|
||||
var cleanupNode = (node) => {
|
||||
if (node._cleanups) {
|
||||
node._cleanups.forEach((dispose) => dispose());
|
||||
node._cleanups.clear();
|
||||
}
|
||||
node.childNodes?.forEach(cleanupNode);
|
||||
};
|
||||
var flushEffects = () => {
|
||||
if (isFlushing)
|
||||
return;
|
||||
isFlushing = true;
|
||||
while (effectQueue.size > 0) {
|
||||
const sortedEffects = Array.from(effectQueue).sort((a, b) => (a.depth || 0) - (b.depth || 0));
|
||||
effectQueue.clear();
|
||||
for (const effect of sortedEffects) {
|
||||
if (!effect._deleted)
|
||||
effect();
|
||||
}
|
||||
}
|
||||
isFlushing = false;
|
||||
};
|
||||
var trackSubscription = (subscribers) => {
|
||||
if (activeEffect && !activeEffect._deleted) {
|
||||
subscribers.add(activeEffect);
|
||||
activeEffect._deps.add(subscribers);
|
||||
}
|
||||
};
|
||||
var triggerUpdate = (subscribers) => {
|
||||
subscribers.forEach((effect) => {
|
||||
if (effect === activeEffect || effect._deleted)
|
||||
return;
|
||||
if (effect._isComputed) {
|
||||
effect.markDirty();
|
||||
if (effect._subs)
|
||||
triggerUpdate(effect._subs);
|
||||
} else {
|
||||
effectQueue.add(effect);
|
||||
}
|
||||
});
|
||||
if (!isFlushing)
|
||||
queueMicrotask(flushEffects);
|
||||
};
|
||||
var Render = (renderFn) => {
|
||||
const cleanups = new Set;
|
||||
const previousOwner = currentOwner;
|
||||
const container = createEl("div");
|
||||
container.style.display = "contents";
|
||||
currentOwner = { cleanups };
|
||||
const processResult = (result) => {
|
||||
if (!result)
|
||||
return;
|
||||
if (result._isRuntime) {
|
||||
cleanups.add(result.destroy);
|
||||
container.appendChild(result.container);
|
||||
} else if (isArr(result)) {
|
||||
result.forEach(processResult);
|
||||
} else {
|
||||
container.appendChild(result instanceof Node ? result : createText(result));
|
||||
}
|
||||
};
|
||||
try {
|
||||
processResult(renderFn({ onCleanup: (fn) => cleanups.add(fn) }));
|
||||
} finally {
|
||||
currentOwner = previousOwner;
|
||||
}
|
||||
return {
|
||||
_isRuntime: true,
|
||||
container,
|
||||
destroy: () => {
|
||||
cleanups.forEach((fn) => fn());
|
||||
cleanupNode(container);
|
||||
container.remove();
|
||||
}
|
||||
};
|
||||
};
|
||||
var $ = (initialValue, storageKey = null) => {
|
||||
const subscribers = new Set;
|
||||
if (isFunc(initialValue)) {
|
||||
let cachedValue, isDirty = true;
|
||||
const effect = () => {
|
||||
if (effect._deleted)
|
||||
return;
|
||||
effect._deps.forEach((dep) => dep.delete(effect));
|
||||
effect._deps.clear();
|
||||
runWithContext(effect, () => {
|
||||
const newValue = initialValue();
|
||||
if (!Object.is(cachedValue, newValue) || isDirty) {
|
||||
cachedValue = newValue;
|
||||
isDirty = false;
|
||||
triggerUpdate(subscribers);
|
||||
}
|
||||
});
|
||||
};
|
||||
assign(effect, {
|
||||
_deps: new Set,
|
||||
_isComputed: true,
|
||||
_subs: subscribers,
|
||||
_deleted: false,
|
||||
markDirty: () => isDirty = true,
|
||||
stop: () => {
|
||||
effect._deleted = true;
|
||||
effect._deps.forEach((dep) => dep.delete(effect));
|
||||
subscribers.clear();
|
||||
}
|
||||
});
|
||||
if (currentOwner)
|
||||
currentOwner.cleanups.add(effect.stop);
|
||||
return () => {
|
||||
if (isDirty)
|
||||
effect();
|
||||
trackSubscription(subscribers);
|
||||
return cachedValue;
|
||||
};
|
||||
}
|
||||
let value = initialValue;
|
||||
if (storageKey) {
|
||||
try {
|
||||
const saved = localStorage.getItem(storageKey);
|
||||
if (saved !== null)
|
||||
value = JSON.parse(saved);
|
||||
} catch (e) {
|
||||
console.warn("SigPro Storage Lock", e);
|
||||
}
|
||||
}
|
||||
return (...args) => {
|
||||
if (args.length) {
|
||||
const nextValue = isFunc(args[0]) ? args[0](value) : args[0];
|
||||
if (!Object.is(value, nextValue)) {
|
||||
value = nextValue;
|
||||
if (storageKey)
|
||||
localStorage.setItem(storageKey, JSON.stringify(value));
|
||||
triggerUpdate(subscribers);
|
||||
}
|
||||
}
|
||||
trackSubscription(subscribers);
|
||||
return value;
|
||||
};
|
||||
};
|
||||
var $$ = (object, cache = new WeakMap) => {
|
||||
if (!isObj(object))
|
||||
return object;
|
||||
if (cache.has(object))
|
||||
return cache.get(object);
|
||||
const keySubscribers = {};
|
||||
const proxy = new Proxy(object, {
|
||||
get(target, key) {
|
||||
if (activeEffect)
|
||||
trackSubscription(keySubscribers[key] ??= new Set);
|
||||
const value = Reflect.get(target, key);
|
||||
return isObj(value) ? $$(value, cache) : value;
|
||||
},
|
||||
set(target, key, value) {
|
||||
if (Object.is(target[key], value))
|
||||
return true;
|
||||
const success = Reflect.set(target, key, value);
|
||||
if (keySubscribers[key])
|
||||
triggerUpdate(keySubscribers[key]);
|
||||
return success;
|
||||
}
|
||||
});
|
||||
cache.set(object, proxy);
|
||||
return proxy;
|
||||
};
|
||||
var Watch = (target, callbackFn) => {
|
||||
const isExplicit = isArr(target);
|
||||
const callback = isExplicit ? callbackFn : target;
|
||||
if (!isFunc(callback))
|
||||
return () => {};
|
||||
const owner = currentOwner;
|
||||
const runner = () => {
|
||||
if (runner._deleted)
|
||||
return;
|
||||
runner._deps.forEach((dep) => dep.delete(runner));
|
||||
runner._deps.clear();
|
||||
runner._cleanups.forEach((cleanup) => cleanup());
|
||||
runner._cleanups.clear();
|
||||
const previousOwner = currentOwner;
|
||||
runner.depth = activeEffect ? activeEffect.depth + 1 : 0;
|
||||
runWithContext(runner, () => {
|
||||
currentOwner = { cleanups: runner._cleanups };
|
||||
if (isExplicit) {
|
||||
runWithContext(null, callback);
|
||||
target.forEach((dep) => isFunc(dep) && dep());
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
currentOwner = previousOwner;
|
||||
});
|
||||
};
|
||||
assign(runner, {
|
||||
_deps: new Set,
|
||||
_cleanups: new Set,
|
||||
_deleted: false,
|
||||
stop: () => {
|
||||
if (runner._deleted)
|
||||
return;
|
||||
runner._deleted = true;
|
||||
effectQueue.delete(runner);
|
||||
runner._deps.forEach((dep) => dep.delete(runner));
|
||||
runner._cleanups.forEach((cleanup) => cleanup());
|
||||
if (owner)
|
||||
owner.cleanups.delete(runner.stop);
|
||||
}
|
||||
});
|
||||
if (owner)
|
||||
owner.cleanups.add(runner.stop);
|
||||
runner();
|
||||
return runner.stop;
|
||||
};
|
||||
var Tag = (tag, props = {}, children = []) => {
|
||||
if (props instanceof Node || isArr(props) || !isObj(props)) {
|
||||
children = props;
|
||||
props = {};
|
||||
}
|
||||
const isSVG = /^(svg|path|circle|rect|line|polyline|polygon|g|defs|text|tspan|use)$/.test(tag);
|
||||
const element = isSVG ? doc.createElementNS("http://www.w3.org/2000/svg", tag) : createEl(tag);
|
||||
element._cleanups = new Set;
|
||||
element.onUnmount = (fn) => element._cleanups.add(fn);
|
||||
const booleanAttributes = ["disabled", "checked", "required", "readonly", "selected", "multiple", "autofocus"];
|
||||
const updateAttribute = (name, value) => {
|
||||
const sanitized = (name === "src" || name === "href") && String(value).toLowerCase().includes("javascript:") ? "#" : value;
|
||||
if (booleanAttributes.includes(name)) {
|
||||
element[name] = !!sanitized;
|
||||
sanitized ? element.setAttribute(name, "") : element.removeAttribute(name);
|
||||
} else {
|
||||
sanitized == null ? element.removeAttribute(name) : element.setAttribute(name, sanitized);
|
||||
}
|
||||
};
|
||||
for (let [key, value] of Object.entries(props)) {
|
||||
if (key === "ref") {
|
||||
isFunc(value) ? value(element) : value.current = element;
|
||||
continue;
|
||||
}
|
||||
const isSignal = isFunc(value);
|
||||
if (key.startsWith("on")) {
|
||||
const eventName = key.slice(2).toLowerCase().split(".")[0];
|
||||
element.addEventListener(eventName, value);
|
||||
element._cleanups.add(() => element.removeEventListener(eventName, value));
|
||||
} else if (isSignal) {
|
||||
element._cleanups.add(Watch(() => {
|
||||
const currentVal = value();
|
||||
key === "class" ? element.className = currentVal || "" : updateAttribute(key, currentVal);
|
||||
}));
|
||||
if (["INPUT", "TEXTAREA", "SELECT"].includes(element.tagName) && (key === "value" || key === "checked")) {
|
||||
const event = key === "checked" ? "change" : "input";
|
||||
const handler = (e) => value(e.target[key]);
|
||||
element.addEventListener(event, handler);
|
||||
element._cleanups.add(() => element.removeEventListener(event, handler));
|
||||
}
|
||||
} else {
|
||||
updateAttribute(key, value);
|
||||
}
|
||||
}
|
||||
const appendChildNode = (child) => {
|
||||
if (isArr(child))
|
||||
return child.forEach(appendChildNode);
|
||||
if (isFunc(child)) {
|
||||
const marker = createText("");
|
||||
element.appendChild(marker);
|
||||
let currentNodes = [];
|
||||
element._cleanups.add(Watch(() => {
|
||||
const result = child();
|
||||
const nextNodes = (isArr(result) ? result : [result]).map((node) => node?._isRuntime ? node.container : node instanceof Node ? node : createText(node));
|
||||
currentNodes.forEach((node) => {
|
||||
cleanupNode(node);
|
||||
node.remove();
|
||||
});
|
||||
nextNodes.forEach((node) => marker.parentNode?.insertBefore(node, marker));
|
||||
currentNodes = nextNodes;
|
||||
}));
|
||||
} else {
|
||||
element.appendChild(child instanceof Node ? child : createText(child));
|
||||
}
|
||||
};
|
||||
appendChildNode(children);
|
||||
return element;
|
||||
};
|
||||
var If = (condition, thenVal, otherwiseVal = null, transition = null) => {
|
||||
const marker = createText("");
|
||||
const container = Tag("div", { style: "display:contents" }, [marker]);
|
||||
let currentView = null, lastState = null;
|
||||
Watch(() => {
|
||||
const state = !!(isFunc(condition) ? condition() : condition);
|
||||
if (state === lastState)
|
||||
return;
|
||||
lastState = state;
|
||||
const dispose = () => {
|
||||
if (currentView)
|
||||
currentView.destroy();
|
||||
currentView = null;
|
||||
};
|
||||
if (currentView && !state && transition?.out) {
|
||||
transition.out(currentView.container, dispose);
|
||||
} else {
|
||||
dispose();
|
||||
}
|
||||
const branch = state ? thenVal : otherwiseVal;
|
||||
if (branch) {
|
||||
currentView = Render(() => isFunc(branch) ? branch() : branch);
|
||||
container.insertBefore(currentView.container, marker);
|
||||
if (state && transition?.in)
|
||||
transition.in(currentView.container);
|
||||
}
|
||||
});
|
||||
return container;
|
||||
};
|
||||
var For = (source, renderFn, keyFn, tag = "div", props = { style: "display:contents" }) => {
|
||||
const marker = createText("");
|
||||
const container = Tag(tag, props, [marker]);
|
||||
let viewCache = new Map;
|
||||
Watch(() => {
|
||||
const items = (isFunc(source) ? source() : source) || [];
|
||||
const nextCache = new Map;
|
||||
const order = [];
|
||||
for (let i = 0;i < items.length; i++) {
|
||||
const item = items[i];
|
||||
const key = keyFn ? keyFn(item, i) : i;
|
||||
let view = viewCache.get(key);
|
||||
if (!view) {
|
||||
const result = renderFn(item, i);
|
||||
view = result instanceof Node ? { container: result, destroy: () => {
|
||||
cleanupNode(result);
|
||||
result.remove();
|
||||
} } : Render(() => result);
|
||||
}
|
||||
viewCache.delete(key);
|
||||
nextCache.set(key, view);
|
||||
order.push(key);
|
||||
}
|
||||
viewCache.forEach((v) => v.destroy());
|
||||
let anchor = marker;
|
||||
for (let i = order.length - 1;i >= 0; i--) {
|
||||
const view = nextCache.get(order[i]);
|
||||
if (view.container.nextSibling !== anchor) {
|
||||
container.insertBefore(view.container, anchor);
|
||||
}
|
||||
anchor = view.container;
|
||||
}
|
||||
viewCache = nextCache;
|
||||
});
|
||||
return container;
|
||||
};
|
||||
var Router = (routes) => {
|
||||
const currentPath = $(window.location.hash.replace(/^#/, "") || "/");
|
||||
window.addEventListener("hashchange", () => currentPath(window.location.hash.replace(/^#/, "") || "/"));
|
||||
const outlet = Tag("div", { class: "router-outlet" });
|
||||
let currentView = null;
|
||||
Watch([currentPath], async () => {
|
||||
const path = currentPath();
|
||||
const route = routes.find((r) => {
|
||||
const routeParts = r.path.split("/").filter(Boolean);
|
||||
const pathParts = path.split("/").filter(Boolean);
|
||||
return routeParts.length === pathParts.length && routeParts.every((part, i) => part.startsWith(":") || part === pathParts[i]);
|
||||
}) || routes.find((r) => r.path === "*");
|
||||
if (route) {
|
||||
let component = route.component;
|
||||
if (isFunc(component) && component.toString().includes("import")) {
|
||||
component = (await component()).default || await component();
|
||||
}
|
||||
const params = {};
|
||||
route.path.split("/").filter(Boolean).forEach((part, i) => {
|
||||
if (part.startsWith(":"))
|
||||
params[part.slice(1)] = path.split("/").filter(Boolean)[i];
|
||||
});
|
||||
if (currentView)
|
||||
currentView.destroy();
|
||||
if (Router.params)
|
||||
Router.params(params);
|
||||
currentView = Render(() => {
|
||||
try {
|
||||
return isFunc(component) ? component(params) : component;
|
||||
} catch (e) {
|
||||
return Tag("div", { class: "p-4 text-error" }, "Error loading view");
|
||||
}
|
||||
});
|
||||
outlet.appendChild(currentView.container);
|
||||
}
|
||||
});
|
||||
return outlet;
|
||||
};
|
||||
Router.params = $({});
|
||||
Router.to = (path) => window.location.hash = path.replace(/^#?\/?/, "#/");
|
||||
Router.back = () => window.history.back();
|
||||
Router.path = () => window.location.hash.replace(/^#/, "") || "/";
|
||||
var Mount = (component, target) => {
|
||||
const targetEl = typeof target === "string" ? doc.querySelector(target) : target;
|
||||
if (!targetEl)
|
||||
return;
|
||||
if (MOUNTED_NODES.has(targetEl))
|
||||
MOUNTED_NODES.get(targetEl).destroy();
|
||||
const instance = Render(isFunc(component) ? component : () => component);
|
||||
targetEl.replaceChildren(instance.container);
|
||||
MOUNTED_NODES.set(targetEl, instance);
|
||||
return instance;
|
||||
};
|
||||
var SigPro = { $, $$, Render, Watch, Tag, If, For, Router, Mount };
|
||||
if (typeof window !== "undefined") {
|
||||
assign(window, SigPro);
|
||||
const tags = `div span p h1 h2 h3 h4 h5 h6 br hr section article aside nav main header footer address ul ol li dl dt dd a em strong small i b u mark time sub sup pre code blockquote details summary dialog form label input textarea select button option fieldset legend table thead tbody tfoot tr th td caption img video audio canvas svg iframe picture source progress meter`.split(" ");
|
||||
tags.forEach((tag) => {
|
||||
const helper = tag[0].toUpperCase() + tag.slice(1);
|
||||
if (!(helper in window))
|
||||
window[helper] = (p, c) => Tag(tag, p, c);
|
||||
});
|
||||
window.SigPro = Object.freeze(SigPro);
|
||||
}
|
||||
})();
|
||||
@@ -30,7 +30,7 @@
|
||||
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 via `$watch`.
|
||||
* **Reactive**: Attributes prefixed with `$` are automatically tracked via `Watch`.
|
||||
* **Self-Sane**: They automatically use `._cleanups` to destroy observers or event listeners when removed from the DOM.
|
||||
* **Themed**: Fully compatible with DaisyUI v5 theme system and Tailwind v4 utility classes.
|
||||
|
||||
@@ -115,7 +115,7 @@ Datepicker({
|
||||
```
|
||||
|
||||
### D. Imperative Toasts & Modals
|
||||
Trigger complex UI elements from your logic. These components use `$mount` internally to ensure they are properly cleaned up from memory after they close.
|
||||
Trigger complex UI elements from your logic. These components use `Mount` internally to ensure they are properly cleaned up from memory after they close.
|
||||
|
||||
```javascript
|
||||
// Show a notification (Self-destroying after 3s)
|
||||
|
||||
@@ -54,8 +54,8 @@ Thanks to **SigPro's synchronous initialization**, you no longer need to wrap yo
|
||||
import SigPro from 'sigpro';
|
||||
import { routes } from 'virtual:sigpro-routes';
|
||||
|
||||
// The Core already has $router ready
|
||||
$mount($router(routes), '#app');
|
||||
// The Core already has Router ready
|
||||
Mount(Router(routes), '#app');
|
||||
```
|
||||
|
||||
</div>
|
||||
@@ -71,12 +71,12 @@ export default () => div({ class: 'layout' }, [
|
||||
header([
|
||||
h1("SigPro App"),
|
||||
nav([
|
||||
button({ onclick: () => $router.go('/') }, "Home"),
|
||||
button({ onclick: () => $router.go('/blog') }, "Blog")
|
||||
button({ onclick: () => Router.go('/') }, "Home"),
|
||||
button({ onclick: () => Router.go('/blog') }, "Blog")
|
||||
])
|
||||
]),
|
||||
// Only the content inside <main> will be swapped reactively
|
||||
main($router(routes))
|
||||
main(Router(routes))
|
||||
]);
|
||||
```
|
||||
|
||||
@@ -132,7 +132,7 @@ The plugin follows a simple convention to transform your file system into a rout
|
||||
|
||||
## 5. How it Works (Vite Virtual Module)
|
||||
|
||||
The plugin generates a virtual module named `virtual:sigpro-routes`. This module exports an array of objects compatible with `$router()`:
|
||||
The plugin generates a virtual module named `virtual:sigpro-routes`. This module exports an array of objects compatible with `Router()`:
|
||||
|
||||
```javascript
|
||||
// Internal representation generated by the plugin
|
||||
|
||||
@@ -37,10 +37,11 @@
|
||||
"clean": "rm -rf dist",
|
||||
"prebuild": "npm run clean",
|
||||
"build:iife": "bun build ./index.js --bundle --outfile=./dist/sigpro.js --format=iife --global-name=SigPro",
|
||||
"build:docs": "bun build ./index.js --bundle --outfile=./docs/sigpro.js --format=iife --global-name=SigPro",
|
||||
"build:iife:min": "bun build ./index.js --bundle --outfile=./dist/sigpro.min.js --format=iife --global-name=SigPro --minify",
|
||||
"build:esm": "bun build ./index.js --bundle --outfile=./dist/sigpro.esm.js --format=esm",
|
||||
"build:esm:min": "bun build ./index.js --bundle --outfile=./dist/sigpro.esm.min.js --format=esm --minify",
|
||||
"build": "npm run build:iife && npm run build:iife:min && npm run build:esm && npm run build:esm:min",
|
||||
"build": "bun run build:iife && bun run build:iife:min && bun run build:esm && bun run build:esm:min && bun run build:docs",
|
||||
"docs": "bun x serve docs",
|
||||
"prepublishOnly": "npm run build"
|
||||
},
|
||||
|
||||
228
sigpro.d.ts
vendored
228
sigpro.d.ts
vendored
@@ -1,25 +1,13 @@
|
||||
// sigpro.d.ts
|
||||
|
||||
export declare module 'sigpro' {
|
||||
declare module 'sigpro' {
|
||||
type Signal<T> = {
|
||||
(): T;
|
||||
(value: T | ((prev: T) => T)): void;
|
||||
(value: T | ((prev: T) => T)): T;
|
||||
};
|
||||
|
||||
type ComputedSignal<T> = {
|
||||
(): T;
|
||||
};
|
||||
|
||||
type ReactiveObject<T extends object> = T;
|
||||
|
||||
type DeepReactive<T> = T extends object
|
||||
? { [K in keyof T]: DeepReactive<T[K]> }
|
||||
: T;
|
||||
type ComputedSignal<T> = () => T;
|
||||
|
||||
type Unsubscribe = () => void;
|
||||
|
||||
type CleanupFn = () => void;
|
||||
|
||||
type ViewInstance = {
|
||||
_isRuntime: true;
|
||||
container: HTMLDivElement;
|
||||
@@ -60,53 +48,46 @@ export declare module 'sigpro' {
|
||||
component: ComponentFunction | (() => Promise<ComponentFunction>);
|
||||
}
|
||||
|
||||
interface RouterOutlet extends HTMLElement {
|
||||
interface RouterOutlet {
|
||||
params: Signal<Record<string, string>>;
|
||||
to: (path: string) => void;
|
||||
back: () => void;
|
||||
path: () => string;
|
||||
}
|
||||
|
||||
function $<T>(initial: T, key?: string): Signal<T>;
|
||||
function $<T>(computed: () => T): ComputedSignal<T>;
|
||||
function $$<T extends object>(obj: T): T;
|
||||
|
||||
function $watch(
|
||||
target: () => any,
|
||||
fn?: never
|
||||
): Unsubscribe;
|
||||
function $watch(
|
||||
target: Array<() => any>,
|
||||
fn: () => void
|
||||
): Unsubscribe;
|
||||
|
||||
function $html(
|
||||
tag: string,
|
||||
props?: HtmlProps | ComponentChildren,
|
||||
content?: ComponentChildren
|
||||
): HTMLElement;
|
||||
|
||||
interface Transition {
|
||||
in: (el: HTMLElement) => void;
|
||||
out: (el: HTMLElement, done: () => void) => void;
|
||||
}
|
||||
|
||||
function $if(
|
||||
function $<T>(initial: T, storageKey?: string): Signal<T>;
|
||||
function $<T>(computed: () => T): ComputedSignal<T>;
|
||||
|
||||
function $$<T extends object>(obj: T): T;
|
||||
|
||||
function Watch(
|
||||
target: () => any,
|
||||
callback?: never
|
||||
): Unsubscribe;
|
||||
function Watch(
|
||||
target: Array<() => any>,
|
||||
callback: () => void
|
||||
): Unsubscribe;
|
||||
|
||||
function Tag(
|
||||
tag: string,
|
||||
props?: HtmlProps | ComponentChildren,
|
||||
children?: ComponentChildren
|
||||
): HTMLElement;
|
||||
|
||||
function If(
|
||||
condition: boolean | (() => boolean),
|
||||
thenVal: ComponentFunction | ComponentChild,
|
||||
otherwiseVal?: ComponentFunction | ComponentChild,
|
||||
transition?: Transition
|
||||
): HTMLDivElement;
|
||||
|
||||
namespace $if {
|
||||
function not(
|
||||
condition: boolean | (() => boolean),
|
||||
thenVal: ComponentFunction | ComponentChild,
|
||||
otherwiseVal?: ComponentFunction | ComponentChild
|
||||
): HTMLDivElement;
|
||||
}
|
||||
|
||||
function $for<T>(
|
||||
function For<T>(
|
||||
source: T[] | (() => T[]),
|
||||
render: (item: T, index: number) => ComponentChild,
|
||||
keyFn?: (item: T, index: number) => string | number,
|
||||
@@ -114,70 +95,115 @@ export declare module 'sigpro' {
|
||||
props?: HtmlProps
|
||||
): HTMLElement;
|
||||
|
||||
function $router(routes: Route[]): RouterOutlet;
|
||||
function Router(routes: Route[]): RouterOutlet;
|
||||
|
||||
function $mount(
|
||||
function Mount(
|
||||
component: ComponentFunction | HTMLElement,
|
||||
target: string | HTMLElement
|
||||
): ViewInstance | undefined;
|
||||
|
||||
const Div: (props?: HtmlProps | ComponentChildren, content?: ComponentChildren) => HTMLElement;
|
||||
const Span: (props?: HtmlProps | ComponentChildren, content?: ComponentChildren) => HTMLElement;
|
||||
const P: (props?: HtmlProps | ComponentChildren, content?: ComponentChildren) => HTMLElement;
|
||||
const H1: (props?: HtmlProps | ComponentChildren, content?: ComponentChildren) => HTMLElement;
|
||||
const H2: (props?: HtmlProps | ComponentChildren, content?: ComponentChildren) => HTMLElement;
|
||||
const H3: (props?: HtmlProps | ComponentChildren, content?: ComponentChildren) => HTMLElement;
|
||||
const H4: (props?: HtmlProps | ComponentChildren, content?: ComponentChildren) => HTMLElement;
|
||||
const H5: (props?: HtmlProps | ComponentChildren, content?: ComponentChildren) => HTMLElement;
|
||||
const H6: (props?: HtmlProps | ComponentChildren, content?: ComponentChildren) => HTMLElement;
|
||||
const Button: (props?: HtmlProps | ComponentChildren, content?: ComponentChildren) => HTMLElement;
|
||||
const Input: (props?: HtmlProps | ComponentChildren, content?: ComponentChildren) => HTMLElement;
|
||||
const Textarea: (props?: HtmlProps | ComponentChildren, content?: ComponentChildren) => HTMLElement;
|
||||
const Select: (props?: HtmlProps | ComponentChildren, content?: ComponentChildren) => HTMLElement;
|
||||
const Option: (props?: HtmlProps | ComponentChildren, content?: ComponentChildren) => HTMLElement;
|
||||
const Label: (props?: HtmlProps | ComponentChildren, content?: ComponentChildren) => HTMLElement;
|
||||
const Form: (props?: HtmlProps | ComponentChildren, content?: ComponentChildren) => HTMLElement;
|
||||
const A: (props?: HtmlProps | ComponentChildren, content?: ComponentChildren) => HTMLElement;
|
||||
const Img: (props?: HtmlProps | ComponentChildren, content?: ComponentChildren) => HTMLElement;
|
||||
const Ul: (props?: HtmlProps | ComponentChildren, content?: ComponentChildren) => HTMLElement;
|
||||
const Ol: (props?: HtmlProps | ComponentChildren, content?: ComponentChildren) => HTMLElement;
|
||||
const Li: (props?: HtmlProps | ComponentChildren, content?: ComponentChildren) => HTMLElement;
|
||||
const Nav: (props?: HtmlProps | ComponentChildren, content?: ComponentChildren) => HTMLElement;
|
||||
const Header: (props?: HtmlProps | ComponentChildren, content?: ComponentChildren) => HTMLElement;
|
||||
const Footer: (props?: HtmlProps | ComponentChildren, content?: ComponentChildren) => HTMLElement;
|
||||
const Section: (props?: HtmlProps | ComponentChildren, content?: ComponentChildren) => HTMLElement;
|
||||
const Article: (props?: HtmlProps | ComponentChildren, content?: ComponentChildren) => HTMLElement;
|
||||
const Aside: (props?: HtmlProps | ComponentChildren, content?: ComponentChildren) => HTMLElement;
|
||||
const Main: (props?: HtmlProps | ComponentChildren, content?: ComponentChildren) => HTMLElement;
|
||||
const Table: (props?: HtmlProps | ComponentChildren, content?: ComponentChildren) => HTMLElement;
|
||||
const Thead: (props?: HtmlProps | ComponentChildren, content?: ComponentChildren) => HTMLElement;
|
||||
const Tbody: (props?: HtmlProps | ComponentChildren, content?: ComponentChildren) => HTMLElement;
|
||||
const Tr: (props?: HtmlProps | ComponentChildren, content?: ComponentChildren) => HTMLElement;
|
||||
const Th: (props?: HtmlProps | ComponentChildren, content?: ComponentChildren) => HTMLElement;
|
||||
const Td: (props?: HtmlProps | ComponentChildren, content?: ComponentChildren) => HTMLElement;
|
||||
const Div: (props?: HtmlProps | ComponentChildren, children?: ComponentChildren) => HTMLElement;
|
||||
const Span: (props?: HtmlProps | ComponentChildren, children?: ComponentChildren) => HTMLElement;
|
||||
const P: (props?: HtmlProps | ComponentChildren, children?: ComponentChildren) => HTMLElement;
|
||||
const H1: (props?: HtmlProps | ComponentChildren, children?: ComponentChildren) => HTMLElement;
|
||||
const H2: (props?: HtmlProps | ComponentChildren, children?: ComponentChildren) => HTMLElement;
|
||||
const H3: (props?: HtmlProps | ComponentChildren, children?: ComponentChildren) => HTMLElement;
|
||||
const H4: (props?: HtmlProps | ComponentChildren, children?: ComponentChildren) => HTMLElement;
|
||||
const H5: (props?: HtmlProps | ComponentChildren, children?: ComponentChildren) => HTMLElement;
|
||||
const H6: (props?: HtmlProps | ComponentChildren, children?: ComponentChildren) => HTMLElement;
|
||||
const Button: (props?: HtmlProps | ComponentChildren, children?: ComponentChildren) => HTMLElement;
|
||||
const Input: (props?: HtmlProps | ComponentChildren, children?: ComponentChildren) => HTMLElement;
|
||||
const Textarea: (props?: HtmlProps | ComponentChildren, children?: ComponentChildren) => HTMLElement;
|
||||
const Select: (props?: HtmlProps | ComponentChildren, children?: ComponentChildren) => HTMLElement;
|
||||
const Option: (props?: HtmlProps | ComponentChildren, children?: ComponentChildren) => HTMLElement;
|
||||
const Label: (props?: HtmlProps | ComponentChildren, children?: ComponentChildren) => HTMLElement;
|
||||
const Form: (props?: HtmlProps | ComponentChildren, children?: ComponentChildren) => HTMLElement;
|
||||
const A: (props?: HtmlProps | ComponentChildren, children?: ComponentChildren) => HTMLElement;
|
||||
const Img: (props?: HtmlProps | ComponentChildren, children?: ComponentChildren) => HTMLElement;
|
||||
const Ul: (props?: HtmlProps | ComponentChildren, children?: ComponentChildren) => HTMLElement;
|
||||
const Ol: (props?: HtmlProps | ComponentChildren, children?: ComponentChildren) => HTMLElement;
|
||||
const Li: (props?: HtmlProps | ComponentChildren, children?: ComponentChildren) => HTMLElement;
|
||||
const Nav: (props?: HtmlProps | ComponentChildren, children?: ComponentChildren) => HTMLElement;
|
||||
const Header: (props?: HtmlProps | ComponentChildren, children?: ComponentChildren) => HTMLElement;
|
||||
const Footer: (props?: HtmlProps | ComponentChildren, children?: ComponentChildren) => HTMLElement;
|
||||
const Section: (props?: HtmlProps | ComponentChildren, children?: ComponentChildren) => HTMLElement;
|
||||
const Article: (props?: HtmlProps | ComponentChildren, children?: ComponentChildren) => HTMLElement;
|
||||
const Aside: (props?: HtmlProps | ComponentChildren, children?: ComponentChildren) => HTMLElement;
|
||||
const Main: (props?: HtmlProps | ComponentChildren, children?: ComponentChildren) => HTMLElement;
|
||||
const Table: (props?: HtmlProps | ComponentChildren, children?: ComponentChildren) => HTMLElement;
|
||||
const Thead: (props?: HtmlProps | ComponentChildren, children?: ComponentChildren) => HTMLElement;
|
||||
const Tbody: (props?: HtmlProps | ComponentChildren, children?: ComponentChildren) => HTMLElement;
|
||||
const Tr: (props?: HtmlProps | ComponentChildren, children?: ComponentChildren) => HTMLElement;
|
||||
const Th: (props?: HtmlProps | ComponentChildren, children?: ComponentChildren) => HTMLElement;
|
||||
const Td: (props?: HtmlProps | ComponentChildren, children?: ComponentChildren) => HTMLElement;
|
||||
|
||||
const SigPro: {
|
||||
$: typeof $;
|
||||
$$: typeof $$;
|
||||
$watch: typeof $watch;
|
||||
$html: typeof $html;
|
||||
$if: typeof $if;
|
||||
$for: typeof $for;
|
||||
$router: typeof $router;
|
||||
$mount: typeof $mount;
|
||||
Watch: typeof Watch;
|
||||
Tag: typeof Tag;
|
||||
If: typeof If;
|
||||
For: typeof For;
|
||||
Router: typeof Router;
|
||||
Mount: typeof Mount;
|
||||
};
|
||||
|
||||
export default SigPro;
|
||||
export {
|
||||
$,
|
||||
$$,
|
||||
Watch,
|
||||
Tag,
|
||||
If,
|
||||
For,
|
||||
Router,
|
||||
Mount,
|
||||
Div,
|
||||
Span,
|
||||
P,
|
||||
H1,
|
||||
H2,
|
||||
H3,
|
||||
H4,
|
||||
H5,
|
||||
H6,
|
||||
Button,
|
||||
Input,
|
||||
Textarea,
|
||||
Select,
|
||||
Option,
|
||||
Label,
|
||||
Form,
|
||||
A,
|
||||
Img,
|
||||
Ul,
|
||||
Ol,
|
||||
Li,
|
||||
Nav,
|
||||
Header,
|
||||
Footer,
|
||||
Section,
|
||||
Article,
|
||||
Aside,
|
||||
Main,
|
||||
Table,
|
||||
Thead,
|
||||
Tbody,
|
||||
Tr,
|
||||
Th,
|
||||
Td,
|
||||
};
|
||||
}
|
||||
|
||||
export declare global {
|
||||
declare global {
|
||||
const $: typeof import('sigpro').$;
|
||||
const $$: typeof import('sigpro').$$;
|
||||
const $watch: typeof import('sigpro').$watch;
|
||||
const $html: typeof import('sigpro').$html;
|
||||
const $if: typeof import('sigpro').$if;
|
||||
const $for: typeof import('sigpro').$for;
|
||||
const $router: typeof import('sigpro').$router;
|
||||
const $mount: typeof import('sigpro').$mount;
|
||||
|
||||
const Watch: typeof import('sigpro').Watch;
|
||||
const Tag: typeof import('sigpro').Tag;
|
||||
const If: typeof import('sigpro').If;
|
||||
const For: typeof import('sigpro').For;
|
||||
const Router: typeof import('sigpro').Router;
|
||||
const Mount: typeof import('sigpro').Mount;
|
||||
const Div: typeof import('sigpro').Div;
|
||||
const Span: typeof import('sigpro').Span;
|
||||
const P: typeof import('sigpro').P;
|
||||
@@ -216,12 +242,12 @@ export declare global {
|
||||
interface Window {
|
||||
$: typeof $;
|
||||
$$: typeof $$;
|
||||
$watch: typeof $watch;
|
||||
$html: typeof $html;
|
||||
$if: typeof $if;
|
||||
$for: typeof $for;
|
||||
$router: typeof $router;
|
||||
$mount: typeof $mount;
|
||||
Watch: typeof Watch;
|
||||
Tag: typeof Tag;
|
||||
If: typeof If;
|
||||
For: typeof For;
|
||||
Router: typeof Router;
|
||||
Mount: typeof Mount;
|
||||
SigPro: typeof import('sigpro').default;
|
||||
Div: typeof Div;
|
||||
Span: typeof Span;
|
||||
|
||||
Reference in New Issue
Block a user