Returno to inytegrate Tags in Core
All checks were successful
Deploy Docs to Synology / deploy (push) Successful in 4s

This commit is contained in:
2026-04-28 18:42:56 +02:00
parent 995f1557bf
commit 2a0ce8c68f
16 changed files with 196 additions and 248 deletions

55
dist/sigpro.esm.js vendored
View File

@@ -15,10 +15,6 @@ var MOUNTED_NODES = new WeakMap;
var SVG_NS = "http://www.w3.org/2000/svg";
var XLINK_NS = "http://www.w3.org/1999/xlink";
var SVG_TAGS = new Set("svg,path,circle,rect,line,polyline,polygon,g,defs,text,textPath,tspan,use,symbol,image,marker,ellipse".split(","));
var attrFilter = null;
var filterXSS = (fn) => {
attrFilter = fn;
};
var dispose = (eff) => {
if (!eff || eff._disposed)
return;
@@ -247,6 +243,21 @@ var cleanupNode = (node) => {
if (node.childNodes)
node.childNodes.forEach((n) => cleanupNode(n));
};
var DANGEROUS_PROTOCOL = /^\s*(javascript|data|vbscript):/i;
var DANGEROUS_URI_ATTRS = new Set(["src", "href", "formaction", "action", "background", "code", "archive"]);
var isDangerousAttr = (key) => DANGEROUS_URI_ATTRS.has(key) || key.startsWith("on");
var validateAttr = (key, val) => {
if (val == null || val === false)
return null;
if (isDangerousAttr(key)) {
const sVal = String(val);
if (DANGEROUS_PROTOCOL.test(sVal)) {
console.warn(`[SigPro] Bloqueado protocolo peligroso en ${key}`);
return "#";
}
}
return val;
};
var h = (tag, props = {}, children = []) => {
if (props instanceof Node || isArr(props) || !isObj(props)) {
children = props;
@@ -285,38 +296,38 @@ var h = (tag, props = {}, children = []) => {
isFunc(v) ? v(el) : v.current = el;
continue;
}
let val = attrFilter ? attrFilter(k, v) : v;
if (isSVG && k.startsWith("xlink:")) {
val == null ? el.removeAttributeNS(XLINK_NS, k.slice(6)) : el.setAttributeNS(XLINK_NS, k.slice(6), val);
const cleanVal = validateAttr(k.slice(6), v);
cleanVal == null ? el.removeAttributeNS(XLINK_NS, k.slice(6)) : el.setAttributeNS(XLINK_NS, k.slice(6), cleanVal);
continue;
}
if (k.startsWith("on")) {
const ev = k.slice(2).toLowerCase();
el.addEventListener(ev, val);
const off = () => el.removeEventListener(ev, val);
el.addEventListener(ev, v);
const off = () => el.removeEventListener(ev, v);
el._cleanups.add(off);
onUnmount(off);
} else if (isFunc(val)) {
} else if (isFunc(v)) {
const effect = createEffect(() => {
const raw = val();
const safeVal = attrFilter ? attrFilter(k, raw) : raw;
const val = validateAttr(k, v());
if (k === "class")
el.className = safeVal || "";
else if (safeVal == null)
el.className = val || "";
else if (val == null)
el.removeAttribute(k);
else if (k in el && !isSVG)
el[k] = safeVal;
el[k] = val;
else
el.setAttribute(k, safeVal === true ? "" : safeVal);
el.setAttribute(k, val === true ? "" : val);
});
effect();
el._cleanups.add(() => dispose(effect));
onUnmount(() => dispose(effect));
if (/^(INPUT|TEXTAREA|SELECT)$/.test(el.tagName) && (k === "value" || k === "checked")) {
const evType = k === "checked" ? "change" : "input";
el.addEventListener(evType, (ev) => val(ev.target[k]));
el.addEventListener(evType, (ev) => v(ev.target[k]));
}
} else {
const val = validateAttr(k, v);
if (val != null) {
if (k in el && !isSVG)
el[k] = val;
@@ -498,13 +509,23 @@ var mount = (comp, target) => {
MOUNTED_NODES.set(t, inst);
return inst;
};
var sigpro = () => {
if (typeof window === "undefined")
return;
Object.assign(window, { $, $$, watch, h, when, each, router, mount, batch });
"a abbr article aside ... video".split(" ").forEach((tag) => {
window[tag] = (props, children) => h(tag, props, children);
});
};
if (typeof import.meta === "undefined" && typeof window !== "undefined")
sigpro();
export {
when,
watch,
sigpro,
router,
mount,
h,
filterXSS,
each,
batch,
$$,

File diff suppressed because one or more lines are too long

91
dist/sigpro.js vendored
View File

@@ -37,15 +37,15 @@
});
};
// sigpro-full.js
var exports_sigpro_full = {};
__export(exports_sigpro_full, {
// index.js
var exports_sigpro = {};
__export(exports_sigpro, {
when: () => when,
watch: () => watch,
sigpro: () => sigpro,
router: () => router,
mount: () => mount,
h: () => h,
filterXSS: () => filterXSS,
each: () => each,
batch: () => batch,
$$: () => $$,
@@ -69,10 +69,6 @@
var SVG_NS = "http://www.w3.org/2000/svg";
var XLINK_NS = "http://www.w3.org/1999/xlink";
var SVG_TAGS = new Set("svg,path,circle,rect,line,polyline,polygon,g,defs,text,textPath,tspan,use,symbol,image,marker,ellipse".split(","));
var attrFilter = null;
var filterXSS = (fn) => {
attrFilter = fn;
};
var dispose = (eff) => {
if (!eff || eff._disposed)
return;
@@ -301,6 +297,21 @@
if (node.childNodes)
node.childNodes.forEach((n) => cleanupNode(n));
};
var DANGEROUS_PROTOCOL = /^\s*(javascript|data|vbscript):/i;
var DANGEROUS_URI_ATTRS = new Set(["src", "href", "formaction", "action", "background", "code", "archive"]);
var isDangerousAttr = (key) => DANGEROUS_URI_ATTRS.has(key) || key.startsWith("on");
var validateAttr = (key, val) => {
if (val == null || val === false)
return null;
if (isDangerousAttr(key)) {
const sVal = String(val);
if (DANGEROUS_PROTOCOL.test(sVal)) {
console.warn(`[SigPro] Bloqueado protocolo peligroso en ${key}`);
return "#";
}
}
return val;
};
var h = (tag, props = {}, children = []) => {
if (props instanceof Node || isArr(props) || !isObj(props)) {
children = props;
@@ -339,38 +350,38 @@
isFunc(v) ? v(el) : v.current = el;
continue;
}
let val = attrFilter ? attrFilter(k, v) : v;
if (isSVG && k.startsWith("xlink:")) {
val == null ? el.removeAttributeNS(XLINK_NS, k.slice(6)) : el.setAttributeNS(XLINK_NS, k.slice(6), val);
const cleanVal = validateAttr(k.slice(6), v);
cleanVal == null ? el.removeAttributeNS(XLINK_NS, k.slice(6)) : el.setAttributeNS(XLINK_NS, k.slice(6), cleanVal);
continue;
}
if (k.startsWith("on")) {
const ev = k.slice(2).toLowerCase();
el.addEventListener(ev, val);
const off = () => el.removeEventListener(ev, val);
el.addEventListener(ev, v);
const off = () => el.removeEventListener(ev, v);
el._cleanups.add(off);
onUnmount(off);
} else if (isFunc(val)) {
} else if (isFunc(v)) {
const effect = createEffect(() => {
const raw = val();
const safeVal = attrFilter ? attrFilter(k, raw) : raw;
const val = validateAttr(k, v());
if (k === "class")
el.className = safeVal || "";
else if (safeVal == null)
el.className = val || "";
else if (val == null)
el.removeAttribute(k);
else if (k in el && !isSVG)
el[k] = safeVal;
el[k] = val;
else
el.setAttribute(k, safeVal === true ? "" : safeVal);
el.setAttribute(k, val === true ? "" : val);
});
effect();
el._cleanups.add(() => dispose(effect));
onUnmount(() => dispose(effect));
if (/^(INPUT|TEXTAREA|SELECT)$/.test(el.tagName) && (k === "value" || k === "checked")) {
const evType = k === "checked" ? "change" : "input";
el.addEventListener(evType, (ev) => val(ev.target[k]));
el.addEventListener(evType, (ev) => v(ev.target[k]));
}
} else {
const val = validateAttr(k, v);
if (val != null) {
if (k in el && !isSVG)
el[k] = val;
@@ -552,38 +563,14 @@
MOUNTED_NODES.set(t, inst);
return inst;
};
// sigpro/tags.js
if (typeof window !== "undefined") {
"a abbr article aside audio b blockquote br button canvas caption cite code col colgroup datalist dd del details dfn dialog div dl dt em embed fieldset figcaption figure footer form h1 h2 h3 h4 h5 h6 header hr i iframe img input ins kbd label legend li main mark meter nav object ol optgroup option output p picture pre progress section select slot small source span strong sub summary sup svg table tbody td template textarea tfoot th thead time tr u ul video".split(" ").forEach((tag) => {
window[tag] = (...args) => h(tag, ...args);
var sigpro = () => {
if (typeof window === "undefined")
return;
Object.assign(window, { $, $$, watch, h, when, each, router, mount, batch });
"a abbr article aside ... video".split(" ").forEach((tag) => {
window[tag] = (props, children) => h(tag, props, children);
});
console.log("SigPro tags ready");
}
// sigpro/xss.js
var DANGEROUS_PROTOCOL = /^\s*(javascript|data|vbscript):/i;
var DANGEROUS_URI_ATTRS = new Set(["src", "href", "formaction", "action", "background", "code", "archive"]);
var isDangerousAttr = (key) => DANGEROUS_URI_ATTRS.has(key) || key.startsWith("on");
var validateAttr = (key, val) => {
if (val == null || val === false)
return null;
if (isDangerousAttr(key)) {
const sVal = String(val);
if (DANGEROUS_PROTOCOL.test(sVal)) {
console.warn(`[SigPro XSS] Locked ${key}`);
return "#";
}
}
return val;
};
filterXSS(validateAttr);
// sigpro-full.js
if (typeof window !== "undefined") {
const props = {};
for (const fn of [["$", $], ["$$", $$], ["watch", watch], ["h", h], ["when", when], ["each", each], ["router", router], ["mount", mount], ["batch", batch]]) {
props[fn[0]] = { value: fn[1], writable: false, configurable: false, enumerable: true };
}
Object.defineProperties(window, props);
}
if (typeof import.meta === "undefined" && typeof window !== "undefined")
sigpro();
})();

2
dist/sigpro.min.js vendored

File diff suppressed because one or more lines are too long

View File

@@ -111,8 +111,9 @@ export const filteredTodos = $(() => {
```javascript
// components/TodoApp.js
import { sigpro } from 'sigpro';
import { todos, filter, addTodo, toggleTodo, filteredTodos } from "../store/todos.js";
import "sigpro/tags" // tags helpers available in global
sigpro(); // tags helpers available in global also core functions
const TodoApp = () =>
div({ class: "todo-app" }, [

View File

@@ -138,7 +138,8 @@ h('svg', { width: 100, height: 100 }, [
## Complete Example
```javascript
import { $, h, mount } from 'sigpro';
import { sigpro } from 'sigpro';
sigpro(); // tags helpers available in global also core functions
const dynamicTag = $('h1');

View File

@@ -119,8 +119,8 @@ When `destroy()` is called (or when a new mount replaces an old one), everything
## Complete Example
```javascript
import { $, mount, div, h1, button } from 'sigpro';
import "sigpro/tags" // tags helpers available in global
import { sigpro } from 'sigpro';
sigpro(); // tags helpers available in global also core functions
const App = () => {
const count = $(0);

View File

@@ -1,18 +1,5 @@
# SigPro Complete API Reference
SigPro is a **RealDOM first** reactive microframework. No virtual DOM, no diffing overhead it updates the DOM directly with surgical precision. Builtin automatic cleanup prevents memory leaks, and the API is designed to be both tiny and powerful.
```javascript
import { $, $$, watch, h, when, each, router, mount, batch } from 'sigpro'
// Optional sideeffects (activate global helpers & security):
import 'sigpro/tags' // → window.div, window.span, etc.
import 'sigpro/xss' // → attribute sanitisation
```
In a classic IIFE script (`<script src="sigpro.min.js">`), **everything** (core, router, tags, XSS, global functions) is available automatically.
---
## Core Reactivity
### `$(value, localStorageKey?)` Signal & Computed
@@ -113,7 +100,8 @@ h('div', {}, [
Tag helpers are **not exported individually** from the core. To use them globally without the `h(...)` wrapper, activate the sideeffect module:
```javascript
import 'sigpro/tags' // now window.div, window.span, etc. are available
import { sigpro } from "sigpro"
sigpro(); // now window.div, window.span, etc. are available
// You can now write:
div({ class: 'container' }, [
@@ -128,21 +116,6 @@ Available tags: `a`, `abbr`, `article`, `aside`, `audio`, `b`, `blockquote`, `br
---
## Builtin XSS Shield (Optional)
SigPro includes an optional security layer that sanitises dangerous attributes (`href`, `src`, `formaction`, etc.) and blocks `javascript:` / `data:` / `vbscript:` protocols.
To enable it in ESM environments:
```javascript
import 'sigpro/xss'
```
In the IIFE bundle, the shield is **active by default**.
When the shield is enabled, trying to set `href="javascript:alert(1)"` will log a warning and replace the value with `'#'`.
---
## Flow Control Components
### `when(condition, thenComponent, elseComponent?)`
@@ -252,9 +225,8 @@ You never need to manually clean up just write reactive code.
## Full Example Counter with Persistence
```javascript
import { $, watch, mount } from 'sigpro'
import 'sigpro/tags' // ← activate global helpers
import 'sigpro/xss' // ← activate security (optional)
import { sigpro } from 'sigpro';
sigpro(); // All in global window
const count = $(0, 'counter') // persists in localStorage
@@ -268,19 +240,3 @@ const App = () =>
mount(App, '#app')
```
---
## 🔧 Customising the API (Renaming)
You can rename everything in one line:
```javascript
import { $ as signal, watch as effect, h as element, mount as render } from 'sigpro'
```
In the IIFE bundle, the core functions are already available as both `window.$` and `window.SigPro.$`, so you can alias them globally if needed:
```javascript
window.myReactive = $ // or window.SigPro.$
```

View File

@@ -152,8 +152,8 @@ If you want the router outlet to have no layout impact, you can set `display: co
## Complete Example
```javascript
import { $, router, mount } from "sigpro";
import "sigpro/tags" // tags helpers available in global
import { sigpro } from 'sigpro';
sigpro(); // tags helpers available in global also core functions
const Home = () => div("Welcome home");
const About = () => div("About us");

View File

@@ -32,21 +32,13 @@ When you use the **IIFE bundle** (`sigpro.js` or `sigpro.min.js`) with a traditi
When you import the **ES module** (`import { ... } from 'sigpro'`), the core **does not** add helpers to `window` by default. To enable global tags, import the dedicated sideeffect module:
```js
import 'sigpro/tags'; // ← activates window.div, window.span, etc.
import { sigpro } from 'sigpro';
sigpro(); // tags helpers available in global also core functions
// Now you can use helpers globally
const App = () => div({ class: "app" }, "Ready!");
```
If you also want builtin **XSS protection**, enable it once:
```js
import 'sigpro/xss'; // ← add security layer
import 'sigpro/tags'; // ← global helpers
```
Both are sideeffect modules, so the order doesnt matter.
> **Important:** The tag helpers are **not** exported as individual named exports from the core (`sigpro`). They become available as global functions (`window.div`, etc.) after the sideeffect runs.
> If you prefer to avoid globals, you can always use `h('div', ...)` directly—its perfectly fine.
@@ -201,12 +193,8 @@ const Timer = () => {
### ESM (modern projects)
```javascript
// Enable global helpers + security
import 'sigpro/tags';
import 'sigpro/xss';
// Import core functions
import { $, mount } from 'sigpro';
import { sigpro } from 'sigpro';
sigpro(); // tags helpers available in global also core functions
const nameSignal = $('');
@@ -240,26 +228,3 @@ mount(App, '#app');
</script>
```
---
## 9. Important Notes
- **Naming:** All tag helpers are **lowercase**.
- **Global availability:**
- **IIFE script** automatically on `window`.
- **ESM module** not global by default; use `import 'sigpro/tags'` to activate them.
- **Custom components:** Use **PascalCase** for your own component functions (e.g., `UserCard`) to visually distinguish them from builtin tags.
---
## 10. Summary
| Feature | Description |
| :--- | :--- |
| **Tag helpers** | Lowercase functions for every HTML element (e.g., `div()`, `button()`). |
| **Activation** | IIFE: automatic. ESM: `import 'sigpro/tags'`. |
| **Reactive attributes** | Pass a function to any attribute to keep it synced. |
| **Twoway binding** | Assign a signal directly to `value` or `checked` on form elements. |
| **Dynamic children** | Pass a function as a child for live updating content. |
| **Autocleanup** | All effects, events, and children are disposed when the element is removed. |
| **Security** | Optional XSS shield: `import 'sigpro/xss'`. |

View File

@@ -48,16 +48,13 @@ bun add sigpro
```html
<script type="module">
// Import the core no global helpers or XSS yet
import { $, h, mount } from 'https://cdn.jsdelivr.net/npm/sigpro@1.2.23/dist/sigpro.esm.min.js';
// Import the core
import { sigpro, $, h, mount } from 'https://cdn.jsdelivr.net/npm/sigpro@latest/dist/sigpro.esm.min.js';
sigpro(); // Optional: now div, span, button, etc. become global
// Option A: use named imports (no globals, recommended)
const count = $(0);
mount(() => h('h1', {}, () => `Count: ${count()}`), '#app');
// Option B: enable global tag helpers (still optional)
import 'https://cdn.jsdelivr.net/npm/sigpro@1.2.23/sigpro/tags.js';
// now div, span, button, etc. become global
</script>
```
@@ -68,7 +65,7 @@ bun add sigpro
```html
<!-- Classic script: full kit (core, router, tags, XSS) autoinstalled -->
<script src="https://cdn.jsdelivr.net/npm/sigpro@1.2.23/dist/sigpro.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/sigpro@latest/dist/sigpro.min.js"></script>
<script>
// $, h, div, button, router, etc. are already global
const count = $(0);
@@ -95,9 +92,8 @@ SigPro uses **lowercase** Tag Helpers (e.g., `div`, `button`) to keep the syntax
```javascript
// App.js Use named imports for the core, activate helpers if needed
import { $, mount } from 'sigpro';
import 'sigpro/tags'; // ← make div, h1, button, etc. global
import 'sigpro/xss'; // ← optional: activate XSS shield
import { sigpro, $, mount } from 'sigpro';
sigpro(); // Optional: now div, span, button, etc. become global
const App = () => {
const count = $(0);
@@ -171,12 +167,8 @@ When you import the **ESM core** (`import { ... } from 'sigpro'`), **only the re
2. **Activate global helpers** (when you want `div`, `span`, etc. without importing each one):
```javascript
import 'sigpro/tags'; // sideeffect: injects all tag helpers into window
```
3. **Activate XSS shield** (optional):
```javascript
import 'sigpro/xss'; // sideeffect: enables attribute sanitization
import { sigpro } from 'sigpro';
sigpro(); // sideeffect: injects all tag helpers into window
```
### Why two modes?

View File

@@ -42,12 +42,9 @@
__export(exports_sigpro, {
when: () => when,
watch: () => watch,
setAttrFilter: () => setAttrFilter,
sigpro: () => sigpro,
router: () => router,
render: () => render,
onUnmount: () => onUnmount,
mount: () => mount,
isFunc: () => isFunc,
h: () => h,
each: () => each,
batch: () => batch,
@@ -72,10 +69,6 @@
var SVG_NS = "http://www.w3.org/2000/svg";
var XLINK_NS = "http://www.w3.org/1999/xlink";
var SVG_TAGS = new Set("svg,path,circle,rect,line,polyline,polygon,g,defs,text,textPath,tspan,use,symbol,image,marker,ellipse".split(","));
var attrFilter = null;
var setAttrFilter = (fn) => {
attrFilter = fn;
};
var dispose = (eff) => {
if (!eff || eff._disposed)
return;
@@ -304,6 +297,21 @@
if (node.childNodes)
node.childNodes.forEach((n) => cleanupNode(n));
};
var DANGEROUS_PROTOCOL = /^\s*(javascript|data|vbscript):/i;
var DANGEROUS_URI_ATTRS = new Set(["src", "href", "formaction", "action", "background", "code", "archive"]);
var isDangerousAttr = (key) => DANGEROUS_URI_ATTRS.has(key) || key.startsWith("on");
var validateAttr = (key, val) => {
if (val == null || val === false)
return null;
if (isDangerousAttr(key)) {
const sVal = String(val);
if (DANGEROUS_PROTOCOL.test(sVal)) {
console.warn(`[SigPro] Bloqueado protocolo peligroso en ${key}`);
return "#";
}
}
return val;
};
var h = (tag, props = {}, children = []) => {
if (props instanceof Node || isArr(props) || !isObj(props)) {
children = props;
@@ -342,38 +350,38 @@
isFunc(v) ? v(el) : v.current = el;
continue;
}
let val = attrFilter ? attrFilter(k, v) : v;
if (isSVG && k.startsWith("xlink:")) {
val == null ? el.removeAttributeNS(XLINK_NS, k.slice(6)) : el.setAttributeNS(XLINK_NS, k.slice(6), val);
const cleanVal = validateAttr(k.slice(6), v);
cleanVal == null ? el.removeAttributeNS(XLINK_NS, k.slice(6)) : el.setAttributeNS(XLINK_NS, k.slice(6), cleanVal);
continue;
}
if (k.startsWith("on")) {
const ev = k.slice(2).toLowerCase();
el.addEventListener(ev, val);
const off = () => el.removeEventListener(ev, val);
el.addEventListener(ev, v);
const off = () => el.removeEventListener(ev, v);
el._cleanups.add(off);
onUnmount(off);
} else if (isFunc(val)) {
} else if (isFunc(v)) {
const effect = createEffect(() => {
const raw = val();
const safeVal = attrFilter ? attrFilter(k, raw) : raw;
const val = validateAttr(k, v());
if (k === "class")
el.className = safeVal || "";
else if (safeVal == null)
el.className = val || "";
else if (val == null)
el.removeAttribute(k);
else if (k in el && !isSVG)
el[k] = safeVal;
el[k] = val;
else
el.setAttribute(k, safeVal === true ? "" : safeVal);
el.setAttribute(k, val === true ? "" : val);
});
effect();
el._cleanups.add(() => dispose(effect));
onUnmount(() => dispose(effect));
if (/^(INPUT|TEXTAREA|SELECT)$/.test(el.tagName) && (k === "value" || k === "checked")) {
const evType = k === "checked" ? "change" : "input";
el.addEventListener(evType, (ev) => val(ev.target[k]));
el.addEventListener(evType, (ev) => v(ev.target[k]));
}
} else {
const val = validateAttr(k, v);
if (val != null) {
if (k in el && !isSVG)
el[k] = val;
@@ -555,4 +563,14 @@
MOUNTED_NODES.set(t, inst);
return inst;
};
var sigpro = () => {
if (typeof window === "undefined")
return;
Object.assign(window, { $, $$, watch, h, when, each, router, mount, batch });
"a abbr article aside ... video".split(" ").forEach((tag) => {
window[tag] = (props, children) => h(tag, props, children);
});
};
if (typeof import.meta === "undefined" && typeof window !== "undefined")
sigpro();
})();

View File

@@ -14,15 +14,12 @@
"script": "./dist/sigpro.min.js",
"types": "./sigpro.d.ts"
},
"./xss": "./sigpro/xss.js",
"./tags": "./sigpro/tags.js",
"./vite": "./vite/index.js",
"./vite/*": "./vite/*.js"
},
"files": [
"index.js",
"sigpro.js",
"sigpro/",
"dist/",
"vite/",
"README.md",
@@ -36,18 +33,18 @@
"bugs": {
"url": "https://github.com/natxocc/sigpro/issues"
},
"scripts": {
"clean": "rm -rf dist",
"prebuild": "npm run clean",
"build:esm": "bun build ./index.js --bundle --outfile=./dist/sigpro.esm.js --format=esm",
"build:esm:min": "bun build ./index.js --bundle --outfile=./dist/sigpro.esm.min.js --format=esm --minify",
"build:iife": "bun build ./sigpro-full.js --bundle --outfile=./dist/sigpro.js --format=iife --global-name=SigPro",
"build:iife:min": "bun build ./sigpro-full.js --bundle --outfile=./dist/sigpro.min.js --format=iife --global-name=SigPro --minify",
"build:docs": "bun build ./sigpro-full.js --bundle --outfile=./docs/sigpro-full.js --format=iife --global-name=SigPro",
"build": "bun run build:esm && bun run build:esm:min && bun run build:iife && bun run build:iife:min && bun run build:docs",
"docs": "bun x serve docs",
"prepublishOnly": "npm run build"
},
"scripts": {
"clean": "rm -rf dist",
"prebuild": "npm run clean",
"build:iife": "bun build ./index.js --bundle --outfile=./dist/sigpro.js --format=iife --global-name=SigPro",
"build:docs": "bun build ./index.js --bundle --outfile=./docs/sigpro.js --format=iife --global-name=SigPro",
"build:iife:min": "bun build ./index.js --bundle --outfile=./dist/sigpro.min.js --format=iife --global-name=SigPro --minify",
"build:esm": "bun build ./index.js --bundle --outfile=./dist/sigpro.esm.js --format=esm",
"build:esm:min": "bun build ./index.js --bundle --outfile=./dist/sigpro.esm.min.js --format=esm --minify",
"build": "bun run build:iife && bun run build:iife:min && bun run build:esm && bun run build:esm:min && bun run build:docs",
"docs": "bun x serve docs",
"prepublishOnly": "npm run build"
},
"keywords": [
"signals",
"reactivity",
@@ -63,4 +60,4 @@
"no-vdom",
"sigpro"
]
}
}

View File

@@ -1,12 +0,0 @@
// sigpro-full.js
export * from './sigpro.js';
import './sigpro/tags.js';
import './sigpro/xss.js';
import { $, $$, watch, h, when, each, router, mount, batch } from './sigpro.js';
if (typeof window !== 'undefined') {
const props = {};
for (const fn of [['$', $], ['$$', $$], ['watch', watch], ['h', h], ['when', when], ['each', each], ['router', router], ['mount', mount], ['batch', batch]]) {
props[fn[0]] = { value: fn[1], writable: false, configurable: false, enumerable: true };
}
Object.defineProperties(window, props);
}

View File

@@ -17,9 +17,6 @@ const SVG_NS = "http://www.w3.org/2000/svg"
const XLINK_NS = "http://www.w3.org/1999/xlink"
const SVG_TAGS = new Set("svg,path,circle,rect,line,polyline,polygon,g,defs,text,textPath,tspan,use,symbol,image,marker,ellipse".split(","))
let attrFilter = null
const filterXSS = fn => { attrFilter = fn }
const dispose = eff => {
if (!eff || eff._disposed) return
eff._disposed = true
@@ -235,6 +232,22 @@ const cleanupNode = (node) => {
if (node.childNodes) node.childNodes.forEach(n => cleanupNode(n));
};
var DANGEROUS_PROTOCOL = /^\s*(javascript|data|vbscript):/i;
var DANGEROUS_URI_ATTRS = new Set(["src", "href", "formaction", "action", "background", "code", "archive"]);
var isDangerousAttr = (key) => DANGEROUS_URI_ATTRS.has(key) || key.startsWith("on");
const validateAttr = (key, val) => {
if (val == null || val === false) return null
if (isDangerousAttr(key)) {
const sVal = String(val)
if (DANGEROUS_PROTOCOL.test(sVal)) {
console.warn(`[SigPro] Bloqueado protocolo peligroso en ${key}`)
return '#'
}
}
return val
}
const h = (tag, props = {}, children = []) => {
if (props instanceof Node || isArr(props) || !isObj(props)) {
children = props
@@ -281,37 +294,36 @@ const h = (tag, props = {}, children = []) => {
isFunc(v) ? v(el) : (v.current = el)
continue
}
let val = attrFilter ? attrFilter(k, v) : v
if (isSVG && k.startsWith("xlink:")) {
val == null
const cleanVal = validateAttr(k.slice(6), v)
cleanVal == null
? el.removeAttributeNS(XLINK_NS, k.slice(6))
: el.setAttributeNS(XLINK_NS, k.slice(6), val)
: el.setAttributeNS(XLINK_NS, k.slice(6), cleanVal)
continue
}
if (k.startsWith("on")) {
const ev = k.slice(2).toLowerCase()
el.addEventListener(ev, val)
const off = () => el.removeEventListener(ev, val)
el.addEventListener(ev, v)
const off = () => el.removeEventListener(ev, v)
el._cleanups.add(off)
onUnmount(off)
} else if (isFunc(val)) {
} else if (isFunc(v)) {
const effect = createEffect(() => {
const raw = val()
const safeVal = attrFilter ? attrFilter(k, raw) : raw
if (k === "class") el.className = safeVal || ""
else if (safeVal == null) el.removeAttribute(k)
else if (k in el && !isSVG) el[k] = safeVal
else el.setAttribute(k, safeVal === true ? "" : safeVal)
const val = validateAttr(k, v())
if (k === "class") el.className = val || ""
else if (val == null) el.removeAttribute(k)
else if (k in el && !isSVG) el[k] = val
else el.setAttribute(k, val === true ? "" : val)
})
effect()
el._cleanups.add(() => dispose(effect))
onUnmount(() => dispose(effect))
if (/^(INPUT|TEXTAREA|SELECT)$/.test(el.tagName) && (k === "value" || k === "checked")) {
const evType = k === "checked" ? "change" : "input"
el.addEventListener(evType, ev => val(ev.target[k]))
el.addEventListener(evType, ev => v(ev.target[k]))
}
} else {
const val = validateAttr(k, v)
if (val != null) {
if (k in el && !isSVG) el[k] = val
else el.setAttribute(k, val === true ? "" : val)
@@ -493,4 +505,14 @@ const mount = (comp, target) => {
return inst
}
export { $, $$, watch, h, when, each, router, mount, batch, filterXSS }
const sigpro = () => {
if (typeof window === "undefined") return
Object.assign(window, { $, $$, watch, h, when, each, router, mount, batch })
"a abbr article aside ... video"
.split(" ")
.forEach(tag => { window[tag] = (props, children) => h(tag, props, children) })
}
if (typeof import.meta === 'undefined' && typeof window !== 'undefined') sigpro()
export { sigpro, $, $$, watch, h, when, each, router, mount, batch }

View File

@@ -3,8 +3,8 @@ import { h } from '../sigpro.js';
if (typeof window !== 'undefined') {
'a abbr article aside audio b blockquote br button canvas caption cite code col colgroup datalist dd del details dfn dialog div dl dt em embed fieldset figcaption figure footer form h1 h2 h3 h4 h5 h6 header hr i iframe img input ins kbd label legend li main mark meter nav object ol optgroup option output p picture pre progress section select slot small source span strong sub summary sup svg table tbody td template textarea tfoot th thead time tr u ul video'
.split(' ').forEach(tag => {
// window[tag] = (props, children) => h(tag, props, children);
window[tag] = (...args) => h(tag, ...args);
window[tag] = (props, children) => h(tag, props, children);
// window[tag] = (...args) => h(tag, ...args);
});
console.log('SigPro tags ready');
}