Repair combo

This commit is contained in:
2026-05-13 13:22:52 +02:00
parent 06c7763b34
commit 1d71340552
8 changed files with 138 additions and 125 deletions

View File

@@ -3,7 +3,7 @@ Blazing fast, zero-overhead, vanilla JS renderer with atomic reactivity.
# `SigPro` # `SigPro`
[![npm version](https://img.shields.io/npm/v/sigpro.svg)](https://www.npmjs.com/package/sigpro) [![npm version](https://img.shields.io/npm/v/sigpro.svg)](https://www.npmjs.com/package/sigpro)
![js size](https://img.shields.io/badge/js_size-2.7_kB_brotli-blue) ![js size](https://img.shields.io/badge/js_size-2.8_kB_brotli-blue)
[![license](https://img.shields.io/npm/l/sigpro)](https://github.com/natxocc/sigpro/blob/main/LICENSE) [![license](https://img.shields.io/npm/l/sigpro)](https://github.com/natxocc/sigpro/blob/main/LICENSE)
[**Explore the Docs →**](https://sigpro.natxocc.com/#/) [**Explore the Docs →**](https://sigpro.natxocc.com/#/)

2
dist/sigpro.ui.css vendored

File diff suppressed because one or more lines are too long

2
dist/sigpro.ui.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -9,72 +9,92 @@ const rango = $({ start: null, end: null });
const pais = $(""); const pais = $("");
// ===== OPCIONES ===== // ===== OPCIONES =====
const frutas = ["Manzana", "Pera", "Plátano", "Fresa", "Mango", "Sandía", "Melón", "Uva"]; const frutas = [
"Manzana",
"Pera",
"Plátano",
"Fresa",
"Mango",
"Sandía",
"Melón",
"Uva",
];
const paises = [ const paises = [
{ label: "🇪🇸 España", value: "ES" }, { label: "🇪🇸 España", value: "ES" },
{ label: "🇲🇽 México", value: "MX" }, { label: "🇲🇽 México", value: "MX" },
{ label: "🇦🇷 Argentina", value: "AR" }, { label: "🇦🇷 Argentina", value: "AR" },
{ label: "🇨🇴 Colombia", value: "CO" }, { label: "🇨🇴 Colombia", value: "CO" },
{ label: "🇨🇱 Chile", value: "CL" } { label: "🇨🇱 Chile", value: "CL" },
]; ];
mount(() => mount(
div({ class: "p-8 max-w-md mx-auto flex flex-col gap-4" }, [ () =>
h1({ class: "text-2xl font-bold" }, "Field Components"), div({ class: "p-8 max-w-md mx-auto flex flex-col gap-4" }, [
h1({ class: "text-2xl font-bold" }, "Field Components"),
// Autocomplete simple // Autocomplete simple
ui.autocomplete({ ui.autocomplete({
label: "Fruta favorita", label: "Fruta favorita",
items: frutas, items: frutas,
value: fruta, value: fruta,
placeholder: "Buscar fruta..." placeholder: "Buscar fruta...",
}),
// Autocomplete con objetos
ui.autocomplete({
label: "País",
items: paises,
value: pais,
placeholder: "Elige un país..."
}),
// Datepicker simple
ui.datepicker({
label: "Fecha de nacimiento",
value: fecha,
placeholder: "Selecciona fecha..."
}),
// Datepicker rango
ui.datepicker({
label: "Estancia",
range: true,
value: rango,
placeholder: "Check-in → Check-out"
}),
// Colorpicker
ui.colorpicker({
label: "Color favorito",
value: color,
placeholder: "Elige un color..."
}),
ui.theme(),
// Preview
div({ class: "bg-base-200 rounded-box p-4 flex flex-col gap-2 text-sm" }, [
div({}, () => `🍎 Fruta: ${val(fruta) || "—"}`),
div({}, () => `🌍 País: ${val(pais) || "—"}`),
div({}, () => `📅 Fecha: ${val(fecha) || "—"}`),
div({}, () => {
const r = val(rango);
return r.start && r.end ? `🏨 Estancia: ${r.start}${r.end}` : "🏨 Estancia: —";
}), }),
div({ class: "flex items-center gap-2" }, [
span({}, "🎨 Color:"), // Autocomplete con objetos
div({ class: "w-6 h-6 rounded border border-base-300", style: () => `background:${val(color)}` }) ui.autocomplete({
]) label: "País",
]) items: paises,
]) value: pais,
, "#ui"); placeholder: "Elige un país...",
}),
// Datepicker simple
ui.datepicker({
label: "Fecha de nacimiento",
value: fecha,
placeholder: "Selecciona fecha...",
}),
// Datepicker rango
ui.datepicker({
label: "Estancia",
range: true,
value: rango,
placeholder: "Check-in → Check-out",
}),
// Colorpicker
ui.colorpicker({
label: "Color favorito",
value: color,
placeholder: "Elige un color...",
}),
ui.password({}),
ui.theme(),
// Preview
div(
{ class: "bg-base-200 rounded-box p-4 flex flex-col gap-2 text-sm" },
[
div({}, () => `🍎 Fruta: ${val(fruta) || "—"}`),
div({}, () => `🌍 País: ${val(pais) || "—"}`),
div({}, () => `📅 Fecha: ${val(fecha) || "—"}`),
div({}, () => {
const r = val(rango);
return r.start && r.end
? `🏨 Estancia: ${r.start}${r.end}`
: "🏨 Estancia: —";
}),
div({ class: "flex items-center gap-2" }, [
span({}, "🎨 Color:"),
div({
class: "w-6 h-6 rounded border border-base-300",
style: () => `background:${val(color)}`,
}),
]),
],
),
]),
"#ui",
);
``` ```

View File

@@ -1,26 +0,0 @@
// export const createTag = (tag, defaultProps = {}) => {
// const fn = (p, c) => {
// const props = { ...defaultProps, ...p };
// return h(tag, props, c);
// };
// return new Proxy(fn, {
// get(_, className) {
// const realClass = className.replace(/_/g, '-');
// return (p, c) => {
// const classProp = p?.class ? `${realClass} ${p.class}` : realClass;
// return fn({ ...p, class: classProp }, c);
// };
// }
// });
// };
const { h } = window.SigPro;
const htmlTags = "a abbr article aside audio b blockquote br button canvas caption cite code col colgroup datalist dd del details dfn dialog div dl dt em embed fieldset figcaption figure footer form h1 h2 h3 h4 h5 h6 header hr i iframe img input ins kbd label legend li main mark meter nav object ol optgroup option output p picture pre progress section select slot small source span strong sub summary sup svg table tbody td template textarea tfoot th thead time tr u ul video";
if (typeof window !== "undefined") {
htmlTags.split(" ").forEach(tag => {
window[tag] = (props, children) => h(tag, props, children);
});
}

View File

@@ -3,7 +3,6 @@ const { $, h, mount, val, isF, isO } = window.SigPro;
export const hide = () => document.activeElement?.blur(); export const hide = () => document.activeElement?.blur();
export const ui = { export const ui = {
_label: (p, c) => h("label", { class: "floating-label" }, [h("span", {}, p.label ?? null), c]),
accordion: (p, c) => h("div", { ...p, class: `collapse ${p.class || ''}` }, [h("input", { type: "radio", name: p.name, checked: p.checked }), c]), accordion: (p, c) => h("div", { ...p, class: `collapse ${p.class || ''}` }, [h("input", { type: "radio", name: p.name, checked: p.checked }), c]),
accordionTitle: (p, c) => h("div", { ...p, class: `collapse-title ${p.class || ''}` }, c), accordionTitle: (p, c) => h("div", { ...p, class: `collapse-title ${p.class || ''}` }, c),
accordionContent: (p, c) => h("div", { ...p, class: `collapse-content ${p.class || ''}` }, c), accordionContent: (p, c) => h("div", { ...p, class: `collapse-content ${p.class || ''}` }, c),
@@ -48,7 +47,8 @@ export const ui = {
chatBubble: (p, c) => h("div", { ...p, class: `chat-bubble ${p.class || ''}` }, c), chatBubble: (p, c) => h("div", { ...p, class: `chat-bubble ${p.class || ''}` }, c),
chatFooter: (p, c) => h("div", { ...p, class: `chat-footer ${p.class || ''}` }, c), chatFooter: (p, c) => h("div", { ...p, class: `chat-footer ${p.class || ''}` }, c),
checkbox: (p) => h("input", { ...p, type: "checkbox", class: `checkbox ${p.class || ''}` }), checkbox: (p) => h("input", { ...p, type: "checkbox", class: `checkbox ${p.class || ''}` }),
colorpicker: (p) => ui.combo({ ...p, style: ()=>`background:${val(p.value) || '#000'}`, custom: () => h("span", { colorpicker: (p) => ui.combo({
...p, custom: () => h("span", {
class: "w-4 h-4 rounded border border-base-300", class: "w-4 h-4 rounded border border-base-300",
style: `background:${val(p.value) || '#000'}` style: `background:${val(p.value) || '#000'}`
}) })
@@ -57,12 +57,12 @@ export const ui = {
), ),
combo: (p, c) => { combo: (p, c) => {
const { placeholder = "", class: cls = "" } = p; const { placeholder = "", class: cls = "" } = p;
const query = $(""); const query = isF(p.value) ? p.value : $(p.value ?? "");
let inputEl, open = $(false); let inputEl, open = $(false);
return ui._label({ label: p.label }, [ return ui.float({ label: p.label }, [
h("div", { class: `dropdown ${cls} ${val(open) ? "dropdown-open" : ""}` }, [ h("div", { class: `dropdown w-full ${cls} ${val(open) ? "dropdown-open" : ""}` }, [
h("label", { class: "input" }, [ h("label", { class: "input w-full" }, [
h("span", { class: p.icon ?? "icon-[lucide--search]" }), h("span", { class: p.icon ?? "icon-[lucide--search]" }),
p.custom ?? null, p.custom ?? null,
h("input", { h("input", {
@@ -73,7 +73,7 @@ export const ui = {
}) })
]), ]),
h("div", { h("div", {
class: "dropdown-content bg-base-100 rounded-box z-50 w-full p-2 shadow-sm", class: "dropdown-content bg-base-100 rounded-box z-50 max-w-80 shadow-sm",
onmousedown: e => e.preventDefault() onmousedown: e => e.preventDefault()
}, () => val(open) && typeof c === "function" }, () => val(open) && typeof c === "function"
? c({ query, open, close: () => { open(false); inputEl?.blur(); }, setValue: v => query(v) }) ? c({ query, open, close: () => { open(false); inputEl?.blur(); }, setValue: v => query(v) })
@@ -82,21 +82,30 @@ export const ui = {
]) ])
]); ]);
}, },
datepicker: (p) => ui.combo(p, ({ close, setValue }) => { datepicker: (p) => {
const range = p.range; const range = isF(p.range) ? p.range() : p.range;
return h("div", { class: "w-80" }, [ if (!range) return ui.combo({ value: (isF(p.value) ? p.value() : p.value) || '', ...p },
calendar({ ({ close, setValue }) => h("div", { class: "w-80" },
...p, calendar({ ...p, class: "w-full", onChange: v => { setValue(v); close(); if (isF(p.value)) p.value(v) } })
class: "w-full", )
onChange: (v) => { );
if (isF(p.value)) p.value(v); const v = $(isF(p.value) ? p.value() : p.value || { start: null, end: null });
if (!range) { setValue(v); close(); } const start = $((v() || {}).start || ''), end = $((v() || {}).end || '');
else if (v.start && v.end) { setValue(`${v.start}${v.end}`); close(); } const sync = () => { v({ start: start(), end: end() }); if (isF(p.value)) p.value(v()); };
else if (v.start) setValue(`${v.start} → ...`); const cal = (key, sig, ph, dis) => ui.combo({ value: sig, placeholder: ph, class: "flex-1", disabled: dis },
} ({ close, setValue }) => h("div", { class: "w-72" },
}) calendar({
...p, class: "w-full", value: v, range: true, onChange: r => {
v(r); start(r?.start || ''); end(r?.end || ''); setValue(r?.[key] || ''); if (r?.end) close(); if (isF(p.value)) p.value(r)
}
})
)
);
return h("div", { class: `flex gap-1 ${p.class || ''}`, onchange: sync }, [
cal('start', start, p.fromPlaceholder || "Inicio"),
cal('end', end, p.toPlaceholder || "Fin", () => !v()?.start)
]); ]);
}), },
divider: (p) => h("div", { ...p, class: `divider ${p.class || ''}` }), divider: (p) => h("div", { ...p, class: `divider ${p.class || ''}` }),
drawer: (p, c) => h("div", { ...p, class: `drawer ${p.class || ''}` }, c), drawer: (p, c) => h("div", { ...p, class: `drawer ${p.class || ''}` }, c),
drawerToggle: (p) => h("input", { ...p, type: "checkbox", class: `drawer-toggle ${p.class || ''}` }), drawerToggle: (p) => h("input", { ...p, type: "checkbox", class: `drawer-toggle ${p.class || ''}` }),
@@ -129,20 +138,16 @@ export const ui = {
) )
), ),
fileError: (p) => h("div", { class: `text-[10px] text-error mt-1 px-1 ${p.class || ''}` }, p.message), fileError: (p) => h("div", { class: `text-[10px] text-error mt-1 px-1 ${p.class || ''}` }, p.message),
float: (p, c) => h("label", { class: "floating-label" }, [h("span", {}, p.label ?? null), c]),
icon: (p) => h("span", { class: p || '' }), icon: (p) => h("span", { class: p || '' }),
indicator: (p, c) => h("div", { ...p, class: `indicator ${p.class || ''}` }, [p.value && h("span", { class: `indicator-item badge ${p.badgeClass || ''}` }, p.value), c]), indicator: (p, c) => h("div", { ...p, class: `indicator ${p.class || ''}` }, [p.value && h("span", { class: `indicator-item badge ${p.badgeClass || ''}` }, p.value), c]),
input: (p) => h("input", { ...p, class: `input ${p.class || ''}` }), input: (p) => ui.float({ label: p.label }, [
inputPass: (p) => { h("label", { class: "input w-full" }, [
const show = $(false); ui.icon(p.icon ?? ""),
return [ h("input", { ...p, class: `w-full ${p.class || ''}` }),
ui.input({ ...p, type: () => val(show) ? "text" : "password" }), p.right || null
ui.swap({ class: "ml-2 swap-rotate" }, [ ])
ui.checkbox({ checked: show }), ]),
ui.swapOn({}, ui.icon("icon-[lucide--eye]")),
ui.swapOff({}, ui.icon("icon-[lucide--eye-off]"))
])
];
},
kbd: (p, c) => h("kbd", { ...p, class: `kbd ${p.class || ''}` }, c), kbd: (p, c) => h("kbd", { ...p, class: `kbd ${p.class || ''}` }, c),
label: (p, c) => h("span", { ...p, class: `label ${p.class || ''}` }, c), label: (p, c) => h("span", { ...p, class: `label ${p.class || ''}` }, c),
loading: (p) => h("span", { ...p, class: `loading loading-spinner ${p.class || ''}` }), loading: (p) => h("span", { ...p, class: `loading loading-spinner ${p.class || ''}` }),
@@ -163,6 +168,20 @@ export const ui = {
modalAction: (p, c) => h("div", { ...p, class: `modal-action ${p.class || ''}` }, c), modalAction: (p, c) => h("div", { ...p, class: `modal-action ${p.class || ''}` }, c),
navbar: (p, c) => h("div", { ...p, class: `navbar ${p.class || ''}` }, c), navbar: (p, c) => h("div", { ...p, class: `navbar ${p.class || ''}` }, c),
option: (p, c) => h("option", { ...p }, c), option: (p, c) => h("option", { ...p }, c),
password: (p) => {
const show = $(false);
const { right, ...rest } = p;
return ui.input({
...rest,
type: () => val(show) ? "text" : "password",
icon: "icon-[lucide--lock]",
right: ui.swap({ class: "swap swap-rotate" }, [
h('input', { type: "checkbox", checked: show }),
ui.swapOn({}, ui.icon("icon-[lucide--eye]")),
ui.swapOff({}, ui.icon("icon-[lucide--eye-off]"))
])
});
},
progress: (p) => h("progress", { ...p, class: `progress ${p.class || ''}` }), progress: (p) => h("progress", { ...p, class: `progress ${p.class || ''}` }),
radial: (p) => h("div", { ...p, class: `radial-progress ${p.class || ''}`, style: `--value:${val(p.value) ?? 0}`, role: "progressbar" }, p.value ?? ""), radial: (p) => h("div", { ...p, class: `radial-progress ${p.class || ''}`, style: `--value:${val(p.value) ?? 0}`, role: "progressbar" }, p.value ?? ""),
radio: (p) => h("input", { ...p, type: "radio", class: `radio ${p.class || ''}` }), radio: (p) => h("input", { ...p, type: "radio", class: `radio ${p.class || ''}` }),
@@ -231,7 +250,7 @@ export const ui = {
export const calendar = p => { export const calendar = p => {
let [d, hv, sh, eh] = [$(new Date()), $(0), $(0), $(0)], now = new Date(), let [d, hv, sh, eh] = [$(new Date()), $(0), $(0), $(0)], now = new Date(),
F = v => v?.toISOString().slice(0, 10), F = v => v ? `${v.getFullYear()}-${String(v.getMonth() + 1).padStart(2, '0')}-${String(v.getDate()).padStart(2, '0')}` : '',
P = n => (n < 10 ? '0' : '') + n, P = n => (n < 10 ? '0' : '') + n,
M = (m, y = 0) => d(new Date(d().getFullYear() + y, d().getMonth() + m, 1)), M = (m, y = 0) => d(new Date(d().getFullYear() + y, d().getMonth() + m, 1)),
V = () => typeof p.value == 'function' ? p.value() : p.value, V = () => typeof p.value == 'function' ? p.value() : p.value,
@@ -248,7 +267,7 @@ export const calendar = p => {
h('span', { class: 'text-sm font-mono' }, () => P(v()) + ':00') h('span', { class: 'text-sm font-mono' }, () => P(v()) + ':00')
]); ]);
return h('div', { class: `p-4 bg-base-100 border shadow-2xl rounded-box w-80 select-none ${p.class || ''}` }, [ return h('div', { class: `p-4 bg-base-100 rounded-box w-80 select-none ${p.class || ''}` }, [
h('div', { class: 'flex justify-between items-center mb-4' }, [ h('div', { class: 'flex justify-between items-center mb-4' }, [
h('div', { class: 'flex' }, [['-1y', -1, 1], ['-1m', -1, 0]].map(([_, m, y]) => h('button', { class: 'btn btn-ghost btn-xs', onclick: () => M(m, y) }, h('span', { class: `icon-[lucide--chevron${y ? 's' : ''}-left]` })))), h('div', { class: 'flex' }, [['-1y', -1, 1], ['-1m', -1, 0]].map(([_, m, y]) => h('button', { class: 'btn btn-ghost btn-xs', onclick: () => M(m, y) }, h('span', { class: `icon-[lucide--chevron${y ? 's' : ''}-left]` })))),
h('span', { class: 'font-bold uppercase' }, () => d().toLocaleString('es', { month: 'short', year: 'numeric' })), h('span', { class: 'font-bold uppercase' }, () => d().toLocaleString('es', { month: 'short', year: 'numeric' })),