2 Commits
1.0.1 ... 1.0.2

Author SHA1 Message Date
Natxo
b1b1099f86 Bump version from 1.0.1 to 1.0.2 2026-03-30 23:25:48 +02:00
ea6d7a85f0 Add FileInput & Table 2026-03-30 23:16:32 +02:00
3 changed files with 1066 additions and 836 deletions

View File

@@ -43,8 +43,8 @@
import * as sigpro from "https://cdn.jsdelivr.net/npm/sigpro@latest/+esm";
import { UI } from "https://cdn.jsdelivr.net/npm/sigpro-ui@latest/+esm";
window.sigpro = sigpro;
const ui = UI("en");
window.sigproui = ui;
// const ui = UI("en");
// window.sigproui = ui;
</script>
<script src="//cdn.jsdelivr.net/npm/docsify/lib/docsify.min.js"></script>
<script src="//cdn.jsdelivr.net/npm/docsify-copy-code/dist/docsify-copy-code.min.js"></script>

284
index.js
View File

@@ -2,7 +2,7 @@
* SigPro UI - daisyUI v5 & Tailwind v4 Plugin
* Provides a set of reactive functional components, flow control and i18n.
*/
import { $, $if, $for, $watch, $html, $mount } from 'sigpro';
import { $, $if, $for, $watch, $html, $mount } from "sigpro";
// --- I18N CORE ---
const i18n = {
@@ -34,13 +34,13 @@ const iconShow =
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";
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAAdgAAAHYBTnsmCAAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAABcSURBVDiN3dIxDoAwCIXhL563g3bSm+hlq4O6GFNbO+k/EV54QIDfsSBk9IA5ZxCQEG+0eGi5BqDHivEhV2xSXXwy2EdOR3xLV+ta0/26wvSm+KTYpPmMzY/0QTZeZR2f+FxhRQAAAABJRU5ErkJggg==";
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=";
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAAdgAAAHYBTnsmCAAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAADFSURBVDiN7dCxSoIBFAXgr1BbgmgSB5ubxKAHaAkcgnBpySVaDET3WhzcpQfoHZojawgX0ZZcfAWDSDdBoeUKP/8ojZ7tnnPv4dzDFv+KZzwl5jf84B354C4wwjdeUV4vl7DCEsXgxmhigDpOMcMVjoKr7cTyI/ZxiE90wmCB4zi+RRatZOxd7OEavxHtBmvjIV5wH2a59N8ZXIZQisMCzkL/wgGq6EYffXzgHHNo4y5h+oBGlLjEBJVUiVP0cJJOtMUG+APtfyYzbH7eVgAAAABJRU5ErkJggg==";
const icon123 =
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAACXBIWXMAAAB2AAAAdgFOeyYIAAAAGXRFWHRTb2Z0d2FyZQB3d3cuaW5rc2NhcGUub3Jnm+48GgAAAMxJREFUOI3t0bFKwlEUBvBfmmBEr1APIDZJ9AJJQyAIvkGP0C4uQruza+DUmuIc9AC9gBG4Nmpkw/8IB3Vw1w8u95zvnvPde77LEeUUV9HAF67QRA2nmMf5A+o4x3cWOsMYy8j7WMX6jaYbLBL/mAWe8RcHm1ihs8G94gVKQQzwlAouMcQo8p/Y28HdYpYFZmsi0MVdxD1MdrxsC500wijdvgtbI1AYtDbxMwkuFAZmE1uYwkkSqOIaHyHcxEU0vUXNPSqKr37fZ6xDwD9DPS0OyHjQHQAAAABJRU5ErkJggg==";
const iconMail =
@@ -61,6 +61,8 @@ const iconLLeft =
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAAdgAAAHYBTnsmCAAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAABlSURBVDiN3ZLBDUBAEEUfmtCchA5woUMlOO1FCQrAwbqwf8eFhHd7mfzJn2Tg82TGvABywAmPUgOLD4XcDK9AJ/y5cOlrNsIvpCdPDL/FUbkX/t6Slv3+SjgQf6QBmIAZGAP+FzZJViOd89x8pAAAAABJRU5ErkJggg==";
const iconRRight =
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAAdgAAAHYBTnsmCAAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAABmSURBVDiN3dGxCoAgEMbxfz1dL1BTREJzmUv08trgDYcg6VCD3/YD7zvkoLmMgFEegLmmwAAecOJVvNeUWCAAt7IHjt9LThkyiRf9qC8oCom70u0BuDL+bngj/tNm/JqJePucW8wDvGYdzT0nMUkAAAAASUVORK5CYII=";
const iconUpload =
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAAdgAAAHYBTnsmCAAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAADNSURBVDiNndOxTgJRFIThz41ZDMFKqH0DLSRSq4lQ0RifUcMzUJlYQKjtLcHVSimBggPRNSzs/sk0kzPnTHEvxZyHKnGJD3yhXSWcYRnKwvvH0Y7wEG/4wQI1XOEek6LLF3FtiDoGoXp4WcxsSXILHjFCH/Nf/jy8ER6KGuTZNNhJvkFpEpygUyHbRi1BFy8VFryilyANlSVFerxn6N36IRVyG0PNEtdbkbmBU8zwdOCSJp4xRWNj3sWS5YGaRvM/f6GBa5ztafCJMb5hBQQ/MMwXLnnZAAAAAElFTkSuQmCC";
// --- UI COMPONENTS ---
@@ -114,7 +116,7 @@ export const Input = (props) => {
disabled: () => val(props.disabled),
});
const leftIcon = icon ? icon : iconsByType[type] ? $html("img", { src: iconsByType[type], class: "w-5 h-5 opacity-50", alt: type }) : null;
const leftIcon = icon ? icon : iconsByType[type] ? $html("img", { src: iconsByType[type], class: "opacity-50", alt: type }) : null;
return $html(
"label",
@@ -155,6 +157,117 @@ export const Input = (props) => {
);
};
/** FILEINPUT */
export const Fileinput = (props) => {
const { tooltip, max = 2, accept = "*", onSelect } = props;
const selectedFiles = $([]);
const isDragging = $(false);
const error = $(null);
const MAX_BYTES = max * 1024 * 1024;
const handleFiles = (files) => {
const fileList = Array.from(files);
error(null);
const oversized = fileList.find((f) => f.size > MAX_BYTES);
if (oversized) {
error(`Máx ${max}MB`);
return;
}
selectedFiles([...selectedFiles(), ...fileList]);
onSelect?.(selectedFiles());
};
const removeFile = (index) => {
const updated = selectedFiles().filter((_, i) => i !== index);
selectedFiles(updated);
onSelect?.(updated);
};
return $html("fieldset", { class: "fieldset w-full p-0" }, [
$html(
"div",
{
class: () => `w-full ${tooltip ? "tooltip tooltip-top before:z-50 after:z-50" : ""}`,
"data-tip": tooltip,
},
[
$html(
"label",
{
class: () => `
relative flex items-center justify-between w-full h-12 px-4
border-2 border-dashed rounded-lg cursor-pointer
transition-all duration-200
${isDragging() ? "border-primary bg-primary/10" : "border-base-content/20 bg-base-100 hover:bg-base-200"}
`,
ondragover: (e) => {
e.preventDefault();
isDragging(true);
},
ondragleave: () => isDragging(false),
ondrop: (e) => {
e.preventDefault();
isDragging(false);
handleFiles(e.dataTransfer.files);
},
},
[
$html("div", { class: "flex items-center gap-3 w-full" }, [
$html("img", { src: iconUpload, class: "w-5 h-5 opacity-50 shrink-0" }),
$html("span", { class: "text-sm opacity-70 truncate grow text-left" }, "Arrastra o selecciona archivos..."),
$html("span", { class: "text-[10px] opacity-40 shrink-0" }, `Máx ${max}MB`),
]),
$html("input", {
type: "file",
multiple: true,
accept: accept,
class: "hidden",
onchange: (e) => handleFiles(e.target.files),
}),
],
),
],
),
() => (error() ? $html("span", { class: "text-[10px] text-error mt-1 px-1 font-medium" }, error()) : null),
$if(
() => selectedFiles().length > 0,
() =>
$html("ul", { class: "mt-2 space-y-1" }, [
$for(
selectedFiles,
(file, index) =>
$html("li", { class: "flex items-center justify-between p-1.5 pl-3 text-xs bg-base-200/50 rounded-md border border-base-300" }, [
$html("div", { class: "flex items-center gap-2 truncate" }, [
$html("span", { class: "opacity-50" }, "📄"),
$html("span", { class: "truncate font-medium max-w-[200px]" }, file.name),
$html("span", { class: "text-[9px] opacity-40" }, `(${(file.size / 1024).toFixed(0)} KB)`),
]),
$html(
"button",
{
type: "button",
class: "btn btn-ghost btn-xs btn-circle",
onclick: (e) => {
e.preventDefault();
e.stopPropagation();
removeFile(index);
},
},
[$html("img", { src: iconClose, class: "w-3 h-3 opacity-70" })],
),
]),
(file) => file.name + file.lastModified,
),
]),
),
]);
};
/** SELECT */
export const Select = (props) => {
const { label, options, value, ...rest } = props;
@@ -277,11 +390,13 @@ export const Autocomplete = (props) => {
/** DATEPICKER */
export const Datepicker = (props) => {
const { value, range, label, placeholder, ...rest } = props;
const { value, range, label, placeholder, hour = false, ...rest } = props;
const isOpen = $(false);
const internalDate = $(new Date());
const hoverDate = $(null);
const startHour = $(0);
const endHour = $(0);
const isRangeMode = () => val(range) === true;
const now = new Date();
@@ -300,16 +415,29 @@ export const Datepicker = (props) => {
if (isRangeMode()) {
if (!current?.start || (current.start && current.end)) {
if (typeof value === "function") value({ start: dateStr, end: null });
if (typeof value === "function") {
value({
start: dateStr,
end: null,
...(hour && { startHour: startHour() }),
});
}
} else {
const start = current.start;
if (typeof value === "function") {
value(dateStr < start ? { start: dateStr, end: start } : { start, end: dateStr });
const newValue = dateStr < start ? { start: dateStr, end: start } : { start, end: dateStr };
if (hour) {
newValue.startHour = current.startHour || startHour();
newValue.endHour = current.endHour || endHour();
}
value(newValue);
}
isOpen(false);
}
} else {
if (typeof value === "function") value(dateStr);
if (typeof value === "function") {
value(hour ? `${dateStr}T${String(startHour()).padStart(2, "0")}:00:00` : dateStr);
}
isOpen(false);
}
};
@@ -317,9 +445,21 @@ export const Datepicker = (props) => {
const displayValue = $(() => {
const v = val(value);
if (!v) return "";
if (typeof v === "string") return v;
if (v.start && v.end) return `${v.start} - ${v.end}`;
if (v.start) return `${v.start}...`;
if (typeof v === "string") {
if (hour && v.includes("T")) {
return v.replace("T", " ");
}
return v;
}
if (v.start && v.end) {
const startStr = hour && v.startHour ? `${v.start} ${String(v.startHour).padStart(2, "0")}:00` : v.start;
const endStr = hour && v.endHour ? `${v.end} ${String(v.endHour).padStart(2, "0")}:00` : v.end;
return `${startStr} - ${endStr}`;
}
if (v.start) {
const startStr = hour && v.startHour ? `${v.start} ${String(v.startHour).padStart(2, "0")}:00` : v.start;
return `${startStr}...`;
}
return "";
});
@@ -333,6 +473,30 @@ export const Datepicker = (props) => {
internalDate(new Date(d.getFullYear() + y, d.getMonth(), 1));
};
// Componente de selector de hora
const HourSlider = ({ value, onChange }) => {
return $html("div", { class: "flex-1" }, [
$html("div", { class: "flex gap-2 items-center" }, [
$html("input", {
type: "range",
min: 0,
max: 23,
value: value,
class: "range range-xs flex-1",
oninput: (e) => {
const newHour = parseInt(e.target.value);
onChange(newHour);
const currentVal = val(value);
if (!isRangeMode() && currentVal && typeof currentVal === "string" && currentVal.includes("-")) {
value(currentVal.split("T")[0] + "T" + String(newHour).padStart(2, "0") + ":00:00");
}
},
}),
$html("span", { class: "text-sm font-mono min-w-[48px] text-center" }, () => String(val(value)).padStart(2, "0") + ":00"),
]),
]);
};
return $html("div", { class: "relative w-full" }, [
Input({
label,
@@ -410,7 +574,7 @@ export const Datepicker = (props) => {
class: () => {
const v = val(value);
const h = hoverDate();
const isStart = typeof v === "string" ? v === dStr : v?.start === dStr;
const isStart = typeof v === "string" ? v.split("T")[0] === dStr : v?.start === dStr;
const isEnd = v?.end === dStr;
let inRange = false;
@@ -441,6 +605,45 @@ export const Datepicker = (props) => {
return nodes;
},
]),
// Selector de hora - modo rango con dos sliders
hour
? $html("div", { class: "mt-3 pt-2 border-t border-base-300" }, [
isRangeMode()
? $html("div", { class: "flex gap-4" }, [
HourSlider({
value: startHour,
onChange: (newHour) => {
startHour(newHour);
const currentVal = val(value);
if (currentVal?.start) {
value({ ...currentVal, startHour: newHour });
}
},
}),
HourSlider({
value: endHour,
onChange: (newHour) => {
endHour(newHour);
const currentVal = val(value);
if (currentVal?.end) {
value({ ...currentVal, endHour: newHour });
}
},
}),
])
: HourSlider({
value: startHour,
onChange: (newHour) => {
startHour(newHour);
const currentVal = val(value);
if (currentVal && typeof currentVal === "string" && currentVal.includes("-")) {
value(currentVal.split("T")[0] + "T" + String(newHour).padStart(2, "0") + ":00:00");
}
},
}),
])
: null,
],
),
),
@@ -583,10 +786,7 @@ export const Range = (props) => {
if (!label && !tooltip) return rangeEl;
const layout = $html("div", { class: "flex flex-col gap-2" }, [
label ? $html("span", { class: "label-text" }, label) : null,
rangeEl
]);
const layout = $html("div", { class: "flex flex-col gap-2" }, [label ? $html("span", { class: "label-text" }, label) : null, rangeEl]);
return tooltip ? $html("div", { class: "tooltip", "data-tip": tooltip }, layout) : layout;
};
@@ -885,7 +1085,9 @@ export const Rating = (props) => {
const { value, count = 5, mask = "mask-star", readonly = false, ...rest } = props;
const ratingGroup = `rating-${Math.random().toString(36).slice(2, 7)}`;
return $html("div", {
return $html(
"div",
{
...rest,
class: () => `rating ${val(readonly) ? "pointer-events-none" : ""} ${props.class || ""}`,
},
@@ -904,7 +1106,8 @@ export const Rating = (props) => {
}
},
});
}));
})
);
};
/** ALERT */
@@ -1075,10 +1278,7 @@ export const Toast = (message, type = "alert-success", duration = 3500) => {
{
class: `alert alert-soft ${type} shadow-lg transition-all duration-300 translate-x-10 opacity-0 pointer-events-auto`,
},
[
$html("span", typeof message === "function" ? message : () => message),
Button({ class: "btn-xs btn-circle btn-ghost", onclick: close }, "✕"),
],
[$html("span", typeof message === "function" ? message : () => message), Button({ class: "btn-xs btn-circle btn-ghost", onclick: close }, "✕")],
);
requestAnimationFrame(() => el.classList.remove("translate-x-10", "opacity-0"));
@@ -1110,10 +1310,40 @@ export const UI = (defaultLang = "es") => {
// Create UI object with all components
const ui = {
Locale, tt, Button, Input, Select, Autocomplete, Datepicker, Colorpicker,
Checkbox, Radio, Range, Modal, Dropdown, Accordion, Tabs, Badge,
Tooltip, Navbar, Menu, Drawer, Fieldset, List, Stack, Stat, Swap, Indicator,
Rating, Alert, Timeline, Fab, Toast, Loading, Table
Locale,
tt,
Button,
Input,
Select,
Autocomplete,
Datepicker,
Colorpicker,
Checkbox,
Radio,
Range,
Modal,
Dropdown,
Accordion,
Tabs,
Badge,
Tooltip,
Navbar,
Menu,
Drawer,
Fieldset,
List,
Stack,
Stat,
Swap,
Indicator,
Rating,
Alert,
Timeline,
Fab,
Toast,
Loading,
Fileinput,
Table
};
// Inject all components into window
@@ -1122,7 +1352,7 @@ export const UI = (defaultLang = "es") => {
value: ui[key],
writable: false,
configurable: true,
enumerable: true
enumerable: true,
});
});

View File

@@ -1,6 +1,6 @@
{
"name": "sigpro-ui",
"version": "1.0.1",
"version": "1.0.2",
"type": "module",
"license": "MIT",
"main": "./index.js",