I’m making a customised element that automatically localises it’s visual text representation:
class LocalDate extends HTMLTimeElement {
// Specify observed attributes so that
// attributeChangedCallback will work
static get observedAttributes() {
return ["datetime"];
}
constructor() {
// Always call super first in constructor
const self = super();
this.formatter = new Intl.DateTimeFormat(navigator.languages, { year: "numeric", month: "short", day: "numeric" });
return self;
}
connectedCallback() {
this._upgradeProperty("datetime");
}
attributeChangedCallback(name, oldValue, newValue) {
if (name === "datetime") {
this.textContent = "";
const dateMiliseconds = Date.parse(newValue);
if (!Number.isNaN(dateMiliseconds)) {
const dateString = this.formatter.format(new Date(dateMiliseconds));
// Bizarrly, this doesn't seem to work without doing this in a timeout?!?!
setTimeout(() => this.textContent = dateString);
}
}
}
_upgradeProperty(prop) {
if (this.hasOwnProperty(prop)) {
let value = this[prop];
delete this[prop];
this[prop] = value;
}
}
set datetime(value) {
if (value instanceof Date) {
this.setAttribute("datetime", value.toISOString());
} else {
this.removeAttribute("datetime");
}
}
get datetime() {
const dateMiliseconds = Date.parse(this.getAttribute("datetime"));
if (Number.isNaN(dateMiliseconds)) {
return null;
}
return new Date(dateMiliseconds);
}
}
customElements.define('local-date', LocalDate, { extends: "time" });
<time is="local-date" datetime="2022-01-13T07:13:00+10:00">13 Jan 2022 - Still here</time>
The kicker is this line of code in the attributeChangedCallback
:
setTimeout(() => this.textContent = dateString);
If it’s instead replaced with the more obvious:
this.textContent = dateString
Then instead of appearing as a date, the element displays the date string in addition to the text that was already in the element.
I’ve reproduced this in both Firefox and Chrome – any ideas what’s going on here?