Preserve active ping editors during sidebar refreshes

This commit is contained in:
GPT on behalf of DHH
2026-10-07 16:55:08 +02:00
parent 765711f27d
commit 8bbe129030
@@ -8,9 +8,12 @@ export default class extends Controller {
#disconnected = true
#connection = 0
#discardDirectEditor = false
#readAt = new Map()
async connect() {
this.element.addEventListener("turbo:click", this.#directNavigation)
this.element.addEventListener("turbo:before-frame-render", this.#preserveDirectEditor)
this.channel ??= await cable.subscribeTo({ channel: "UnreadRoomsChannel" }, {
connected: this.#channelConnected.bind(this),
disconnected: this.#channelDisconnected.bind(this),
@@ -19,6 +22,9 @@ export default class extends Controller {
}
disconnect() {
this.element.removeEventListener("turbo:click", this.#directNavigation)
this.element.removeEventListener("turbo:before-frame-render", this.#preserveDirectEditor)
this.#discardDirectEditor = false
ignoringBriefDisconnects(this.element, () => {
this.#channelDisconnected()
this.channel?.unsubscribe()
@@ -43,6 +49,36 @@ export default class extends Controller {
}
}
// Preserve the live picker through background refreshes, but allow deliberate navigation.
#directNavigation = event => {
if (event.target.closest("a")?.dataset.turboFrame === this.element.id) {
this.#discardDirectEditor = true
}
}
#preserveDirectEditor = event => {
if (event.target !== this.element) return
const discard = this.#discardDirectEditor
this.#discardDirectEditor = false
if (discard) return
const current = this.element.querySelector("#direct_rooms_control")
const incoming = event.detail.newFrame.querySelector("#direct_rooms_control")
if (!current?.querySelector('[data-autocomplete-target="input"]') || !incoming) return
// Turbo's Bardo moves the actual element, keeping input, selections and controllers.
const controls = [ current, incoming ].map(element => [ element, element.hasAttribute("data-turbo-permanent") ])
for (const [ element ] of controls) element.setAttribute("data-turbo-permanent", "")
const render = event.detail.render
event.detail.render = (...args) => {
try {
return render(...args)
} finally {
for (const [ element, permanent ] of controls) element.toggleAttribute("data-turbo-permanent", permanent)
}
}
}
async #channelConnected() {
if (this.#disconnected) {
this.#disconnected = false