change components
All checks were successful
Deploy Docs to Synology / deploy (push) Successful in 3s
All checks were successful
Deploy Docs to Synology / deploy (push) Successful in 3s
This commit is contained in:
287
dist/sigpro-ui.esm.js
vendored
287
dist/sigpro-ui.esm.js
vendored
@@ -825,14 +825,15 @@ var exports_Datepicker = {};
|
|||||||
__export(exports_Datepicker, {
|
__export(exports_Datepicker, {
|
||||||
Datepicker: () => Datepicker
|
Datepicker: () => Datepicker
|
||||||
});
|
});
|
||||||
var Datepicker = (props) => {
|
|
||||||
const { class: className, value, range, label, placeholder, hour = false, ...rest } = props;
|
// src/components/Calendar.js
|
||||||
const isOpen = B(false);
|
var Calendar = (props) => {
|
||||||
|
const { value, range = false, hour = false, onChange, class: className = "" } = props;
|
||||||
const internalDate = B(new Date);
|
const internalDate = B(new Date);
|
||||||
const hoverDate = B(null);
|
const hoverDate = B(null);
|
||||||
const startHour = B(0);
|
const startHour = B(0);
|
||||||
const endHour = B(0);
|
const endHour = B(0);
|
||||||
const isRangeMode = () => val2(range) === true;
|
const isRangeMode = () => range === true;
|
||||||
const now = new Date;
|
const now = new Date;
|
||||||
const todayStr = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}-${String(now.getDate()).padStart(2, "0")}`;
|
const todayStr = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}-${String(now.getDate()).padStart(2, "0")}`;
|
||||||
const formatDate = (d) => {
|
const formatDate = (d) => {
|
||||||
@@ -846,34 +847,142 @@ var Datepicker = (props) => {
|
|||||||
const current = val2(value);
|
const current = val2(value);
|
||||||
if (isRangeMode()) {
|
if (isRangeMode()) {
|
||||||
if (!current?.start || current.start && current.end) {
|
if (!current?.start || current.start && current.end) {
|
||||||
if (typeof value === "function") {
|
const newValue = {
|
||||||
value({
|
start: dateStr,
|
||||||
start: dateStr,
|
end: null,
|
||||||
end: null,
|
...hour && { startHour: startHour() }
|
||||||
...hour && { startHour: startHour() }
|
};
|
||||||
});
|
onChange?.(newValue);
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
const start = current.start;
|
const start = current.start;
|
||||||
if (typeof value === "function") {
|
let newValue;
|
||||||
const newValue = dateStr < start ? { start: dateStr, end: start } : { start, end: dateStr };
|
if (dateStr < start) {
|
||||||
if (hour) {
|
newValue = { start: dateStr, end: start };
|
||||||
newValue.startHour = current.startHour || startHour();
|
} else {
|
||||||
newValue.endHour = current.endHour || endHour();
|
newValue = { start, end: dateStr };
|
||||||
}
|
|
||||||
value(newValue);
|
|
||||||
}
|
}
|
||||||
isOpen(false);
|
if (hour) {
|
||||||
|
newValue.startHour = current.startHour !== undefined ? current.startHour : startHour();
|
||||||
|
newValue.endHour = endHour();
|
||||||
|
}
|
||||||
|
onChange?.(newValue);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (typeof value === "function") {
|
const newValue = hour ? `${dateStr}T${String(startHour()).padStart(2, "0")}:00:00` : dateStr;
|
||||||
value(hour ? `${dateStr}T${String(startHour()).padStart(2, "0")}:00:00` : dateStr);
|
onChange?.(newValue);
|
||||||
}
|
|
||||||
isOpen(false);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
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("");
|
const displayValue = B("");
|
||||||
Watch(() => {
|
R(() => {
|
||||||
const v2 = val2(value);
|
const v2 = val2(value);
|
||||||
if (!v2) {
|
if (!v2) {
|
||||||
displayValue("");
|
displayValue("");
|
||||||
@@ -892,31 +1001,13 @@ var Datepicker = (props) => {
|
|||||||
}
|
}
|
||||||
displayValue(text);
|
displayValue(text);
|
||||||
});
|
});
|
||||||
const move = (m2) => {
|
const handleCalendarChange = (newValue) => {
|
||||||
const d = internalDate();
|
if (typeof value === "function") {
|
||||||
internalDate(new Date(d.getFullYear(), d.getMonth() + m2, 1));
|
value(newValue);
|
||||||
};
|
}
|
||||||
const moveYear = (y) => {
|
if (!isRangeMode() || newValue?.end !== undefined && newValue?.end !== null) {
|
||||||
const d = internalDate();
|
isOpen(false);
|
||||||
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")
|
|
||||||
])
|
|
||||||
]);
|
|
||||||
};
|
};
|
||||||
return S("div", { class: ui("relative w-full", className) }, [
|
return S("div", { class: ui("relative w-full", className) }, [
|
||||||
Input({
|
Input({
|
||||||
@@ -932,99 +1023,15 @@ var Datepicker = (props) => {
|
|||||||
...rest
|
...rest
|
||||||
}),
|
}),
|
||||||
J(isOpen, () => S("div", {
|
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()
|
onclick: (e) => e.stopPropagation()
|
||||||
}, [
|
}, [
|
||||||
S("div", { class: "flex justify-between items-center mb-4 gap-1" }, [
|
Calendar({
|
||||||
S("div", { class: "flex gap-0.5" }, [
|
value,
|
||||||
S("button", { type: "button", class: "btn btn-ghost btn-xs px-1", onclick: () => moveYear(-1) }, getIcon("icon-[lucide--chevrons-left]")),
|
range: isRangeMode(),
|
||||||
S("button", { type: "button", class: "btn btn-ghost btn-xs px-1", onclick: () => move(-1) }, getIcon("icon-[lucide--chevron-left]"))
|
hour,
|
||||||
]),
|
onChange: handleCalendarChange
|
||||||
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
|
|
||||||
])),
|
])),
|
||||||
J(isOpen, () => S("div", { class: "fixed inset-0 z-[90]", onclick: () => isOpen(false) }))
|
J(isOpen, () => S("div", { class: "fixed inset-0 z-[90]", onclick: () => isOpen(false) }))
|
||||||
]);
|
]);
|
||||||
|
|||||||
8
dist/sigpro-ui.esm.min.js
vendored
8
dist/sigpro-ui.esm.min.js
vendored
File diff suppressed because one or more lines are too long
287
dist/sigpro-ui.js
vendored
287
dist/sigpro-ui.js
vendored
@@ -856,14 +856,15 @@
|
|||||||
__export(exports_Datepicker, {
|
__export(exports_Datepicker, {
|
||||||
Datepicker: () => Datepicker
|
Datepicker: () => Datepicker
|
||||||
});
|
});
|
||||||
var Datepicker = (props) => {
|
|
||||||
const { class: className, value, range, label, placeholder, hour = false, ...rest } = props;
|
// src/components/Calendar.js
|
||||||
const isOpen = B(false);
|
var Calendar = (props) => {
|
||||||
|
const { value, range = false, hour = false, onChange, class: className = "" } = props;
|
||||||
const internalDate = B(new Date);
|
const internalDate = B(new Date);
|
||||||
const hoverDate = B(null);
|
const hoverDate = B(null);
|
||||||
const startHour = B(0);
|
const startHour = B(0);
|
||||||
const endHour = B(0);
|
const endHour = B(0);
|
||||||
const isRangeMode = () => val2(range) === true;
|
const isRangeMode = () => range === true;
|
||||||
const now = new Date;
|
const now = new Date;
|
||||||
const todayStr = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}-${String(now.getDate()).padStart(2, "0")}`;
|
const todayStr = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}-${String(now.getDate()).padStart(2, "0")}`;
|
||||||
const formatDate = (d) => {
|
const formatDate = (d) => {
|
||||||
@@ -877,34 +878,142 @@
|
|||||||
const current = val2(value);
|
const current = val2(value);
|
||||||
if (isRangeMode()) {
|
if (isRangeMode()) {
|
||||||
if (!current?.start || current.start && current.end) {
|
if (!current?.start || current.start && current.end) {
|
||||||
if (typeof value === "function") {
|
const newValue = {
|
||||||
value({
|
start: dateStr,
|
||||||
start: dateStr,
|
end: null,
|
||||||
end: null,
|
...hour && { startHour: startHour() }
|
||||||
...hour && { startHour: startHour() }
|
};
|
||||||
});
|
onChange?.(newValue);
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
const start = current.start;
|
const start = current.start;
|
||||||
if (typeof value === "function") {
|
let newValue;
|
||||||
const newValue = dateStr < start ? { start: dateStr, end: start } : { start, end: dateStr };
|
if (dateStr < start) {
|
||||||
if (hour) {
|
newValue = { start: dateStr, end: start };
|
||||||
newValue.startHour = current.startHour || startHour();
|
} else {
|
||||||
newValue.endHour = current.endHour || endHour();
|
newValue = { start, end: dateStr };
|
||||||
}
|
|
||||||
value(newValue);
|
|
||||||
}
|
}
|
||||||
isOpen(false);
|
if (hour) {
|
||||||
|
newValue.startHour = current.startHour !== undefined ? current.startHour : startHour();
|
||||||
|
newValue.endHour = endHour();
|
||||||
|
}
|
||||||
|
onChange?.(newValue);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (typeof value === "function") {
|
const newValue = hour ? `${dateStr}T${String(startHour()).padStart(2, "0")}:00:00` : dateStr;
|
||||||
value(hour ? `${dateStr}T${String(startHour()).padStart(2, "0")}:00:00` : dateStr);
|
onChange?.(newValue);
|
||||||
}
|
|
||||||
isOpen(false);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
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("");
|
const displayValue = B("");
|
||||||
Watch(() => {
|
R(() => {
|
||||||
const v2 = val2(value);
|
const v2 = val2(value);
|
||||||
if (!v2) {
|
if (!v2) {
|
||||||
displayValue("");
|
displayValue("");
|
||||||
@@ -923,31 +1032,13 @@
|
|||||||
}
|
}
|
||||||
displayValue(text);
|
displayValue(text);
|
||||||
});
|
});
|
||||||
const move = (m2) => {
|
const handleCalendarChange = (newValue) => {
|
||||||
const d = internalDate();
|
if (typeof value === "function") {
|
||||||
internalDate(new Date(d.getFullYear(), d.getMonth() + m2, 1));
|
value(newValue);
|
||||||
};
|
}
|
||||||
const moveYear = (y) => {
|
if (!isRangeMode() || newValue?.end !== undefined && newValue?.end !== null) {
|
||||||
const d = internalDate();
|
isOpen(false);
|
||||||
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")
|
|
||||||
])
|
|
||||||
]);
|
|
||||||
};
|
};
|
||||||
return S("div", { class: ui("relative w-full", className) }, [
|
return S("div", { class: ui("relative w-full", className) }, [
|
||||||
Input({
|
Input({
|
||||||
@@ -963,99 +1054,15 @@
|
|||||||
...rest
|
...rest
|
||||||
}),
|
}),
|
||||||
J(isOpen, () => S("div", {
|
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()
|
onclick: (e) => e.stopPropagation()
|
||||||
}, [
|
}, [
|
||||||
S("div", { class: "flex justify-between items-center mb-4 gap-1" }, [
|
Calendar({
|
||||||
S("div", { class: "flex gap-0.5" }, [
|
value,
|
||||||
S("button", { type: "button", class: "btn btn-ghost btn-xs px-1", onclick: () => moveYear(-1) }, getIcon("icon-[lucide--chevrons-left]")),
|
range: isRangeMode(),
|
||||||
S("button", { type: "button", class: "btn btn-ghost btn-xs px-1", onclick: () => move(-1) }, getIcon("icon-[lucide--chevron-left]"))
|
hour,
|
||||||
]),
|
onChange: handleCalendarChange
|
||||||
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
|
|
||||||
])),
|
])),
|
||||||
J(isOpen, () => S("div", { class: "fixed inset-0 z-[90]", onclick: () => isOpen(false) }))
|
J(isOpen, () => S("div", { class: "fixed inset-0 z-[90]", onclick: () => isOpen(false) }))
|
||||||
]);
|
]);
|
||||||
|
|||||||
8
dist/sigpro-ui.min.js
vendored
8
dist/sigpro-ui.min.js
vendored
File diff suppressed because one or more lines are too long
3
dist/sigpro.css
vendored
3
dist/sigpro.css
vendored
@@ -3339,9 +3339,6 @@
|
|||||||
.z-50 {
|
.z-50 {
|
||||||
z-index: 50;
|
z-index: 50;
|
||||||
}
|
}
|
||||||
.z-90 {
|
|
||||||
z-index: 90;
|
|
||||||
}
|
|
||||||
.z-\[1\] {
|
.z-\[1\] {
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
}
|
}
|
||||||
|
|||||||
2
dist/sigpro.min.css
vendored
2
dist/sigpro.min.css
vendored
File diff suppressed because one or more lines are too long
8
docs/sigpro-ui.min.js
vendored
8
docs/sigpro-ui.min.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
181
src/components/Calendar.js
Normal file
181
src/components/Calendar.js
Normal 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,
|
||||||
|
]);
|
||||||
|
};
|
||||||
@@ -1,83 +1,23 @@
|
|||||||
// components/Datepicker.js
|
import { $, Tag, If, Watch } from "sigpro";
|
||||||
import { $, Tag, If } from "sigpro";
|
|
||||||
import { val, ui, getIcon } from "../utils.js";
|
import { val, ui, getIcon } from "../utils.js";
|
||||||
import { Input } from "./Input.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) => {
|
export const Datepicker = (props) => {
|
||||||
const { class: className, value, range, label, placeholder, hour = false, ...rest } = props;
|
const { class: className, value, range, label, placeholder, hour = false, ...rest } = props;
|
||||||
|
|
||||||
const isOpen = $(false);
|
const isOpen = $(false);
|
||||||
const internalDate = $(new Date());
|
|
||||||
const hoverDate = $(null);
|
|
||||||
const startHour = $(0);
|
|
||||||
const endHour = $(0);
|
|
||||||
const isRangeMode = () => val(range) === true;
|
const isRangeMode = () => val(range) === true;
|
||||||
|
|
||||||
const now = new Date();
|
// Formatear el valor para mostrarlo en el input
|
||||||
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 displayValue = $("");
|
||||||
|
|
||||||
Watch(() => {
|
Watch(() => {
|
||||||
const v = val(value);
|
const v = val(value);
|
||||||
if (!v) {
|
if (!v) {
|
||||||
displayValue("");
|
displayValue("");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let text = "";
|
let text = "";
|
||||||
if (typeof v === "string") {
|
if (typeof v === "string") {
|
||||||
text = (hour && v.includes("T")) ? v.replace("T", " ") : v;
|
text = (hour && v.includes("T")) ? v.replace("T", " ") : v;
|
||||||
@@ -91,38 +31,17 @@ export const Datepicker = (props) => {
|
|||||||
}
|
}
|
||||||
displayValue(text);
|
displayValue(text);
|
||||||
});
|
});
|
||||||
|
|
||||||
const move = (m) => {
|
const handleCalendarChange = (newValue) => {
|
||||||
const d = internalDate();
|
if (typeof value === "function") {
|
||||||
internalDate(new Date(d.getFullYear(), d.getMonth() + m, 1));
|
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) }, [
|
return Tag("div", { class: ui('relative w-full', className) }, [
|
||||||
Input({
|
Input({
|
||||||
label,
|
label,
|
||||||
@@ -136,127 +55,21 @@ export const Datepicker = (props) => {
|
|||||||
},
|
},
|
||||||
...rest,
|
...rest,
|
||||||
}),
|
}),
|
||||||
|
|
||||||
If(isOpen, () =>
|
If(isOpen, () =>
|
||||||
Tag(
|
Tag("div", {
|
||||||
"div",
|
class: "absolute left-0 mt-2 z-[100]",
|
||||||
{
|
onclick: (e) => e.stopPropagation(),
|
||||||
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(),
|
Calendar({
|
||||||
},
|
value,
|
||||||
[
|
range: isRangeMode(),
|
||||||
Tag("div", { class: "flex justify-between items-center mb-4 gap-1" }, [
|
hour,
|
||||||
Tag("div", { class: "flex gap-0.5" }, [
|
onChange: handleCalendarChange,
|
||||||
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,
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
|
|
||||||
If(isOpen, () => Tag("div", { class: "fixed inset-0 z-[90]", onclick: () => isOpen(false) })),
|
If(isOpen, () => Tag("div", { class: "fixed inset-0 z-[90]", onclick: () => isOpen(false) })),
|
||||||
]);
|
]);
|
||||||
};
|
};
|
||||||
44
src/components/InputPopover.js
Normal file
44
src/components/InputPopover.js
Normal 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 }))
|
||||||
|
]);
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user