Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f39359ef67 | ||
| 9fefa6dcb1 | |||
|
|
8f9d01e766 | ||
|
|
7956d2b9b0 | ||
| 3f8273523c | |||
|
|
7d2af57ac1 | ||
|
|
a677f5d00b | ||
|
|
95042a2e36 | ||
|
|
ede3caa7d6 | ||
|
|
78bac75fd5 | ||
|
|
1478a7d63d | ||
| bd63afa23b | |||
| 00d114630d | |||
| ddcc960e1f |
153
Readme.md
153
Readme.md
@@ -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.
|
||||
|
||||
@@ -8,96 +10,73 @@ Unlike heavy frameworks, SigPro UI focuses on a **"Zero-Build"** philosophy, all
|
||||
|
||||
## Features
|
||||
|
||||
* **Signals-Based Reactivity**: Powered by SigPro for granular DOM updates.
|
||||
* **DaisyUI v5 Integration**: Beautiful, semantic components out of the box.
|
||||
* **Tree Shaking Ready**: Import only what you need.
|
||||
* **Zero-Import Option**: Inject all components into the global scope with one command.
|
||||
* **Lightweight**: Minimal footprint with a focus on performance.
|
||||
- **Signals-Based Reactivity**: Powered by SigPro for granular DOM updates
|
||||
- **Self-Contained Styling**: Full CSS included - no external frameworks needed
|
||||
- **Built on daisyUI v5**: Modern, utility-first styling out of the box
|
||||
- **Tree Shaking Ready**: Import only what you need
|
||||
- **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).
|
||||
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
|
||||
```bash
|
||||
npm install sigpro-ui
|
||||
```
|
||||
|
||||
### Via CDN (Browser)
|
||||
|
||||
```html
|
||||
<script src="https://unpkg.com/sigpro"></script>
|
||||
<!-- SigPro UI with styles included -->
|
||||
<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)
|
||||
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.
|
||||
## Usage
|
||||
|
||||
Create or edit your global CSS file (e.g., style.css):
|
||||
|
||||
```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).
|
||||
You can use SigPro UI in two ways: **Modular** (Recommended) or **Global** (Fastest for prototyping).
|
||||
|
||||
### 1. Modular Approach (Tree Shaking)
|
||||
Import only the components you need. This keeps your bundle small.
|
||||
|
||||
Import only the components you need:
|
||||
|
||||
```javascript
|
||||
import { $, $mount } from "sigpro";
|
||||
import { Button, Modal, Table } from "sigpro-ui";
|
||||
import { $, $mount, Button, Modal, Input, Alert } from "sigpro-ui";
|
||||
import "sigpro-ui/css";
|
||||
|
||||
const App = () => {
|
||||
const show = $(false);
|
||||
return Button({ onclick: () => show(true) }, "Open Modal");
|
||||
|
||||
return Button(
|
||||
{
|
||||
class: "btn-primary",
|
||||
onclick: () => show(true)
|
||||
},
|
||||
"Open Modal"
|
||||
);
|
||||
};
|
||||
|
||||
$mount(App, "#app");
|
||||
```
|
||||
|
||||
### 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
|
||||
import SigproUI from "sigpro-ui";
|
||||
import "sigpro-ui";
|
||||
import "sigpro-ui/css";
|
||||
|
||||
// Injects Button, Table, Input, Icons, Utils, etc. into window
|
||||
SigproUI.install();
|
||||
// All components (Button, Table, Input, Alert, etc.) are now globally available.
|
||||
// No need to import anything else!
|
||||
|
||||
// Now you can use them directly anywhere:
|
||||
const myApp = () => Table({ items: [], columns: [] });
|
||||
```
|
||||
|
||||
@@ -106,8 +85,8 @@ const myApp = () => Table({ items: [], columns: [] });
|
||||
## Basic Example
|
||||
|
||||
```javascript
|
||||
import { $ } from "sigpro";
|
||||
import { Button, Toast, Div, H1 } from "sigpro-ui";
|
||||
import { $, $mount, Button, Toast, Div, H1 } from "sigpro-ui";
|
||||
import "sigpro-ui/css";
|
||||
|
||||
const App = () => {
|
||||
const count = $(0);
|
||||
@@ -124,23 +103,57 @@ const App = () => {
|
||||
}, () => `Clicks: ${count()}`)
|
||||
]);
|
||||
};
|
||||
|
||||
$mount(App, "#app");
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Components Included
|
||||
## What's Included?
|
||||
|
||||
| Category | Components |
|
||||
| :--- | :--- |
|
||||
| **Form** | `Input`, `Select`, `Checkbox`, `Radio`, `Range`, `Datepicker`, `Colorpicker`, `Autocomplete`, `Rating` |
|
||||
| **Data** | `Table`, `List`, `Stat`, `Timeline`, `Badge`, `Indicator` |
|
||||
| **Layout** | `Navbar`, `Menu`, `Drawer`, `Fieldset`, `Stack`, `Tabs`, `Accordion` |
|
||||
| **Feedback** | `Alert`, `Modal`, `Toast`, `Loading`, `Tooltip` |
|
||||
| **Interaction** | `Button`, `Dropdown`, `Swap`, `Fab` |
|
||||
### Core Functions (from SigPro)
|
||||
- `$()` - Reactive signals
|
||||
- `$watch()` - Watch reactive dependencies
|
||||
- `$html()` - Create HTML elements with reactivity
|
||||
- `$if()` - Conditional rendering
|
||||
- `$for()` - List rendering
|
||||
- `$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
|
||||
|
||||
MIT © 2026 **SigPro Team**.
|
||||
MIT © 2026 **SigPro Team**
|
||||
*Engineered for speed, designed for clarity, built for the modern web.*
|
||||
|
||||
@@ -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,);
|
||||
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-property: all;
|
||||
transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));
|
||||
|
||||
2
css/sigpro.min.css
vendored
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
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
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
35
dist/sigpro-ui.js
vendored
@@ -270,7 +270,10 @@
|
||||
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;
|
||||
const boolAttrs = ["disabled", "checked", "required", "readonly", "selected", "multiple", "autofocus"];
|
||||
for (let [key, val] of Object.entries(props)) {
|
||||
@@ -306,7 +309,13 @@
|
||||
el[key] = false;
|
||||
}
|
||||
} 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 {
|
||||
@@ -347,20 +356,32 @@
|
||||
append(content);
|
||||
return el;
|
||||
};
|
||||
var $if = (condition, thenVal, otherwiseVal = null) => {
|
||||
var $if = (condition, thenVal, otherwiseVal = null, transition = null) => {
|
||||
const marker = document.createTextNode("");
|
||||
const container = $html2("div", { style: "display:contents" }, [marker]);
|
||||
let current = null, last = null;
|
||||
$watch2(() => {
|
||||
const state = !!(typeof condition === "function" ? condition() : condition);
|
||||
if (state !== last) {
|
||||
last = state;
|
||||
if (state === last)
|
||||
return;
|
||||
last = state;
|
||||
if (current && !state && transition?.out) {
|
||||
transition.out(current.container, () => {
|
||||
current.destroy();
|
||||
current = null;
|
||||
});
|
||||
} else {
|
||||
if (current)
|
||||
current.destroy();
|
||||
current = null;
|
||||
}
|
||||
if (state || !state && otherwiseVal) {
|
||||
const branch = state ? thenVal : otherwiseVal;
|
||||
if (branch) {
|
||||
current = _view(() => typeof branch === "function" ? branch() : branch);
|
||||
container.insertBefore(current.container, marker);
|
||||
if (state && transition?.in)
|
||||
transition.in(current.container);
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -455,7 +476,8 @@
|
||||
MOUNTED_NODES.set(el, 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") {
|
||||
const install = (registry) => {
|
||||
Object.keys(registry).forEach((key) => {
|
||||
@@ -468,6 +490,7 @@
|
||||
window[helperName] = (props, content) => $html2(tagName, props, content);
|
||||
}
|
||||
});
|
||||
window.Fragment = Fragment;
|
||||
window.SigPro = Object.freeze(registry);
|
||||
};
|
||||
install(SigProCore);
|
||||
|
||||
8
dist/sigpro-ui.min.js
vendored
8
dist/sigpro-ui.min.js
vendored
File diff suppressed because one or more lines are too long
8
docs/sigpro-ui.min.js
vendored
8
docs/sigpro-ui.min.js
vendored
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
498
index.d.ts
vendored
Normal 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;
|
||||
}
|
||||
}
|
||||
4
index.js
4
index.js
@@ -1,13 +1,10 @@
|
||||
// index.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 Icons from './src/core/icons.js'; // ELIMINAR
|
||||
import * as Utils from './src/core/utils.js';
|
||||
import { tt } from './src/core/i18n.js';
|
||||
|
||||
export * from './src/components/index.js';
|
||||
// export * from './src/core/icons.js'; // ELIMINAR
|
||||
export * from './src/core/utils.js';
|
||||
export { tt };
|
||||
|
||||
@@ -16,7 +13,6 @@ if (typeof window !== 'undefined') {
|
||||
window[name] = component;
|
||||
});
|
||||
|
||||
// window.Icons = Icons; // ELIMINAR
|
||||
window.Utils = Utils;
|
||||
window.tt = tt;
|
||||
window.SigProUI = { ...Components, Utils, tt };
|
||||
|
||||
33
package.json
33
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "sigpro-ui",
|
||||
"version": "1.1.0",
|
||||
"version": "1.1.4",
|
||||
"main": "./index.js",
|
||||
"module": "./index.js",
|
||||
"devDependencies": {
|
||||
@@ -12,16 +12,26 @@
|
||||
},
|
||||
"exports": {
|
||||
".": {
|
||||
"import": "./index.js",
|
||||
"script": "./dist/sigpro-ui.js"
|
||||
"import": "./dist/sigpro-ui.esm.js",
|
||||
"script": "./dist/sigpro-ui.js",
|
||||
"types": "./index.d.ts"
|
||||
},
|
||||
"./css": {
|
||||
"import": "./css/index.js",
|
||||
"default": "./css/index.js"
|
||||
}
|
||||
},
|
||||
"homepage": "https://natxocc.github.io/sigpro-ui/",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/natxocc/sigpro-ui.git"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/natxocc/sigpro-ui/issues"
|
||||
},
|
||||
"files": [
|
||||
"index.js",
|
||||
"index.d.ts",
|
||||
"dist",
|
||||
"css",
|
||||
"README.md",
|
||||
@@ -29,13 +39,20 @@
|
||||
],
|
||||
"jsdelivr": "./dist/sigpro-ui.min.js",
|
||||
"license": "MIT",
|
||||
"sideEffects": [
|
||||
"./css/*",
|
||||
"**/*.css"
|
||||
],
|
||||
"scripts": {
|
||||
"build:cssmin": "./node_modules/.bin/tailwindcss -i ./src/css/sigpro.css -o ./css/sigpro.min.css --content './src/**/*.js' --minify",
|
||||
"build:css": "./node_modules/.bin/tailwindcss -i ./src/css/sigpro.css -o ./css/sigpro.css --content './src/**/*.js'",
|
||||
"build:cssdocs": "./node_modules/.bin/tailwindcss -i ./src/css/sigpro.css -o ./docs/sigpro.css --content './src/**/*.js' --minify",
|
||||
"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",
|
||||
"clean": "rm -rf ./dist ./css/*.css ./docs/*.js ./docs/*.css",
|
||||
"build:cssmin": "tailwindcss -i ./src/css/sigpro.css -o ./css/sigpro.min.css --content './src/**/*.js' --minify",
|
||||
"build:css": "tailwindcss -i ./src/css/sigpro.css -o ./css/sigpro.css --content './src/**/*.js'",
|
||||
"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": "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",
|
||||
"docs": "bun x serve docs"
|
||||
},
|
||||
|
||||
149
src/sigpro.js
149
src/sigpro.js
@@ -7,7 +7,6 @@ const effectQueue = new Set();
|
||||
let isFlushing = false;
|
||||
const MOUNTED_NODES = new WeakMap();
|
||||
|
||||
/** flush */
|
||||
const flush = () => {
|
||||
if (isFlushing) return;
|
||||
isFlushing = true;
|
||||
@@ -19,7 +18,6 @@ const flush = () => {
|
||||
isFlushing = false;
|
||||
};
|
||||
|
||||
/** track */
|
||||
const track = (subs) => {
|
||||
if (activeEffect && !activeEffect._deleted) {
|
||||
subs.add(activeEffect);
|
||||
@@ -27,7 +25,6 @@ const track = (subs) => {
|
||||
}
|
||||
};
|
||||
|
||||
/** trigger */
|
||||
const trigger = (subs) => {
|
||||
for (const eff of subs) {
|
||||
if (eff === activeEffect || eff._deleted) continue;
|
||||
@@ -41,7 +38,6 @@ const trigger = (subs) => {
|
||||
if (!isFlushing) queueMicrotask(flush);
|
||||
};
|
||||
|
||||
/** sweep */
|
||||
const sweep = (node) => {
|
||||
if (node._cleanups) {
|
||||
node._cleanups.forEach((f) => f());
|
||||
@@ -50,7 +46,6 @@ const sweep = (node) => {
|
||||
node.childNodes?.forEach(sweep);
|
||||
};
|
||||
|
||||
/** _view */
|
||||
const _view = (fn) => {
|
||||
const cleanups = new Set();
|
||||
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) => {
|
||||
if (typeof initial === "function") {
|
||||
const subs = new Set();
|
||||
@@ -148,16 +132,39 @@ const $ = (initial, key = null) => {
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Watches for signal changes and executes a side effect.
|
||||
* Handles automatic cleanup of previous effects.
|
||||
* @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.
|
||||
* @returns {Function} Function to manually stop the watcher.
|
||||
* @example
|
||||
* $watch(() => console.log("Count is:", count()));
|
||||
* $watch([count], () => console.log("Only runs when count changes"));
|
||||
*/
|
||||
const $$ = (obj, cache = new WeakMap()) => {
|
||||
if (typeof obj !== "object" || obj === null) return obj;
|
||||
if (cache.has(obj)) return cache.get(obj);
|
||||
|
||||
const subs = {};
|
||||
|
||||
const proxy = new Proxy(obj, {
|
||||
get(target, key) {
|
||||
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 isExplicit = Array.isArray(target);
|
||||
@@ -212,28 +219,25 @@ const $watch = (target, fn) => {
|
||||
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 = []) => {
|
||||
if (props instanceof Node || Array.isArray(props) || typeof props !== "object") {
|
||||
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();
|
||||
|
||||
const boolAttrs = ["disabled", "checked", "required", "readonly", "selected", "multiple", "autofocus"];
|
||||
|
||||
for (let [key, val] of Object.entries(props)) {
|
||||
if (key === "ref") { (typeof val === "function" ? val(el) : (val.current = el)); continue; }
|
||||
const isSignal = typeof val === "function",
|
||||
isInput = ["INPUT", "TEXTAREA", "SELECT"].includes(el.tagName),
|
||||
isBindAttr = (key === "value" || key === "checked");
|
||||
const isSignal = typeof val === "function", isInput = ["INPUT", "TEXTAREA", "SELECT"].includes(el.tagName), isBindAttr = (key === "value" || key === "checked");
|
||||
|
||||
if (isInput && isBindAttr && isSignal) {
|
||||
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;
|
||||
}
|
||||
} 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 {
|
||||
@@ -298,42 +308,41 @@ const $html = (tag, props = {}, content = []) => {
|
||||
return el;
|
||||
};
|
||||
|
||||
/**
|
||||
* 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 $if = (condition, thenVal, otherwiseVal = null, transition = null) => {
|
||||
const marker = document.createTextNode("");
|
||||
const container = $html("div", { style: "display:contents" }, [marker]);
|
||||
let current = null, last = null;
|
||||
|
||||
$watch(() => {
|
||||
const state = !!(typeof condition === "function" ? condition() : condition);
|
||||
if (state !== last) {
|
||||
last = state;
|
||||
if (state === last) return;
|
||||
last = state;
|
||||
|
||||
if (current && !state && transition?.out) {
|
||||
transition.out(current.container, () => {
|
||||
current.destroy();
|
||||
current = null;
|
||||
});
|
||||
} else {
|
||||
if (current) current.destroy();
|
||||
current = null;
|
||||
}
|
||||
|
||||
if (state || (!state && otherwiseVal)) {
|
||||
const branch = state ? thenVal : otherwiseVal;
|
||||
if (branch) {
|
||||
current = _view(() => typeof branch === "function" ? branch() : branch);
|
||||
container.insertBefore(current.container, marker);
|
||||
if (state && transition?.in) transition.in(current.container);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return container;
|
||||
};
|
||||
|
||||
$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 marker = document.createTextNode("");
|
||||
const container = $html(tag, props, [marker]);
|
||||
@@ -379,11 +388,6 @@ const $for = (source, render, keyFn, tag = "div", props = { style: "display:cont
|
||||
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 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.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 el = typeof target === "string" ? document.querySelector(target) : target;
|
||||
if (!el) return;
|
||||
@@ -454,8 +443,9 @@ const $mount = (component, target) => {
|
||||
return instance;
|
||||
};
|
||||
|
||||
/** GLOBAL CORE REGISTRY */
|
||||
const SigProCore = { $, $watch, $html, $if, $for, $router, $mount };
|
||||
export const Fragment = ({ children }) => children;
|
||||
|
||||
const SigProCore = { $, $watch, $html, $if, $for, $router, $mount, Fragment };
|
||||
|
||||
if (typeof window !== "undefined") {
|
||||
const install = (registry) => {
|
||||
@@ -471,6 +461,7 @@ if (typeof window !== "undefined") {
|
||||
}
|
||||
});
|
||||
|
||||
window.Fragment = Fragment;
|
||||
window.SigPro = Object.freeze(registry);
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user