12 Commits
1.1.1 ... 1.1.3

Author SHA1 Message Date
9fefa6dcb1 repair docs 2026-04-04 18:09:23 +02:00
Natxo
8f9d01e766 Update Readme.md 2026-04-04 14:50:55 +02:00
Natxo
7956d2b9b0 Update Readme.md 2026-04-04 14:49:32 +02:00
3f8273523c Docs 2026-04-04 14:48:40 +02:00
Natxo
7d2af57ac1 Update Readme.md 2026-04-04 14:43:22 +02:00
Natxo
a677f5d00b Update Readme.md 2026-04-04 14:37:31 +02:00
Natxo
95042a2e36 Update Readme.md 2026-04-04 14:36:11 +02:00
Natxo
ede3caa7d6 Update Readme.md 2026-04-04 14:35:50 +02:00
Natxo
78bac75fd5 Update Readme.md 2026-04-04 14:35:29 +02:00
Natxo
1478a7d63d Update Readme.md 2026-04-04 14:33:34 +02:00
bd63afa23b new build esm 2026-04-04 14:07:21 +02:00
00d114630d New types 2026-04-04 03:00:16 +02:00
13 changed files with 2629 additions and 179 deletions

153
Readme.md
View File

@@ -1,6 +1,8 @@
# SigPro UI (W.I.P.) # SigPro UI
**SigPro UI** is a lightweight, ultra-fast, and reactive component library built for the **SigPro** reactivity core. It provides a set of high-quality, accessible, and themeable UI components leveraging the power of **Tailwind CSS v4** and **daisyUI v5**. **SigPro UI** is a lightweight, ultra-fast, and reactive component library built for the **SigPro** reactivity core. It provides a set of high-quality, accessible, and themeable UI components with **zero external dependencies** - everything is included.
**SigPro UI** is a complete, self-contained UI library + reactive core in under **45KB gzip** (<15KB JS + <30KB CSS). 🎉
Unlike heavy frameworks, SigPro UI focuses on a **"Zero-Build"** philosophy, allowing you to build complex reactive interfaces with a functional, declarative syntax that runs natively in the browser. Unlike heavy frameworks, SigPro UI focuses on a **"Zero-Build"** philosophy, allowing you to build complex reactive interfaces with a functional, declarative syntax that runs natively in the browser.
@@ -8,96 +10,73 @@ Unlike heavy frameworks, SigPro UI focuses on a **"Zero-Build"** philosophy, all
## Features ## Features
* **Signals-Based Reactivity**: Powered by SigPro for granular DOM updates. - **Signals-Based Reactivity**: Powered by SigPro for granular DOM updates
* **DaisyUI v5 Integration**: Beautiful, semantic components out of the box. - **Self-Contained Styling**: Full CSS included - no external frameworks needed
* **Tree Shaking Ready**: Import only what you need. - **Built on daisyUI v5**: Modern, utility-first styling out of the box
* **Zero-Import Option**: Inject all components into the global scope with one command. - **Tree Shaking Ready**: Import only what you need
* **Lightweight**: Minimal footprint with a focus on performance. - **Zero-Import Option**: Inject all components into the global scope with one command
- **Lightweight**: Minimal footprint with everything bundled
--- ---
## Prerequisites ## Installation
To use SigPro UI, your project must include: ### Via NPM
1. **SigPro Core**: `npm install sigpro` (or via CDN). ```bash
2. **Tailwind CSS v4**: For utility-first styling.
3. **daisyUI v5**: The component engine for Tailwind.
## 1. Prerequisites & Installation
SigPro UI is built as an extension of the SigPro reactivity system. You need to install the core library first.
### Step 1: Install SigPro Core
```Bash
bun add sigpro
# or
npm install sigpro
```
### Step 2: Install SigPro UI
```Bash
bun add sigpro-ui
# or
npm install sigpro-ui npm install sigpro-ui
``` ```
### Via CDN (Browser) ### Via CDN (Browser)
```html ```html
<script src="https://unpkg.com/sigpro"></script> <!-- SigPro UI with styles included -->
<script src="https://unpkg.com/sigpro-ui"></script> <script src="https://unpkg.com/sigpro-ui"></script>
<link href="https://unpkg.com/sigpro-ui/css" rel="stylesheet">
``` ```
**That's it!** No additional CSS files, no configuration, no build step. SigPro core is included internally.
--- ---
## 2. Styling Setup (Tailwind CSS v4) ## Usage
SigPro UI components rely on **Tailwind CSS** and **daisyUI** for styling. You don't need a complex tailwind.config.js; simply configure your main CSS entry point.
Create or edit your global CSS file (e.g., style.css): You can use SigPro UI in two ways: **Modular** (Recommended) or **Global** (Fastest for prototyping).
```css
/* src/style.css */
@import "tailwindcss";
@plugin "daisyui";
/* Optional: Your custom theme overrides */
:root {
--primary: #570df8;
--secondary: #f000b8;
}
```
---
## Setup & Usage
You can use SigPro UI in two ways: **Modular** (Recommended for production) or **Global** (Fastest for prototyping).
### 1. Modular Approach (Tree Shaking) ### 1. Modular Approach (Tree Shaking)
Import only the components you need. This keeps your bundle small.
Import only the components you need:
```javascript ```javascript
import { $, $mount } from "sigpro"; import { $, $mount, Button, Modal, Input, Alert } from "sigpro-ui";
import { Button, Modal, Table } from "sigpro-ui"; import "sigpro-ui/css";
const App = () => { const App = () => {
const show = $(false); const show = $(false);
return Button({ onclick: () => show(true) }, "Open Modal");
return Button(
{
class: "btn-primary",
onclick: () => show(true)
},
"Open Modal"
);
}; };
$mount(App, "#app"); $mount(App, "#app");
``` ```
### 2. Global Approach (Zero-Import) ### 2. Global Approach (Zero-Import)
Inject all components and utilities into the `window` object. This makes all components available everywhere without manual imports.
Inject all components into the `window` object:
```javascript ```javascript
import SigproUI from "sigpro-ui"; import "sigpro-ui";
import "sigpro-ui/css";
// Injects Button, Table, Input, Icons, Utils, etc. into window // All components (Button, Table, Input, Alert, etc.) are now globally available.
SigproUI.install(); // No need to import anything else!
// Now you can use them directly anywhere:
const myApp = () => Table({ items: [], columns: [] }); const myApp = () => Table({ items: [], columns: [] });
``` ```
@@ -106,8 +85,8 @@ const myApp = () => Table({ items: [], columns: [] });
## Basic Example ## Basic Example
```javascript ```javascript
import { $ } from "sigpro"; import { $, $mount, Button, Toast, Div, H1 } from "sigpro-ui";
import { Button, Toast, Div, H1 } from "sigpro-ui"; import "sigpro-ui/css";
const App = () => { const App = () => {
const count = $(0); const count = $(0);
@@ -124,23 +103,57 @@ const App = () => {
}, () => `Clicks: ${count()}`) }, () => `Clicks: ${count()}`)
]); ]);
}; };
$mount(App, "#app");
``` ```
--- ---
## Components Included ## What's Included?
| Category | Components | ### Core Functions (from SigPro)
| :--- | :--- | - `$()` - Reactive signals
| **Form** | `Input`, `Select`, `Checkbox`, `Radio`, `Range`, `Datepicker`, `Colorpicker`, `Autocomplete`, `Rating` | - `$watch()` - Watch reactive dependencies
| **Data** | `Table`, `List`, `Stat`, `Timeline`, `Badge`, `Indicator` | - `$html()` - Create HTML elements with reactivity
| **Layout** | `Navbar`, `Menu`, `Drawer`, `Fieldset`, `Stack`, `Tabs`, `Accordion` | - `$if()` - Conditional rendering
| **Feedback** | `Alert`, `Modal`, `Toast`, `Loading`, `Tooltip` | - `$for()` - List rendering
| **Interaction** | `Button`, `Dropdown`, `Swap`, `Fab` | - `$router()` - Hash-based routing
- `$mount()` - Mount components to DOM
Explore [SigPro Core Docs](https://natxocc.github.io/sigpro/#/) for more information.
### UI Components
- `Button`, `Input`, `Select`, `Checkbox`, `Radio`
- `Modal`, `Alert`, `Toast`, `Tooltip`
- `Table`, `List`, `Badge`, `Stat`, `Timeline`
- `Tabs`, `Accordion`, `Dropdown`, `Drawer`
- `Datepicker`, `Colorpicker`, `Autocomplete`, `Rating`
- `Fileinput`, `Fab`, `Swap`, `Indicator`
- And 30+ more!
### Utilities
- `tt()` - i18n translation function (ES/EN)
- `Locale()` - Set global language
---
## Language Support
Built-in i18n with Spanish and English:
```javascript
import { tt, Locale } from "sigpro-ui";
// Change locale (default is 'es')
Locale('en');
// Use translations
const closeButton = Button({}, tt('close')());
```
--- ---
## License ## License
MIT © 2026 **SigPro Team**. MIT © 2026 **SigPro Team**
*Engineered for speed, designed for clarity, built for the modern web.* *Engineered for speed, designed for clarity, built for the modern web.*

View File

@@ -6230,6 +6230,11 @@
-webkit-backdrop-filter: var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,); -webkit-backdrop-filter: var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);
backdrop-filter: var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,); backdrop-filter: var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);
} }
.transition {
transition-property: color, background-color, border-color, outline-color, text-decoration-color, fill, stroke, --tw-gradient-from, --tw-gradient-via, --tw-gradient-to, opacity, box-shadow, transform, translate, scale, rotate, filter, -webkit-backdrop-filter, backdrop-filter, display, content-visibility, overlay, pointer-events;
transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));
transition-duration: var(--tw-duration, var(--default-transition-duration));
}
.transition-all { .transition-all {
transition-property: all; transition-property: all;
transition-timing-function: var(--tw-ease, var(--default-transition-timing-function)); transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));

2
css/sigpro.min.css vendored

File diff suppressed because one or more lines are too long

1908
dist/sigpro-ui.esm.js vendored Normal file

File diff suppressed because it is too large Load Diff

7
dist/sigpro-ui.esm.min.js vendored Normal file

File diff suppressed because one or more lines are too long

35
dist/sigpro-ui.js vendored
View File

@@ -270,7 +270,10 @@
content = props; content = props;
props = {}; props = {};
} }
const el = document.createElement(tag), _sanitize = (key, val) => (key === "src" || key === "href") && String(val).toLowerCase().includes("javascript:") ? "#" : val; const svgTags = ["svg", "path", "circle", "rect", "line", "polyline", "polygon", "g", "defs", "text", "tspan", "use"];
const isSVG = svgTags.includes(tag);
const el = isSVG ? document.createElementNS("http://www.w3.org/2000/svg", tag) : document.createElement(tag);
const _sanitize = (key, val) => (key === "src" || key === "href") && String(val).toLowerCase().includes("javascript:") ? "#" : val;
el._cleanups = new Set; el._cleanups = new Set;
const boolAttrs = ["disabled", "checked", "required", "readonly", "selected", "multiple", "autofocus"]; const boolAttrs = ["disabled", "checked", "required", "readonly", "selected", "multiple", "autofocus"];
for (let [key, val] of Object.entries(props)) { for (let [key, val] of Object.entries(props)) {
@@ -306,7 +309,13 @@
el[key] = false; el[key] = false;
} }
} else { } else {
currentVal == null ? el.removeAttribute(key) : el.setAttribute(key, currentVal); if (currentVal == null) {
el.removeAttribute(key);
} else if (isSVG && typeof currentVal === "number") {
el.setAttribute(key, currentVal);
} else {
el.setAttribute(key, currentVal);
}
} }
})); }));
} else { } else {
@@ -347,20 +356,32 @@
append(content); append(content);
return el; return el;
}; };
var $if = (condition, thenVal, otherwiseVal = null) => { var $if = (condition, thenVal, otherwiseVal = null, transition = null) => {
const marker = document.createTextNode(""); const marker = document.createTextNode("");
const container = $html2("div", { style: "display:contents" }, [marker]); const container = $html2("div", { style: "display:contents" }, [marker]);
let current = null, last = null; let current = null, last = null;
$watch2(() => { $watch2(() => {
const state = !!(typeof condition === "function" ? condition() : condition); const state = !!(typeof condition === "function" ? condition() : condition);
if (state !== last) { if (state === last)
last = state; return;
last = state;
if (current && !state && transition?.out) {
transition.out(current.container, () => {
current.destroy();
current = null;
});
} else {
if (current) if (current)
current.destroy(); current.destroy();
current = null;
}
if (state || !state && otherwiseVal) {
const branch = state ? thenVal : otherwiseVal; const branch = state ? thenVal : otherwiseVal;
if (branch) { if (branch) {
current = _view(() => typeof branch === "function" ? branch() : branch); current = _view(() => typeof branch === "function" ? branch() : branch);
container.insertBefore(current.container, marker); container.insertBefore(current.container, marker);
if (state && transition?.in)
transition.in(current.container);
} }
} }
}); });
@@ -455,7 +476,8 @@
MOUNTED_NODES.set(el, instance); MOUNTED_NODES.set(el, instance);
return instance; return instance;
}; };
var SigProCore = { $, $watch: $watch2, $html: $html2, $if, $for, $router, $mount }; var Fragment = ({ children }) => children;
var SigProCore = { $, $watch: $watch2, $html: $html2, $if, $for, $router, $mount, Fragment };
if (typeof window !== "undefined") { if (typeof window !== "undefined") {
const install = (registry) => { const install = (registry) => {
Object.keys(registry).forEach((key) => { Object.keys(registry).forEach((key) => {
@@ -468,6 +490,7 @@
window[helperName] = (props, content) => $html2(tagName, props, content); window[helperName] = (props, content) => $html2(tagName, props, content);
} }
}); });
window.Fragment = Fragment;
window.SigPro = Object.freeze(registry); window.SigPro = Object.freeze(registry);
}; };
install(SigProCore); install(SigProCore);

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

498
index.d.ts vendored Normal file
View File

@@ -0,0 +1,498 @@
// sigpro-ui.d.ts
declare module 'sigpro-ui' {
// Tipos básicos
type Signal<T> = {
(): T;
(value: T | ((prev: T) => T)): void;
};
type ComponentFunction<P = {}> = (props?: P, children?: any) => HTMLElement | string | null;
type ComponentChild = HTMLElement | string | number | boolean | null | undefined;
type ComponentChildren = ComponentChild | ComponentChild[];
// Utils
function val<T>(value: T | (() => T)): T;
function ui(baseClass: string, ...additional: (string | (() => string) | undefined)[]): string | (() => string);
function getIcon(icon: string | (() => string) | HTMLElement | null | undefined): HTMLElement | null;
function tt(key: 'close' | 'confirm' | 'cancel' | 'search' | 'loading' | 'nodata'): () => string;
// Props comunes
interface BaseProps {
class?: string | (() => string);
style?: string | Record<string, string> | (() => string | Record<string, string>);
id?: string | (() => string);
}
interface EventProps {
onclick?: (event: MouseEvent) => void;
oninput?: (event: Event) => void;
onchange?: (event: Event) => void;
onblur?: (event: FocusEvent) => void;
onfocus?: (event: FocusEvent) => void;
onkeydown?: (event: KeyboardEvent) => void;
onkeyup?: (event: KeyboardEvent) => void;
onmouseenter?: (event: MouseEvent) => void;
onmouseleave?: (event: MouseEvent) => void;
onsubmit?: (event: Event) => void;
ondragover?: (event: DragEvent) => void;
ondragleave?: (event: DragEvent) => void;
ondrop?: (event: DragEvent) => void;
[key: `on${string}`]: ((event: any) => void) | undefined;
}
// Accordion
interface AccordionProps extends BaseProps, EventProps {
title: string | (() => string);
name?: string;
open?: boolean | (() => boolean);
}
function Accordion(props: AccordionProps, children: ComponentChildren): HTMLElement;
// Alert
type AlertType = 'info' | 'success' | 'warning' | 'error';
interface AlertProps extends BaseProps, EventProps {
type?: AlertType;
soft?: boolean;
actions?: ComponentFunction | ComponentChildren;
message?: string | (() => string);
}
function Alert(props: AlertProps, children?: ComponentChildren): HTMLElement;
// Autocomplete
interface AutocompleteOption {
label: string;
value: string;
}
interface AutocompleteProps extends BaseProps, EventProps {
items?: string[] | AutocompleteOption[] | (() => (string[] | AutocompleteOption[]));
value?: Signal<string> | string;
onSelect?: (option: string | AutocompleteOption) => void;
label?: string | (() => string);
placeholder?: string | (() => string);
}
function Autocomplete(props: AutocompleteProps): HTMLElement;
// Badge
interface BadgeProps extends BaseProps, EventProps {}
function Badge(props: BadgeProps, children: ComponentChildren): HTMLElement;
// Button
interface ButtonProps extends BaseProps, EventProps {
disabled?: boolean | (() => boolean);
loading?: boolean | (() => boolean);
icon?: string | (() => string) | HTMLElement;
}
function Button(props: ButtonProps, children: ComponentChildren): HTMLElement;
// Checkbox
interface CheckboxProps extends BaseProps, EventProps {
value?: Signal<boolean> | boolean;
label?: string | (() => string);
tooltip?: string | (() => string);
toggle?: boolean | (() => boolean);
}
function Checkbox(props: CheckboxProps): HTMLElement;
// Colorpicker
interface ColorpickerProps extends BaseProps, EventProps {
value?: Signal<string> | string;
label?: string | (() => string);
}
function Colorpicker(props: ColorpickerProps): HTMLElement;
// Datepicker
interface DateRange {
start: string;
end: string | null;
startHour?: number;
endHour?: number;
}
interface DatepickerProps extends BaseProps, EventProps {
value?: Signal<string | DateRange> | string | DateRange;
range?: boolean | (() => boolean);
label?: string | (() => string);
placeholder?: string | (() => string);
hour?: boolean;
}
function Datepicker(props: DatepickerProps): HTMLElement;
// Drawer
interface DrawerProps extends BaseProps, EventProps {
id?: string;
open?: Signal<boolean> | boolean;
side: ComponentFunction | ComponentChildren;
content: ComponentFunction | ComponentChildren;
}
function Drawer(props: DrawerProps, children?: ComponentChildren): HTMLElement;
// Dropdown
interface DropdownItem {
label: string | (() => string);
icon?: string | HTMLElement | (() => string | HTMLElement);
onclick?: (event: MouseEvent) => void;
class?: string;
}
interface DropdownProps extends BaseProps, EventProps {
label?: string | (() => string);
icon?: string | HTMLElement | (() => string | HTMLElement);
items?: DropdownItem[] | (() => DropdownItem[]);
}
function Dropdown(props: DropdownProps): HTMLElement;
// Fab
interface FabAction {
label?: string;
icon?: string | HTMLElement;
text?: string;
onclick?: (event: MouseEvent) => void;
class?: string;
}
interface FabProps extends BaseProps, EventProps {
icon?: string | HTMLElement;
label?: string;
actions?: FabAction[] | (() => FabAction[]);
position?: string;
}
function Fab(props: FabProps): HTMLElement;
// Fieldset
interface FieldsetProps extends BaseProps, EventProps {
legend?: string | (() => string);
}
function Fieldset(props: FieldsetProps, children: ComponentChildren): HTMLElement;
// Fileinput
interface FileinputProps extends BaseProps, EventProps {
tooltip?: string;
max?: number;
accept?: string;
onSelect?: (files: File[]) => void;
}
function Fileinput(props: FileinputProps): HTMLElement;
// Indicator
interface IndicatorProps extends BaseProps, EventProps {
value?: number | string | (() => number | string);
}
function Indicator(props: IndicatorProps, children: ComponentChildren): HTMLElement;
// Input
interface InputProps extends BaseProps, EventProps {
value?: Signal<string> | string;
type?: string;
icon?: string | HTMLElement | (() => string | HTMLElement);
placeholder?: string | (() => string);
disabled?: boolean | (() => boolean);
size?: string;
validate?: (value: string) => string | null | undefined;
label?: string | (() => string);
}
function Input(props: InputProps): HTMLElement;
// Label
interface LabelProps extends BaseProps, EventProps {
value?: string | (() => string);
floating?: boolean;
error?: string | (() => string);
required?: boolean;
}
function Label(props: LabelProps, children: ComponentChildren): HTMLElement;
// List
interface ListProps<T = any> extends BaseProps, EventProps {
items: T[] | (() => T[]);
header?: string | HTMLElement | (() => string | HTMLElement);
render: (item: T, index: number) => ComponentChild;
keyFn?: (item: T, index: number) => string | number;
}
function List<T>(props: ListProps<T>): HTMLElement;
// Menu
interface MenuItem {
label: string | (() => string);
icon?: string | HTMLElement;
onclick?: (event: MouseEvent) => void;
active?: boolean | (() => boolean);
children?: MenuItem[];
open?: boolean;
}
interface MenuProps extends BaseProps, EventProps {
items: MenuItem[] | (() => MenuItem[]);
}
function Menu(props: MenuProps): HTMLElement;
// Modal
interface ModalProps extends BaseProps, EventProps {
open?: Signal<boolean> | boolean;
title?: string | HTMLElement | (() => string | HTMLElement);
buttons?: ComponentFunction | ComponentFunction[];
}
function Modal(props: ModalProps, children: ComponentChildren): HTMLElement;
// Navbar
interface NavbarProps extends BaseProps, EventProps {}
function Navbar(props: NavbarProps, children: ComponentChildren): HTMLElement;
// Radio
interface RadioProps extends BaseProps, EventProps {
value?: Signal<any> | any;
inputValue?: any;
name?: string;
label?: string | (() => string);
tooltip?: string | (() => string);
}
function Radio(props: RadioProps): HTMLElement;
// Range
interface RangeProps extends BaseProps, EventProps {
value?: Signal<number> | number;
label?: string | (() => string);
tooltip?: string | (() => string);
min?: number;
max?: number;
step?: number;
disabled?: boolean | (() => boolean);
}
function Range(props: RangeProps): HTMLElement;
// Rating
interface RatingProps extends BaseProps, EventProps {
value?: Signal<number> | number;
count?: number | (() => number);
mask?: string;
readonly?: boolean | (() => boolean);
onchange?: (value: number) => void;
}
function Rating(props: RatingProps): HTMLElement;
// Select
interface SelectOption {
label: string;
value: string | number;
}
interface SelectProps extends BaseProps, EventProps {
label?: string | (() => string);
items?: SelectOption[] | (() => SelectOption[]);
value?: Signal<string | number> | string | number;
}
function Select(props: SelectProps): HTMLElement;
// Stack
interface StackProps extends BaseProps, EventProps {}
function Stack(props: StackProps, children: ComponentChildren): HTMLElement;
// Stat
interface StatProps extends BaseProps, EventProps {
icon?: string | HTMLElement;
label?: string | (() => string);
value?: string | number | (() => string | number);
desc?: string | (() => string);
}
function Stat(props: StatProps): HTMLElement;
// Swap
interface SwapProps extends BaseProps, EventProps {
value?: Signal<boolean> | boolean;
on: ComponentChildren;
off: ComponentChildren;
}
function Swap(props: SwapProps): HTMLElement;
// Table
interface TableColumn<T = any> {
label: string | (() => string);
key?: keyof T;
render?: (item: T, index: number) => ComponentChild;
class?: string;
}
interface TableProps<T = any> extends BaseProps, EventProps {
items: T[] | (() => T[]);
columns: TableColumn<T>[];
keyFn?: (item: T, index: number) => string | number;
zebra?: boolean | (() => boolean);
pinRows?: boolean | (() => boolean);
empty?: string | HTMLElement | (() => string | HTMLElement);
}
function Table<T>(props: TableProps<T>): HTMLElement;
// Tabs
interface TabItem {
label: string | HTMLElement | (() => string | HTMLElement);
content: ComponentFunction | ComponentChildren;
active?: boolean | (() => boolean);
disabled?: boolean | (() => boolean);
onclick?: () => void;
}
interface TabsProps extends BaseProps, EventProps {
items: TabItem[] | (() => TabItem[]);
}
function Tabs(props: TabsProps): HTMLElement;
// Timeline
interface TimelineItem {
title: string | HTMLElement | (() => string | HTMLElement);
detail: string | HTMLElement | (() => string | HTMLElement);
type?: 'info' | 'success' | 'warning' | 'error';
icon?: string | HTMLElement;
completed?: boolean | (() => boolean);
}
interface TimelineProps extends BaseProps, EventProps {
items: TimelineItem[] | (() => TimelineItem[]);
vertical?: boolean | (() => boolean);
compact?: boolean | (() => boolean);
}
function Timeline(props: TimelineProps): HTMLElement;
// Toast
type ToastType = 'alert-info' | 'alert-success' | 'alert-warning' | 'alert-error';
function Toast(
message: string | (() => string),
type?: ToastType,
duration?: number
): () => void;
// Tooltip
interface TooltipProps extends BaseProps, EventProps {
tip: string | (() => string);
ui?: string;
}
function Tooltip(props: TooltipProps, children: ComponentChildren): HTMLElement;
// Objeto principal
const SigProUI: {
Accordion: typeof Accordion;
Alert: typeof Alert;
Autocomplete: typeof Autocomplete;
Badge: typeof Badge;
Button: typeof Button;
Checkbox: typeof Checkbox;
Colorpicker: typeof Colorpicker;
Datepicker: typeof Datepicker;
Drawer: typeof Drawer;
Dropdown: typeof Dropdown;
Fab: typeof Fab;
Fieldset: typeof Fieldset;
Fileinput: typeof Fileinput;
Indicator: typeof Indicator;
Input: typeof Input;
Label: typeof Label;
List: typeof List;
Menu: typeof Menu;
Modal: typeof Modal;
Navbar: typeof Navbar;
Radio: typeof Radio;
Range: typeof Range;
Rating: typeof Rating;
Select: typeof Select;
Stack: typeof Stack;
Stat: typeof Stat;
Swap: typeof Swap;
Table: typeof Table;
Tabs: typeof Tabs;
Timeline: typeof Timeline;
Toast: typeof Toast;
Tooltip: typeof Tooltip;
Utils: {
val: typeof val;
ui: typeof ui;
getIcon: typeof getIcon;
};
tt: typeof tt;
install: (target?: Window) => void;
};
export default SigProUI;
export {
Accordion, Alert, Autocomplete, Badge, Button, Checkbox, Colorpicker,
Datepicker, Drawer, Dropdown, Fab, Fieldset, Fileinput, Indicator,
Input, Label, List, Menu, Modal, Navbar, Radio, Range, Rating,
Select, Stack, Stat, Swap, Table, Tabs, Timeline, Toast, Tooltip,
val, ui, getIcon, tt,
// Tipos
AccordionProps, AlertProps, AutocompleteProps, BadgeProps, ButtonProps,
CheckboxProps, ColorpickerProps, DatepickerProps, DrawerProps, DropdownProps,
FabProps, FieldsetProps, FileinputProps, IndicatorProps, InputProps,
LabelProps, ListProps, MenuProps, ModalProps, NavbarProps, RadioProps,
RangeProps, RatingProps, SelectProps, StackProps, StatProps, SwapProps,
TableProps, TabsProps, TimelineProps, TooltipProps,
DateRange, AutocompleteOption, DropdownItem, FabAction, MenuItem,
SelectOption, TabItem, TableColumn, TimelineItem, ToastType, AlertType
};
}
// Declaraciones globales
declare global {
const Accordion: typeof import('sigpro-ui').Accordion;
const Alert: typeof import('sigpro-ui').Alert;
const Autocomplete: typeof import('sigpro-ui').Autocomplete;
const Badge: typeof import('sigpro-ui').Badge;
const Button: typeof import('sigpro-ui').Button;
const Checkbox: typeof import('sigpro-ui').Checkbox;
const Colorpicker: typeof import('sigpro-ui').Colorpicker;
const Datepicker: typeof import('sigpro-ui').Datepicker;
const Drawer: typeof import('sigpro-ui').Drawer;
const Dropdown: typeof import('sigpro-ui').Dropdown;
const Fab: typeof import('sigpro-ui').Fab;
const Fieldset: typeof import('sigpro-ui').Fieldset;
const Fileinput: typeof import('sigpro-ui').Fileinput;
const Indicator: typeof import('sigpro-ui').Indicator;
const Input: typeof import('sigpro-ui').Input;
const Label: typeof import('sigpro-ui').Label;
const List: typeof import('sigpro-ui').List;
const Menu: typeof import('sigpro-ui').Menu;
const Modal: typeof import('sigpro-ui').Modal;
const Navbar: typeof import('sigpro-ui').Navbar;
const Radio: typeof import('sigpro-ui').Radio;
const Range: typeof import('sigpro-ui').Range;
const Rating: typeof import('sigpro-ui').Rating;
const Select: typeof import('sigpro-ui').Select;
const Stack: typeof import('sigpro-ui').Stack;
const Stat: typeof import('sigpro-ui').Stat;
const Swap: typeof import('sigpro-ui').Swap;
const Table: typeof import('sigpro-ui').Table;
const Tabs: typeof import('sigpro-ui').Tabs;
const Timeline: typeof import('sigpro-ui').Timeline;
const Toast: typeof import('sigpro-ui').Toast;
const Tooltip: typeof import('sigpro-ui').Tooltip;
const Utils: typeof import('sigpro-ui').Utils;
const tt: typeof import('sigpro-ui').tt;
interface Window {
Accordion: typeof Accordion;
Alert: typeof Alert;
Autocomplete: typeof Autocomplete;
Badge: typeof Badge;
Button: typeof Button;
Checkbox: typeof Checkbox;
Colorpicker: typeof Colorpicker;
Datepicker: typeof Datepicker;
Drawer: typeof Drawer;
Dropdown: typeof Dropdown;
Fab: typeof Fab;
Fieldset: typeof Fieldset;
Fileinput: typeof Fileinput;
Indicator: typeof Indicator;
Input: typeof Input;
Label: typeof Label;
List: typeof List;
Menu: typeof Menu;
Modal: typeof Modal;
Navbar: typeof Navbar;
Radio: typeof Radio;
Range: typeof Range;
Rating: typeof Rating;
Select: typeof Select;
Stack: typeof Stack;
Stat: typeof Stat;
Swap: typeof Swap;
Table: typeof Table;
Tabs: typeof Tabs;
Timeline: typeof Timeline;
Toast: typeof Toast;
Tooltip: typeof Tooltip;
Utils: typeof Utils;
tt: typeof tt;
SigProUI: typeof import('sigpro-ui').default;
}
}

View File

@@ -1,13 +1,10 @@
// index.js // index.js
import './src/sigpro.js'; import './src/sigpro.js';
// import './src/css/sigpro.css'; // No importes CSS en JS
import * as Components from './src/components/index.js'; import * as Components from './src/components/index.js';
// import * as Icons from './src/core/icons.js'; // ELIMINAR
import * as Utils from './src/core/utils.js'; import * as Utils from './src/core/utils.js';
import { tt } from './src/core/i18n.js'; import { tt } from './src/core/i18n.js';
export * from './src/components/index.js'; export * from './src/components/index.js';
// export * from './src/core/icons.js'; // ELIMINAR
export * from './src/core/utils.js'; export * from './src/core/utils.js';
export { tt }; export { tt };
@@ -16,7 +13,6 @@ if (typeof window !== 'undefined') {
window[name] = component; window[name] = component;
}); });
// window.Icons = Icons; // ELIMINAR
window.Utils = Utils; window.Utils = Utils;
window.tt = tt; window.tt = tt;
window.SigProUI = { ...Components, Utils, tt }; window.SigProUI = { ...Components, Utils, tt };

View File

@@ -1,6 +1,6 @@
{ {
"name": "sigpro-ui", "name": "sigpro-ui",
"version": "1.1.1", "version": "1.1.2",
"main": "./index.js", "main": "./index.js",
"module": "./index.js", "module": "./index.js",
"devDependencies": { "devDependencies": {
@@ -12,8 +12,9 @@
}, },
"exports": { "exports": {
".": { ".": {
"import": "./index.js", "import": "./dist/sigpro-ui.esm.js",
"script": "./dist/sigpro-ui.js" "script": "./dist/sigpro-ui.js",
"types": "./index.d.ts"
}, },
"./css": { "./css": {
"import": "./css/index.js", "import": "./css/index.js",
@@ -30,6 +31,7 @@
}, },
"files": [ "files": [
"index.js", "index.js",
"index.d.ts",
"dist", "dist",
"css", "css",
"README.md", "README.md",
@@ -37,13 +39,20 @@
], ],
"jsdelivr": "./dist/sigpro-ui.min.js", "jsdelivr": "./dist/sigpro-ui.min.js",
"license": "MIT", "license": "MIT",
"sideEffects": [
"./css/*",
"**/*.css"
],
"scripts": { "scripts": {
"build:cssmin": "./node_modules/.bin/tailwindcss -i ./src/css/sigpro.css -o ./css/sigpro.min.css --content './src/**/*.js' --minify", "clean": "rm -rf ./dist ./css/*.css ./docs/*.js ./docs/*.css",
"build:css": "./node_modules/.bin/tailwindcss -i ./src/css/sigpro.css -o ./css/sigpro.css --content './src/**/*.js'", "build:cssmin": "tailwindcss -i ./src/css/sigpro.css -o ./css/sigpro.min.css --content './src/**/*.js' --minify",
"build:cssdocs": "./node_modules/.bin/tailwindcss -i ./src/css/sigpro.css -o ./docs/sigpro.css --content './src/**/*.js' --minify", "build:css": "tailwindcss -i ./src/css/sigpro.css -o ./css/sigpro.css --content './src/**/*.js'",
"build:js": "bun build ./index.js --bundle --outfile=./dist/sigpro-ui.js --format=iife --global-name=SigProUI && bun build ./index.js --bundle --outfile=./dist/sigpro-ui.min.js --format=iife --global-name=SigProUI --minify", "build:cssdocs": "tailwindcss -i ./src/css/sigpro.css -o ./docs/sigpro.css --content './src/**/*.js' --minify",
"build:js": "bun run build:js:iife && bun run build:js:esm",
"build:js:iife": "bun build ./index.js --bundle --outfile=./dist/sigpro-ui.js --format=iife --global-name=SigProUI && bun build ./index.js --bundle --outfile=./dist/sigpro-ui.min.js --format=iife --global-name=SigProUI --minify",
"build:js:esm": "bun build ./index.js --bundle --outfile=./dist/sigpro-ui.esm.js --format=esm && bun build ./index.js --bundle --outfile=./dist/sigpro-ui.esm.min.js --format=esm --minify",
"build:jsdocs": "bun build ./index.js --bundle --outfile=./docs/sigpro-ui.min.js --format=iife --global-name=SigProUI --minify", "build:jsdocs": "bun build ./index.js --bundle --outfile=./docs/sigpro-ui.min.js --format=iife --global-name=SigProUI --minify",
"build": "bun run build:css && bun run build:js && bun run build:jsdocs && bun run build:cssdocs && bun run build:cssmin", "build": "bun run clean && bun run build:css && bun run build:js && bun run build:jsdocs && bun run build:cssdocs && bun run build:cssmin",
"prepublishOnly": "bun run build", "prepublishOnly": "bun run build",
"docs": "bun x serve docs" "docs": "bun x serve docs"
}, },

View File

@@ -7,7 +7,6 @@ const effectQueue = new Set();
let isFlushing = false; let isFlushing = false;
const MOUNTED_NODES = new WeakMap(); const MOUNTED_NODES = new WeakMap();
/** flush */
const flush = () => { const flush = () => {
if (isFlushing) return; if (isFlushing) return;
isFlushing = true; isFlushing = true;
@@ -19,7 +18,6 @@ const flush = () => {
isFlushing = false; isFlushing = false;
}; };
/** track */
const track = (subs) => { const track = (subs) => {
if (activeEffect && !activeEffect._deleted) { if (activeEffect && !activeEffect._deleted) {
subs.add(activeEffect); subs.add(activeEffect);
@@ -27,7 +25,6 @@ const track = (subs) => {
} }
}; };
/** trigger */
const trigger = (subs) => { const trigger = (subs) => {
for (const eff of subs) { for (const eff of subs) {
if (eff === activeEffect || eff._deleted) continue; if (eff === activeEffect || eff._deleted) continue;
@@ -41,7 +38,6 @@ const trigger = (subs) => {
if (!isFlushing) queueMicrotask(flush); if (!isFlushing) queueMicrotask(flush);
}; };
/** sweep */
const sweep = (node) => { const sweep = (node) => {
if (node._cleanups) { if (node._cleanups) {
node._cleanups.forEach((f) => f()); node._cleanups.forEach((f) => f());
@@ -50,7 +46,6 @@ const sweep = (node) => {
node.childNodes?.forEach(sweep); node.childNodes?.forEach(sweep);
}; };
/** _view */
const _view = (fn) => { const _view = (fn) => {
const cleanups = new Set(); const cleanups = new Set();
const prev = currentOwner; const prev = currentOwner;
@@ -80,17 +75,6 @@ const _view = (fn) => {
}; };
}; };
/**
* Creates a reactive Signal or a Computed Value.
* @param {any|Function} initial - Initial value or a getter function for computed state.
* @param {string} [key] - Optional. Key for automatic persistence in localStorage.
* @returns {Function} Signal getter/setter. Use `sig()` to read and `sig(val)` to write.
* @example
* const count = $(0); // Simple signal
* const double = $(() => count() * 2); // Computed signal
* const name = $("John", "user-name"); // Persisted signal
*/
const $ = (initial, key = null) => { const $ = (initial, key = null) => {
if (typeof initial === "function") { if (typeof initial === "function") {
const subs = new Set(); const subs = new Set();
@@ -148,16 +132,39 @@ const $ = (initial, key = null) => {
}; };
}; };
/** const $$ = (obj, cache = new WeakMap()) => {
* Watches for signal changes and executes a side effect. if (typeof obj !== "object" || obj === null) return obj;
* Handles automatic cleanup of previous effects. if (cache.has(obj)) return cache.get(obj);
* @param {Function|Array} target - Function to execute or Array of signals for explicit dependency tracking.
* @param {Function} [fn] - If the first parameter is an Array, this is the callback function. const subs = {};
* @returns {Function} Function to manually stop the watcher.
* @example const proxy = new Proxy(obj, {
* $watch(() => console.log("Count is:", count())); get(target, key) {
* $watch([count], () => console.log("Only runs when count changes")); if (activeEffect)
*/ track(subs[key] ??= new Set());
const value = Reflect.get(target, key);
return (typeof value === "object" && value !== null)
? $$(value, cache)
: value;
},
set(target, key, value) {
if (Object.is(target[key], value)) return true;
const res = Reflect.set(target, key, value);
if (subs[key])
trigger(subs[key]);
return res;
}
});
cache.set(obj, proxy);
return proxy;
};
const $watch = (target, fn) => { const $watch = (target, fn) => {
const isExplicit = Array.isArray(target); const isExplicit = Array.isArray(target);
@@ -212,28 +219,25 @@ const $watch = (target, fn) => {
return runner.stop; return runner.stop;
}; };
/**
* DOM element rendering engine with built-in reactivity.
* @param {string} tag - HTML tag name (e.g., 'div', 'span').
* @param {Object} [props] - Attributes, events (onEvent), or two-way bindings (value, checked).
* @param {Array|any} [content] - Children: text, other nodes, or reactive signals.
* @returns {HTMLElement} The configured reactive DOM element.
*/
const $html = (tag, props = {}, content = []) => { const $html = (tag, props = {}, content = []) => {
if (props instanceof Node || Array.isArray(props) || typeof props !== "object") { if (props instanceof Node || Array.isArray(props) || typeof props !== "object") {
content = props; props = {}; content = props; props = {};
} }
const el = document.createElement(tag),
_sanitize = (key, val) => (key === 'src' || key === 'href') && String(val).toLowerCase().includes('javascript:') ? '#' : val; const svgTags = ["svg", "path", "circle", "rect", "line", "polyline", "polygon", "g", "defs", "text", "tspan", "use"];
const isSVG = svgTags.includes(tag);
const el = isSVG
? document.createElementNS("http://www.w3.org/2000/svg", tag)
: document.createElement(tag);
const _sanitize = (key, val) => (key === 'src' || key === 'href') && String(val).toLowerCase().includes('javascript:') ? '#' : val;
el._cleanups = new Set(); el._cleanups = new Set();
const boolAttrs = ["disabled", "checked", "required", "readonly", "selected", "multiple", "autofocus"]; const boolAttrs = ["disabled", "checked", "required", "readonly", "selected", "multiple", "autofocus"];
for (let [key, val] of Object.entries(props)) { for (let [key, val] of Object.entries(props)) {
if (key === "ref") { (typeof val === "function" ? val(el) : (val.current = el)); continue; } if (key === "ref") { (typeof val === "function" ? val(el) : (val.current = el)); continue; }
const isSignal = typeof val === "function", const isSignal = typeof val === "function", isInput = ["INPUT", "TEXTAREA", "SELECT"].includes(el.tagName), isBindAttr = (key === "value" || key === "checked");
isInput = ["INPUT", "TEXTAREA", "SELECT"].includes(el.tagName),
isBindAttr = (key === "value" || key === "checked");
if (isInput && isBindAttr && isSignal) { if (isInput && isBindAttr && isSignal) {
el._cleanups.add($watch(() => { const currentVal = val(); if (el[key] !== currentVal) el[key] = currentVal; })); el._cleanups.add($watch(() => { const currentVal = val(); if (el[key] !== currentVal) el[key] = currentVal; }));
@@ -258,7 +262,13 @@ const $html = (tag, props = {}, content = []) => {
el[key] = false; el[key] = false;
} }
} else { } else {
currentVal == null ? el.removeAttribute(key) : el.setAttribute(key, currentVal); if (currentVal == null) {
el.removeAttribute(key);
} else if (isSVG && typeof currentVal === 'number') {
el.setAttribute(key, currentVal);
} else {
el.setAttribute(key, currentVal);
}
} }
})); }));
} else { } else {
@@ -298,42 +308,41 @@ const $html = (tag, props = {}, content = []) => {
return el; return el;
}; };
/** const $if = (condition, thenVal, otherwiseVal = null, transition = null) => {
* Conditional rendering component.
* @param {Function|boolean} condition - Reactive signal or boolean value.
* @param {Function|HTMLElement} thenVal - Content to show if true.
* @param {Function|HTMLElement} [otherwiseVal] - Content to show if false (optional).
* @returns {HTMLElement} A reactive container (display: contents).
*/
const $if = (condition, thenVal, otherwiseVal = null) => {
const marker = document.createTextNode(""); const marker = document.createTextNode("");
const container = $html("div", { style: "display:contents" }, [marker]); const container = $html("div", { style: "display:contents" }, [marker]);
let current = null, last = null; let current = null, last = null;
$watch(() => { $watch(() => {
const state = !!(typeof condition === "function" ? condition() : condition); const state = !!(typeof condition === "function" ? condition() : condition);
if (state !== last) { if (state === last) return;
last = state; last = state;
if (current && !state && transition?.out) {
transition.out(current.container, () => {
current.destroy();
current = null;
});
} else {
if (current) current.destroy(); if (current) current.destroy();
current = null;
}
if (state || (!state && otherwiseVal)) {
const branch = state ? thenVal : otherwiseVal; const branch = state ? thenVal : otherwiseVal;
if (branch) { if (branch) {
current = _view(() => typeof branch === "function" ? branch() : branch); current = _view(() => typeof branch === "function" ? branch() : branch);
container.insertBefore(current.container, marker); container.insertBefore(current.container, marker);
if (state && transition?.in) transition.in(current.container);
} }
} }
}); });
return container; return container;
}; };
$if.not = (condition, thenVal, otherwiseVal) => $if(() => !(typeof condition === "function" ? condition() : condition), thenVal, otherwiseVal); $if.not = (condition, thenVal, otherwiseVal) => $if(() => !(typeof condition === "function" ? condition() : condition), thenVal, otherwiseVal);
/**
* Optimized reactive loop with key-based reconciliation.
* @param {Function|Array} source - Signal containing an Array of data.
* @param {Function} render - Function receiving (item, index) and returning a node.
* @param {Function} keyFn - Function to extract a unique key from the item.
* @returns {HTMLElement} A reactive container (display: contents).
*/
const $for = (source, render, keyFn, tag = "div", props = { style: "display:contents" }) => { const $for = (source, render, keyFn, tag = "div", props = { style: "display:contents" }) => {
const marker = document.createTextNode(""); const marker = document.createTextNode("");
const container = $html(tag, props, [marker]); const container = $html(tag, props, [marker]);
@@ -347,7 +356,7 @@ const $for = (source, render, keyFn, tag = "div", props = { style: "display:cont
for (let i = 0; i < items.length; i++) { for (let i = 0; i < items.length; i++) {
const item = items[i]; const item = items[i];
const key = keyFn ? keyFn(item, i) : i; const key = keyFn ? keyFn(item, i) : i;
let run = cache.get(key); let run = cache.get(key);
if (!run) { if (!run) {
run = _view(() => render(item, i)); run = _view(() => render(item, i));
@@ -379,11 +388,6 @@ const $for = (source, render, keyFn, tag = "div", props = { style: "display:cont
return container; return container;
}; };
/**
* Hash-based (#) routing system.
* @param {Array<{path: string, component: Function}>} routes - Route definitions.
* @returns {HTMLElement} The router outlet container.
*/
const $router = (routes) => { const $router = (routes) => {
const sPath = $(window.location.hash.replace(/^#/, "") || "/"); const sPath = $(window.location.hash.replace(/^#/, "") || "/");
window.addEventListener("hashchange", () => sPath(window.location.hash.replace(/^#/, "") || "/")); window.addEventListener("hashchange", () => sPath(window.location.hash.replace(/^#/, "") || "/"));
@@ -429,21 +433,6 @@ $router.to = (path) => (window.location.hash = path.replace(/^#?\/?/, "#/"));
$router.back = () => window.history.back(); $router.back = () => window.history.back();
$router.path = () => window.location.hash.replace(/^#/, "") || "/"; $router.path = () => window.location.hash.replace(/^#/, "") || "/";
/**
* Mounts a component or node into a DOM target element.
* It automatically handles the cleanup of any previously mounted SigPro instances
* in that target to prevent memory leaks and duplicate renders.
* * @param {Function|HTMLElement} component - The component function to render or a pre-built DOM node.
* @param {string|HTMLElement} target - A CSS selector string or a direct DOM element to mount into.
* @returns {Object|undefined} The view instance containing the `container` and `destroy` method, or undefined if target is not found.
* * @example
* // Mount using a component function
* $mount(() => Div({ class: "app" }, "Hello World"), "#root");
* * // Mount using a direct element
* const myApp = Div("Hello");
* $mount(myApp, document.getElementById("app"));
*/
const $mount = (component, target) => { const $mount = (component, target) => {
const el = typeof target === "string" ? document.querySelector(target) : target; const el = typeof target === "string" ? document.querySelector(target) : target;
if (!el) return; if (!el) return;
@@ -454,8 +443,9 @@ const $mount = (component, target) => {
return instance; return instance;
}; };
/** GLOBAL CORE REGISTRY */ export const Fragment = ({ children }) => children;
const SigProCore = { $, $watch, $html, $if, $for, $router, $mount };
const SigProCore = { $, $watch, $html, $if, $for, $router, $mount, Fragment };
if (typeof window !== "undefined") { if (typeof window !== "undefined") {
const install = (registry) => { const install = (registry) => {
@@ -470,7 +460,8 @@ if (typeof window !== "undefined") {
window[helperName] = (props, content) => $html(tagName, props, content); window[helperName] = (props, content) => $html(tagName, props, content);
} }
}); });
window.Fragment = Fragment;
window.SigPro = Object.freeze(registry); window.SigPro = Object.freeze(registry);
}; };