Datepicker working with sigpro updated
This commit is contained in:
@@ -5,7 +5,7 @@
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>svelte</title>
|
||||
<title>SigPro</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
|
||||
11
src/App.js
11
src/App.js
@@ -40,7 +40,8 @@ const Home = () => {
|
||||
};
|
||||
|
||||
const Profile = (params) => {
|
||||
const miFecha = $({ start: null, end: null });
|
||||
const miFecha = $();
|
||||
const miRango = $();
|
||||
const selectedFruit = $("Apple");
|
||||
const fruits = ["Apple", "Banana", "Cherry", "Dragonfruit", "Elderberry"];
|
||||
|
||||
@@ -58,7 +59,11 @@ const Profile = (params) => {
|
||||
options: fruits,
|
||||
onSelect: (val) => console.log("Seleccionado:", val),
|
||||
}),
|
||||
Input({type: "number", label: "Number", icon: "🔍"}),
|
||||
Datepicker({ $value: miFecha, label: "Fecha", placeholder: textoInput }),
|
||||
Datepicker({ $value: miRango, label: "Fecha", placeholder: textoInput, range: true }),
|
||||
Input({ type: "number", label: "Number" }),
|
||||
Input({ type: "email", label: "Email" }),
|
||||
Input({ label: "Text" }),
|
||||
Input({ type: "date", label: "Date" }),
|
||||
Input({ type: "password", label: "Password" }),
|
||||
$.html("p", {}, () => `Has elegido: ${selectedFruit()}`),
|
||||
@@ -81,7 +86,7 @@ export const App = () => {
|
||||
const isDark = $(false, "sigpro-theme");
|
||||
|
||||
// Efecto para cambiar el tema en el HTML
|
||||
$$(() => {
|
||||
$.effect(() => {
|
||||
document.documentElement.setAttribute("data-theme", isDark() ? "dark" : "light");
|
||||
});
|
||||
|
||||
|
||||
356
src/sigpro-ui.js
356
src/sigpro-ui.js
@@ -1,3 +1,5 @@
|
||||
import { $ } from "./sigpro";
|
||||
|
||||
/**
|
||||
* SigPro UI - daisyUI v5 & Tailwind v4 Plugin
|
||||
* Provides a set of reactive functional components, flow control and i18n.
|
||||
@@ -29,6 +31,23 @@ export const UI = ($, defaultLang = "es") => {
|
||||
return `${base} ${extra || ""}`.trim();
|
||||
};
|
||||
|
||||
/** ICONS */
|
||||
const iconShow =
|
||||
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAAdgAAAHYBTnsmCAAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAADjSURBVDiN3dJNSgNBEAXgz4DZeAAVJ9tko2St3kaIFxAVt4KZeAD1GKKi7vQSydI/yHgALxAXU02GxniAFBR0v1ev+3V1sZSxjxtM8BM5wTX2/hNu4gFvOMI21iJ3cIwP3GMjF/dQ4RyraOMS34GPAmvjIrBeEnfwjoPGgSM8ooh8QtngB6Ep4BWnmaMqkY1LqqzmDC8tzNDK3/RHzLL9SloUYWfQIMuw3Yl8xrDBH6qbvZWALqbqBqVmlWF7GuKEDwPr5hbXcYdPnKBv/o39wL5wG7ULY1c9NGPzQRrjKrhli1/02zEjWyWMBwAAAABJRU5ErkJggg==";
|
||||
const iconHide =
|
||||
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAAdgAAAHYBTnsmCAAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAAEDSURBVDiN1dK/K8VhFAbwD+VLGSxKcu9guSQ/Zils/gNkuaX4BxRZDTdklYU/QAaDlEVGGwu2Kz/uVbKJzWDwfuv1+jHz1Km3c85znuf0Hv4jxnD2W8MItnCJ5xAX2MQcHsOQL+jEAapYQD9aQwxiDy+B3JKSe1DHCpqQYQ0PeMJOpDyAmyAAirjGbDRwFYcoYCZSzjGP+8B1gqXEUT2QxyPlqaRnGceNeENzUswwil1MBocbSU9DCAXUUI6K25HtIo5QSVaooitP9OEO65iIbE+HXSvBVRbeNZQSR9pxGil3o83HNw5hEbfYR0dKFki5ci+u8OrzIQ1/R8xx7ocL+9t4B0HPOVXjoptxAAAAAElFTkSuQmCC";
|
||||
const iconClose =
|
||||
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAAdgAAAHYBTnsmCAAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAAESSURBVDiNtdO7LoRBHAXwXyhVG5ddEq9AgbXiskS1dOJS6DyDRKLmFRCvQY9aIi6FnpDsrkuiQCWKmS/5dmW/VXCSyWTm/GfmnPzP8A8oYBc3eEMd59iKXCZW8YpDzCOHHszgAE9Ya3V4EY8YzXhgAndYaib68YxiO4kYRg290Bk3t/GAvbiuII/7uJ7CGG5RxSCGcJrceh2LEkzGwnIctTgnGMdFWtZ7IimFcrykirkmrkvokI7WVn1lcD9wpdFCKfVyYmE2xRdFC4mCY2ykCgaEfp/gTGhbX4pfx1FaQUEIyW/bWBUC1oAFIUgjGYdLWgQpwTJesC/4z6Eb00JG6lhpJzGPHVziEx/CZ9qM3N/iGy1pNoTrsd1eAAAAAElFTkSuQmCC";
|
||||
const iconCalendar =
|
||||
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAAdgAAAHYBTnsmCAAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAACLSURBVDiN7dO9CQJBFEXhb38K0FwQrMNEVpuwB0NjrcYabECsQk0sQ1mTF4zIjrgmBh54MMx998AEwzOrmC5e8gJjbDHCJO7PHYI0v2JT4Ig9DljGwq5DkOZTLOCOMoIhBpknpHmFWx3ldaaUo6oTc2/ab7rl+508f8GvCC5oenTn4tM1cWg/nBNmD4fBH/Kfvt2TAAAAAElFTkSuQmCC";
|
||||
const iconLock =
|
||||
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAAWQAAAFkBqp2phgAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAACQSURBVDiN7dKxDcJQDATQJ0YgXQQ1bAgDEIZBETPQwjakIjRQ8CMSyR8SiZKTrvHZd/r+JsYSNZrEI1ZR4ywzfElcJ55xwiITOECNTVDf4jDGoEEZ1Etcxxg8pmjRDiahb7BH20uKKPVUkVmL+YjQArdI+PT2bO9Pd/A34O71Rd9QeN/LAFUSckfUscWuG3oCgP8nrDH6T5AAAAAASUVORK5CYII=";
|
||||
const iconAbc =
|
||||
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAACXBIWXMAAAB2AAAAdgFOeyYIAAAAGXRFWHRTb2Z0d2FyZQB3d3cuaW5rc2NhcGUub3Jnm+48GgAAAMRJREFUOI3t0bFKAmAUBeAPURD1HQwUTKPJEmzQoSWQcKpVfIuWdvU9WnqNhsYWBx0a2lvLSMKGbvQ7SO564HA497/3cu/92SPFAS5QDN9CftviDhZYYRpNPtH/rzATOsQT6jhCFzmc4DTJL6AX067hPiimuAr95RglzMJ/4AyyUXSMw3iEauhN6C0eUEMFAyzTFZ7xiOvwL3jbsPYSr3hPg3dB/o43SVYY+TnsPPwXztMG5SDr39dGM8kr4RKNDdPtJL4BNXEmsdKC+S4AAAAASUVORK5CYII=";
|
||||
const icon123 =
|
||||
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAACXBIWXMAAAB2AAAAdgFOeyYIAAAAGXRFWHRTb2Z0d2FyZQB3d3cuaW5rc2NhcGUub3Jnm+48GgAAAMxJREFUOI3t0bFKwlEUBvBfmmBEr1APIDZJ9AJJQyAIvkGP0C4uQruza+DUmuIc9AC9gBG4Nmpkw/8IB3Vw1w8u95zvnvPde77LEeUUV9HAF67QRA2nmMf5A+o4x3cWOsMYy8j7WMX6jaYbLBL/mAWe8RcHm1ihs8G94gVKQQzwlAouMcQo8p/Y28HdYpYFZmsi0MVdxD1MdrxsC500wijdvgtbI1AYtDbxMwkuFAZmE1uYwkkSqOIaHyHcxEU0vUXNPSqKr37fZ6xDwD9DPS0OyHjQHQAAAABJRU5ErkJggg==";
|
||||
const iconMail =
|
||||
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAAdgAAAHYBTnsmCAAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAAC4SURBVDiNxdIxagJRFIXhLzLFBNJYaJslSEylWOhq3IorMGQ16SyjYCFiZWU5pTaDFvOUyTAZ8RHID69555577oXLf/OEGaY4R3g/4IhORHg3eOXYYvSAeRQ8OWQYYoNPvDQYnxUr7zBB1grCAv3QbIlxjXmAb7Txhq+rkFUKq9NUU8vcJiizwDtOWGEdmvTKqT+61H0GXsP7jSxpEGF/R1e3wkO0FBeVRnhTSBTneBB3yvOI4D/mAnvrIwKM5s4AAAAAAElFTkSuQmCC";
|
||||
// --- UTILITY FUNCTIONS ---
|
||||
|
||||
/** IF */
|
||||
@@ -51,7 +70,7 @@ export const UI = ($, defaultLang = "es") => {
|
||||
const container = $.html("div", { style: "display:contents" }, [marker]);
|
||||
const cache = new Map();
|
||||
|
||||
$$(() => {
|
||||
$.effect(() => {
|
||||
const items = val(source) || [];
|
||||
const newKeys = new Set();
|
||||
|
||||
@@ -181,53 +200,75 @@ export const UI = ($, defaultLang = "es") => {
|
||||
};
|
||||
|
||||
/** INPUT */
|
||||
ui.Input = (props) => {
|
||||
const { label, tip, $value, $error, isSearch, icon, ...rest } = props;
|
||||
|
||||
// Estado local para alternar visibilidad si es password
|
||||
const isPassword = props.type === "password";
|
||||
const showPassword = $(false);
|
||||
ui.Input = (props) => {
|
||||
const { label, tip, $value, $error, isSearch, icon, type = "text", ...rest } = props;
|
||||
|
||||
const isPassword = type === "password";
|
||||
const visible = $(false);
|
||||
|
||||
const iconsByType = {
|
||||
text: iconAbc,
|
||||
password: iconLock,
|
||||
date: iconCalendar,
|
||||
number: icon123,
|
||||
email: iconMail,
|
||||
};
|
||||
|
||||
const inputEl = $.html("input", {
|
||||
...rest,
|
||||
// El tipo cambia dinámicamente si es password
|
||||
type: () => (isPassword ? (showPassword() ? "text" : "password") : (props.type || "text")),
|
||||
placeholder: props.placeholder || (isSearch ? tt("search")() : " "),
|
||||
class: joinClass("grow order-2", props.$class || props.class),
|
||||
$value: $value || props.value,
|
||||
type: () => (isPassword ? (visible() ? "text" : "password") : type),
|
||||
placeholder: props.placeholder || label || (isSearch ? tt("search")() : " "),
|
||||
class: joinClass("grow order-2 focus:outline-none", props.$class || props.class),
|
||||
$value: $value,
|
||||
oninput: (e) => {
|
||||
if (typeof $value === "function") $value(e.target.value);
|
||||
if (typeof props.oninput === "function") props.oninput(e);
|
||||
$value?.(e.target.value);
|
||||
props.oninput?.(e);
|
||||
},
|
||||
$disabled: () => val(props.$disabled) || val(props.disabled),
|
||||
});
|
||||
|
||||
return $.html("label", {
|
||||
class: "input input-bordered floating-label flex items-center gap-2 w-full relative"
|
||||
}, [
|
||||
// 1. Icono Izquierda (opcional)
|
||||
icon ? $.html("div", { class: "order-1 flex items-center opacity-50 shrink-0" }, icon) : null,
|
||||
const leftIcon = icon ? icon : iconsByType[type] ? $.html("img", { src: iconsByType[type], class: "w-5 h-5 opacity-50", alt: type }) : null;
|
||||
|
||||
// 2. Texto del Label
|
||||
label ? $.html("span", { class: "order-0" }, label) : null,
|
||||
|
||||
// 3. Input
|
||||
return $.html(
|
||||
"label",
|
||||
{
|
||||
class: () => joinClass("input input-bordered floating-label flex items-center gap-2 w-full relative", val($error) ? "input-error" : ""),
|
||||
},
|
||||
[
|
||||
leftIcon ? $.html("div", { class: "order-1 shrink-0" }, leftIcon) : null,
|
||||
label ? $.html("span", { class: "text-base-content/60 order-0" }, label) : null,
|
||||
inputEl,
|
||||
|
||||
// 4. Botón Ojo (Solo si es type="password")
|
||||
isPassword ? $.html("button", {
|
||||
isPassword
|
||||
? $.html(
|
||||
"button",
|
||||
{
|
||||
type: "button",
|
||||
class: "order-3 btn btn-ghost btn-xs btn-circle opacity-50 hover:opacity-100",
|
||||
onclick: (e) => {
|
||||
e.preventDefault();
|
||||
showPassword(!showPassword());
|
||||
}
|
||||
}, () => (showPassword() ? "🙈" : "👁️")) : null,
|
||||
visible(!visible());
|
||||
},
|
||||
},
|
||||
() =>
|
||||
$.html("img", {
|
||||
class: "w-5 h-5",
|
||||
src: visible() ? iconShow : iconHide,
|
||||
}),
|
||||
)
|
||||
: null,
|
||||
|
||||
// 5. Tooltip/Error
|
||||
tip ? $.html("div", { class: "tooltip tooltip-right order-4", "data-tip": tip }, $.html("span", { class: "badge badge-ghost badge-xs" }, "?")) : null,
|
||||
() => (val($error) ? $.html("span", { class: "text-error text-xs absolute -bottom-5 left-0" }, val($error)) : null),
|
||||
]);
|
||||
tip
|
||||
? $.html(
|
||||
"div",
|
||||
{ class: "tooltip tooltip-left order-4", "data-tip": tip },
|
||||
$.html("span", { class: "badge badge-ghost badge-xs cursor-help" }, "?"),
|
||||
)
|
||||
: null,
|
||||
|
||||
() => (val($error) ? $.html("span", { class: "text-error text-[10px] absolute -bottom-5 left-2" }, val($error)) : null),
|
||||
],
|
||||
);
|
||||
};
|
||||
|
||||
/** SELECT */
|
||||
@@ -268,62 +309,60 @@ ui.Input = (props) => {
|
||||
|
||||
const query = $(val($value) || "");
|
||||
const isOpen = $(false);
|
||||
const highlightedIndex = $(-1);
|
||||
const cursor = $(-1);
|
||||
|
||||
const filtered = $(() => {
|
||||
const list = $(() => {
|
||||
const q = query().toLowerCase();
|
||||
const list = val(options) || [];
|
||||
if (!q) return list;
|
||||
return list.filter((opt) => {
|
||||
const text = typeof opt === "string" ? opt : opt.label;
|
||||
return text.toLowerCase().includes(q);
|
||||
});
|
||||
const data = val(options) || [];
|
||||
return q ? data.filter((o) => (typeof o === "string" ? o : o.label).toLowerCase().includes(q)) : data;
|
||||
});
|
||||
|
||||
const select = (opt) => {
|
||||
const v = typeof opt === "string" ? opt : opt.value;
|
||||
const l = typeof opt === "string" ? opt : opt.label;
|
||||
query(l);
|
||||
if (typeof $value === "function") $value(v);
|
||||
const pick = (opt) => {
|
||||
const value = typeof opt === "string" ? opt : opt.value;
|
||||
const label = typeof opt === "string" ? opt : opt.label;
|
||||
|
||||
query(label);
|
||||
$value?.(value);
|
||||
onSelect?.(opt);
|
||||
|
||||
isOpen(false);
|
||||
highlightedIndex(-1);
|
||||
cursor(-1);
|
||||
};
|
||||
|
||||
const onKeyDown = (e) => {
|
||||
const list = filtered();
|
||||
const nav = (e) => {
|
||||
const items = list();
|
||||
|
||||
if (e.key === "ArrowDown") {
|
||||
e.preventDefault();
|
||||
isOpen(true);
|
||||
highlightedIndex((prev) => Math.min(prev + 1, list.length - 1));
|
||||
} else if (e.key === "ArrowUp") {
|
||||
e.preventDefault();
|
||||
highlightedIndex((prev) => Math.max(prev - 1, 0));
|
||||
} else if (e.key === "Enter" && highlightedIndex() >= 0) {
|
||||
e.preventDefault();
|
||||
select(list[highlightedIndex()]);
|
||||
} else if (e.key === "Escape") {
|
||||
isOpen(false);
|
||||
cursor((i) => Math.min(i + 1, items.length - 1));
|
||||
}
|
||||
|
||||
if (e.key === "ArrowUp") {
|
||||
e.preventDefault();
|
||||
cursor((i) => Math.max(i - 1, 0));
|
||||
}
|
||||
|
||||
if (e.key === "Enter" && cursor() >= 0) {
|
||||
e.preventDefault();
|
||||
pick(items[cursor()]);
|
||||
}
|
||||
|
||||
if (e.key === "Escape") isOpen(false);
|
||||
};
|
||||
|
||||
return $.html(
|
||||
"div",
|
||||
{
|
||||
class: "relative w-full",
|
||||
},
|
||||
[
|
||||
return $.html("div", { class: "relative w-full" }, [
|
||||
ui.Input({
|
||||
label,
|
||||
placeholder: placeholder || tt("search")(),
|
||||
$value: query,
|
||||
onfocus: () => isOpen(true),
|
||||
onblur: () => setTimeout(() => isOpen(false), 200),
|
||||
onkeydown: onKeyDown,
|
||||
onblur: () => setTimeout(() => isOpen(false), 150),
|
||||
onkeydown: nav,
|
||||
oninput: (e) => {
|
||||
query(e.target.value);
|
||||
isOpen(true);
|
||||
highlightedIndex(-1);
|
||||
cursor(-1);
|
||||
},
|
||||
...rest,
|
||||
}),
|
||||
@@ -331,34 +370,195 @@ ui.Input = (props) => {
|
||||
$.html(
|
||||
"ul",
|
||||
{
|
||||
// AÑADIDO: w-full y box-border para que no se pase ni un píxel
|
||||
class: "absolute left-0 w-80 max-w-[95vw] menu bg-base-100 rounded-box z-[100] mt-1 p-2 shadow-2xl max-h-60 overflow-y-auto border border-base-300",
|
||||
style: () => (isOpen() && filtered().length > 0 ? "display: block" : "display: none"),
|
||||
class: "absolute left-0 w-full menu bg-base-100 rounded-box mt-1 p-2 shadow-xl max-h-60 overflow-y-auto border border-base-300 z-50",
|
||||
style: () => (isOpen() && list().length ? "display:block" : "display:none"),
|
||||
},
|
||||
[
|
||||
ui.For(
|
||||
filtered,
|
||||
list,
|
||||
(opt, i) =>
|
||||
$.html("li", { class: "w-full" }, [
|
||||
// li al 100%
|
||||
$.html("li", {}, [
|
||||
$.html(
|
||||
"a",
|
||||
{
|
||||
// AÑADIDO: block w-full para que el azul cubra todo el ancho
|
||||
class: () => joinClass("block w-full", highlightedIndex() === i && "active bg-primary text-primary-content"),
|
||||
onclick: () => select(opt),
|
||||
onmouseenter: () => highlightedIndex(i),
|
||||
class: () => `block w-full ${cursor() === i ? "active bg-primary text-primary-content" : ""}`,
|
||||
onclick: () => pick(opt),
|
||||
onmouseenter: () => cursor(i),
|
||||
},
|
||||
typeof opt === "string" ? opt : opt.label,
|
||||
),
|
||||
]),
|
||||
(opt, i) => (typeof opt === "string" ? opt : opt.value) + i,
|
||||
),
|
||||
() => (filtered().length === 0 ? $.html("li", { class: "disabled p-2 text-center opacity-50" }, "No hay resultados") : null),
|
||||
|
||||
() => (!list().length ? $.html("li", { class: "p-2 text-center opacity-50" }, "No results") : null),
|
||||
],
|
||||
),
|
||||
],
|
||||
]);
|
||||
};
|
||||
|
||||
/** DATEPICKER */
|
||||
|
||||
ui.Datepicker = (props) => {
|
||||
const { $value, range, label, placeholder, ...rest } = props;
|
||||
|
||||
const isOpen = $(false);
|
||||
const internalDate = $(new Date());
|
||||
const hoverDate = $(null);
|
||||
|
||||
// Determinamos si es rango (si no se pasa nada, por defecto es falso para fecha única)
|
||||
const isRangeMode = () => val(range) === true;
|
||||
|
||||
const now = new Date();
|
||||
const todayStr = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}-${String(now.getDate()).padStart(2, "0")}`;
|
||||
|
||||
const formatDate = (d) => {
|
||||
const year = d.getFullYear();
|
||||
const month = String(d.getMonth() + 1).padStart(2, "0");
|
||||
const day = String(d.getDate()).padStart(2, "0");
|
||||
return `${year}-${month}-${day}`;
|
||||
};
|
||||
|
||||
const selectDate = (date) => {
|
||||
const dateStr = formatDate(date);
|
||||
const current = val($value);
|
||||
|
||||
if (isRangeMode()) {
|
||||
// MODO RANGO
|
||||
if (!current?.start || (current.start && current.end)) {
|
||||
$value({ start: dateStr, end: null });
|
||||
} else {
|
||||
const start = current.start;
|
||||
$value(dateStr < start ? { start: dateStr, end: start } : { start, end: dateStr });
|
||||
isOpen(false);
|
||||
}
|
||||
} else {
|
||||
// MODO FECHA ÚNICA
|
||||
$value(dateStr);
|
||||
isOpen(false);
|
||||
}
|
||||
};
|
||||
|
||||
const displayValue = $(() => {
|
||||
const v = val($value);
|
||||
if (!v) return "";
|
||||
// Si es un string (fecha única)
|
||||
if (typeof v === "string") return v;
|
||||
// Si es un objeto (rango)
|
||||
if (v.start && v.end) return `${v.start} - ${v.end}`;
|
||||
if (v.start) return `${v.start}...`;
|
||||
return "";
|
||||
});
|
||||
|
||||
// ... resto del move, moveYear ...
|
||||
const move = (m) => {
|
||||
const d = internalDate();
|
||||
internalDate(new Date(d.getFullYear(), d.getMonth() + m, 1));
|
||||
};
|
||||
const moveYear = (y) => {
|
||||
const d = internalDate();
|
||||
internalDate(new Date(d.getFullYear() + y, d.getMonth(), 1));
|
||||
};
|
||||
|
||||
return $.html("div", { class: "relative w-full" }, [
|
||||
ui.Input({
|
||||
label,
|
||||
placeholder: placeholder || (isRangeMode() ? "Seleccionar rango..." : "Seleccionar fecha..."),
|
||||
$value: displayValue,
|
||||
readonly: true,
|
||||
icon: $.html("img", { src: iconCalendar, class: "w-4 h-4 opacity-40" }),
|
||||
onclick: (e) => {
|
||||
e.stopPropagation();
|
||||
isOpen(!isOpen());
|
||||
},
|
||||
...rest,
|
||||
}),
|
||||
|
||||
ui.If(isOpen, () =>
|
||||
$.html(
|
||||
"div",
|
||||
{
|
||||
class: "absolute left-0 mt-2 p-4 bg-base-100 border border-base-300 shadow-2xl rounded-box z-[100] w-80 select-none",
|
||||
onclick: (e) => e.stopPropagation(),
|
||||
},
|
||||
[
|
||||
// Header (Igual que el tuyo)
|
||||
$.html("div", { class: "flex justify-between items-center mb-4 gap-1" }, [
|
||||
$.html("div", { class: "flex gap-0.5" }, [
|
||||
$.html("button", { type: "button", class: "btn btn-ghost btn-xs px-1", onclick: () => moveYear(-1) }, "<<"),
|
||||
$.html("button", { type: "button", class: "btn btn-ghost btn-xs px-1", onclick: () => move(-1) }, "<"),
|
||||
]),
|
||||
$.html("span", { class: "text-xs font-bold uppercase flex-1 text-center" }, [
|
||||
() => internalDate().toLocaleString("es-ES", { month: "long", year: "numeric" }),
|
||||
]),
|
||||
$.html("div", { class: "flex gap-0.5" }, [
|
||||
$.html("button", { type: "button", class: "btn btn-ghost btn-xs px-1", onclick: () => move(1) }, ">"),
|
||||
$.html("button", { type: "button", class: "btn btn-ghost btn-xs px-1", onclick: () => moveYear(1) }, ">>"),
|
||||
]),
|
||||
]),
|
||||
|
||||
// Grid
|
||||
$.html("div", { class: "grid grid-cols-7 gap-1", onmouseleave: () => hoverDate(null) }, [
|
||||
...["L", "M", "X", "J", "V", "S", "D"].map((d) => $.html("div", { class: "text-[10px] opacity-40 font-bold text-center" }, d)),
|
||||
() => {
|
||||
const d = internalDate();
|
||||
const year = d.getFullYear();
|
||||
const month = d.getMonth();
|
||||
const firstDay = new Date(year, month, 1).getDay();
|
||||
const offset = firstDay === 0 ? 6 : firstDay - 1;
|
||||
const daysInMonth = new Date(year, month + 1, 0).getDate();
|
||||
|
||||
const nodes = [];
|
||||
for (let i = 0; i < offset; i++) nodes.push($.html("div"));
|
||||
|
||||
for (let i = 1; i <= daysInMonth; i++) {
|
||||
const date = new Date(year, month, i);
|
||||
const dStr = formatDate(date);
|
||||
|
||||
nodes.push(
|
||||
$.html(
|
||||
"button",
|
||||
{
|
||||
type: "button",
|
||||
class: () => {
|
||||
const v = val($value);
|
||||
const h = hoverDate();
|
||||
const isToday = dStr === todayStr;
|
||||
|
||||
// Lógica de selección según el tipo de dato
|
||||
const isStart = typeof v === "string" ? v === dStr : v?.start === dStr;
|
||||
const isEnd = v?.end === dStr;
|
||||
|
||||
let inRange = false;
|
||||
// Solo calculamos sombreado si estamos en modo rango
|
||||
if (isRangeMode()) {
|
||||
if (v?.start && !v.end && h) {
|
||||
inRange = (dStr > v.start && dStr <= h) || (dStr < v.start && dStr >= h);
|
||||
} else if (v?.start && v?.end) {
|
||||
inRange = dStr > v.start && dStr < v.end;
|
||||
}
|
||||
}
|
||||
|
||||
return `btn btn-xs p-0 aspect-square min-h-0 h-auto font-normal relative
|
||||
${isStart || isEnd ? "btn-primary z-10" : inRange ? "bg-primary/20 border-none rounded-none" : "btn-ghost"}
|
||||
${isToday ? "ring-1 ring-primary ring-inset font-black text-primary" : ""}`;
|
||||
},
|
||||
onmouseenter: () => isRangeMode() && hoverDate(dStr),
|
||||
onclick: () => selectDate(date),
|
||||
},
|
||||
i.toString(),
|
||||
),
|
||||
);
|
||||
}
|
||||
return nodes;
|
||||
},
|
||||
]),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
ui.If(isOpen, () => $.html("div", { class: "fixed inset-0 z-[90]", onclick: () => isOpen(false) })),
|
||||
]);
|
||||
};
|
||||
|
||||
/** CHECKBOX */
|
||||
|
||||
138
src/sigpro.js
138
src/sigpro.js
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* SigPro Core - Estabilidad Total
|
||||
* SigPro Core - Refactor Completo 2026
|
||||
*/
|
||||
(() => {
|
||||
let activeEffect = null;
|
||||
@@ -40,8 +40,9 @@
|
||||
};
|
||||
|
||||
const isObj = (v) => v && typeof v === "object" && !(v instanceof Node);
|
||||
const PROXIES = new WeakMap();
|
||||
|
||||
const RAW_SUBS = new WeakMap();
|
||||
const PROXIES = new WeakMap();
|
||||
|
||||
const getPropSubs = (target, prop) => {
|
||||
let props = RAW_SUBS.get(target);
|
||||
@@ -51,38 +52,10 @@
|
||||
return subs;
|
||||
};
|
||||
|
||||
// -------------------------
|
||||
// --- $ = Signal puro ---
|
||||
// -------------------------
|
||||
const $ = (initial) => {
|
||||
if (initial && typeof initial === "object" && !(initial instanceof Node)) {
|
||||
if (PROXIES.has(initial)) return PROXIES.get(initial);
|
||||
|
||||
const proxy = new Proxy(initial, {
|
||||
get(t, p, r) {
|
||||
track(getPropSubs(t, p));
|
||||
const val = Reflect.get(t, p, r);
|
||||
return val && typeof val === "object" ? $(val) : val;
|
||||
},
|
||||
set(t, p, v, r) {
|
||||
const old = Reflect.get(t, p, r);
|
||||
if (Object.is(old, v)) return true;
|
||||
const res = Reflect.set(t, p, v, r);
|
||||
|
||||
trigger(getPropSubs(t, p));
|
||||
if (Array.isArray(t) && p !== "length") {
|
||||
trigger(getPropSubs(t, "length"));
|
||||
}
|
||||
return res;
|
||||
},
|
||||
deleteProperty(t, p) {
|
||||
const res = Reflect.deleteProperty(t, p);
|
||||
trigger(getPropSubs(t, p));
|
||||
return res;
|
||||
},
|
||||
});
|
||||
|
||||
PROXIES.set(initial, proxy);
|
||||
return proxy;
|
||||
}
|
||||
|
||||
if (typeof initial === "function") {
|
||||
const subs = new Set();
|
||||
let cached;
|
||||
@@ -90,13 +63,11 @@
|
||||
|
||||
const effect = () => {
|
||||
if (effect._deleted) return;
|
||||
|
||||
effect._deps.forEach((s) => s.delete(effect));
|
||||
effect._deps.clear();
|
||||
|
||||
const prev = activeEffect;
|
||||
activeEffect = effect;
|
||||
|
||||
try {
|
||||
const val = initial();
|
||||
if (!Object.is(cached, val) || dirty) {
|
||||
@@ -137,7 +108,6 @@
|
||||
return (...args) => {
|
||||
if (args.length) {
|
||||
const next = typeof args[0] === "function" ? args[0](value) : args[0];
|
||||
|
||||
if (!Object.is(value, next)) {
|
||||
value = next;
|
||||
trigger(subs);
|
||||
@@ -148,10 +118,14 @@
|
||||
};
|
||||
};
|
||||
|
||||
const $$ = (fn) => {
|
||||
// -------------------------
|
||||
// --- $.effect = efectos ---
|
||||
// -------------------------
|
||||
$.effect = (fn) => {
|
||||
const owner = currentOwner;
|
||||
|
||||
const effect = () => {
|
||||
if (effect._deleted) return;
|
||||
|
||||
effect._deps.forEach((s) => s.delete(effect));
|
||||
effect._deps.clear();
|
||||
effect._cleanups.forEach((c) => c());
|
||||
@@ -161,7 +135,6 @@
|
||||
const prevOwner = currentOwner;
|
||||
activeEffect = effect;
|
||||
currentOwner = { cleanups: effect._cleanups };
|
||||
|
||||
effect.depth = prevEffect ? prevEffect.depth + 1 : 0;
|
||||
|
||||
try {
|
||||
@@ -177,19 +150,60 @@
|
||||
effect._deleted = false;
|
||||
|
||||
effect.stop = () => {
|
||||
if (effect._deleted) return;
|
||||
effect._deleted = true;
|
||||
effectQueue.delete(effect);
|
||||
effect._deps.forEach((s) => s.delete(effect));
|
||||
effect._deps.clear();
|
||||
effect._cleanups.forEach((c) => c());
|
||||
effect._cleanups.clear();
|
||||
|
||||
if (owner) {
|
||||
owner.cleanups.delete(effect.stop);
|
||||
}
|
||||
};
|
||||
|
||||
if (currentOwner) currentOwner.cleanups.add(effect.stop);
|
||||
if (owner) owner.cleanups.add(effect.stop);
|
||||
|
||||
effect();
|
||||
return effect.stop;
|
||||
};
|
||||
|
||||
// -------------------------
|
||||
// --- $.proxy = Proxy profundo ---
|
||||
// -------------------------
|
||||
$.proxy = (obj) => {
|
||||
if (!isObj(obj)) throw new Error("$.proxy only works with objects or arrays");
|
||||
if (PROXIES.has(obj)) return PROXIES.get(obj);
|
||||
|
||||
const proxy = new Proxy(obj, {
|
||||
get(t, p, r) {
|
||||
track(getPropSubs(t, p));
|
||||
const val = Reflect.get(t, p, r);
|
||||
return val && typeof val === "object" ? $.proxy(val) : val;
|
||||
},
|
||||
set(t, p, v, r) {
|
||||
const old = Reflect.get(t, p, r);
|
||||
if (Object.is(old, v)) return true;
|
||||
const res = Reflect.set(t, p, v, r);
|
||||
trigger(getPropSubs(t, p));
|
||||
if (Array.isArray(t) && p !== "length") trigger(getPropSubs(t, "length"));
|
||||
return res;
|
||||
},
|
||||
deleteProperty(t, p) {
|
||||
const res = Reflect.deleteProperty(t, p);
|
||||
trigger(getPropSubs(t, p));
|
||||
return res;
|
||||
},
|
||||
});
|
||||
|
||||
PROXIES.set(obj, proxy);
|
||||
return proxy;
|
||||
};
|
||||
|
||||
// -------------------------
|
||||
// --- Sweep nodos y limpieza ---
|
||||
// -------------------------
|
||||
const sweep = (node) => {
|
||||
if (node._cleanups) {
|
||||
node._cleanups.forEach((f) => f());
|
||||
@@ -198,6 +212,9 @@
|
||||
node.childNodes?.forEach(sweep);
|
||||
};
|
||||
|
||||
// -------------------------
|
||||
// --- Vistas reactivas ---
|
||||
// -------------------------
|
||||
$.view = (fn) => {
|
||||
const cleanups = new Set();
|
||||
const prev = currentOwner;
|
||||
@@ -229,6 +246,9 @@
|
||||
};
|
||||
};
|
||||
|
||||
// -------------------------
|
||||
// --- HTML con bindings ---
|
||||
// -------------------------
|
||||
$.html = (tag, props = {}, content = []) => {
|
||||
if (props instanceof Node || Array.isArray(props) || typeof props !== "object") {
|
||||
content = props;
|
||||
@@ -250,11 +270,9 @@
|
||||
el._cleanups.add(() => el.removeEventListener(name, handler));
|
||||
} else if (k.startsWith("$")) {
|
||||
const attr = k.slice(1);
|
||||
|
||||
const stopAttr = $$(() => {
|
||||
const stopAttr = $.effect(() => {
|
||||
const val = typeof v === "function" ? v() : v;
|
||||
if (el[attr] === val) return;
|
||||
|
||||
if (attr === "value" || attr === "checked") el[attr] = val;
|
||||
else if (typeof val === "boolean") el.toggleAttribute(attr, val);
|
||||
else val == null ? el.removeAttribute(attr) : el.setAttribute(attr, val);
|
||||
@@ -263,13 +281,12 @@
|
||||
|
||||
if (typeof v === "function") {
|
||||
const evt = attr === "checked" ? "change" : "input";
|
||||
const h = (e) => v(e.target[attr]);
|
||||
el.addEventListener(evt, h);
|
||||
el._cleanups.add(() => el.removeEventListener(evt, h));
|
||||
const handler = (e) => v(e.target[attr]);
|
||||
el.addEventListener(evt, handler);
|
||||
el._cleanups.add(() => el.removeEventListener(evt, handler));
|
||||
}
|
||||
} else {
|
||||
if (typeof v === "function") {
|
||||
const stopAttr = $$(() => {
|
||||
} else if (typeof v === "function") {
|
||||
const stopAttr = $.effect(() => {
|
||||
const val = v();
|
||||
if (k === "class" || k === "className") el.className = val || "";
|
||||
else if (typeof val === "boolean") el.toggleAttribute(k, val);
|
||||
@@ -282,7 +299,6 @@
|
||||
else v == null ? el.removeAttribute(k) : el.setAttribute(k, v);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const append = (c) => {
|
||||
if (Array.isArray(c)) return c.forEach(append);
|
||||
@@ -290,7 +306,7 @@
|
||||
const marker = document.createTextNode("");
|
||||
el.appendChild(marker);
|
||||
let nodes = [];
|
||||
const stopList = $$(() => {
|
||||
const stopList = $.effect(() => {
|
||||
const res = c();
|
||||
const next = (Array.isArray(res) ? res : [res]).map((i) =>
|
||||
i?._isRuntime ? i.container : i instanceof Node ? i : document.createTextNode(i ?? ""),
|
||||
@@ -309,6 +325,9 @@
|
||||
return el;
|
||||
};
|
||||
|
||||
// -------------------------
|
||||
// --- Ignorar reactividad temporal ---
|
||||
// -------------------------
|
||||
$.ignore = (fn) => {
|
||||
const prev = activeEffect;
|
||||
activeEffect = null;
|
||||
@@ -319,6 +338,9 @@
|
||||
}
|
||||
};
|
||||
|
||||
// -------------------------
|
||||
// --- Router simple ---
|
||||
// -------------------------
|
||||
$.router = (routes) => {
|
||||
const sPath = $(window.location.hash.replace(/^#/, "") || "/");
|
||||
window.addEventListener("hashchange", () => sPath(window.location.hash.replace(/^#/, "") || "/"));
|
||||
@@ -326,9 +348,8 @@
|
||||
const outlet = Div({ class: "router-outlet" });
|
||||
let current = null;
|
||||
|
||||
$$(() => {
|
||||
$.effect(() => {
|
||||
const path = sPath();
|
||||
|
||||
if (current) current.destroy();
|
||||
outlet.innerHTML = "";
|
||||
|
||||
@@ -364,6 +385,9 @@
|
||||
|
||||
$.router.go = (p) => (window.location.hash = p.replace(/^#?\/?/, "#/"));
|
||||
|
||||
// -------------------------
|
||||
// --- Montaje de componentes ---
|
||||
// -------------------------
|
||||
$.mount = (component, target) => {
|
||||
const el = typeof target === "string" ? document.querySelector(target) : target;
|
||||
if (!el) return;
|
||||
@@ -374,14 +398,18 @@
|
||||
return instance;
|
||||
};
|
||||
|
||||
// -------------------------
|
||||
// --- Shortcuts para tags ---
|
||||
// -------------------------
|
||||
const tags =
|
||||
`div span p h1 h2 h3 h4 h5 h6 br hr section article aside nav main header footer address ul ol li dl dt dd a em strong small i b u mark time sub sup pre code blockquote details summary dialog form label input textarea select button option fieldset legend table thead tbody tfoot tr th td caption img video audio canvas svg iframe picture source progress meter`.split(
|
||||
/\s+/,
|
||||
);
|
||||
|
||||
tags.forEach((t) => {
|
||||
window[t.charAt(0).toUpperCase() + t.slice(1)] = (p, c) => $.html(t, p, c);
|
||||
});
|
||||
|
||||
window.$ = $;
|
||||
window.$$ = $$;
|
||||
})();
|
||||
export const { $, $$ } = window;
|
||||
export const { $ } = window;
|
||||
|
||||
Reference in New Issue
Block a user