[Editor] Remove a popup from the DOM when it's deleted

This commit is contained in:
Calixte Denizet 2025-07-09 20:23:27 +02:00
parent 2e0f1ec515
commit b07914843b
4 changed files with 103 additions and 16 deletions

View File

@ -2125,6 +2125,8 @@ class PopupElement {
#popup = null; #popup = null;
#popupAbortController = null;
#position = null; #position = null;
#rect = null; #rect = null;
@ -2166,18 +2168,7 @@ class PopupElement {
this.#dateObj = PDFDateString.toDateObject(modificationDate); this.#dateObj = PDFDateString.toDateObject(modificationDate);
this.trigger = elements.flatMap(e => e.getElementsToTriggerPopup()); this.trigger = elements.flatMap(e => e.getElementsToTriggerPopup());
// Attach the event listeners to the trigger element. this.#addEventListeners();
for (const element of this.trigger) {
element.addEventListener("click", this.#boundToggle);
element.addEventListener("mouseenter", this.#boundShow);
element.addEventListener("mouseleave", this.#boundHide);
element.classList.add("popupTriggerArea");
}
// Attach the event listener to toggle the popup with the keyboard.
for (const element of elements) {
element.container?.addEventListener("keydown", this.#boundKeyDown);
}
this.#container.hidden = true; this.#container.hidden = true;
if (open) { if (open) {
@ -2195,6 +2186,29 @@ class PopupElement {
} }
} }
#addEventListeners() {
if (this.#popupAbortController) {
return;
}
this.#popupAbortController = new AbortController();
const { signal } = this.#popupAbortController;
// Attach the event listeners to the trigger element.
for (const element of this.trigger) {
element.addEventListener("click", this.#boundToggle, { signal });
element.addEventListener("mouseenter", this.#boundShow, { signal });
element.addEventListener("mouseleave", this.#boundHide, { signal });
element.classList.add("popupTriggerArea");
}
// Attach the event listener to toggle the popup with the keyboard.
for (const element of this.#elements) {
element.container?.addEventListener("keydown", this.#boundKeyDown, {
signal,
});
}
}
render() { render() {
if (this.#popup) { if (this.#popup) {
return; return;
@ -2338,7 +2352,12 @@ class PopupElement {
} }
} }
updateEdited({ rect, popupContent }) { updateEdited({ rect, popupContent, deleted }) {
if (deleted) {
this.remove();
return;
}
this.#addEventListeners();
this.#updates ||= { this.#updates ||= {
contentsObj: this.#contentsObj, contentsObj: this.#contentsObj,
richText: this.#richText, richText: this.#richText,
@ -2366,6 +2385,18 @@ class PopupElement {
this.#position = null; this.#position = null;
} }
remove() {
this.#popupAbortController?.abort();
this.#popupAbortController = null;
this.#popup?.remove();
this.#popup = null;
this.#wasVisible = false;
this.#pinned = false;
for (const element of this.trigger) {
element.classList.remove("popupTriggerArea");
}
}
#setPosition() { #setPosition() {
if (this.#position !== null) { if (this.#position !== null) {
return; return;
@ -2465,6 +2496,7 @@ class PopupElement {
} }
maybeShow() { maybeShow() {
this.#addEventListeners();
if (!this.#wasVisible) { if (!this.#wasVisible) {
return; return;
} }

View File

@ -359,6 +359,9 @@ class AnnotationEditorLayer {
for (const editable of editables) { for (const editable of editables) {
const { id } = editable.data; const { id } = editable.data;
if (this.#uiManager.isDeletedAnnotationElement(id)) { if (this.#uiManager.isDeletedAnnotationElement(id)) {
editable.updateEdited({
deleted: true,
});
continue; continue;
} }
let editor = resetAnnotations.get(id); let editor = resetAnnotations.get(id);

View File

@ -860,9 +860,6 @@ class FreeTextEditor extends AnnotationEditor {
/** @inheritdoc */ /** @inheritdoc */
renderAnnotationElement(annotation) { renderAnnotationElement(annotation) {
const content = super.renderAnnotationElement(annotation); const content = super.renderAnnotationElement(annotation);
if (this.deleted) {
return content;
}
const { style } = content; const { style } = content;
style.fontSize = `calc(${this.#fontSize}px * var(--total-scale-factor))`; style.fontSize = `calc(${this.#fontSize}px * var(--total-scale-factor))`;
style.color = this.#color; style.color = this.#color;

View File

@ -1086,6 +1086,61 @@ describe("FreeText Editor", () => {
}) })
); );
}); });
it("must delete an existing annotation with a popup", async () => {
await Promise.all(
pages.map(async ([browserName, page]) => {
await page.click("[data-annotation-id='26R']");
// Wait for the popup to be displayed.
const popupSelector = "[data-annotation-id='popup_26R'] .popup";
await page.waitForSelector(popupSelector, { visible: true });
await switchToFreeText(page);
const editorSelector = getEditorSelector(0);
await selectEditor(page, editorSelector);
await page.keyboard.press("Backspace");
await page.waitForFunction(
sel => !document.querySelector(sel),
{},
editorSelector
);
await waitForSerialized(page, 1);
const serialized = await getSerialized(page);
expect(serialized).toEqual([
{
pageIndex: 0,
id: "26R",
deleted: true,
popupRef: "",
},
]);
// Disable editing mode.
await switchToFreeText(page, /* disable = */ true);
await page.waitForSelector(":not([data-annotation-id='26R'] .popup)");
// Re-enable editing mode.
await switchToFreeText(page);
await page.focus(".annotationEditorLayer");
await kbUndo(page);
await waitForSerialized(page, 0);
// Disable editing mode.
await switchToFreeText(page, /* disable = */ true);
const popupAreaSelector =
"[data-annotation-id='26R'].popupTriggerArea";
await page.waitForSelector(popupAreaSelector, { visible: true });
await page.click("[data-annotation-id='26R']");
// Wait for the popup to be displayed.
await page.waitForSelector(popupSelector, { visible: true });
})
);
});
}); });
describe("FreeText (copy/paste existing)", () => { describe("FreeText (copy/paste existing)", () => {