mirror of
https://github.com/ClaperCo/Claper.git
synced 2026-08-29 10:09:08 +02:00
Mirror datetime inputs to value attribute
This commit is contained in:
@@ -17,7 +17,7 @@ const DateTimeLocal = {
|
||||
},
|
||||
syncLocalTime() {
|
||||
if (!this.utcTime.value) {
|
||||
this.localTime.value = "";
|
||||
this.setValue(this.localTime, "");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -26,21 +26,33 @@ const DateTimeLocal = {
|
||||
|
||||
if (!Number.isNaN(date.getTime())) {
|
||||
const pad = (part) => String(part).padStart(2, "0");
|
||||
this.localTime.value =
|
||||
this.setValue(
|
||||
this.localTime,
|
||||
`${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}` +
|
||||
`T${pad(date.getHours())}:${pad(date.getMinutes())}`;
|
||||
`T${pad(date.getHours())}:${pad(date.getMinutes())}`,
|
||||
);
|
||||
}
|
||||
},
|
||||
syncUtcTime() {
|
||||
if (!this.localTime.value) {
|
||||
this.utcTime.value = "";
|
||||
this.setValue(this.utcTime, "");
|
||||
return;
|
||||
}
|
||||
|
||||
const date = new Date(this.localTime.value);
|
||||
|
||||
if (!Number.isNaN(date.getTime())) {
|
||||
this.utcTime.value = date.toISOString().slice(0, 19);
|
||||
this.setValue(this.utcTime, date.toISOString().slice(0, 19));
|
||||
this.setValue(this.localTime, this.localTime.value);
|
||||
}
|
||||
},
|
||||
|
||||
setValue(input, value) {
|
||||
input.value = value;
|
||||
if (value) {
|
||||
input.setAttribute("value", value);
|
||||
} else {
|
||||
input.removeAttribute("value");
|
||||
}
|
||||
},
|
||||
destroyed() {
|
||||
|
||||
@@ -3,9 +3,27 @@ import test from "node:test";
|
||||
|
||||
import DateTimeLocal from "../js/date_time_local.mjs";
|
||||
|
||||
function mountHook() {
|
||||
const localTime = { value: "" };
|
||||
const utcTime = { value: "2026-08-04T12:00:00" };
|
||||
function makeInput(value = "") {
|
||||
const attributes = new Map();
|
||||
if (value) attributes.set("value", value);
|
||||
|
||||
return {
|
||||
value,
|
||||
setAttribute(name, val) {
|
||||
attributes.set(name, String(val));
|
||||
},
|
||||
removeAttribute(name) {
|
||||
attributes.delete(name);
|
||||
},
|
||||
getAttribute(name) {
|
||||
return attributes.has(name) ? attributes.get(name) : null;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function mountHook(utcValue = "2026-08-04T12:00:00") {
|
||||
const localTime = makeInput();
|
||||
const utcTime = makeInput(utcValue);
|
||||
const listeners = new Map();
|
||||
|
||||
const el = {
|
||||
@@ -29,6 +47,15 @@ function mountHook() {
|
||||
return { el, hook, localTime, utcTime };
|
||||
}
|
||||
|
||||
function toLocalString(utcValue) {
|
||||
const date = new Date(`${utcValue}Z`);
|
||||
const pad = (part) => String(part).padStart(2, "0");
|
||||
return (
|
||||
`${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}` +
|
||||
`T${pad(date.getHours())}:${pad(date.getMinutes())}`
|
||||
);
|
||||
}
|
||||
|
||||
test("syncs the hidden UTC value when the datetime control only emits change", () => {
|
||||
const { el, localTime, utcTime } = mountHook();
|
||||
localTime.value = "2030-01-02T10:30";
|
||||
@@ -37,3 +64,40 @@ test("syncs the hidden UTC value when the datetime control only emits change", (
|
||||
|
||||
assert.equal(utcTime.value, new Date(localTime.value).toISOString().slice(0, 19));
|
||||
});
|
||||
|
||||
test("mirrors synced values into the value attribute so LiveView sees a DOM diff", () => {
|
||||
const { el, localTime, utcTime } = mountHook();
|
||||
|
||||
assert.equal(localTime.getAttribute("value"), localTime.value);
|
||||
|
||||
localTime.value = "2030-01-02T10:30";
|
||||
el.dispatch("input", localTime);
|
||||
|
||||
assert.equal(utcTime.getAttribute("value"), utcTime.value);
|
||||
assert.equal(localTime.getAttribute("value"), "2030-01-02T10:30");
|
||||
});
|
||||
|
||||
test("restores the local value after a patch outside the form wipes the input", () => {
|
||||
const utcValue = "2026-08-04T12:00:00";
|
||||
const { hook, localTime } = mountHook(utcValue);
|
||||
|
||||
// Simulate morphdom patching against server HTML that has no value
|
||||
// attribute (e.g. re-render caused by an upload finishing): the input
|
||||
// property is reset and the attribute removed, then `updated` fires.
|
||||
localTime.value = "";
|
||||
localTime.removeAttribute("value");
|
||||
hook.updated();
|
||||
|
||||
assert.equal(localTime.value, toLocalString(utcValue));
|
||||
assert.equal(localTime.getAttribute("value"), toLocalString(utcValue));
|
||||
});
|
||||
|
||||
test("clears the local value and attribute when the UTC value is empty", () => {
|
||||
const { hook, localTime, utcTime } = mountHook();
|
||||
|
||||
utcTime.value = "";
|
||||
hook.updated();
|
||||
|
||||
assert.equal(localTime.value, "");
|
||||
assert.equal(localTime.getAttribute("value"), null);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user