fix(modals): nested-dialog rendering, CM6 destroy on close, mount idempotency
Three coupled lifecycle bugs surfaced during Task 5-8 reviews: 1. overlay_file_editor.html emitted a <dialog open> that nested inside the outer <dialog id="modal-container">, collapsing the modal to 2px tall. Replaced with <div role="document" aria-labelledby="…"> so a11y semantics survive and the layout actually renders. 2. modal-router.js's close-event handler now tears down CM6 controllers via controller.destroy() and clears #modal-content innerHTML, fixing a real leak (each open/close cycle was orphaning an EditorView and a matchMedia "change" listener on window). 3. mountOne in editor.js now short-circuits if the textarea already has a controller, defending against future double-mount paths. CSS: added div.modal and div.modal.modal-wide selectors alongside the existing dialog.modal ones so the editor <div> gets correct styling. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
f426970d4c
commit
f6b8ecfd5d
4 changed files with 22 additions and 4 deletions
|
|
@ -111,7 +111,8 @@ a.button.danger {
|
||||||
max-width: 28rem;
|
max-width: 28rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
dialog.modal {
|
dialog.modal,
|
||||||
|
div.modal {
|
||||||
background: var(--color-surface);
|
background: var(--color-surface);
|
||||||
color: var(--color-text);
|
color: var(--color-text);
|
||||||
border: var(--line);
|
border: var(--line);
|
||||||
|
|
@ -659,7 +660,8 @@ button.danger-outline:hover {
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Wider modal for the editor (textarea needs the breathing room). */
|
/* Wider modal for the editor (textarea needs the breathing room). */
|
||||||
dialog.modal.modal-wide {
|
dialog.modal.modal-wide,
|
||||||
|
div.modal.modal-wide {
|
||||||
width: min(48rem, 92vw);
|
width: min(48rem, 92vw);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -41,6 +41,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
async function mountOne(textarea) {
|
async function mountOne(textarea) {
|
||||||
|
if (textarea.__editorController) return; // idempotency: don't double-mount
|
||||||
let lang = textarea.getAttribute("data-editor-language") || "plain";
|
let lang = textarea.getAttribute("data-editor-language") || "plain";
|
||||||
let filenameInput = null;
|
let filenameInput = null;
|
||||||
let dropdown = null;
|
let dropdown = null;
|
||||||
|
|
|
||||||
|
|
@ -54,6 +54,21 @@
|
||||||
url.searchParams.delete("modal");
|
url.searchParams.delete("modal");
|
||||||
history.pushState({}, "", url.toString());
|
history.pushState({}, "", url.toString());
|
||||||
}
|
}
|
||||||
|
// Tear down any CM6 controllers attached to swapped-in editor textareas
|
||||||
|
// so close/reopen cycles don't leak EditorView instances and matchMedia
|
||||||
|
// listeners. The bundle exposes controller.destroy() on the controller
|
||||||
|
// stored at textarea.__editorController.
|
||||||
|
const slot = document.getElementById("modal-content");
|
||||||
|
if (slot) {
|
||||||
|
for (const ta of slot.querySelectorAll("textarea[data-editor-language]")) {
|
||||||
|
const ctrl = ta.__editorController;
|
||||||
|
if (ctrl && typeof ctrl.destroy === "function") {
|
||||||
|
ctrl.destroy();
|
||||||
|
}
|
||||||
|
ta.__editorController = null;
|
||||||
|
}
|
||||||
|
slot.innerHTML = "";
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Esc key fires 'cancel' on a <dialog>; preventDefault so we control the
|
// Esc key fires 'cancel' on a <dialog>; preventDefault so we control the
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
{% block title %}Edit {{ rel_path }} · {{ overlay.name }}{% endblock %}
|
{% block title %}Edit {{ rel_path }} · {{ overlay.name }}{% endblock %}
|
||||||
{% block extra_head %}{% include "_editor_assets.html" %}{% endblock %}
|
{% block extra_head %}{% include "_editor_assets.html" %}{% endblock %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<dialog id="files-editor-modal" class="modal modal-wide" open aria-labelledby="files-editor-title">
|
<div id="files-editor-modal" class="modal modal-wide" role="document" aria-labelledby="files-editor-title">
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
<h2 id="files-editor-title" class="files-editor-path">
|
<h2 id="files-editor-title" class="files-editor-path">
|
||||||
<span class="files-editor-title-text">{{ rel_path }}</span>
|
<span class="files-editor-title-text">{{ rel_path }}</span>
|
||||||
|
|
@ -43,5 +43,5 @@
|
||||||
<button type="button" class="button-secondary" data-modal-dismiss>Cancel</button>
|
<button type="button" class="button-secondary" data-modal-dismiss>Cancel</button>
|
||||||
<button type="button" class="files-editor-save">Save</button>
|
<button type="button" class="files-editor-save">Save</button>
|
||||||
</div>
|
</div>
|
||||||
</dialog>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue