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

161
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,52 +847,31 @@ var Datepicker = (props) => {
const current = val2(value);
if (isRangeMode()) {
if (!current?.start || current.start && current.end) {
if (typeof value === "function") {
value({
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 };
let newValue;
if (dateStr < start) {
newValue = { start: dateStr, end: start };
} else {
newValue = { start, end: dateStr };
}
if (hour) {
newValue.startHour = current.startHour || startHour();
newValue.endHour = current.endHour || endHour();
newValue.startHour = current.startHour !== undefined ? current.startHour : startHour();
newValue.endHour = endHour();
}
value(newValue);
}
isOpen(false);
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 displayValue = B("");
Watch(() => {
const v2 = val2(value);
if (!v2) {
displayValue("");
return;
}
let text = "";
if (typeof v2 === "string") {
text = hour && v2.includes("T") ? v2.replace("T", " ") : v2;
} else if (v2.start && v2.end) {
const startStr = hour && v2.startHour !== undefined ? `${v2.start} ${String(v2.startHour).padStart(2, "0")}:00` : v2.start;
const endStr = hour && v2.endHour !== undefined ? `${v2.end} ${String(v2.endHour).padStart(2, "0")}:00` : v2.end;
text = `${startStr} - ${endStr}`;
} else if (v2.start) {
const startStr = hour && v2.startHour !== undefined ? `${v2.start} ${String(v2.startHour).padStart(2, "0")}:00` : v2.start;
text = `${startStr}...`;
}
displayValue(text);
});
const move = (m2) => {
const d = internalDate();
internalDate(new Date(d.getFullYear(), d.getMonth() + m2, 1));
@@ -900,7 +880,7 @@ var Datepicker = (props) => {
const d = internalDate();
internalDate(new Date(d.getFullYear() + y, d.getMonth(), 1));
};
const HourSlider = ({ value: hVal, onChange }) => {
const HourSlider = ({ value: hVal, onChange: onHourChange }) => {
return S("div", { class: "flex-1" }, [
S("div", { class: "flex gap-2 items-center" }, [
S("input", {
@@ -911,30 +891,14 @@ var Datepicker = (props) => {
class: "range range-xs flex-1",
oninput: (e) => {
const newHour = parseInt(e.target.value);
onChange(newHour);
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: 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
}),
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",
onclick: (e) => e.stopPropagation()
}, [
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]")),
@@ -998,33 +962,76 @@ var Datepicker = (props) => {
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 });
}
onChange: (newHour) => startHour(newHour)
}),
HourSlider({
value: endHour,
onChange: (newHour) => {
endHour(newHour);
const currentVal = val2(value);
if (currentVal?.end)
value({ ...currentVal, endHour: newHour });
}
onChange: (newHour) => 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");
}
}
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("");
R(() => {
const v2 = val2(value);
if (!v2) {
displayValue("");
return;
}
let text = "";
if (typeof v2 === "string") {
text = hour && v2.includes("T") ? v2.replace("T", " ") : v2;
} else if (v2.start && v2.end) {
const startStr = hour && v2.startHour !== undefined ? `${v2.start} ${String(v2.startHour).padStart(2, "0")}:00` : v2.start;
const endStr = hour && v2.endHour !== undefined ? `${v2.end} ${String(v2.endHour).padStart(2, "0")}:00` : v2.end;
text = `${startStr} - ${endStr}`;
} else if (v2.start) {
const startStr = hour && v2.startHour !== undefined ? `${v2.start} ${String(v2.startHour).padStart(2, "0")}:00` : v2.start;
text = `${startStr}...`;
}
displayValue(text);
});
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({
label,
placeholder: placeholder || (isRangeMode() ? "Seleccionar rango..." : "Seleccionar fecha..."),
value: displayValue,
readonly: true,
icon: getIcon("icon-[lucide--calendar]"),
onclick: (e) => {
e.stopPropagation();
isOpen(!isOpen());
},
...rest
}),
J(isOpen, () => S("div", {
class: "absolute left-0 mt-2 z-[100]",
onclick: (e) => e.stopPropagation()
}, [
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

161
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,52 +878,31 @@
const current = val2(value);
if (isRangeMode()) {
if (!current?.start || current.start && current.end) {
if (typeof value === "function") {
value({
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 };
let newValue;
if (dateStr < start) {
newValue = { start: dateStr, end: start };
} else {
newValue = { start, end: dateStr };
}
if (hour) {
newValue.startHour = current.startHour || startHour();
newValue.endHour = current.endHour || endHour();
newValue.startHour = current.startHour !== undefined ? current.startHour : startHour();
newValue.endHour = endHour();
}
value(newValue);
}
isOpen(false);
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 displayValue = B("");
Watch(() => {
const v2 = val2(value);
if (!v2) {
displayValue("");
return;
}
let text = "";
if (typeof v2 === "string") {
text = hour && v2.includes("T") ? v2.replace("T", " ") : v2;
} else if (v2.start && v2.end) {
const startStr = hour && v2.startHour !== undefined ? `${v2.start} ${String(v2.startHour).padStart(2, "0")}:00` : v2.start;
const endStr = hour && v2.endHour !== undefined ? `${v2.end} ${String(v2.endHour).padStart(2, "0")}:00` : v2.end;
text = `${startStr} - ${endStr}`;
} else if (v2.start) {
const startStr = hour && v2.startHour !== undefined ? `${v2.start} ${String(v2.startHour).padStart(2, "0")}:00` : v2.start;
text = `${startStr}...`;
}
displayValue(text);
});
const move = (m2) => {
const d = internalDate();
internalDate(new Date(d.getFullYear(), d.getMonth() + m2, 1));
@@ -931,7 +911,7 @@
const d = internalDate();
internalDate(new Date(d.getFullYear() + y, d.getMonth(), 1));
};
const HourSlider = ({ value: hVal, onChange }) => {
const HourSlider = ({ value: hVal, onChange: onHourChange }) => {
return S("div", { class: "flex-1" }, [
S("div", { class: "flex gap-2 items-center" }, [
S("input", {
@@ -942,30 +922,14 @@
class: "range range-xs flex-1",
oninput: (e) => {
const newHour = parseInt(e.target.value);
onChange(newHour);
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: 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
}),
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",
onclick: (e) => e.stopPropagation()
}, [
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]")),
@@ -1029,33 +993,76 @@
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 });
}
onChange: (newHour) => startHour(newHour)
}),
HourSlider({
value: endHour,
onChange: (newHour) => {
endHour(newHour);
const currentVal = val2(value);
if (currentVal?.end)
value({ ...currentVal, endHour: newHour });
}
onChange: (newHour) => 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");
}
}
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("");
R(() => {
const v2 = val2(value);
if (!v2) {
displayValue("");
return;
}
let text = "";
if (typeof v2 === "string") {
text = hour && v2.includes("T") ? v2.replace("T", " ") : v2;
} else if (v2.start && v2.end) {
const startStr = hour && v2.startHour !== undefined ? `${v2.start} ${String(v2.startHour).padStart(2, "0")}:00` : v2.start;
const endStr = hour && v2.endHour !== undefined ? `${v2.end} ${String(v2.endHour).padStart(2, "0")}:00` : v2.end;
text = `${startStr} - ${endStr}`;
} else if (v2.start) {
const startStr = hour && v2.startHour !== undefined ? `${v2.start} ${String(v2.startHour).padStart(2, "0")}:00` : v2.start;
text = `${startStr}...`;
}
displayValue(text);
});
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({
label,
placeholder: placeholder || (isRangeMode() ? "Seleccionar rango..." : "Seleccionar fecha..."),
value: displayValue,
readonly: true,
icon: getIcon("icon-[lucide--calendar]"),
onclick: (e) => {
e.stopPropagation();
isOpen(!isOpen());
},
...rest
}),
J(isOpen, () => S("div", {
class: "absolute left-0 mt-2 z-[100]",
onclick: (e) => e.stopPropagation()
}, [
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,74 +1,15 @@
// 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(() => {
@@ -77,7 +18,6 @@ export const Datepicker = (props) => {
displayValue("");
return;
}
let text = "";
if (typeof v === "string") {
text = (hour && v.includes("T")) ? v.replace("T", " ") : v;
@@ -92,35 +32,14 @@ export const Datepicker = (props) => {
displayValue(text);
});
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 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"
),
]),
]);
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);
}
};
return Tag("div", { class: ui('relative w-full', className) }, [
@@ -138,123 +57,17 @@ export const Datepicker = (props) => {
}),
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",
Tag("div", {
class: "absolute left-0 mt-2 z-[100]",
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 });
},
}, [
Calendar({
value,
range: isRangeMode(),
hour,
onChange: handleCalendarChange,
}),
])
: 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,
],
),
),
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 }))
]);
};