change components
All checks were successful
Deploy Docs to Synology / deploy (push) Successful in 3s

This commit is contained in:
2026-04-20 00:18:27 +02:00
parent 600c78510a
commit dfd358c950
12 changed files with 562 additions and 513 deletions

287
dist/sigpro-ui.esm.js vendored
View File

@@ -825,14 +825,15 @@ var exports_Datepicker = {};
__export(exports_Datepicker, {
Datepicker: () => Datepicker
});
var Datepicker = (props) => {
const { class: className, value, range, label, placeholder, hour = false, ...rest } = props;
const isOpen = B(false);
// src/components/Calendar.js
var Calendar = (props) => {
const { value, range = false, hour = false, onChange, class: className = "" } = props;
const internalDate = B(new Date);
const hoverDate = B(null);
const startHour = B(0);
const endHour = B(0);
const isRangeMode = () => val2(range) === true;
const isRangeMode = () => 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) => {
@@ -846,34 +847,142 @@ var Datepicker = (props) => {
const current = val2(value);
if (isRangeMode()) {
if (!current?.start || current.start && current.end) {
if (typeof value === "function") {
value({
start: dateStr,
end: null,
...hour && { startHour: startHour() }
});
}
const newValue = {
start: dateStr,
end: null,
...hour && { startHour: startHour() }
};
onChange?.(newValue);
} 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);
let newValue;
if (dateStr < start) {
newValue = { start: dateStr, end: start };
} else {
newValue = { start, end: dateStr };
}
isOpen(false);
if (hour) {
newValue.startHour = current.startHour !== undefined ? current.startHour : startHour();
newValue.endHour = endHour();
}
onChange?.(newValue);
}
} else {
if (typeof value === "function") {
value(hour ? `${dateStr}T${String(startHour()).padStart(2, "0")}:00:00` : dateStr);
}
isOpen(false);
const newValue = hour ? `${dateStr}T${String(startHour()).padStart(2, "0")}:00:00` : dateStr;
onChange?.(newValue);
}
};
const move = (m2) => {
const d = internalDate();
internalDate(new Date(d.getFullYear(), d.getMonth() + m2, 1));
};
const moveYear = (y) => {
const d = internalDate();
internalDate(new Date(d.getFullYear() + y, d.getMonth(), 1));
};
const HourSlider = ({ value: hVal, onChange: onHourChange }) => {
return S("div", { class: "flex-1" }, [
S("div", { class: "flex gap-2 items-center" }, [
S("input", {
type: "range",
min: 0,
max: 23,
value: hVal,
class: "range range-xs flex-1",
oninput: (e) => {
const newHour = parseInt(e.target.value);
onHourChange(newHour);
}
}),
S("span", { class: "text-sm font-mono min-w-[48px] text-center" }, () => String(val2(hVal)).padStart(2, "0") + ":00")
])
]);
};
return S("div", { class: `p-4 bg-base-100 border border-base-300 shadow-2xl rounded-box w-80 select-none ${className}` }, [
S("div", { class: "flex justify-between items-center mb-4 gap-1" }, [
S("div", { class: "flex gap-0.5" }, [
S("button", { type: "button", class: "btn btn-ghost btn-xs px-1", onclick: () => moveYear(-1) }, getIcon("icon-[lucide--chevrons-left]")),
S("button", { type: "button", class: "btn btn-ghost btn-xs px-1", onclick: () => move(-1) }, getIcon("icon-[lucide--chevron-left]"))
]),
S("span", { class: "font-bold uppercase flex-1 text-center" }, [
() => internalDate().toLocaleString("es-ES", { month: "short", year: "numeric" })
]),
S("div", { class: "flex gap-0.5" }, [
S("button", { type: "button", class: "btn btn-ghost btn-xs px-1", onclick: () => move(1) }, getIcon("icon-[lucide--chevron-right]")),
S("button", { type: "button", class: "btn btn-ghost btn-xs px-1", onclick: () => moveYear(1) }, getIcon("icon-[lucide--chevrons-right]"))
])
]),
S("div", { class: "grid grid-cols-7 gap-1", onmouseleave: () => hoverDate(null) }, [
...["L", "M", "X", "J", "V", "S", "D"].map((d) => S("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(S("div"));
for (let i = 1;i <= daysInMonth; i++) {
const date = new Date(year, month, i);
const dStr = formatDate(date);
nodes.push(S("button", {
type: "button",
class: () => {
const v2 = val2(value);
const h = hoverDate();
const isStart = typeof v2 === "string" ? v2.split("T")[0] === dStr : v2?.start === dStr;
const isEnd = v2?.end === dStr;
let inRange = false;
if (isRangeMode() && v2?.start) {
const start = v2.start;
if (!v2.end && h) {
inRange = dStr > start && dStr <= h || dStr < start && dStr >= h;
} else if (v2.end) {
inRange = dStr > start && dStr < v2.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 ? S("div", { class: "mt-3 pt-2 border-t border-base-300" }, [
isRangeMode() ? S("div", { class: "flex gap-4" }, [
HourSlider({
value: startHour,
onChange: (newHour) => startHour(newHour)
}),
HourSlider({
value: endHour,
onChange: (newHour) => endHour(newHour)
})
]) : HourSlider({
value: startHour,
onChange: (newHour) => startHour(newHour)
})
]) : null
]);
};
// src/components/Datepicker.js
var Datepicker = (props) => {
const { class: className, value, range, label, placeholder, hour = false, ...rest } = props;
const isOpen = B(false);
const isRangeMode = () => val2(range) === true;
const displayValue = B("");
Watch(() => {
R(() => {
const v2 = val2(value);
if (!v2) {
displayValue("");
@@ -892,31 +1001,13 @@ var Datepicker = (props) => {
}
displayValue(text);
});
const move = (m2) => {
const d = internalDate();
internalDate(new Date(d.getFullYear(), d.getMonth() + m2, 1));
};
const moveYear = (y) => {
const d = internalDate();
internalDate(new Date(d.getFullYear() + y, d.getMonth(), 1));
};
const HourSlider = ({ value: hVal, onChange }) => {
return S("div", { class: "flex-1" }, [
S("div", { class: "flex gap-2 items-center" }, [
S("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);
}
}),
S("span", { class: "text-sm font-mono min-w-[48px] text-center" }, () => String(val2(hVal)).padStart(2, "0") + ":00")
])
]);
const handleCalendarChange = (newValue) => {
if (typeof value === "function") {
value(newValue);
}
if (!isRangeMode() || newValue?.end !== undefined && newValue?.end !== null) {
isOpen(false);
}
};
return S("div", { class: ui("relative w-full", className) }, [
Input({
@@ -932,99 +1023,15 @@ var Datepicker = (props) => {
...rest
}),
J(isOpen, () => S("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",
class: "absolute left-0 mt-2 z-[100]",
onclick: (e) => e.stopPropagation()
}, [
S("div", { class: "flex justify-between items-center mb-4 gap-1" }, [
S("div", { class: "flex gap-0.5" }, [
S("button", { type: "button", class: "btn btn-ghost btn-xs px-1", onclick: () => moveYear(-1) }, getIcon("icon-[lucide--chevrons-left]")),
S("button", { type: "button", class: "btn btn-ghost btn-xs px-1", onclick: () => move(-1) }, getIcon("icon-[lucide--chevron-left]"))
]),
S("span", { class: "font-bold uppercase flex-1 text-center" }, [
() => internalDate().toLocaleString("es-ES", { month: "short", year: "numeric" })
]),
S("div", { class: "flex gap-0.5" }, [
S("button", { type: "button", class: "btn btn-ghost btn-xs px-1", onclick: () => move(1) }, getIcon("icon-[lucide--chevron-right]")),
S("button", { type: "button", class: "btn btn-ghost btn-xs px-1", onclick: () => moveYear(1) }, getIcon("icon-[lucide--chevrons-right]"))
])
]),
S("div", { class: "grid grid-cols-7 gap-1", onmouseleave: () => hoverDate(null) }, [
...["L", "M", "X", "J", "V", "S", "D"].map((d) => S("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(S("div"));
for (let i = 1;i <= daysInMonth; i++) {
const date = new Date(year, month, i);
const dStr = formatDate(date);
nodes.push(S("button", {
type: "button",
class: () => {
const v2 = val2(value);
const h = hoverDate();
const isStart = typeof v2 === "string" ? v2.split("T")[0] === dStr : v2?.start === dStr;
const isEnd = v2?.end === dStr;
let inRange = false;
if (isRangeMode() && v2?.start) {
const start = v2.start;
if (!v2.end && h) {
inRange = dStr > start && dStr <= h || dStr < start && dStr >= h;
} else if (v2.end) {
inRange = dStr > start && dStr < v2.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 ? S("div", { class: "mt-3 pt-2 border-t border-base-300" }, [
isRangeMode() ? S("div", { class: "flex gap-4" }, [
HourSlider({
value: startHour,
onChange: (newHour) => {
startHour(newHour);
const currentVal = val2(value);
if (currentVal?.start)
value({ ...currentVal, startHour: newHour });
}
}),
HourSlider({
value: endHour,
onChange: (newHour) => {
endHour(newHour);
const currentVal = val2(value);
if (currentVal?.end)
value({ ...currentVal, endHour: newHour });
}
})
]) : HourSlider({
value: startHour,
onChange: (newHour) => {
startHour(newHour);
const currentVal = val2(value);
if (currentVal && typeof currentVal === "string") {
value(currentVal.split("T")[0] + "T" + String(newHour).padStart(2, "0") + ":00:00");
}
}
})
]) : null
Calendar({
value,
range: isRangeMode(),
hour,
onChange: handleCalendarChange
})
])),
J(isOpen, () => S("div", { class: "fixed inset-0 z-[90]", onclick: () => isOpen(false) }))
]);

File diff suppressed because one or more lines are too long

287
dist/sigpro-ui.js vendored
View File

@@ -856,14 +856,15 @@
__export(exports_Datepicker, {
Datepicker: () => Datepicker
});
var Datepicker = (props) => {
const { class: className, value, range, label, placeholder, hour = false, ...rest } = props;
const isOpen = B(false);
// src/components/Calendar.js
var Calendar = (props) => {
const { value, range = false, hour = false, onChange, class: className = "" } = props;
const internalDate = B(new Date);
const hoverDate = B(null);
const startHour = B(0);
const endHour = B(0);
const isRangeMode = () => val2(range) === true;
const isRangeMode = () => 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) => {
@@ -877,34 +878,142 @@
const current = val2(value);
if (isRangeMode()) {
if (!current?.start || current.start && current.end) {
if (typeof value === "function") {
value({
start: dateStr,
end: null,
...hour && { startHour: startHour() }
});
}
const newValue = {
start: dateStr,
end: null,
...hour && { startHour: startHour() }
};
onChange?.(newValue);
} 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);
let newValue;
if (dateStr < start) {
newValue = { start: dateStr, end: start };
} else {
newValue = { start, end: dateStr };
}
isOpen(false);
if (hour) {
newValue.startHour = current.startHour !== undefined ? current.startHour : startHour();
newValue.endHour = endHour();
}
onChange?.(newValue);
}
} else {
if (typeof value === "function") {
value(hour ? `${dateStr}T${String(startHour()).padStart(2, "0")}:00:00` : dateStr);
}
isOpen(false);
const newValue = hour ? `${dateStr}T${String(startHour()).padStart(2, "0")}:00:00` : dateStr;
onChange?.(newValue);
}
};
const move = (m2) => {
const d = internalDate();
internalDate(new Date(d.getFullYear(), d.getMonth() + m2, 1));
};
const moveYear = (y) => {
const d = internalDate();
internalDate(new Date(d.getFullYear() + y, d.getMonth(), 1));
};
const HourSlider = ({ value: hVal, onChange: onHourChange }) => {
return S("div", { class: "flex-1" }, [
S("div", { class: "flex gap-2 items-center" }, [
S("input", {
type: "range",
min: 0,
max: 23,
value: hVal,
class: "range range-xs flex-1",
oninput: (e) => {
const newHour = parseInt(e.target.value);
onHourChange(newHour);
}
}),
S("span", { class: "text-sm font-mono min-w-[48px] text-center" }, () => String(val2(hVal)).padStart(2, "0") + ":00")
])
]);
};
return S("div", { class: `p-4 bg-base-100 border border-base-300 shadow-2xl rounded-box w-80 select-none ${className}` }, [
S("div", { class: "flex justify-between items-center mb-4 gap-1" }, [
S("div", { class: "flex gap-0.5" }, [
S("button", { type: "button", class: "btn btn-ghost btn-xs px-1", onclick: () => moveYear(-1) }, getIcon("icon-[lucide--chevrons-left]")),
S("button", { type: "button", class: "btn btn-ghost btn-xs px-1", onclick: () => move(-1) }, getIcon("icon-[lucide--chevron-left]"))
]),
S("span", { class: "font-bold uppercase flex-1 text-center" }, [
() => internalDate().toLocaleString("es-ES", { month: "short", year: "numeric" })
]),
S("div", { class: "flex gap-0.5" }, [
S("button", { type: "button", class: "btn btn-ghost btn-xs px-1", onclick: () => move(1) }, getIcon("icon-[lucide--chevron-right]")),
S("button", { type: "button", class: "btn btn-ghost btn-xs px-1", onclick: () => moveYear(1) }, getIcon("icon-[lucide--chevrons-right]"))
])
]),
S("div", { class: "grid grid-cols-7 gap-1", onmouseleave: () => hoverDate(null) }, [
...["L", "M", "X", "J", "V", "S", "D"].map((d) => S("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(S("div"));
for (let i = 1;i <= daysInMonth; i++) {
const date = new Date(year, month, i);
const dStr = formatDate(date);
nodes.push(S("button", {
type: "button",
class: () => {
const v2 = val2(value);
const h = hoverDate();
const isStart = typeof v2 === "string" ? v2.split("T")[0] === dStr : v2?.start === dStr;
const isEnd = v2?.end === dStr;
let inRange = false;
if (isRangeMode() && v2?.start) {
const start = v2.start;
if (!v2.end && h) {
inRange = dStr > start && dStr <= h || dStr < start && dStr >= h;
} else if (v2.end) {
inRange = dStr > start && dStr < v2.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 ? S("div", { class: "mt-3 pt-2 border-t border-base-300" }, [
isRangeMode() ? S("div", { class: "flex gap-4" }, [
HourSlider({
value: startHour,
onChange: (newHour) => startHour(newHour)
}),
HourSlider({
value: endHour,
onChange: (newHour) => endHour(newHour)
})
]) : HourSlider({
value: startHour,
onChange: (newHour) => startHour(newHour)
})
]) : null
]);
};
// src/components/Datepicker.js
var Datepicker = (props) => {
const { class: className, value, range, label, placeholder, hour = false, ...rest } = props;
const isOpen = B(false);
const isRangeMode = () => val2(range) === true;
const displayValue = B("");
Watch(() => {
R(() => {
const v2 = val2(value);
if (!v2) {
displayValue("");
@@ -923,31 +1032,13 @@
}
displayValue(text);
});
const move = (m2) => {
const d = internalDate();
internalDate(new Date(d.getFullYear(), d.getMonth() + m2, 1));
};
const moveYear = (y) => {
const d = internalDate();
internalDate(new Date(d.getFullYear() + y, d.getMonth(), 1));
};
const HourSlider = ({ value: hVal, onChange }) => {
return S("div", { class: "flex-1" }, [
S("div", { class: "flex gap-2 items-center" }, [
S("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);
}
}),
S("span", { class: "text-sm font-mono min-w-[48px] text-center" }, () => String(val2(hVal)).padStart(2, "0") + ":00")
])
]);
const handleCalendarChange = (newValue) => {
if (typeof value === "function") {
value(newValue);
}
if (!isRangeMode() || newValue?.end !== undefined && newValue?.end !== null) {
isOpen(false);
}
};
return S("div", { class: ui("relative w-full", className) }, [
Input({
@@ -963,99 +1054,15 @@
...rest
}),
J(isOpen, () => S("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",
class: "absolute left-0 mt-2 z-[100]",
onclick: (e) => e.stopPropagation()
}, [
S("div", { class: "flex justify-between items-center mb-4 gap-1" }, [
S("div", { class: "flex gap-0.5" }, [
S("button", { type: "button", class: "btn btn-ghost btn-xs px-1", onclick: () => moveYear(-1) }, getIcon("icon-[lucide--chevrons-left]")),
S("button", { type: "button", class: "btn btn-ghost btn-xs px-1", onclick: () => move(-1) }, getIcon("icon-[lucide--chevron-left]"))
]),
S("span", { class: "font-bold uppercase flex-1 text-center" }, [
() => internalDate().toLocaleString("es-ES", { month: "short", year: "numeric" })
]),
S("div", { class: "flex gap-0.5" }, [
S("button", { type: "button", class: "btn btn-ghost btn-xs px-1", onclick: () => move(1) }, getIcon("icon-[lucide--chevron-right]")),
S("button", { type: "button", class: "btn btn-ghost btn-xs px-1", onclick: () => moveYear(1) }, getIcon("icon-[lucide--chevrons-right]"))
])
]),
S("div", { class: "grid grid-cols-7 gap-1", onmouseleave: () => hoverDate(null) }, [
...["L", "M", "X", "J", "V", "S", "D"].map((d) => S("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(S("div"));
for (let i = 1;i <= daysInMonth; i++) {
const date = new Date(year, month, i);
const dStr = formatDate(date);
nodes.push(S("button", {
type: "button",
class: () => {
const v2 = val2(value);
const h = hoverDate();
const isStart = typeof v2 === "string" ? v2.split("T")[0] === dStr : v2?.start === dStr;
const isEnd = v2?.end === dStr;
let inRange = false;
if (isRangeMode() && v2?.start) {
const start = v2.start;
if (!v2.end && h) {
inRange = dStr > start && dStr <= h || dStr < start && dStr >= h;
} else if (v2.end) {
inRange = dStr > start && dStr < v2.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 ? S("div", { class: "mt-3 pt-2 border-t border-base-300" }, [
isRangeMode() ? S("div", { class: "flex gap-4" }, [
HourSlider({
value: startHour,
onChange: (newHour) => {
startHour(newHour);
const currentVal = val2(value);
if (currentVal?.start)
value({ ...currentVal, startHour: newHour });
}
}),
HourSlider({
value: endHour,
onChange: (newHour) => {
endHour(newHour);
const currentVal = val2(value);
if (currentVal?.end)
value({ ...currentVal, endHour: newHour });
}
})
]) : HourSlider({
value: startHour,
onChange: (newHour) => {
startHour(newHour);
const currentVal = val2(value);
if (currentVal && typeof currentVal === "string") {
value(currentVal.split("T")[0] + "T" + String(newHour).padStart(2, "0") + ":00:00");
}
}
})
]) : null
Calendar({
value,
range: isRangeMode(),
hour,
onChange: handleCalendarChange
})
])),
J(isOpen, () => S("div", { class: "fixed inset-0 z-[90]", onclick: () => isOpen(false) }))
]);

File diff suppressed because one or more lines are too long

3
dist/sigpro.css vendored
View File

@@ -3339,9 +3339,6 @@
.z-50 {
z-index: 50;
}
.z-90 {
z-index: 90;
}
.z-\[1\] {
z-index: 1;
}

2
dist/sigpro.min.css 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

181
src/components/Calendar.js Normal file
View File

@@ -0,0 +1,181 @@
import { $, Tag, Watch } from "sigpro";
import { val, getIcon } from "../utils.js";
export const Calendar = (props) => {
const { value, range = false, hour = false, onChange, class: className = "" } = props;
const internalDate = $(new Date());
const hoverDate = $(null);
const startHour = $(0);
const endHour = $(0);
const isRangeMode = () => 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)) {
// primera selección
const newValue = {
start: dateStr,
end: null,
...(hour && { startHour: startHour() }),
};
onChange?.(newValue);
} else {
// segunda selección
const start = current.start;
let newValue;
if (dateStr < start) {
newValue = { start: dateStr, end: start };
} else {
newValue = { start, end: dateStr };
}
if (hour) {
newValue.startHour = current.startHour !== undefined ? current.startHour : startHour();
newValue.endHour = endHour();
}
onChange?.(newValue);
}
} else {
// modo fecha simple
const newValue = hour ? `${dateStr}T${String(startHour()).padStart(2, "0")}:00:00` : dateStr;
onChange?.(newValue);
}
};
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: onHourChange }) => {
return Tag("div", { class: "flex-1" }, [
Tag("div", { class: "flex gap-2 items-center" }, [
Tag("input", {
type: "range",
min: 0,
max: 23,
value: hVal,
class: "range range-xs flex-1",
oninput: (e) => {
const newHour = parseInt(e.target.value);
onHourChange(newHour);
},
}),
Tag("span", { class: "text-sm font-mono min-w-[48px] text-center" },
() => String(val(hVal)).padStart(2, "0") + ":00"
),
]),
]);
};
return Tag("div", { class: `p-4 bg-base-100 border border-base-300 shadow-2xl rounded-box w-80 select-none ${className}` }, [
Tag("div", { class: "flex justify-between items-center mb-4 gap-1" }, [
Tag("div", { class: "flex gap-0.5" }, [
Tag("button", { type: "button", class: "btn btn-ghost btn-xs px-1", onclick: () => moveYear(-1) },
getIcon("icon-[lucide--chevrons-left]")
),
Tag("button", { type: "button", class: "btn btn-ghost btn-xs px-1", onclick: () => move(-1) },
getIcon("icon-[lucide--chevron-left]")
),
]),
Tag("span", { class: "font-bold uppercase flex-1 text-center" }, [
() => internalDate().toLocaleString("es-ES", { month: "short", year: "numeric" }),
]),
Tag("div", { class: "flex gap-0.5" }, [
Tag("button", { type: "button", class: "btn btn-ghost btn-xs px-1", onclick: () => move(1) },
getIcon("icon-[lucide--chevron-right]")
),
Tag("button", { type: "button", class: "btn btn-ghost btn-xs px-1", onclick: () => moveYear(1) },
getIcon("icon-[lucide--chevrons-right]")
),
]),
]),
Tag("div", { class: "grid grid-cols-7 gap-1", onmouseleave: () => hoverDate(null) }, [
...["L", "M", "X", "J", "V", "S", "D"].map((d) => Tag("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(Tag("div"));
for (let i = 1; i <= daysInMonth; i++) {
const date = new Date(year, month, i);
const dStr = formatDate(date);
nodes.push(
Tag("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 ? Tag("div", { class: "mt-3 pt-2 border-t border-base-300" }, [
isRangeMode()
? Tag("div", { class: "flex gap-4" }, [
HourSlider({
value: startHour,
onChange: (newHour) => startHour(newHour),
}),
HourSlider({
value: endHour,
onChange: (newHour) => endHour(newHour),
}),
])
: HourSlider({
value: startHour,
onChange: (newHour) => startHour(newHour),
}),
]) : null,
]);
};

View File

@@ -1,83 +1,23 @@
// components/Datepicker.js
import { $, Tag, If } from "sigpro";
import { $, Tag, If, Watch } from "sigpro";
import { val, ui, getIcon } from "../utils.js";
import { Input } from "./Input.js";
import { Calendar } from "./Calendar.js";
/**
* Datepicker component
*
* daisyUI classes used:
* - input, input-bordered, input-primary
* - btn, btn-ghost, btn-xs, btn-circle
* - bg-base-100, border, border-base-300, shadow-2xl, rounded-box
* - absolute, left-0, mt-2, p-4, w-80, z-100, z-90
* - grid, grid-cols-7, gap-1, text-center
* - ring, ring-primary, ring-inset, font-black
* - range, range-xs
* - tooltip, tooltip-top, tooltip-bottom
*/
export const 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);
}
};
// Formatear el valor para mostrarlo en el input
const displayValue = $("");
Watch(() => {
const v = val(value);
if (!v) {
displayValue("");
return;
}
let text = "";
if (typeof v === "string") {
text = (hour && v.includes("T")) ? v.replace("T", " ") : v;
@@ -91,38 +31,17 @@ export const Datepicker = (props) => {
}
displayValue(text);
});
const move = (m) => {
const d = internalDate();
internalDate(new Date(d.getFullYear(), d.getMonth() + m, 1));
const handleCalendarChange = (newValue) => {
if (typeof value === "function") {
value(newValue);
}
// Cerrar el popover si es modo simple o si el rango está completo (end existe)
if (!isRangeMode() || (newValue?.end !== undefined && newValue?.end !== null)) {
isOpen(false);
}
};
const moveYear = (y) => {
const d = internalDate();
internalDate(new Date(d.getFullYear() + y, d.getMonth(), 1));
};
const HourSlider = ({ value: hVal, onChange }) => {
return Tag("div", { class: "flex-1" }, [
Tag("div", { class: "flex gap-2 items-center" }, [
Tag("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);
},
}),
Tag("span", { class: "text-sm font-mono min-w-[48px] text-center" },
() => String(val(hVal)).padStart(2, "0") + ":00"
),
]),
]);
};
return Tag("div", { class: ui('relative w-full', className) }, [
Input({
label,
@@ -136,127 +55,21 @@ export const Datepicker = (props) => {
},
...rest,
}),
If(isOpen, () =>
Tag(
"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(),
},
[
Tag("div", { class: "flex justify-between items-center mb-4 gap-1" }, [
Tag("div", { class: "flex gap-0.5" }, [
Tag("button", { type: "button", class: "btn btn-ghost btn-xs px-1", onclick: () => moveYear(-1) },
getIcon("icon-[lucide--chevrons-left]")
),
Tag("button", { type: "button", class: "btn btn-ghost btn-xs px-1", onclick: () => move(-1) },
getIcon("icon-[lucide--chevron-left]")
),
]),
Tag("span", { class: "font-bold uppercase flex-1 text-center" }, [
() => internalDate().toLocaleString("es-ES", { month: "short", year: "numeric" }),
]),
Tag("div", { class: "flex gap-0.5" }, [
Tag("button", { type: "button", class: "btn btn-ghost btn-xs px-1", onclick: () => move(1) },
getIcon("icon-[lucide--chevron-right]")
),
Tag("button", { type: "button", class: "btn btn-ghost btn-xs px-1", onclick: () => moveYear(1) },
getIcon("icon-[lucide--chevrons-right]")
),
]),
]),
Tag("div", { class: "grid grid-cols-7 gap-1", onmouseleave: () => hoverDate(null) }, [
...["L", "M", "X", "J", "V", "S", "D"].map((d) => Tag("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(Tag("div"));
for (let i = 1; i <= daysInMonth; i++) {
const date = new Date(year, month, i);
const dStr = formatDate(date);
nodes.push(
Tag(
"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 ? Tag("div", { class: "mt-3 pt-2 border-t border-base-300" }, [
isRangeMode()
? Tag("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") {
value(currentVal.split("T")[0] + "T" + String(newHour).padStart(2, "0") + ":00:00");
}
},
}),
]) : null,
],
),
Tag("div", {
class: "absolute left-0 mt-2 z-[100]",
onclick: (e) => e.stopPropagation(),
}, [
Calendar({
value,
range: isRangeMode(),
hour,
onChange: handleCalendarChange,
}),
])
),
If(isOpen, () => Tag("div", { class: "fixed inset-0 z-[90]", onclick: () => isOpen(false) })),
]);
};

View File

@@ -0,0 +1,44 @@
// components/InputPopover.js
export const InputPopover = (props) => {
const {
signal,
format = (v) => String(v ?? ""),
content,
placeholder,
label,
icon = "icon-[lucide--calendar]",
readonly = false,
class: className = "",
...rest
} = props;
const isOpen = $(false);
const displayValue = $(format(val(signal)));
Watch(signal, () => {
displayValue(format(val(signal)));
});
const close = () => isOpen(false);
const toggle = () => isOpen(!isOpen());
return Tag("div", { class: ui("relative w-full", className) }, [
Input({
label,
placeholder,
value: displayValue,
readonly,
icon: getIcon(icon),
onclick: (e) => { e.stopPropagation(); toggle(); },
onfocus: () => isOpen(true),
...rest,
}),
If(isOpen, () => Tag("div", {
class: "absolute left-0 mt-2 z-[100]",
onclick: (e) => e.stopPropagation(),
}, [
typeof content === "function" ? content({ signal, close }) : content
])),
If(isOpen, () => Tag("div", { class: "fixed inset-0 z-[90]", onclick: close }))
]);
};