1677 lines
60 KiB
JavaScript
1677 lines
60 KiB
JavaScript
(() => {
|
|
var __defProp = Object.defineProperty;
|
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
var __moduleCache = /* @__PURE__ */ new WeakMap;
|
|
var __toCommonJS = (from) => {
|
|
var entry = __moduleCache.get(from), desc;
|
|
if (entry)
|
|
return entry;
|
|
entry = __defProp({}, "__esModule", { value: true });
|
|
if (from && typeof from === "object" || typeof from === "function")
|
|
__getOwnPropNames(from).map((key) => !__hasOwnProp.call(entry, key) && __defProp(entry, key, {
|
|
get: () => from[key],
|
|
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
}));
|
|
__moduleCache.set(from, entry);
|
|
return entry;
|
|
};
|
|
var __export = (target, all) => {
|
|
for (var name in all)
|
|
__defProp(target, name, {
|
|
get: all[name],
|
|
enumerable: true,
|
|
configurable: true,
|
|
set: (newValue) => all[name] = () => newValue
|
|
});
|
|
};
|
|
|
|
// index.js
|
|
var exports_sigpro_ui = {};
|
|
__export(exports_sigpro_ui, {
|
|
val: () => val,
|
|
ui: () => ui,
|
|
tt: () => tt,
|
|
getIcon: () => getIcon,
|
|
Tooltip: () => Tooltip,
|
|
Toast: () => Toast,
|
|
Timeline: () => Timeline,
|
|
Tabs: () => Tabs,
|
|
Table: () => Table,
|
|
Swap: () => Swap,
|
|
Stat: () => Stat,
|
|
Stack: () => Stack,
|
|
Select: () => Select,
|
|
Rating: () => Rating,
|
|
Range: () => Range,
|
|
Radio: () => Radio,
|
|
Navbar: () => Navbar,
|
|
Modal: () => Modal,
|
|
Menu: () => Menu,
|
|
List: () => List,
|
|
Label: () => Label,
|
|
Input: () => Input,
|
|
Indicator: () => Indicator,
|
|
Fileinput: () => Fileinput,
|
|
Fieldset: () => Fieldset,
|
|
Fab: () => Fab,
|
|
Dropdown: () => Dropdown,
|
|
Drawer: () => Drawer,
|
|
Datepicker: () => Datepicker,
|
|
Colorpicker: () => Colorpicker,
|
|
Checkbox: () => Checkbox,
|
|
Button: () => Button,
|
|
Badge: () => Badge,
|
|
Autocomplete: () => Autocomplete,
|
|
Alert: () => Alert,
|
|
Accordion: () => Accordion
|
|
});
|
|
|
|
// src/sigpro.js
|
|
var activeEffect = null;
|
|
var currentOwner = null;
|
|
var effectQueue = new Set;
|
|
var isFlushing = false;
|
|
var MOUNTED_NODES = new WeakMap;
|
|
var flush = () => {
|
|
if (isFlushing)
|
|
return;
|
|
isFlushing = true;
|
|
while (effectQueue.size > 0) {
|
|
const sorted = Array.from(effectQueue).sort((a, b) => (a.depth || 0) - (b.depth || 0));
|
|
effectQueue.clear();
|
|
for (const eff of sorted)
|
|
if (!eff._deleted)
|
|
eff();
|
|
}
|
|
isFlushing = false;
|
|
};
|
|
var track = (subs) => {
|
|
if (activeEffect && !activeEffect._deleted) {
|
|
subs.add(activeEffect);
|
|
activeEffect._deps.add(subs);
|
|
}
|
|
};
|
|
var trigger = (subs) => {
|
|
for (const eff of subs) {
|
|
if (eff === activeEffect || eff._deleted)
|
|
continue;
|
|
if (eff._isComputed) {
|
|
eff.markDirty();
|
|
if (eff._subs)
|
|
trigger(eff._subs);
|
|
} else {
|
|
effectQueue.add(eff);
|
|
}
|
|
}
|
|
if (!isFlushing)
|
|
queueMicrotask(flush);
|
|
};
|
|
var sweep = (node) => {
|
|
if (node._cleanups) {
|
|
node._cleanups.forEach((f) => f());
|
|
node._cleanups.clear();
|
|
}
|
|
node.childNodes?.forEach(sweep);
|
|
};
|
|
var _view = (fn) => {
|
|
const cleanups = new Set;
|
|
const prev = currentOwner;
|
|
const container = document.createElement("div");
|
|
container.style.display = "contents";
|
|
currentOwner = { cleanups };
|
|
try {
|
|
const res = fn({ onCleanup: (f) => cleanups.add(f) });
|
|
const process = (n) => {
|
|
if (!n)
|
|
return;
|
|
if (n._isRuntime) {
|
|
cleanups.add(n.destroy);
|
|
container.appendChild(n.container);
|
|
} else if (Array.isArray(n))
|
|
n.forEach(process);
|
|
else
|
|
container.appendChild(n instanceof Node ? n : document.createTextNode(String(n)));
|
|
};
|
|
process(res);
|
|
} finally {
|
|
currentOwner = prev;
|
|
}
|
|
return {
|
|
_isRuntime: true,
|
|
container,
|
|
destroy: () => {
|
|
cleanups.forEach((f) => f());
|
|
sweep(container);
|
|
container.remove();
|
|
}
|
|
};
|
|
};
|
|
var $ = (initial, key = null) => {
|
|
if (typeof initial === "function") {
|
|
const subs2 = new Set;
|
|
let cached, dirty = true;
|
|
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) {
|
|
cached = val;
|
|
dirty = false;
|
|
trigger(subs2);
|
|
}
|
|
} finally {
|
|
activeEffect = prev;
|
|
}
|
|
};
|
|
effect._deps = new Set;
|
|
effect._isComputed = true;
|
|
effect._subs = subs2;
|
|
effect._deleted = false;
|
|
effect.markDirty = () => dirty = true;
|
|
effect.stop = () => {
|
|
effect._deleted = true;
|
|
effect._deps.forEach((s) => s.delete(effect));
|
|
subs2.clear();
|
|
};
|
|
if (currentOwner)
|
|
currentOwner.cleanups.add(effect.stop);
|
|
return () => {
|
|
if (dirty)
|
|
effect();
|
|
track(subs2);
|
|
return cached;
|
|
};
|
|
}
|
|
let value = initial;
|
|
if (key) {
|
|
try {
|
|
const saved = localStorage.getItem(key);
|
|
if (saved !== null)
|
|
value = JSON.parse(saved);
|
|
} catch (e) {
|
|
console.warn("SigPro: LocalStorage locked", e);
|
|
}
|
|
}
|
|
const subs = new Set;
|
|
return (...args) => {
|
|
if (args.length) {
|
|
const next = typeof args[0] === "function" ? args[0](value) : args[0];
|
|
if (!Object.is(value, next)) {
|
|
value = next;
|
|
if (key)
|
|
localStorage.setItem(key, JSON.stringify(value));
|
|
trigger(subs);
|
|
}
|
|
}
|
|
track(subs);
|
|
return value;
|
|
};
|
|
};
|
|
var $watch2 = (target, fn) => {
|
|
const isExplicit = Array.isArray(target);
|
|
const callback = isExplicit ? fn : target;
|
|
const depsInput = isExplicit ? target : null;
|
|
if (typeof callback !== "function")
|
|
return () => {};
|
|
const owner = currentOwner;
|
|
const runner = () => {
|
|
if (runner._deleted)
|
|
return;
|
|
runner._deps.forEach((s) => s.delete(runner));
|
|
runner._deps.clear();
|
|
runner._cleanups.forEach((c) => c());
|
|
runner._cleanups.clear();
|
|
const prevEffect = activeEffect;
|
|
const prevOwner = currentOwner;
|
|
activeEffect = runner;
|
|
currentOwner = { cleanups: runner._cleanups };
|
|
runner.depth = prevEffect ? prevEffect.depth + 1 : 0;
|
|
try {
|
|
if (isExplicit) {
|
|
activeEffect = null;
|
|
callback();
|
|
activeEffect = runner;
|
|
depsInput.forEach((d) => typeof d === "function" && d());
|
|
} else {
|
|
callback();
|
|
}
|
|
} finally {
|
|
activeEffect = prevEffect;
|
|
currentOwner = prevOwner;
|
|
}
|
|
};
|
|
runner._deps = new Set;
|
|
runner._cleanups = new Set;
|
|
runner._deleted = false;
|
|
runner.stop = () => {
|
|
if (runner._deleted)
|
|
return;
|
|
runner._deleted = true;
|
|
effectQueue.delete(runner);
|
|
runner._deps.forEach((s) => s.delete(runner));
|
|
runner._cleanups.forEach((c) => c());
|
|
if (owner)
|
|
owner.cleanups.delete(runner.stop);
|
|
};
|
|
if (owner)
|
|
owner.cleanups.add(runner.stop);
|
|
runner();
|
|
return runner.stop;
|
|
};
|
|
var $html2 = (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;
|
|
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";
|
|
if (isInput && isBindAttr && isSignal) {
|
|
el._cleanups.add($watch2(() => {
|
|
const currentVal = val();
|
|
if (el[key] !== currentVal)
|
|
el[key] = currentVal;
|
|
}));
|
|
const eventName = key === "checked" ? "change" : "input", handler = (event) => val(event.target[key]);
|
|
el.addEventListener(eventName, handler);
|
|
el._cleanups.add(() => el.removeEventListener(eventName, handler));
|
|
} else if (key.startsWith("on")) {
|
|
const eventName = key.slice(2).toLowerCase().split(".")[0], handler = (event) => val(event);
|
|
el.addEventListener(eventName, handler);
|
|
el._cleanups.add(() => el.removeEventListener(eventName, handler));
|
|
} else if (isSignal) {
|
|
el._cleanups.add($watch2(() => {
|
|
const currentVal = _sanitize(key, val());
|
|
if (key === "class") {
|
|
el.className = currentVal || "";
|
|
} else if (boolAttrs.includes(key)) {
|
|
if (currentVal) {
|
|
el.setAttribute(key, "");
|
|
el[key] = true;
|
|
} else {
|
|
el.removeAttribute(key);
|
|
el[key] = false;
|
|
}
|
|
} else {
|
|
currentVal == null ? el.removeAttribute(key) : el.setAttribute(key, currentVal);
|
|
}
|
|
}));
|
|
} else {
|
|
if (boolAttrs.includes(key)) {
|
|
if (val) {
|
|
el.setAttribute(key, "");
|
|
el[key] = true;
|
|
} else {
|
|
el.removeAttribute(key);
|
|
el[key] = false;
|
|
}
|
|
} else {
|
|
el.setAttribute(key, _sanitize(key, val));
|
|
}
|
|
}
|
|
}
|
|
const append = (child) => {
|
|
if (Array.isArray(child))
|
|
return child.forEach(append);
|
|
if (child instanceof Node) {
|
|
el.appendChild(child);
|
|
} else if (typeof child === "function") {
|
|
const marker = document.createTextNode("");
|
|
el.appendChild(marker);
|
|
let nodes = [];
|
|
el._cleanups.add($watch2(() => {
|
|
const res = child(), next = (Array.isArray(res) ? res : [res]).map((i) => i?._isRuntime ? i.container : i instanceof Node ? i : document.createTextNode(i ?? ""));
|
|
nodes.forEach((n) => {
|
|
sweep?.(n);
|
|
n.remove();
|
|
});
|
|
next.forEach((n) => marker.parentNode?.insertBefore(n, marker));
|
|
nodes = next;
|
|
}));
|
|
} else
|
|
el.appendChild(document.createTextNode(child ?? ""));
|
|
};
|
|
append(content);
|
|
return el;
|
|
};
|
|
var $if = (condition, thenVal, otherwiseVal = 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 (current)
|
|
current.destroy();
|
|
const branch = state ? thenVal : otherwiseVal;
|
|
if (branch) {
|
|
current = _view(() => typeof branch === "function" ? branch() : branch);
|
|
container.insertBefore(current.container, marker);
|
|
}
|
|
}
|
|
});
|
|
return container;
|
|
};
|
|
$if.not = (condition, thenVal, otherwiseVal) => $if(() => !(typeof condition === "function" ? condition() : condition), thenVal, otherwiseVal);
|
|
var $for = (source, render, keyFn, tag = "div", props = { style: "display:contents" }) => {
|
|
const marker = document.createTextNode("");
|
|
const container = $html2(tag, props, [marker]);
|
|
let cache = new Map;
|
|
$watch2(() => {
|
|
const items = (typeof source === "function" ? source() : source) || [];
|
|
const newCache = new Map;
|
|
const newOrder = [];
|
|
for (let i = 0;i < items.length; i++) {
|
|
const item = items[i];
|
|
const key = keyFn ? keyFn(item, i) : i;
|
|
let run = cache.get(key);
|
|
if (!run) {
|
|
run = _view(() => render(item, i));
|
|
} else {
|
|
cache.delete(key);
|
|
}
|
|
newCache.set(key, run);
|
|
newOrder.push(key);
|
|
}
|
|
cache.forEach((run) => {
|
|
run.destroy();
|
|
run.container.remove();
|
|
});
|
|
let anchor = marker;
|
|
for (let i = newOrder.length - 1;i >= 0; i--) {
|
|
const run = newCache.get(newOrder[i]);
|
|
if (run.container.nextSibling !== anchor) {
|
|
container.insertBefore(run.container, anchor);
|
|
}
|
|
anchor = run.container;
|
|
}
|
|
cache = newCache;
|
|
});
|
|
return container;
|
|
};
|
|
var $router = (routes) => {
|
|
const sPath = $(window.location.hash.replace(/^#/, "") || "/");
|
|
window.addEventListener("hashchange", () => sPath(window.location.hash.replace(/^#/, "") || "/"));
|
|
const outlet = $html2("div", { class: "router-outlet" });
|
|
let current = null;
|
|
$watch2([sPath], async () => {
|
|
const path = sPath();
|
|
const route = routes.find((r) => {
|
|
const rp = r.path.split("/").filter(Boolean), pp = path.split("/").filter(Boolean);
|
|
return rp.length === pp.length && rp.every((p, i) => p.startsWith(":") || p === pp[i]);
|
|
}) || routes.find((r) => r.path === "*");
|
|
if (route) {
|
|
let comp = route.component;
|
|
if (typeof comp === "function" && comp.toString().includes("import")) {
|
|
comp = (await comp()).default || await comp();
|
|
}
|
|
const params = {};
|
|
route.path.split("/").filter(Boolean).forEach((p, i) => {
|
|
if (p.startsWith(":"))
|
|
params[p.slice(1)] = path.split("/").filter(Boolean)[i];
|
|
});
|
|
if (current)
|
|
current.destroy();
|
|
if ($router.params)
|
|
$router.params(params);
|
|
current = _view(() => {
|
|
try {
|
|
return typeof comp === "function" ? comp(params) : comp;
|
|
} catch (e) {
|
|
return $html2("div", { class: "p-4 text-error" }, "Error loading view");
|
|
}
|
|
});
|
|
outlet.appendChild(current.container);
|
|
}
|
|
});
|
|
return outlet;
|
|
};
|
|
$router.params = $({});
|
|
$router.to = (path) => window.location.hash = path.replace(/^#?\/?/, "#/");
|
|
$router.back = () => window.history.back();
|
|
$router.path = () => window.location.hash.replace(/^#/, "") || "/";
|
|
var $mount = (component, target) => {
|
|
const el = typeof target === "string" ? document.querySelector(target) : target;
|
|
if (!el)
|
|
return;
|
|
if (MOUNTED_NODES.has(el))
|
|
MOUNTED_NODES.get(el).destroy();
|
|
const instance = _view(typeof component === "function" ? component : () => component);
|
|
el.replaceChildren(instance.container);
|
|
MOUNTED_NODES.set(el, instance);
|
|
return instance;
|
|
};
|
|
var SigProCore = { $, $watch: $watch2, $html: $html2, $if, $for, $router, $mount };
|
|
if (typeof window !== "undefined") {
|
|
const install = (registry) => {
|
|
Object.keys(registry).forEach((key) => {
|
|
window[key] = registry[key];
|
|
});
|
|
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((tagName) => {
|
|
const helperName = tagName.charAt(0).toUpperCase() + tagName.slice(1);
|
|
if (!(helperName in window)) {
|
|
window[helperName] = (props, content) => $html2(tagName, props, content);
|
|
}
|
|
});
|
|
window.SigPro = Object.freeze(registry);
|
|
};
|
|
install(SigProCore);
|
|
}
|
|
|
|
// src/core/utils.js
|
|
var val = (t) => typeof t === "function" ? t() : t;
|
|
var ui = (baseClass, additionalClassOrFn) => typeof additionalClassOrFn === "function" ? () => `${baseClass} ${additionalClassOrFn() || ""}`.trim() : `${baseClass} ${additionalClassOrFn || ""}`.trim();
|
|
var getIcon = (icon) => {
|
|
if (!icon)
|
|
return null;
|
|
if (typeof icon === "function") {
|
|
return $html2("span", { class: "mr-1" }, icon());
|
|
}
|
|
if (typeof icon === "object") {
|
|
return $html2("span", { class: "mr-1" }, icon);
|
|
}
|
|
if (typeof icon === "string") {
|
|
const parts = icon.trim().split(/\s+/);
|
|
const hasRight = parts[parts.length - 1] === "right";
|
|
const iconClass = hasRight ? parts.slice(0, -1).join(" ") : icon;
|
|
const spacing = hasRight ? "ml-1" : "mr-1";
|
|
if (iconClass && !iconClass.startsWith("icon-[") && !iconClass.includes("--")) {
|
|
return $html2("span", { class: spacing }, iconClass);
|
|
}
|
|
return $html2("span", { class: `${iconClass} ${spacing}`.trim() });
|
|
}
|
|
return null;
|
|
};
|
|
|
|
// src/components/Accordion.js
|
|
var Accordion = (props, children) => {
|
|
const { class: className, title, name, open, ...rest } = props;
|
|
return $html2("div", {
|
|
...rest,
|
|
class: ui("collapse collapse-arrow bg-base-200 mb-2", className)
|
|
}, [
|
|
$html2("input", {
|
|
type: name ? "radio" : "checkbox",
|
|
name,
|
|
checked: val(open)
|
|
}),
|
|
$html2("div", { class: "collapse-title text-xl font-medium" }, title),
|
|
$html2("div", { class: "collapse-content" }, children)
|
|
]);
|
|
};
|
|
|
|
// src/components/Alert.js
|
|
var Alert = (props, children) => {
|
|
const { class: className, actions, type = "info", soft = true, ...rest } = props;
|
|
const iconMap = {
|
|
info: "icon-[lucide--info]",
|
|
success: "icon-[lucide--check-circle]",
|
|
warning: "icon-[lucide--alert-triangle]",
|
|
error: "icon-[lucide--alert-circle]"
|
|
};
|
|
const typeClass = `alert-${type}`;
|
|
const softClass = soft ? "alert-soft" : "";
|
|
const allClasses = [typeClass, softClass, className].filter(Boolean).join(" ");
|
|
const content = children || props.message;
|
|
return $html2("div", {
|
|
...rest,
|
|
role: "alert",
|
|
class: ui("alert", allClasses)
|
|
}, () => [
|
|
getIcon(iconMap[type]),
|
|
$html2("div", { class: "flex-1" }, [
|
|
$html2("span", {}, [typeof content === "function" ? content() : content])
|
|
]),
|
|
actions ? $html2("div", { class: "flex-none" }, [
|
|
typeof actions === "function" ? actions() : actions
|
|
]) : null
|
|
].filter(Boolean));
|
|
};
|
|
|
|
// src/core/i18n.js
|
|
var i18n = {
|
|
es: {
|
|
close: "Cerrar",
|
|
confirm: "Confirmar",
|
|
cancel: "Cancelar",
|
|
search: "Buscar...",
|
|
loading: "Cargando...",
|
|
nodata: "Sin datos"
|
|
},
|
|
en: {
|
|
close: "Close",
|
|
confirm: "Confirm",
|
|
cancel: "Cancel",
|
|
search: "Search...",
|
|
loading: "Loading...",
|
|
nodata: "No data"
|
|
}
|
|
};
|
|
var currentLocale = $("es");
|
|
var tt = (t) => () => i18n[currentLocale()][t] || t;
|
|
|
|
// src/components/Input.js
|
|
var Input = (props) => {
|
|
const {
|
|
class: className,
|
|
value,
|
|
type = "text",
|
|
icon,
|
|
oninput,
|
|
placeholder,
|
|
disabled,
|
|
size,
|
|
validate,
|
|
...rest
|
|
} = props;
|
|
const isPassword = type === "password";
|
|
const visible = $(false);
|
|
const errorMsg = $(null);
|
|
const iconMap = {
|
|
text: "icon-[lucide--text]",
|
|
password: "icon-[lucide--lock]",
|
|
date: "icon-[lucide--calendar]",
|
|
number: "icon-[lucide--hash]",
|
|
email: "icon-[lucide--mail]",
|
|
search: "icon-[lucide--search]",
|
|
tel: "icon-[lucide--phone]",
|
|
url: "icon-[lucide--link]"
|
|
};
|
|
const leftIcon = icon ? getIcon(icon) : iconMap[type] ? getIcon(iconMap[type]) : null;
|
|
const getPasswordIcon = () => getIcon(visible() ? "icon-[lucide--eye-off]" : "icon-[lucide--eye]");
|
|
const paddingLeft = leftIcon ? "pl-10" : "";
|
|
const paddingRight = isPassword ? "pr-10" : "";
|
|
const buttonSize = () => {
|
|
if (className?.includes("input-xs"))
|
|
return "btn-xs";
|
|
if (className?.includes("input-sm"))
|
|
return "btn-sm";
|
|
if (className?.includes("input-lg"))
|
|
return "btn-lg";
|
|
return "btn-md";
|
|
};
|
|
const handleInput = (e) => {
|
|
const newValue = e.target.value;
|
|
if (validate) {
|
|
const result = validate(newValue);
|
|
errorMsg(result || null);
|
|
}
|
|
oninput?.(e);
|
|
};
|
|
const hasError = () => errorMsg() && errorMsg() !== "";
|
|
const inputClasses = () => {
|
|
let classes = `input w-full ${paddingLeft} ${paddingRight}`;
|
|
if (className)
|
|
classes += ` ${className}`;
|
|
if (hasError())
|
|
classes += " input-error";
|
|
return classes.trim();
|
|
};
|
|
const inputElement = $html2("input", {
|
|
...rest,
|
|
type: () => isPassword ? visible() ? "text" : "password" : type,
|
|
placeholder: placeholder || " ",
|
|
class: inputClasses,
|
|
value,
|
|
oninput: handleInput,
|
|
disabled: () => val(disabled),
|
|
"aria-invalid": () => hasError() ? "true" : "false"
|
|
});
|
|
return $html2("div", { class: "relative w-full" }, () => [
|
|
inputElement,
|
|
leftIcon ? $html2("div", {
|
|
class: "absolute left-3 inset-y-0 flex items-center pointer-events-none text-base-content/60"
|
|
}, leftIcon) : null,
|
|
isPassword ? $html2("button", {
|
|
type: "button",
|
|
class: ui("absolute right-3 inset-y-0 flex items-center", "btn btn-ghost btn-circle opacity-50 hover:opacity-100", buttonSize()),
|
|
onclick: (e) => {
|
|
e.preventDefault();
|
|
visible(!visible());
|
|
}
|
|
}, () => getPasswordIcon()) : null,
|
|
$html2("div", {
|
|
class: "text-error text-xs mt-1 px-3 absolute -bottom-5 left-0"
|
|
}, () => hasError() ? errorMsg() : null)
|
|
]);
|
|
};
|
|
|
|
// src/components/Autocomplete.js
|
|
var Autocomplete = (props) => {
|
|
const { class: className, items = [], value, onSelect, label, placeholder, ...rest } = props;
|
|
const query = $(val(value) || "");
|
|
const isOpen = $(false);
|
|
const cursor = $(-1);
|
|
const list = $(() => {
|
|
const q = query().toLowerCase();
|
|
const data = val(items) || [];
|
|
return q ? data.filter((o) => (typeof o === "string" ? o : o.label).toLowerCase().includes(q)) : data;
|
|
});
|
|
const pick = (opt) => {
|
|
const valStr = typeof opt === "string" ? opt : opt.value;
|
|
const labelStr = typeof opt === "string" ? opt : opt.label;
|
|
query(labelStr);
|
|
if (typeof value === "function")
|
|
value(valStr);
|
|
onSelect?.(opt);
|
|
isOpen(false);
|
|
cursor(-1);
|
|
};
|
|
const nav = (e) => {
|
|
const items2 = list();
|
|
if (e.key === "ArrowDown") {
|
|
e.preventDefault();
|
|
isOpen(true);
|
|
cursor(Math.min(cursor() + 1, items2.length - 1));
|
|
} else if (e.key === "ArrowUp") {
|
|
e.preventDefault();
|
|
cursor(Math.max(cursor() - 1, 0));
|
|
} else if (e.key === "Enter" && cursor() >= 0) {
|
|
e.preventDefault();
|
|
pick(items2[cursor()]);
|
|
} else if (e.key === "Escape") {
|
|
isOpen(false);
|
|
}
|
|
};
|
|
return $html2("div", { class: "relative w-full" }, [
|
|
Input({
|
|
label,
|
|
class: className,
|
|
placeholder: placeholder || tt("search")(),
|
|
value: query,
|
|
onfocus: () => isOpen(true),
|
|
onblur: () => setTimeout(() => isOpen(false), 150),
|
|
onkeydown: nav,
|
|
oninput: (e) => {
|
|
const v = e.target.value;
|
|
query(v);
|
|
if (typeof value === "function")
|
|
value(v);
|
|
isOpen(true);
|
|
cursor(-1);
|
|
},
|
|
...rest
|
|
}),
|
|
$html2("ul", {
|
|
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"
|
|
}, [
|
|
$for(list, (opt, i) => $html2("li", {}, [
|
|
$html2("a", {
|
|
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),
|
|
() => list().length ? null : $html2("li", { class: "p-2 text-center opacity-50" }, tt("nodata")())
|
|
])
|
|
]);
|
|
};
|
|
|
|
// src/components/Badge.js
|
|
var Badge = (props, children) => {
|
|
const { class: className, ...rest } = props;
|
|
return $html2("span", {
|
|
...rest,
|
|
class: ui("badge", className)
|
|
}, children);
|
|
};
|
|
|
|
// src/components/Button.js
|
|
var Button = (props, children) => {
|
|
const { class: className, loading, icon, ...rest } = props;
|
|
const iconEl = getIcon(icon);
|
|
return $html2("button", {
|
|
...rest,
|
|
class: ui("btn", className),
|
|
disabled: () => val(loading) || val(props.disabled)
|
|
}, () => [
|
|
val(loading) && $html2("span", { class: "loading loading-spinner" }),
|
|
iconEl,
|
|
children
|
|
].filter(Boolean));
|
|
};
|
|
|
|
// src/components/Checkbox.js
|
|
var Checkbox = (props) => {
|
|
const { class: className, value, tooltip, toggle, label, ...rest } = props;
|
|
const checkEl = $html2("input", {
|
|
...rest,
|
|
type: "checkbox",
|
|
class: () => ui(val(toggle) ? "toggle" : "checkbox", className),
|
|
checked: value
|
|
});
|
|
const layout = $html2("label", { class: "label cursor-pointer justify-start gap-3" }, [
|
|
checkEl,
|
|
label ? $html2("span", { class: "label-text" }, label) : null
|
|
]);
|
|
return tooltip ? $html2("div", { class: "tooltip", "data-tip": tooltip }, layout) : layout;
|
|
};
|
|
|
|
// src/components/Colorpicker.js
|
|
var Colorpicker = (props) => {
|
|
const { class: className, value, label, ...rest } = props;
|
|
const isOpen = $(false);
|
|
const palette = [
|
|
...["#000", "#1A1A1A", "#333", "#4D4D4D", "#666", "#808080", "#B3B3B3", "#FFF"],
|
|
...["#450a0a", "#7f1d1d", "#991b1b", "#b91c1c", "#dc2626", "#ef4444", "#f87171", "#fca5a5"],
|
|
...["#431407", "#7c2d12", "#9a3412", "#c2410c", "#ea580c", "#f97316", "#fb923c", "#ffedd5"],
|
|
...["#713f12", "#a16207", "#ca8a04", "#eab308", "#facc15", "#fde047", "#fef08a", "#fff9c4"],
|
|
...["#064e3b", "#065f46", "#059669", "#10b981", "#34d399", "#4ade80", "#84cc16", "#d9f99d"],
|
|
...["#082f49", "#075985", "#0284c7", "#0ea5e9", "#38bdf8", "#7dd3fc", "#22d3ee", "#cffafe"],
|
|
...["#1e1b4b", "#312e81", "#4338ca", "#4f46e5", "#6366f1", "#818cf8", "#a5b4fc", "#e0e7ff"],
|
|
...["#2e1065", "#4c1d95", "#6d28d9", "#7c3aed", "#8b5cf6", "#a855f7", "#d946ef", "#fae8ff"]
|
|
];
|
|
const getColor = () => val(value) || "#000000";
|
|
return $html2("div", { class: ui("relative w-fit", className) }, [
|
|
$html2("button", {
|
|
type: "button",
|
|
class: "btn px-3 bg-base-100 border-base-300 hover:border-primary/50 flex items-center gap-2 shadow-sm font-normal normal-case",
|
|
onclick: (e) => {
|
|
e.stopPropagation();
|
|
isOpen(!isOpen());
|
|
},
|
|
...rest
|
|
}, [
|
|
$html2("div", {
|
|
class: "size-5 rounded-sm shadow-inner border border-black/10 shrink-0",
|
|
style: () => `background-color: ${getColor()}`
|
|
}),
|
|
label ? $html2("span", { class: "opacity-80" }, label) : null
|
|
]),
|
|
$if(isOpen, () => $html2("div", {
|
|
class: "absolute left-0 mt-2 p-3 bg-base-100 border border-base-300 shadow-2xl rounded-box z-[110] w-64 select-none",
|
|
onclick: (e) => e.stopPropagation()
|
|
}, [
|
|
$html2("div", { class: "grid grid-cols-8 gap-1" }, palette.map((c) => $html2("button", {
|
|
type: "button",
|
|
style: `background-color: ${c}`,
|
|
class: () => {
|
|
const active = getColor().toLowerCase() === c.toLowerCase();
|
|
return `size-6 rounded-sm cursor-pointer transition-all hover:scale-125 hover:z-10 active:scale-95 outline-none border border-black/5
|
|
${active ? "ring-2 ring-offset-1 ring-primary z-10 scale-110" : ""}`;
|
|
},
|
|
onclick: () => {
|
|
if (typeof value === "function")
|
|
value(c);
|
|
isOpen(false);
|
|
}
|
|
})))
|
|
])),
|
|
$if(isOpen, () => $html2("div", {
|
|
class: "fixed inset-0 z-[100]",
|
|
onclick: () => isOpen(false)
|
|
}))
|
|
]);
|
|
};
|
|
|
|
// src/components/Datepicker.js
|
|
var Datepicker = (props) => {
|
|
const { class: className, 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;
|
|
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()) {
|
|
if (!current?.start || current.start && current.end) {
|
|
if (typeof value === "function") {
|
|
value({
|
|
start: dateStr,
|
|
end: null,
|
|
...hour && { startHour: startHour() }
|
|
});
|
|
}
|
|
} else {
|
|
const start = current.start;
|
|
if (typeof value === "function") {
|
|
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(hour ? `${dateStr}T${String(startHour()).padStart(2, "0")}:00:00` : dateStr);
|
|
}
|
|
isOpen(false);
|
|
}
|
|
};
|
|
const displayValue = $(() => {
|
|
const v = val(value);
|
|
if (!v)
|
|
return "";
|
|
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 "";
|
|
});
|
|
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));
|
|
};
|
|
const HourSlider = ({ value: hVal, onChange }) => {
|
|
return $html2("div", { class: "flex-1" }, [
|
|
$html2("div", { class: "flex gap-2 items-center" }, [
|
|
$html2("input", {
|
|
type: "range",
|
|
min: 0,
|
|
max: 23,
|
|
value: hVal,
|
|
class: "range range-xs flex-1",
|
|
oninput: (e) => {
|
|
const newHour = parseInt(e.target.value);
|
|
onChange(newHour);
|
|
}
|
|
}),
|
|
$html2("span", { class: "text-sm font-mono min-w-[48px] text-center" }, () => String(val(hVal)).padStart(2, "0") + ":00")
|
|
])
|
|
]);
|
|
};
|
|
return $html2("div", { class: ui("relative w-full", className) }, [
|
|
Input({
|
|
label,
|
|
placeholder: placeholder || (isRangeMode() ? "Seleccionar rango..." : "Seleccionar fecha..."),
|
|
value: displayValue,
|
|
readonly: true,
|
|
icon: getIcon("icon-[lucide--calendar]"),
|
|
onclick: (e) => {
|
|
e.stopPropagation();
|
|
isOpen(!isOpen());
|
|
},
|
|
...rest
|
|
}),
|
|
$if(isOpen, () => $html2("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()
|
|
}, [
|
|
$html2("div", { class: "flex justify-between items-center mb-4 gap-1" }, [
|
|
$html2("div", { class: "flex gap-0.5" }, [
|
|
$html2("button", { type: "button", class: "btn btn-ghost btn-xs px-1", onclick: () => moveYear(-1) }, getIcon("icon-[lucide--chevrons-left]")),
|
|
$html2("button", { type: "button", class: "btn btn-ghost btn-xs px-1", onclick: () => move(-1) }, getIcon("icon-[lucide--chevron-left]"))
|
|
]),
|
|
$html2("span", { class: "font-bold uppercase flex-1 text-center" }, [
|
|
() => internalDate().toLocaleString("es-ES", { month: "short", year: "numeric" })
|
|
]),
|
|
$html2("div", { class: "flex gap-0.5" }, [
|
|
$html2("button", { type: "button", class: "btn btn-ghost btn-xs px-1", onclick: () => move(1) }, getIcon("icon-[lucide--chevron-right]")),
|
|
$html2("button", { type: "button", class: "btn btn-ghost btn-xs px-1", onclick: () => moveYear(1) }, getIcon("icon-[lucide--chevrons-right]"))
|
|
])
|
|
]),
|
|
$html2("div", { class: "grid grid-cols-7 gap-1", onmouseleave: () => hoverDate(null) }, [
|
|
...["L", "M", "X", "J", "V", "S", "D"].map((d) => $html2("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($html2("div"));
|
|
for (let i = 1;i <= daysInMonth; i++) {
|
|
const date = new Date(year, month, i);
|
|
const dStr = formatDate(date);
|
|
nodes.push($html2("button", {
|
|
type: "button",
|
|
class: () => {
|
|
const v = val(value);
|
|
const h = hoverDate();
|
|
const isStart = typeof v === "string" ? v.split("T")[0] === dStr : v?.start === dStr;
|
|
const isEnd = v?.end === dStr;
|
|
let inRange = false;
|
|
if (isRangeMode() && v?.start) {
|
|
const start = v.start;
|
|
if (!v.end && h) {
|
|
inRange = dStr > start && dStr <= h || dStr < start && dStr >= h;
|
|
} else if (v.end) {
|
|
inRange = dStr > start && dStr < v.end;
|
|
}
|
|
}
|
|
const base = "btn btn-xs p-0 aspect-square min-h-0 h-auto font-normal relative";
|
|
const state = isStart || isEnd ? "btn-primary z-10" : inRange ? "bg-primary/20 border-none rounded-none" : "btn-ghost";
|
|
const today = dStr === todayStr ? "ring-1 ring-primary ring-inset font-black text-primary" : "";
|
|
return `${base} ${state} ${today}`;
|
|
},
|
|
onmouseenter: () => {
|
|
if (isRangeMode())
|
|
hoverDate(dStr);
|
|
},
|
|
onclick: () => selectDate(date)
|
|
}, [i.toString()]));
|
|
}
|
|
return nodes;
|
|
}
|
|
]),
|
|
hour ? $html2("div", { class: "mt-3 pt-2 border-t border-base-300" }, [
|
|
isRangeMode() ? $html2("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
|
|
])),
|
|
$if(isOpen, () => $html2("div", { class: "fixed inset-0 z-[90]", onclick: () => isOpen(false) }))
|
|
]);
|
|
};
|
|
|
|
// src/components/Drawer.js
|
|
var Drawer = (props, children) => {
|
|
const { class: className, id, open, side, content, ...rest } = props;
|
|
const drawerId = id || `drawer-${Math.random().toString(36).slice(2, 9)}`;
|
|
return $html2("div", {
|
|
...rest,
|
|
class: ui("drawer", className)
|
|
}, [
|
|
$html2("input", {
|
|
id: drawerId,
|
|
type: "checkbox",
|
|
class: "drawer-toggle",
|
|
checked: () => typeof open === "function" ? open() : open,
|
|
onchange: (e) => {
|
|
if (typeof open === "function")
|
|
open(e.target.checked);
|
|
}
|
|
}),
|
|
$html2("div", { class: "drawer-content" }, [
|
|
typeof content === "function" ? content() : content
|
|
]),
|
|
$html2("div", { class: "drawer-side" }, [
|
|
$html2("label", {
|
|
for: drawerId,
|
|
class: "drawer-overlay",
|
|
onclick: () => {
|
|
if (typeof open === "function")
|
|
open(false);
|
|
}
|
|
}),
|
|
$html2("div", { class: "min-h-full bg-base-200 w-80" }, [
|
|
typeof side === "function" ? side() : side
|
|
])
|
|
])
|
|
]);
|
|
};
|
|
|
|
// src/components/Dropdown.js
|
|
var currentOpen = null;
|
|
if (typeof window !== "undefined" && !window.__dropdownHandlerRegistered) {
|
|
window.addEventListener("click", (e) => {
|
|
if (currentOpen && !currentOpen.contains(e.target)) {
|
|
currentOpen.open = false;
|
|
currentOpen = null;
|
|
}
|
|
});
|
|
window.__dropdownHandlerRegistered = true;
|
|
}
|
|
var Dropdown = (props) => {
|
|
const { class: className, label, icon, items, ...rest } = props;
|
|
return $html("details", {
|
|
...rest,
|
|
class: ui("dropdown", className)
|
|
}, [
|
|
$html("summary", {
|
|
class: "btn m-1 flex items-center gap-2 list-none cursor-pointer",
|
|
style: "display: inline-flex;",
|
|
onclick: (e) => {
|
|
const details = e.currentTarget.closest("details");
|
|
if (currentOpen && currentOpen !== details) {
|
|
currentOpen.open = false;
|
|
}
|
|
setTimeout(() => {
|
|
currentOpen = details.open ? details : null;
|
|
}, 0);
|
|
}
|
|
}, [
|
|
() => icon ? typeof icon === "function" ? icon() : icon : null,
|
|
() => label ? typeof label === "function" ? label() : label : null
|
|
]),
|
|
$html("ul", {
|
|
tabindex: "-1",
|
|
class: "dropdown-content z-[50] menu p-2 shadow bg-base-100 rounded-box w-52 border border-base-300"
|
|
}, [
|
|
() => {
|
|
const currentItems = typeof items === "function" ? items() : items || [];
|
|
return currentItems.map((item) => $html("li", {}, [
|
|
$html("a", {
|
|
class: item.class || "",
|
|
onclick: (e) => {
|
|
if (item.onclick)
|
|
item.onclick(e);
|
|
const details = e.currentTarget.closest("details");
|
|
if (details) {
|
|
details.open = false;
|
|
if (currentOpen === details)
|
|
currentOpen = null;
|
|
}
|
|
}
|
|
}, [
|
|
item.icon ? $html("span", {}, item.icon) : null,
|
|
$html("span", {}, item.label)
|
|
])
|
|
]));
|
|
}
|
|
])
|
|
]);
|
|
};
|
|
|
|
// src/components/Fab.js
|
|
var Fab = (props) => {
|
|
const { class: className, icon, label, actions = [], position = "bottom-6 right-6", ...rest } = props;
|
|
return $html2("div", {
|
|
...rest,
|
|
class: ui(`fab absolute ${position} flex flex-col-reverse items-end gap-3 z-[100]`, className)
|
|
}, [
|
|
$html2("div", {
|
|
tabindex: 0,
|
|
role: "button",
|
|
class: "btn btn-lg btn-circle btn-primary shadow-2xl"
|
|
}, [
|
|
icon ? getIcon(icon) : null,
|
|
!icon && label ? label : null
|
|
]),
|
|
...val(actions).map((act) => $html2("div", { class: "flex items-center gap-3 transition-all duration-300" }, [
|
|
act.label ? $html2("span", { class: "badge badge-ghost shadow-sm whitespace-nowrap" }, act.label) : null,
|
|
$html2("button", {
|
|
type: "button",
|
|
class: `btn btn-circle shadow-lg ${act.class || ""}`,
|
|
onclick: (e) => {
|
|
e.stopPropagation();
|
|
act.onclick?.(e);
|
|
}
|
|
}, [act.icon ? getIcon(act.icon) : act.text || ""])
|
|
]))
|
|
]);
|
|
};
|
|
|
|
// src/components/Fieldset.js
|
|
var Fieldset = (props, children) => {
|
|
const { class: className, legend, ...rest } = props;
|
|
return $html2("fieldset", {
|
|
...rest,
|
|
class: ui("fieldset bg-base-200 border border-base-300 p-4 rounded-lg", className)
|
|
}, [
|
|
() => {
|
|
const legendText = val(legend);
|
|
return legendText ? $html2("legend", { class: "fieldset-legend font-bold" }, [legendText]) : null;
|
|
},
|
|
children
|
|
]);
|
|
};
|
|
|
|
// src/components/Fileinput.js
|
|
var Fileinput = (props) => {
|
|
const { class: className, tooltip, max = 2, accept = "*", onSelect, ...rest } = 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 $html2("fieldset", { ...rest, class: ui("fieldset w-full p-0", className) }, [
|
|
$html2("div", {
|
|
class: () => `w-full ${tooltip ? "tooltip tooltip-top before:z-50 after:z-50" : ""}`,
|
|
"data-tip": tooltip
|
|
}, [
|
|
$html2("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);
|
|
}
|
|
}, [
|
|
$html2("div", { class: "flex items-center gap-3 w-full" }, [
|
|
getIcon("icon-[lucide--upload]"),
|
|
$html2("span", { class: "text-sm opacity-70 truncate grow text-left" }, "Arrastra o selecciona archivos..."),
|
|
$html2("span", { class: "text-[10px] opacity-40 shrink-0" }, `Máx ${max}MB`)
|
|
]),
|
|
$html2("input", {
|
|
type: "file",
|
|
multiple: true,
|
|
accept,
|
|
class: "hidden",
|
|
onchange: (e) => handleFiles(e.target.files)
|
|
})
|
|
])
|
|
]),
|
|
() => error() ? $html2("span", { class: "text-[10px] text-error mt-1 px-1 font-medium" }, error()) : null,
|
|
$if(() => selectedFiles().length > 0, () => $html2("ul", { class: "mt-2 space-y-1" }, [
|
|
$for(selectedFiles, (file, index) => $html2("li", { class: "flex items-center justify-between p-1.5 pl-3 text-xs bg-base-200/50 rounded-md border border-base-300" }, [
|
|
$html2("div", { class: "flex items-center gap-2 truncate" }, [
|
|
$html2("span", { class: "opacity-50" }, "\uD83D\uDCC4"),
|
|
$html2("span", { class: "truncate font-medium max-w-[200px]" }, file.name),
|
|
$html2("span", { class: "text-[9px] opacity-40" }, `(${(file.size / 1024).toFixed(0)} KB)`)
|
|
]),
|
|
$html2("button", {
|
|
type: "button",
|
|
class: "btn btn-ghost btn-xs btn-circle",
|
|
onclick: (e) => {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
removeFile(index);
|
|
}
|
|
}, [getIcon("icon-[lucide--x]")])
|
|
]), (file) => file.name + file.lastModified)
|
|
]))
|
|
]);
|
|
};
|
|
|
|
// src/components/Indicator.js
|
|
var Indicator = (props, children) => {
|
|
const { value, class: className, ...rest } = props;
|
|
return $html2("div", {
|
|
...rest,
|
|
class: "indicator"
|
|
}, () => [
|
|
value ? $html2("span", {
|
|
class: ui("indicator-item badge", className)
|
|
}, () => typeof value === "function" ? value() : value) : null,
|
|
children
|
|
].filter(Boolean));
|
|
};
|
|
|
|
// src/components/Label.js
|
|
var Label = (props) => {
|
|
const { children, value, floating = false, error, required, class: className, ...rest } = props;
|
|
if (floating) {
|
|
return $html2("label", { class: ui("floating-label w-full", className), ...rest }, () => [
|
|
value ? $html2("span", {}, value) : null,
|
|
children,
|
|
error ? $html2("span", { class: "text-error text-xs" }, val(error)) : null
|
|
]);
|
|
}
|
|
return $html2("label", { class: ui("input w-full", className), ...rest }, () => [
|
|
value ? $html2("span", { class: "label" }, value) : null,
|
|
children,
|
|
error ? $html2("span", { class: "text-error text-xs" }, val(error)) : null
|
|
]);
|
|
};
|
|
|
|
// src/components/List.js
|
|
var List = (props) => {
|
|
const { class: className, items, header, render, keyFn = (item, index) => item.id ?? index, ...rest } = props;
|
|
const listItems = $for(items, (item, index) => $html2("li", { class: "list-row" }, [render(item, index)]), keyFn);
|
|
return $html2("ul", {
|
|
...rest,
|
|
class: ui("list bg-base-100 rounded-box shadow-md", className)
|
|
}, header ? [$if(header, () => $html2("li", { class: "p-4 pb-2 text-xs opacity-60" }, [val(header)])), listItems] : listItems);
|
|
};
|
|
|
|
// src/components/Menu.js
|
|
var Menu = (props) => {
|
|
const { class: className, items, ...rest } = props;
|
|
const renderItems = (items2) => $for(() => items2 || [], (it) => $html2("li", {}, [
|
|
it.children ? $html2("details", { open: it.open }, [
|
|
$html2("summary", {}, [it.icon && $html2("span", { class: "mr-2" }, it.icon), it.label]),
|
|
$html2("ul", {}, renderItems(it.children))
|
|
]) : $html2("a", { class: () => val(it.active) ? "active" : "", onclick: it.onclick }, [
|
|
it.icon && $html2("span", { class: "mr-2" }, it.icon),
|
|
it.label
|
|
])
|
|
]), (it, i) => it.label || i);
|
|
return $html2("ul", { ...rest, class: ui("menu bg-base-200 rounded-box", className) }, renderItems(items));
|
|
};
|
|
|
|
// src/components/Modal.js
|
|
var Modal = (props, children) => {
|
|
const { class: className, title, buttons, open, ...rest } = props;
|
|
let dialogElement = null;
|
|
const handleOpen = () => {
|
|
const isOpen = typeof open === "function" ? open() : open;
|
|
if (!dialogElement)
|
|
return;
|
|
if (isOpen) {
|
|
if (!dialogElement.open)
|
|
dialogElement.showModal();
|
|
} else {
|
|
if (dialogElement.open)
|
|
dialogElement.close();
|
|
}
|
|
};
|
|
$watch2(() => handleOpen());
|
|
const close = () => {
|
|
if (typeof open === "function")
|
|
open(false);
|
|
};
|
|
return $html2("dialog", {
|
|
...rest,
|
|
ref: (el) => {
|
|
dialogElement = el;
|
|
if (el)
|
|
handleOpen();
|
|
},
|
|
class: ui("modal", className),
|
|
onclose: close,
|
|
oncancel: close
|
|
}, [
|
|
$html2("div", { class: "modal-box" }, [
|
|
title ? $html2("h3", { class: "text-lg font-bold mb-4" }, () => typeof title === "function" ? title() : title) : null,
|
|
$html2("div", { class: "py-2" }, [
|
|
typeof children === "function" ? children() : children
|
|
]),
|
|
$html2("div", { class: "modal-action" }, [
|
|
$html2("form", { method: "dialog", class: "flex gap-2" }, [
|
|
...(Array.isArray(buttons) ? buttons : [buttons]).filter(Boolean),
|
|
Button({ type: "submit" }, tt("close")())
|
|
])
|
|
])
|
|
]),
|
|
$html2("form", { method: "dialog", class: "modal-backdrop" }, [
|
|
$html2("button", {}, "close")
|
|
])
|
|
]);
|
|
};
|
|
|
|
// src/components/Navbar.js
|
|
var Navbar = (props, children) => {
|
|
const { class: className, ...rest } = props;
|
|
return $html2("div", { ...rest, class: ui("navbar bg-base-100 shadow-sm px-4", className) }, children);
|
|
};
|
|
|
|
// src/components/Radio.js
|
|
var Radio = (props) => {
|
|
const { class: className, label, tooltip, value, inputValue, name, ...rest } = props;
|
|
const radioEl = $html2("input", {
|
|
...rest,
|
|
type: "radio",
|
|
name,
|
|
class: ui("radio", className),
|
|
checked: () => val(value) === inputValue,
|
|
onclick: () => {
|
|
if (typeof value === "function")
|
|
value(inputValue);
|
|
}
|
|
});
|
|
if (!label && !tooltip)
|
|
return radioEl;
|
|
const layout = $html2("label", { class: "label cursor-pointer justify-start gap-3" }, [
|
|
radioEl,
|
|
label ? $html2("span", { class: "label-text" }, label) : null
|
|
]);
|
|
return tooltip ? $html2("div", { class: "tooltip", "data-tip": tooltip }, layout) : layout;
|
|
};
|
|
|
|
// src/components/Range.js
|
|
var Range = (props) => {
|
|
const { class: className, label, tooltip, value, ...rest } = props;
|
|
const rangeEl = $html2("input", {
|
|
...rest,
|
|
type: "range",
|
|
class: ui("range", className),
|
|
value,
|
|
disabled: () => val(props.disabled)
|
|
});
|
|
if (!label && !tooltip)
|
|
return rangeEl;
|
|
const layout = $html2("div", { class: "flex flex-col gap-2" }, [
|
|
label ? $html2("span", { class: "label-text" }, label) : null,
|
|
rangeEl
|
|
]);
|
|
return tooltip ? $html2("div", { class: "tooltip", "data-tip": tooltip }, layout) : layout;
|
|
};
|
|
|
|
// src/components/Rating.js
|
|
var Rating = (props) => {
|
|
const { class: className, value, count = 5, mask = "mask-star", readonly = false, onchange, ...rest } = props;
|
|
const ratingGroup = `rating-${Math.random().toString(36).slice(2, 7)}`;
|
|
return $html2("div", {
|
|
...rest,
|
|
class: () => ui(`rating ${val(readonly) ? "pointer-events-none" : ""}`, className)
|
|
}, Array.from({ length: val(count) }, (_, i) => {
|
|
const starValue = i + 1;
|
|
return $html2("input", {
|
|
type: "radio",
|
|
name: ratingGroup,
|
|
class: `mask ${mask}`,
|
|
checked: () => Math.round(val(value)) === starValue,
|
|
onchange: () => {
|
|
if (!val(readonly)) {
|
|
if (typeof onchange === "function") {
|
|
onchange(starValue);
|
|
} else if (typeof value === "function") {
|
|
value(starValue);
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}));
|
|
};
|
|
|
|
// src/components/Select.js
|
|
var Select = (props) => {
|
|
const { class: className, label, items, value, ...rest } = props;
|
|
const selectEl = $html2("select", {
|
|
...rest,
|
|
class: ui("select select-bordered w-full", className),
|
|
value
|
|
}, $for(() => val(items) || [], (opt) => $html2("option", {
|
|
value: opt.value,
|
|
$selected: () => String(val(value)) === String(opt.value)
|
|
}, opt.label), (opt) => opt.value));
|
|
if (!label)
|
|
return selectEl;
|
|
return $html2("label", { class: "fieldset-label flex flex-col gap-1" }, [
|
|
$html2("span", {}, label),
|
|
selectEl
|
|
]);
|
|
};
|
|
|
|
// src/components/Stack.js
|
|
var Stack = (props, children) => {
|
|
const { class: className, ...rest } = props;
|
|
return $html2("div", { ...rest, class: ui("stack", className) }, children);
|
|
};
|
|
|
|
// src/components/Stat.js
|
|
var Stat = (props) => {
|
|
const { class: className, icon, label, value, desc, ...rest } = props;
|
|
return $html2("div", { ...rest, class: ui("stat", className) }, [
|
|
icon && $html2("div", { class: "stat-figure text-secondary" }, icon),
|
|
label && $html2("div", { class: "stat-title" }, label),
|
|
$html2("div", { class: "stat-value" }, () => val(value) ?? value),
|
|
desc && $html2("div", { class: "stat-desc" }, desc)
|
|
]);
|
|
};
|
|
|
|
// src/components/Swap.js
|
|
var Swap = (props) => {
|
|
const { class: className, value, on, off, ...rest } = props;
|
|
return $html2("label", { ...rest, class: ui("swap", className) }, [
|
|
$html2("input", {
|
|
type: "checkbox",
|
|
checked: () => val(value),
|
|
onclick: (e) => {
|
|
if (typeof value === "function") {
|
|
value(e.target.checked);
|
|
}
|
|
}
|
|
}),
|
|
$html2("div", { class: "swap-on" }, on),
|
|
$html2("div", { class: "swap-off" }, off)
|
|
]);
|
|
};
|
|
|
|
// src/components/Table.js
|
|
var Table = (props) => {
|
|
const { class: className, items = [], columns = [], keyFn, zebra = false, pinRows = false, empty = tt("nodata")(), ...rest } = props;
|
|
const tableClass = () => {
|
|
const zebraClass = val(zebra) ? "table-zebra" : "";
|
|
const pinRowsClass = val(pinRows) ? "table-pin-rows" : "";
|
|
return ui("table", className, zebraClass, pinRowsClass);
|
|
};
|
|
const getInternalKeyFn = keyFn || ((item, idx) => item.id || idx);
|
|
return $html2("div", { class: "overflow-x-auto w-full bg-base-100 rounded-box border border-base-300" }, [
|
|
$html2("table", { ...rest, class: tableClass }, [
|
|
$html2("thead", {}, [
|
|
$html2("tr", {}, columns.map((col) => $html2("th", { class: col.class || "" }, col.label)))
|
|
]),
|
|
$html2("tbody", {}, [
|
|
$for(items, (item, index) => {
|
|
const it = () => {
|
|
const currentItems = val(items);
|
|
const key = getInternalKeyFn(item, index);
|
|
return currentItems.find((u, i) => getInternalKeyFn(u, i) === key) || item;
|
|
};
|
|
return $html2("tr", { class: "hover" }, columns.map((col) => {
|
|
const cellContent = () => {
|
|
const latestItem = it();
|
|
if (col.render)
|
|
return col.render(latestItem, index);
|
|
return val(latestItem[col.key]);
|
|
};
|
|
return $html2("td", { class: col.class || "" }, [cellContent]);
|
|
}));
|
|
}, getInternalKeyFn),
|
|
$if(() => val(items).length === 0, () => $html2("tr", {}, [
|
|
$html2("td", { colspan: columns.length, class: "text-center p-10 opacity-50" }, [
|
|
val(empty)
|
|
])
|
|
]))
|
|
])
|
|
])
|
|
]);
|
|
};
|
|
|
|
// src/components/Tabs.js
|
|
var Tabs = (props) => {
|
|
const { items, class: className, ...rest } = props;
|
|
const itemsSignal = typeof items === "function" ? items : () => items || [];
|
|
const activeIndex = $(0);
|
|
$watch(() => {
|
|
const idx = itemsSignal().findIndex((it) => val(it.active) === true);
|
|
if (idx !== -1 && idx !== activeIndex())
|
|
activeIndex(idx);
|
|
});
|
|
return $html2("div", { ...rest, class: "w-full" }, [
|
|
$html2("div", {
|
|
role: "tablist",
|
|
class: ui("tabs", className || "tabs-box")
|
|
}, () => {
|
|
const list = itemsSignal();
|
|
return list.map((it, idx) => {
|
|
const isSelected = () => activeIndex() === idx;
|
|
const tab = $html2("button", {
|
|
role: "tab",
|
|
class: () => ui("tab", isSelected() ? "tab-active" : ""),
|
|
onclick: (e) => {
|
|
e.preventDefault();
|
|
if (!val(it.disabled)) {
|
|
if (it.onclick)
|
|
it.onclick();
|
|
activeIndex(idx);
|
|
}
|
|
}
|
|
});
|
|
$watch(() => {
|
|
const content = val(it.label);
|
|
if (content instanceof Node) {
|
|
tab.replaceChildren(content);
|
|
} else {
|
|
tab.textContent = String(content);
|
|
}
|
|
});
|
|
return tab;
|
|
});
|
|
}),
|
|
$html2("div", { class: "tab-panels" }, () => {
|
|
return itemsSignal().map((it, idx) => {
|
|
const isVisible = () => activeIndex() === idx;
|
|
return $html2("div", {
|
|
role: "tabpanel",
|
|
class: "tab-content bg-base-100 border-base-300 p-6",
|
|
style: () => isVisible() ? "display: block" : "display: none"
|
|
}, [
|
|
() => typeof it.content === "function" ? it.content() : it.content
|
|
]);
|
|
});
|
|
})
|
|
]);
|
|
};
|
|
|
|
// src/components/Timeline.js
|
|
var Timeline = (props) => {
|
|
const { class: className, items = [], vertical = true, compact = false, ...rest } = props;
|
|
const iconMap = {
|
|
info: "icon-[lucide--info]",
|
|
success: "icon-[lucide--check-circle]",
|
|
warning: "icon-[lucide--alert-triangle]",
|
|
error: "icon-[lucide--alert-circle]"
|
|
};
|
|
return $html2("ul", {
|
|
...rest,
|
|
class: () => ui(`timeline ${val(vertical) ? "timeline-vertical" : "timeline-horizontal"} ${val(compact) ? "timeline-compact" : ""}`, className)
|
|
}, () => {
|
|
const list = (typeof items === "function" ? items() : items) || [];
|
|
return list.map((item, i) => {
|
|
const isFirst = i === 0;
|
|
const isLast = i === list.length - 1;
|
|
const itemType = item.type || "success";
|
|
const isCompleted = () => val(item.completed);
|
|
const prevCompleted = () => i > 0 && val(list[i - 1].completed);
|
|
const renderSlot = (content) => typeof content === "function" ? content() : content;
|
|
return $html2("li", { class: "flex-1" }, [
|
|
!isFirst ? $html2("hr", { class: () => prevCompleted() ? "bg-primary" : "" }) : null,
|
|
$html2("div", { class: "timeline-start" }, [() => renderSlot(item.title)]),
|
|
$html2("div", { class: "timeline-middle" }, [
|
|
() => item.icon ? getIcon(item.icon) : getIcon(iconMap[itemType] || iconMap.success)
|
|
]),
|
|
$html2("div", { class: "timeline-end timeline-box shadow-sm" }, [() => renderSlot(item.detail)]),
|
|
!isLast ? $html2("hr", { class: () => isCompleted() ? "bg-primary" : "" }) : null
|
|
]);
|
|
});
|
|
});
|
|
};
|
|
|
|
// src/components/Toast.js
|
|
var Toast = (message, type = "alert-success", duration = 3500) => {
|
|
let container = document.getElementById("sigpro-toast-container");
|
|
if (!container) {
|
|
container = $html2("div", {
|
|
id: "sigpro-toast-container",
|
|
class: "fixed top-0 right-0 z-[9999] p-4 flex flex-col gap-2 pointer-events-none"
|
|
});
|
|
document.body.appendChild(container);
|
|
}
|
|
const toastHost = $html2("div", { style: "display: contents" });
|
|
container.appendChild(toastHost);
|
|
let timeoutId;
|
|
const close = () => {
|
|
clearTimeout(timeoutId);
|
|
const el = toastHost.firstElementChild;
|
|
if (el && !el.classList.contains("opacity-0")) {
|
|
el.classList.add("translate-x-full", "opacity-0");
|
|
setTimeout(() => {
|
|
instance.destroy();
|
|
toastHost.remove();
|
|
if (!container.hasChildNodes())
|
|
container.remove();
|
|
}, 300);
|
|
} else {
|
|
instance.destroy();
|
|
toastHost.remove();
|
|
}
|
|
};
|
|
const ToastComponent = () => {
|
|
const closeIcon = getIcon("icon-[lucide--x]");
|
|
const el = $html2("div", {
|
|
class: `alert alert-soft ${type} shadow-lg transition-all duration-300 translate-x-10 opacity-0 pointer-events-auto`
|
|
}, [
|
|
$html2("span", {}, [typeof message === "function" ? message() : message]),
|
|
Button({
|
|
class: "btn-xs btn-circle btn-ghost",
|
|
onclick: close
|
|
}, closeIcon)
|
|
]);
|
|
requestAnimationFrame(() => el.classList.remove("translate-x-10", "opacity-0"));
|
|
return el;
|
|
};
|
|
const instance = $mount(ToastComponent, toastHost);
|
|
if (duration > 0) {
|
|
timeoutId = setTimeout(close, duration);
|
|
}
|
|
return close;
|
|
};
|
|
|
|
// src/components/Tooltip.js
|
|
var Tooltip = (props, children) => $html2("div", {
|
|
...props,
|
|
class: () => ui("tooltip", props.ui, props.class),
|
|
"data-tip": props.tip
|
|
}, children);
|
|
})();
|