Mirror datetime inputs to value attribute

This commit is contained in:
Alex Lion
2026-08-06 16:16:33 +07:00
parent c0611f41a2
commit f2a0395b98
2 changed files with 84 additions and 8 deletions

View File

@@ -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() {

View File

@@ -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);
});