improve docs
This commit is contained in:
30
package.json
30
package.json
@@ -4,10 +4,34 @@
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"exports": {
|
||||
".": "./index.js",
|
||||
"./plugins": "./plugins/index.js",
|
||||
"./vite/*": "./vite/*.js"
|
||||
".": {
|
||||
"import": "./index.js",
|
||||
"types": "./sigpro/sigpro.d.ts"
|
||||
},
|
||||
"./plugins": {
|
||||
"import": "./plugins/index.js",
|
||||
"types": "./plugins/index.d.ts"
|
||||
},
|
||||
"./vite/*": {
|
||||
"import": "./vite/*.js",
|
||||
"types": "./vite/*.d.ts"
|
||||
}
|
||||
},
|
||||
"types": "./sigpro/sigpro.d.ts",
|
||||
"typesVersions": {
|
||||
"*": {
|
||||
"*": ["./sigpro/sigpro.d.ts"],
|
||||
"plugins": ["./plugins/index.d.ts"]
|
||||
}
|
||||
},
|
||||
"files": [
|
||||
"index.js",
|
||||
"sigpro/",
|
||||
"plugins/",
|
||||
"vite/",
|
||||
"README.md",
|
||||
"LICENSE"
|
||||
],
|
||||
"homepage": "https://natxocc.github.io/sigpro/",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
106
plugins/index.d.ts
vendored
Normal file
106
plugins/index.d.ts
vendored
Normal file
@@ -0,0 +1,106 @@
|
||||
/**
|
||||
* SigPro Plugins
|
||||
* Official plugins for SigPro reactive framework
|
||||
*
|
||||
* @module sigpro/plugins
|
||||
*/
|
||||
|
||||
import type { SigPro } from '../sigpro/sigpro';
|
||||
|
||||
/**
|
||||
* Plugin context passed to all plugins
|
||||
*/
|
||||
export interface PluginContext {
|
||||
$: SigPro;
|
||||
}
|
||||
|
||||
/**
|
||||
* Base plugin interface
|
||||
*/
|
||||
export interface SigProPlugin {
|
||||
/** Plugin name for debugging */
|
||||
name: string;
|
||||
/** Plugin version */
|
||||
version?: string;
|
||||
/** Initialize plugin with SigPro instance */
|
||||
install: (context: PluginContext) => void | Promise<void>;
|
||||
}
|
||||
|
||||
/**
|
||||
* UI Plugin - Creates reactive UI components
|
||||
*
|
||||
* @example
|
||||
* ```javascript
|
||||
* import { UI } from 'sigpro/plugins';
|
||||
*
|
||||
* $.plugin(UI);
|
||||
*
|
||||
* // Now you can use UI components
|
||||
* const modal = UI.modal({
|
||||
* title: 'Hello',
|
||||
* content: 'This is a modal'
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
export const UI: SigProPlugin;
|
||||
|
||||
/**
|
||||
* Fetch Plugin - Reactive data fetching
|
||||
*
|
||||
* @example
|
||||
* ```javascript
|
||||
* import { Fetch } from 'sigpro/plugins';
|
||||
*
|
||||
* $.plugin(Fetch);
|
||||
*
|
||||
* // Reactive data fetching
|
||||
* const users = $.fetch('/api/users');
|
||||
* const user = $.fetch(() => `/api/users/${userId()}`);
|
||||
* ```
|
||||
*/
|
||||
export const Fetch: SigProPlugin;
|
||||
|
||||
/**
|
||||
* Debug Plugin - Development tools and logging
|
||||
*
|
||||
* @example
|
||||
* ```javascript
|
||||
* import { Debug } from 'sigpro/plugins';
|
||||
*
|
||||
* $.plugin(Debug);
|
||||
*
|
||||
* // Debug signals in console
|
||||
* $.debug(count); // Logs changes to console
|
||||
* ```
|
||||
*/
|
||||
export const Debug: SigProPlugin;
|
||||
|
||||
/**
|
||||
* Plugin options for each plugin
|
||||
*/
|
||||
export namespace PluginOptions {
|
||||
interface UIOptions {
|
||||
/** Prefix for CSS classes */
|
||||
classPrefix?: string;
|
||||
/** Default animation duration */
|
||||
animationDuration?: number;
|
||||
}
|
||||
|
||||
interface FetchOptions {
|
||||
/** Base URL for all requests */
|
||||
baseURL?: string;
|
||||
/** Default headers */
|
||||
headers?: Record<string, string>;
|
||||
/** Timeout in milliseconds */
|
||||
timeout?: number;
|
||||
}
|
||||
|
||||
interface DebugOptions {
|
||||
/** Enable verbose logging */
|
||||
verbose?: boolean;
|
||||
/** Log to console */
|
||||
logToConsole?: boolean;
|
||||
/** Enable time travel debugging */
|
||||
timeTravel?: boolean;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,4 @@
|
||||
// /plugins/index.js
|
||||
export { UI } from './ui.js';
|
||||
export { Fetch } from './fetch.js';
|
||||
export { Storage } from './storage.js';
|
||||
export { Debug } from './debug.js';
|
||||
export { Router } from './router.js';
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
// plugins/router.js
|
||||
export const Router = ($) => {
|
||||
|
||||
$.router = (routes) => {
|
||||
const sPath = $(window.location.hash.replace(/^#/, "") || "/");
|
||||
window.addEventListener("hashchange", () => sPath(window.location.hash.replace(/^#/, "") || "/"));
|
||||
|
||||
return div([
|
||||
() => {
|
||||
const current = sPath();
|
||||
const route = routes.find(r => {
|
||||
const rP = r.path.split('/').filter(Boolean);
|
||||
const cP = current.split('/').filter(Boolean);
|
||||
if (rP.length !== cP.length) return false;
|
||||
return rP.every((part, i) => part.startsWith(':') || part === cP[i]);
|
||||
}) || routes.find(r => r.path === "*");
|
||||
|
||||
if (!route) return h1("404 - Not Found");
|
||||
|
||||
// --- LA MEJORA AQUÍ ---
|
||||
const result = typeof route.component === 'function' ? route.component() : route.component;
|
||||
|
||||
// Si el componente es una Promesa (Lazy Loading de Vite), esperamos
|
||||
if (result instanceof Promise) {
|
||||
const $lazyNode = $(span("Cargando página..."));
|
||||
result.then(m => {
|
||||
// Si el módulo tiene un .default (export default), lo usamos
|
||||
const comp = typeof m === 'function' ? m() : (m.default ? m.default() : m);
|
||||
$lazyNode(comp);
|
||||
});
|
||||
return () => $lazyNode();
|
||||
}
|
||||
|
||||
return result instanceof Node ? result : span(String(result));
|
||||
}
|
||||
]);
|
||||
};
|
||||
|
||||
$.router.go = (path) => {
|
||||
window.location.hash = path.startsWith('/') ? path : `/${path}`;
|
||||
};
|
||||
|
||||
window._router = $.router;
|
||||
};
|
||||
@@ -1,31 +0,0 @@
|
||||
/**
|
||||
* SigPro Storage Plugin
|
||||
* Automatically synchronizes signals with localStorage.
|
||||
*/
|
||||
export const Storage = ($) => {
|
||||
/**
|
||||
* Persists a signal's value in localStorage.
|
||||
* @param {Function} $sig - The signal to persist.
|
||||
* @param {string} key - The localStorage key name.
|
||||
* @returns {Function} The same signal for chaining.
|
||||
*/
|
||||
_storage = ($sig, key) => {
|
||||
// 1. Initial Load: If there's data in storage, update the signal immediately
|
||||
const saved = localStorage.getItem(key);
|
||||
if (saved !== null) {
|
||||
try {
|
||||
$sig(JSON.parse(saved));
|
||||
} catch (e) {
|
||||
console.error(`[SigPro Storage] Error parsing key "${key}":`, e);
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Auto-Save: Every time the signal changes, update localStorage
|
||||
$(() => {
|
||||
const val = $sig();
|
||||
localStorage.setItem(key, JSON.stringify(val));
|
||||
});
|
||||
|
||||
return $sig;
|
||||
};
|
||||
};
|
||||
387
sigpro/sigpro.d.ts
vendored
Normal file
387
sigpro/sigpro.d.ts
vendored
Normal file
@@ -0,0 +1,387 @@
|
||||
/**
|
||||
* SigPro - Atomic Unified Reactive Engine
|
||||
* A lightweight, fine-grained reactivity system with built-in routing and plugin support.
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* // Create a reactive signal
|
||||
* const count = $(0);
|
||||
*
|
||||
* // Create a computed value
|
||||
* const double = $(() => count() * 2);
|
||||
*
|
||||
* // Create reactive HTML
|
||||
* const app = div({ $class: () => count() > 5 ? 'high' : 'low' }, [
|
||||
* h1("Counter Demo"),
|
||||
* p("Count: ", () => count()),
|
||||
* p("Double: ", () => double()),
|
||||
* button({ onclick: () => count(count() + 1) }, "Increment")
|
||||
* ]);
|
||||
*
|
||||
* // Mount to DOM
|
||||
* $.mount(app);
|
||||
* ```
|
||||
*/
|
||||
declare global {
|
||||
interface Window {
|
||||
/** Main SigPro instance */
|
||||
$: SigPro;
|
||||
/** HTML element creators (auto-generated from tags list) */
|
||||
div: typeof html;
|
||||
span: typeof html;
|
||||
p: typeof html;
|
||||
button: typeof html;
|
||||
h1: typeof html;
|
||||
h2: typeof html;
|
||||
h3: typeof html;
|
||||
ul: typeof html;
|
||||
ol: typeof html;
|
||||
li: typeof html;
|
||||
a: typeof html;
|
||||
label: typeof html;
|
||||
section: typeof html;
|
||||
nav: typeof html;
|
||||
main: typeof html;
|
||||
header: typeof html;
|
||||
footer: typeof html;
|
||||
input: typeof html;
|
||||
form: typeof html;
|
||||
img: typeof html;
|
||||
select: typeof html;
|
||||
option: typeof html;
|
||||
table: typeof html;
|
||||
thead: typeof html;
|
||||
tbody: typeof html;
|
||||
tr: typeof html;
|
||||
th: typeof html;
|
||||
td: typeof html;
|
||||
canvas: typeof html;
|
||||
video: typeof html;
|
||||
audio: typeof html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reactive Signal - A reactive state container
|
||||
* @template T Type of the stored value
|
||||
*/
|
||||
interface Signal<T> {
|
||||
/** Get the current value */
|
||||
(): T;
|
||||
/** Set a new value */
|
||||
(value: T): T;
|
||||
/** Update value based on previous value */
|
||||
(updater: (prev: T) => T): T;
|
||||
}
|
||||
|
||||
/**
|
||||
* Computed Signal - A reactive derived value
|
||||
* @template T Type of the computed value
|
||||
*/
|
||||
interface Computed<T> {
|
||||
/** Get the current computed value */
|
||||
(): T;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reactive Effect - A function that re-runs when dependencies change
|
||||
*/
|
||||
type Effect = () => void;
|
||||
|
||||
/**
|
||||
* HTML Content Types
|
||||
*/
|
||||
type HtmlContent =
|
||||
| string
|
||||
| number
|
||||
| boolean
|
||||
| null
|
||||
| undefined
|
||||
| Node
|
||||
| HtmlContent[]
|
||||
| (() => string | number | Node | null | undefined);
|
||||
|
||||
/**
|
||||
* HTML Attributes and Event Handlers
|
||||
*/
|
||||
interface HtmlProps extends Record<string, any> {
|
||||
/** Two-way binding for input values */
|
||||
$value?: Signal<any> | ((val: any) => void);
|
||||
/** Two-way binding for checkbox/radio checked state */
|
||||
$checked?: Signal<boolean> | ((val: boolean) => void);
|
||||
/** Reactive class binding */
|
||||
$class?: string | (() => string);
|
||||
/** Reactive style binding */
|
||||
$style?: string | object | (() => string | object);
|
||||
/** Reactive attribute binding (any attribute can be prefixed with $) */
|
||||
[key: `$${string}`]: any;
|
||||
/** Standard event handlers */
|
||||
onclick?: (event: MouseEvent) => void;
|
||||
oninput?: (event: Event) => void;
|
||||
onchange?: (event: Event) => void;
|
||||
onsubmit?: (event: Event) => void;
|
||||
onkeydown?: (event: KeyboardEvent) => void;
|
||||
onkeyup?: (event: KeyboardEvent) => void;
|
||||
onfocus?: (event: FocusEvent) => void;
|
||||
onblur?: (event: FocusEvent) => void;
|
||||
onmouseover?: (event: MouseEvent) => void;
|
||||
onmouseout?: (event: MouseEvent) => void;
|
||||
[key: `on${string}`]: ((event: any) => void) | undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Route Configuration
|
||||
*/
|
||||
interface Route {
|
||||
/** URL path pattern (supports :params and * wildcard) */
|
||||
path: string;
|
||||
/** Component to render (can be sync, async, or Promise) */
|
||||
component: ((params: Record<string, string>) => HTMLElement | Promise<HTMLElement>) | Promise<any> | HTMLElement;
|
||||
}
|
||||
|
||||
/**
|
||||
* Router Instance
|
||||
*/
|
||||
interface Router {
|
||||
/** Router container element */
|
||||
(routes: Route[]): HTMLElement;
|
||||
/** Programmatic navigation */
|
||||
go: (path: string) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Plugin System
|
||||
*/
|
||||
interface Plugin {
|
||||
/**
|
||||
* Extend SigPro with custom functionality or load external scripts
|
||||
* @param source - Plugin function, script URL, or array of URLs
|
||||
* @returns SigPro instance (sync) or Promise (async loading)
|
||||
*/
|
||||
(source: ((sigpro: SigPro) => void) | string | string[]): SigPro | Promise<SigPro>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Main SigPro Interface
|
||||
*/
|
||||
interface SigPro {
|
||||
/**
|
||||
* Create a reactive Signal or Computed value
|
||||
* @template T Type of the value
|
||||
* @param initial - Initial value or computed function
|
||||
* @param key - Optional localStorage key for persistence
|
||||
* @returns Reactive signal or computed function
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* // Signal with localStorage persistence
|
||||
* const count = $(0, 'app.count');
|
||||
*
|
||||
* // Computed value
|
||||
* const double = $(() => count() * 2);
|
||||
*
|
||||
* // Reactive effect (runs automatically)
|
||||
* $(() => console.log('Count changed:', count()));
|
||||
* ```
|
||||
*/
|
||||
<T>(initial: T, key?: string): Signal<T>;
|
||||
<T>(computed: () => T, key?: string): Computed<T>;
|
||||
|
||||
/**
|
||||
* Create reactive HTML elements with hyperscript syntax
|
||||
* @param tag - HTML tag name
|
||||
* @param props - Attributes, events, and reactive bindings
|
||||
* @param content - Child nodes or content
|
||||
* @returns Live DOM element with reactivity
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* const name = $('World');
|
||||
*
|
||||
* const element = $.html('div',
|
||||
* { class: 'greeting', $class: () => name().length > 5 ? 'long' : '' },
|
||||
* [
|
||||
* $.html('h1', 'Hello'),
|
||||
* $.html('p', () => `Hello, ${name()}!`),
|
||||
* $.html('input', {
|
||||
* $value: name,
|
||||
* placeholder: 'Enter your name'
|
||||
* })
|
||||
* ]
|
||||
* );
|
||||
* ```
|
||||
*/
|
||||
html: typeof html;
|
||||
|
||||
/**
|
||||
* Mount a component to the DOM
|
||||
* @param node - Component or element to mount
|
||||
* @param target - Target element or CSS selector (default: document.body)
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* // Mount to body
|
||||
* $.mount(app);
|
||||
*
|
||||
* // Mount to specific element
|
||||
* $.mount(app, '#app');
|
||||
*
|
||||
* // Mount with component function
|
||||
* $.mount(() => div("Dynamic component"));
|
||||
* ```
|
||||
*/
|
||||
mount: typeof mount;
|
||||
|
||||
/**
|
||||
* Initialize a hash-based router
|
||||
* @param routes - Array of route configurations
|
||||
* @returns Router container element
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* const routes = [
|
||||
* { path: '/', component: Home },
|
||||
* { path: '/user/:id', component: UserProfile },
|
||||
* { path: '*', component: NotFound }
|
||||
* ];
|
||||
*
|
||||
* const router = $.router(routes);
|
||||
* $.mount(router);
|
||||
*
|
||||
* // Navigate programmatically
|
||||
* $.router.go('/user/42');
|
||||
* ```
|
||||
*/
|
||||
router: Router;
|
||||
|
||||
/**
|
||||
* Extend SigPro with plugins or load external scripts
|
||||
* @param source - Plugin function, script URL, or array of URLs
|
||||
* @returns SigPro instance or Promise
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* // Load external library
|
||||
* await $.plugin('https://cdn.jsdelivr.net/npm/lodash/lodash.min.js');
|
||||
*
|
||||
* // Register plugin
|
||||
* $.plugin(($) => {
|
||||
* $.customMethod = () => console.log('Custom method');
|
||||
* });
|
||||
*
|
||||
* // Load multiple scripts
|
||||
* await $.plugin(['lib1.js', 'lib2.js']);
|
||||
* ```
|
||||
*/
|
||||
plugin: Plugin;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a reactive HTML element
|
||||
* @param tag - HTML tag name
|
||||
* @param props - Attributes and event handlers
|
||||
* @param content - Child content
|
||||
* @returns Live DOM element
|
||||
*/
|
||||
function html(tag: string, props?: HtmlProps | HtmlContent, content?: HtmlContent): HTMLElement;
|
||||
|
||||
/**
|
||||
* Mount a component to the DOM
|
||||
* @param node - Component or element to mount
|
||||
* @param target - Target element or CSS selector
|
||||
*/
|
||||
function mount(node: HTMLElement | (() => HTMLElement), target?: HTMLElement | string): void;
|
||||
|
||||
/**
|
||||
* Type-safe HTML element creators for common tags
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* // Using tag functions directly
|
||||
* const myDiv = div({ class: 'container' }, [
|
||||
* h1("Title"),
|
||||
* p("Paragraph text"),
|
||||
* button({ onclick: () => alert('Clicked!') }, "Click me")
|
||||
* ]);
|
||||
* ```
|
||||
*/
|
||||
interface HtmlTagCreator {
|
||||
/**
|
||||
* Create HTML element with props and content
|
||||
* @param props - HTML attributes and event handlers
|
||||
* @param content - Child nodes or content
|
||||
* @returns HTMLElement
|
||||
*/
|
||||
(props?: HtmlProps | HtmlContent, content?: HtmlContent): HTMLElement;
|
||||
}
|
||||
|
||||
// Type-safe tag creators
|
||||
const div: HtmlTagCreator;
|
||||
const span: HtmlTagCreator;
|
||||
const p: HtmlTagCreator;
|
||||
const button: HtmlTagCreator;
|
||||
const h1: HtmlTagCreator;
|
||||
const h2: HtmlTagCreator;
|
||||
const h3: HtmlTagCreator;
|
||||
const ul: HtmlTagCreator;
|
||||
const ol: HtmlTagCreator;
|
||||
const li: HtmlTagCreator;
|
||||
const a: HtmlTagCreator;
|
||||
const label: HtmlTagCreator;
|
||||
const section: HtmlTagCreator;
|
||||
const nav: HtmlTagCreator;
|
||||
const main: HtmlTagCreator;
|
||||
const header: HtmlTagCreator;
|
||||
const footer: HtmlTagCreator;
|
||||
const input: HtmlTagCreator;
|
||||
const form: HtmlTagCreator;
|
||||
const img: HtmlTagCreator;
|
||||
const select: HtmlTagCreator;
|
||||
const option: HtmlTagCreator;
|
||||
const table: HtmlTagCreator;
|
||||
const thead: HtmlTagCreator;
|
||||
const tbody: HtmlTagCreator;
|
||||
const tr: HtmlTagCreator;
|
||||
const th: HtmlTagCreator;
|
||||
const td: HtmlTagCreator;
|
||||
const canvas: HtmlTagCreator;
|
||||
const video: HtmlTagCreator;
|
||||
const audio: HtmlTagCreator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper types for common use cases
|
||||
*/
|
||||
export namespace SigProTypes {
|
||||
/**
|
||||
* Extract the value type from a Signal
|
||||
*/
|
||||
type SignalValue<T> = T extends Signal<infer U> ? U : never;
|
||||
|
||||
/**
|
||||
* Extract the return type from a Computed
|
||||
*/
|
||||
type ComputedValue<T> = T extends Computed<infer U> ? U : never;
|
||||
|
||||
/**
|
||||
* Props for a component function
|
||||
*/
|
||||
interface ComponentProps {
|
||||
children?: HtmlContent;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
/**
|
||||
* Component function type
|
||||
*/
|
||||
type Component<P extends ComponentProps = ComponentProps> = (props: P) => HTMLElement;
|
||||
|
||||
/**
|
||||
* Async component type (for lazy loading)
|
||||
*/
|
||||
type AsyncComponent = () => Promise<{ default: Component }>;
|
||||
}
|
||||
|
||||
export {};
|
||||
|
||||
// Make sure $ is available globally
|
||||
declare const $: SigPro;
|
||||
@@ -2,28 +2,19 @@
|
||||
* SigPro - Atomic Unified Reactive Engine
|
||||
* A lightweight, fine-grained reactivity system with built-in routing and plugin support.
|
||||
* @author Gemini & User
|
||||
*
|
||||
* Type definitions available in sigpro.d.ts
|
||||
*/
|
||||
(() => {
|
||||
/** @type {Function|null} Internal tracker for the currently executing reactive effect. */
|
||||
let activeEffect = null;
|
||||
|
||||
/**
|
||||
* @typedef {Object} SigPro
|
||||
* @property {function(any|function, string=): Function} $ - Creates a Signal or Computed. Optional key for localStorage.
|
||||
* @property {function(string, Object=, any=): HTMLElement} html - Creates a reactive HTML element.
|
||||
* @property {function((HTMLElement|function), (HTMLElement|string)=): void} mount - Mounts a component to the DOM.
|
||||
* @property {function(Array<Object>): HTMLElement} router - Initializes a hash-based router.
|
||||
* @property {function(string): void} router.go - Programmatic navigation to a hash path.
|
||||
* @property {function((function|string|Array<string>)): (Promise<SigPro>|SigPro)} plugin - Extends SigPro or loads external scripts.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Creates a Signal (state) or a Computed/Effect (reaction).
|
||||
* Supports optional persistence in localStorage.
|
||||
* * @param {any|function} initial - Initial value or a function for computed logic.
|
||||
* @param {string} [key] - Optional localStorage key for automatic state persistence.
|
||||
* @returns {Function} A reactive accessor/mutator function.
|
||||
*/
|
||||
* Creates a reactive Signal or Computed value
|
||||
* @param {any|Function} initial - Initial value or computed function
|
||||
* @param {string} [key] - Optional localStorage key for persistence
|
||||
* @returns {Function} Reactive accessor/mutator function
|
||||
*/
|
||||
const $ = (initial, key) => {
|
||||
const subs = new Set();
|
||||
|
||||
@@ -69,11 +60,11 @@
|
||||
};
|
||||
|
||||
/**
|
||||
* Hyperscript engine to render reactive HTML nodes.
|
||||
* @param {string} tag - The HTML tag name (e.g., 'div', 'button').
|
||||
* @param {Object} [props] - Attributes, events (onclick), or reactive props ($value, $class).
|
||||
* @param {any} [content] - String, Node, Array of nodes, or reactive function.
|
||||
* @returns {HTMLElement} A live DOM element linked to SigPro signals.
|
||||
* Creates reactive HTML elements
|
||||
* @param {string} tag - HTML tag name
|
||||
* @param {Object} [props] - Attributes and event handlers
|
||||
* @param {any} [content] - Child content
|
||||
* @returns {HTMLElement}
|
||||
*/
|
||||
$.html = (tag, props = {}, content = []) => {
|
||||
const el = document.createElement(tag);
|
||||
@@ -87,12 +78,10 @@
|
||||
el.addEventListener(key.toLowerCase().slice(2), val);
|
||||
} else if (key.startsWith('$')) {
|
||||
const attr = key.slice(1);
|
||||
// Two-way binding for inputs
|
||||
if ((attr === 'value' || attr === 'checked') && typeof val === 'function') {
|
||||
const ev = attr === 'checked' ? 'change' : 'input';
|
||||
el.addEventListener(ev, e => val(attr === 'checked' ? e.target.checked : e.target.value));
|
||||
}
|
||||
// Reactive attribute update
|
||||
$(() => {
|
||||
const v = typeof val === 'function' ? val() : val;
|
||||
if (attr === 'value' || attr === 'checked') el[attr] = v;
|
||||
@@ -126,9 +115,9 @@
|
||||
tags.forEach(t => window[t] = (p, c) => $.html(t, p, c));
|
||||
|
||||
/**
|
||||
* Application mounter.
|
||||
* @param {HTMLElement|function} node - Root component or element to mount.
|
||||
* @param {HTMLElement|string} [target=document.body] - Target element or CSS selector.
|
||||
* Mounts a component to the DOM
|
||||
* @param {HTMLElement|Function} node - Component or element to mount
|
||||
* @param {HTMLElement|string} [target=document.body] - Target element or selector
|
||||
*/
|
||||
$.mount = (node, target = document.body) => {
|
||||
const el = typeof target === 'string' ? document.querySelector(target) : target;
|
||||
@@ -139,11 +128,10 @@
|
||||
};
|
||||
|
||||
/**
|
||||
* Initializes a reactive hash-based router.
|
||||
* Maps URL hash changes to component rendering and supports Vite's dynamic imports.
|
||||
* * @param {Array<{path: string, component: Function|Promise|HTMLElement}>} routes - Array of route objects.
|
||||
* @returns {HTMLElement} A reactive div container that swaps content based on the current hash.
|
||||
*/
|
||||
* Initializes a hash-based router
|
||||
* @param {Array<{path: string, component: Function|Promise|HTMLElement}>} routes
|
||||
* @returns {HTMLElement}
|
||||
*/
|
||||
$.router = (routes) => {
|
||||
const sPath = $(window.location.hash.replace(/^#/, "") || "/");
|
||||
window.addEventListener("hashchange", () => sPath(window.location.hash.replace(/^#/, "") || "/"));
|
||||
@@ -185,20 +173,17 @@
|
||||
};
|
||||
|
||||
/**
|
||||
* Programmatically navigates to a specific path using the hash.
|
||||
* * @param {string} path - The destination path (e.g., '/home' or 'settings').
|
||||
* @example
|
||||
* $.router.go('/profile/42');
|
||||
* Programmatic navigation
|
||||
* @param {string} path - Destination path
|
||||
*/
|
||||
$.router.go = (path) => {
|
||||
window.location.hash = path.startsWith('/') ? path : `/${path}`;
|
||||
};
|
||||
|
||||
/**
|
||||
* Polymorphic Plugin System.
|
||||
* Registers internal functions or loads external .js files as plugins.
|
||||
* @param {function|string|Array<string>} source - Plugin function or URL(s).
|
||||
* @returns {Promise<SigPro>|SigPro} Resolves with the $ instance after loading or registering.
|
||||
* Plugin system - extends SigPro or loads external scripts
|
||||
* @param {Function|string|string[]} source - Plugin or script URL(s)
|
||||
* @returns {Promise<SigPro>|SigPro}
|
||||
*/
|
||||
$.plugin = (source) => {
|
||||
if (typeof source === 'function') {
|
||||
|
||||
261
vite/index.d.ts
vendored
Normal file
261
vite/index.d.ts
vendored
Normal file
@@ -0,0 +1,261 @@
|
||||
/**
|
||||
* SigPro Vite Plugin
|
||||
* Provides file-based routing and development tools for Vite
|
||||
*
|
||||
* @example
|
||||
* ```javascript
|
||||
* // vite.config.js
|
||||
* import { defineConfig } from 'vite';
|
||||
* import sigproRouter from 'sigpro/vite';
|
||||
*
|
||||
* export default defineConfig({
|
||||
* plugins: [sigproRouter()]
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
|
||||
import type { Plugin } from 'vite';
|
||||
|
||||
/**
|
||||
* File-based routing configuration options
|
||||
*/
|
||||
export interface SigProRouterOptions {
|
||||
/**
|
||||
* Directory where page components are stored
|
||||
* @default 'src/pages'
|
||||
* @example
|
||||
* ```javascript
|
||||
* sigproRouter({ pagesDir: 'src/views' })
|
||||
* ```
|
||||
*/
|
||||
pagesDir?: string;
|
||||
|
||||
/**
|
||||
* File extensions to consider as pages
|
||||
* @default ['.js', '.jsx', '.ts', '.tsx']
|
||||
* @example
|
||||
* ```javascript
|
||||
* sigproRouter({ extensions: ['.js', '.jsx'] })
|
||||
* ```
|
||||
*/
|
||||
extensions?: string[];
|
||||
|
||||
/**
|
||||
* Custom route transformation function
|
||||
* @param filePath - Absolute path to the file
|
||||
* @returns Custom route path or null to skip
|
||||
* @example
|
||||
* ```javascript
|
||||
* sigproRouter({
|
||||
* transformRoute: (path) => {
|
||||
* // Convert 'src/pages/blog/post.js' to '/blog/post'
|
||||
* return path.replace(/^src\/pages/, '').replace(/\.js$/, '');
|
||||
* }
|
||||
* })
|
||||
* ```
|
||||
*/
|
||||
transformRoute?: (filePath: string) => string | null;
|
||||
|
||||
/**
|
||||
* Base path prefix for all routes
|
||||
* @default ''
|
||||
* @example
|
||||
* ```javascript
|
||||
* sigproRouter({ basePath: '/app' }) // Routes become /app/about, /app/users, etc.
|
||||
* ```
|
||||
*/
|
||||
basePath?: string;
|
||||
|
||||
/**
|
||||
* Custom 404 component path
|
||||
* @default null (uses default "404 - Not Found")
|
||||
* @example
|
||||
* ```javascript
|
||||
* sigproRouter({ notFoundComponent: 'src/pages/NotFound.js' })
|
||||
* ```
|
||||
*/
|
||||
notFoundComponent?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Route configuration generated by the plugin
|
||||
*/
|
||||
export interface GeneratedRoute {
|
||||
/** URL path pattern (supports :params and * wildcard) */
|
||||
path: string;
|
||||
/** Lazy-loaded component */
|
||||
component: () => Promise<any>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Virtual module ID for generated routes
|
||||
* @example
|
||||
* ```javascript
|
||||
* import { routes } from 'virtual:sigpro-routes';
|
||||
* ```
|
||||
*/
|
||||
export const virtualModuleId = 'virtual:sigpro-routes';
|
||||
|
||||
/**
|
||||
* Vite plugin for SigPro file-based routing
|
||||
*
|
||||
* Features:
|
||||
* - 📁 File-based routing from `src/pages` directory
|
||||
* - 🔄 Automatic route generation
|
||||
* - 🚀 Lazy-loading support with code splitting
|
||||
* - 📦 Dynamic routes with `[param]` syntax → `/:param`
|
||||
* - ⭐ Catch-all routes with `[...param]` syntax → `/*`
|
||||
* - 🎯 Index routes (`index.js` becomes `/`)
|
||||
* - 🧹 Automatic sorting (static routes first, then dynamic)
|
||||
* - 🎨 404 fallback generation
|
||||
*
|
||||
* @example
|
||||
* **File Structure:**
|
||||
* ```
|
||||
* src/pages/
|
||||
* ├── index.js → '/'
|
||||
* ├── about.js → '/about'
|
||||
* ├── blog/
|
||||
* │ ├── index.js → '/blog'
|
||||
* │ ├── [slug].js → '/blog/:slug'
|
||||
* │ └── [...catch].js → '/blog/*'
|
||||
* └── user/
|
||||
* ├── index.js → '/user'
|
||||
* └── [id].js → '/user/:id'
|
||||
* ```
|
||||
*
|
||||
* @example
|
||||
* **Generated Routes:**
|
||||
* ```javascript
|
||||
* // virtual:sigpro-routes
|
||||
* export const routes = [
|
||||
* { path: '/', component: () => import('src/pages/index.js') },
|
||||
* { path: '/about', component: () => import('src/pages/about.js') },
|
||||
* { path: '/blog', component: () => import('src/pages/blog/index.js') },
|
||||
* { path: '/blog/:slug', component: () => import('src/pages/blog/[slug].js') },
|
||||
* { path: '/blog/*', component: () => import('src/pages/blog/[...catch].js') },
|
||||
* { path: '/user', component: () => import('src/pages/user/index.js') },
|
||||
* { path: '/user/:id', component: () => import('src/pages/user/[id].js') },
|
||||
* { path: '*', component: () => import('src/pages/NotFound.js') } // If exists
|
||||
* ];
|
||||
* ```
|
||||
*
|
||||
* @example
|
||||
* **Usage with SigPro:**
|
||||
* ```javascript
|
||||
* // main.js
|
||||
* import { routes } from 'virtual:sigpro-routes';
|
||||
* import { $ } from 'sigpro';
|
||||
*
|
||||
* const router = $.router(routes);
|
||||
* $.mount(router, '#app');
|
||||
* ```
|
||||
*
|
||||
* @param options - Plugin configuration options
|
||||
* @returns Vite plugin object
|
||||
*/
|
||||
export default function sigproRouter(options?: SigProRouterOptions): Plugin;
|
||||
|
||||
/**
|
||||
* Page component props for route components
|
||||
* @template Params - Type of route parameters
|
||||
*/
|
||||
export interface PageProps<Params = Record<string, string>> {
|
||||
/** URL parameters from dynamic routes (e.g., :id, :slug) */
|
||||
params: Params;
|
||||
/** URL search parameters (query string) */
|
||||
query: URLSearchParams;
|
||||
/** Current pathname without query string */
|
||||
pathname: string;
|
||||
/** Children components (for nested routes) */
|
||||
children?: any;
|
||||
}
|
||||
|
||||
/**
|
||||
* Route component type
|
||||
* @template Params - Type of route parameters
|
||||
*/
|
||||
export type PageComponent<Params = Record<string, string>> = (
|
||||
props: PageProps<Params>
|
||||
) => HTMLElement | Promise<HTMLElement>;
|
||||
|
||||
/**
|
||||
* Helper function to create typed route components
|
||||
* Provides better TypeScript support and autocompletion
|
||||
*
|
||||
* @example
|
||||
* ```javascript
|
||||
* // pages/user/[id].js
|
||||
* import { createPage } from 'sigpro/vite';
|
||||
*
|
||||
* export default createPage(({ params, query }) => {
|
||||
* const { id } = params; // TypeScript knows this is a string
|
||||
* const page = query.get('page') || 1;
|
||||
*
|
||||
* return div({ class: 'user-profile' }, [
|
||||
* h1(`User ${id}`),
|
||||
* p(`Page: ${page}`),
|
||||
* button({
|
||||
* onclick: () => console.log('Viewing user', id)
|
||||
* }, 'View Profile')
|
||||
* ]);
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* @example
|
||||
* **With TypeScript:**
|
||||
* ```typescript
|
||||
* // pages/user/[id].tsx
|
||||
* interface UserParams {
|
||||
* id: string;
|
||||
* }
|
||||
*
|
||||
* export default createPage<UserParams>(({ params, query }) => {
|
||||
* const userId = params.id; // TypeScript knows this is string
|
||||
* // ... rest of component
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
export function createPage<Params = Record<string, string>>(
|
||||
component: (props: PageProps<Params>) => HTMLElement | Promise<HTMLElement>
|
||||
): PageComponent<Params>;
|
||||
|
||||
/**
|
||||
* Type guard to check if a component is lazy-loaded
|
||||
* @param component - Component to check
|
||||
* @returns True if component is a lazy-loaded module
|
||||
*/
|
||||
export function isLazyComponent(component: any): component is () => Promise<any>;
|
||||
|
||||
/**
|
||||
* Utility to preload routes for better performance
|
||||
* @example
|
||||
* ```javascript
|
||||
* import { preloadRoutes } from 'sigpro/vite';
|
||||
*
|
||||
* // Preload routes after initial load
|
||||
* preloadRoutes(['/about', '/blog']);
|
||||
* ```
|
||||
*/
|
||||
export function preloadRoutes(paths: string[]): Promise<void>;
|
||||
|
||||
/**
|
||||
* Debug helper to inspect generated routes
|
||||
* @example
|
||||
* ```javascript
|
||||
* // Only in development
|
||||
* if (import.meta.env.DEV) {
|
||||
* const routes = await import('virtual:sigpro-routes');
|
||||
* console.table(routes);
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
export function inspectRoutes(): void;
|
||||
|
||||
/**
|
||||
* Virtual module declarations for client-side code
|
||||
* This allows TypeScript to understand the virtual module
|
||||
*/
|
||||
declare module 'virtual:sigpro-routes' {
|
||||
export const routes: GeneratedRoute[];
|
||||
}
|
||||
@@ -1,4 +1,28 @@
|
||||
// src/vite/index.js
|
||||
import sigproRouterPlugin from './sigproRouter.js';
|
||||
export const sigproRouter = sigproRouterPlugin;
|
||||
export default sigproRouterPlugin;
|
||||
/**
|
||||
* SigPro Vite Plugin
|
||||
* @module sigpro/vite
|
||||
*/
|
||||
|
||||
// ✅ Correcto: importas la función y la llamas como quieras
|
||||
import sigproRouter from './router.js';
|
||||
|
||||
/**
|
||||
* Vite plugin for SigPro file-based routing
|
||||
*
|
||||
* @example
|
||||
* ```javascript
|
||||
* // vite.config.js
|
||||
* import sigproRouter from 'sigpro/vite';
|
||||
*
|
||||
* export default {
|
||||
* plugins: [sigproRouter()]
|
||||
* };
|
||||
* ```
|
||||
*
|
||||
* @param {import('./index.d.ts').SigProRouterOptions} [options] - Plugin configuration options
|
||||
* @returns {import('vite').Plugin} Vite plugin instance
|
||||
*/
|
||||
export default sigproRouter;
|
||||
|
||||
// Opcional: también exportar como named export
|
||||
export { sigproRouter };
|
||||
Reference in New Issue
Block a user