[Editor] Change mode when double clicking on an editor

It was only possible to double click on a FreeText editor while being in ink mode (or any other).
This commit is contained in:
Calixte Denizet 2025-05-22 20:54:16 +02:00
parent a8e05d82e2
commit 47e69e93a3
7 changed files with 152 additions and 48 deletions

View File

@ -148,10 +148,10 @@ class AnnotationEditorLayer {
/** /**
* Update the toolbar if it's required to reflect the tool currently used. * Update the toolbar if it's required to reflect the tool currently used.
* @param {number} mode * @param {Object} options
*/ */
updateToolbar(mode) { updateToolbar(options) {
this.#uiManager.updateToolbar(mode); this.#uiManager.updateToolbar(options);
} }
/** /**
@ -618,12 +618,12 @@ class AnnotationEditorLayer {
/** /**
* Paste some content into a new editor. * Paste some content into a new editor.
* @param {number} mode * @param {Object} options
* @param {Object} params * @param {Object} params
*/ */
async pasteEditor(mode, params) { async pasteEditor(options, params) {
this.#uiManager.updateToolbar(mode); this.updateToolbar(options);
await this.#uiManager.updateMode(mode); await this.#uiManager.updateMode(options.mode);
const { offsetX, offsetY } = this.#getCenterPoint(); const { offsetX, offsetY } = this.#getCenterPoint();
const id = this.getNextId(); const id = this.getNextId();

View File

@ -90,6 +90,8 @@ class AnnotationEditor {
#touchManager = null; #touchManager = null;
isSelected = false;
_isCopy = false; _isCopy = false;
_editToolbar = null; _editToolbar = null;
@ -1170,7 +1172,7 @@ class AnnotationEditor {
const [tx, ty] = this.getInitialTranslation(); const [tx, ty] = this.getInitialTranslation();
this.translate(tx, ty); this.translate(tx, ty);
bindEvents(this, div, ["keydown", "pointerdown"]); bindEvents(this, div, ["keydown", "pointerdown", "dblclick"]);
if (this.isResizable && this._uiManager._supportsPinchToZoom) { if (this.isResizable && this._uiManager._supportsPinchToZoom) {
this.#touchManager ||= new TouchManager({ this.#touchManager ||= new TouchManager({
@ -1279,10 +1281,6 @@ class AnnotationEditor {
this.#selectOnPointerEvent(event); this.#selectOnPointerEvent(event);
} }
get isSelected() {
return this._uiManager.isSelected(this);
}
#selectOnPointerEvent(event) { #selectOnPointerEvent(event) {
const { isMac } = FeatureTest.platform; const { isMac } = FeatureTest.platform;
if ( if (
@ -1499,16 +1497,30 @@ class AnnotationEditor {
/** /**
* Enable edit mode. * Enable edit mode.
* @returns {boolean} - true if the edit mode has been enabled.
*/ */
enableEditMode() { enableEditMode() {
if (this.isInEditMode()) {
return false;
}
this.parent.setEditingState(false);
this.#isInEditMode = true; this.#isInEditMode = true;
return true;
} }
/** /**
* Disable edit mode. * Disable edit mode.
* @returns {boolean} - true if the edit mode has been disabled.
*/ */
disableEditMode() { disableEditMode() {
if (!this.isInEditMode()) {
return false;
}
this.parent.setEditingState(true);
this.#isInEditMode = false; this.#isInEditMode = false;
return true;
} }
/** /**
@ -1832,6 +1844,10 @@ class AnnotationEditor {
* Select this editor. * Select this editor.
*/ */
select() { select() {
if (this.isSelected && this._editToolbar) {
return;
}
this.isSelected = true;
this.makeResizable(); this.makeResizable();
this.div?.classList.add("selectedEditor"); this.div?.classList.add("selectedEditor");
if (!this._editToolbar) { if (!this._editToolbar) {
@ -1853,6 +1869,10 @@ class AnnotationEditor {
* Unselect this editor. * Unselect this editor.
*/ */
unselect() { unselect() {
if (!this.isSelected) {
return;
}
this.isSelected = false;
this.#resizersDiv?.classList.add("hidden"); this.#resizersDiv?.classList.add("hidden");
this.div?.classList.remove("selectedEditor"); this.div?.classList.remove("selectedEditor");
if (this.div?.contains(document.activeElement)) { if (this.div?.contains(document.activeElement)) {
@ -1885,10 +1905,38 @@ class AnnotationEditor {
*/ */
enableEditing() {} enableEditing() {}
/**
* Check if the content of this editor can be changed.
* For example, a FreeText editor can be changed (the user can change the
* text), but a Stamp editor cannot.
* @returns {boolean}
*/
get canChangeContent() {
return false;
}
/** /**
* The editor is about to be edited. * The editor is about to be edited.
*/ */
enterInEditMode() {} enterInEditMode() {
if (!this.canChangeContent) {
return;
}
this.enableEditMode();
this.div.focus();
}
/**
* ondblclick callback.
* @param {MouseEvent} event
*/
dblclick(event) {
this.enterInEditMode();
this.parent.updateToolbar({
mode: this.constructor._editorType,
editId: this.id,
});
}
/** /**
* @returns {HTMLElement | null} the element requiring an alt text. * @returns {HTMLElement | null} the element requiring an alt text.

View File

@ -24,11 +24,7 @@ import {
shadow, shadow,
Util, Util,
} from "../../shared/util.js"; } from "../../shared/util.js";
import { import { AnnotationEditorUIManager, KeyboardManager } from "./tools.js";
AnnotationEditorUIManager,
bindEvents,
KeyboardManager,
} from "./tools.js";
import { AnnotationEditor } from "./editor.js"; import { AnnotationEditor } from "./editor.js";
import { FreeTextAnnotationElement } from "../annotation_layer.js"; import { FreeTextAnnotationElement } from "../annotation_layer.js";
@ -284,13 +280,10 @@ class FreeTextEditor extends AnnotationEditor {
/** @inheritdoc */ /** @inheritdoc */
enableEditMode() { enableEditMode() {
if (this.isInEditMode()) { if (!super.enableEditMode()) {
return; return false;
} }
this.parent.setEditingState(false);
this.parent.updateToolbar(AnnotationEditorType.FREETEXT);
super.enableEditMode();
this.overlayDiv.classList.remove("enabled"); this.overlayDiv.classList.remove("enabled");
this.editorDiv.contentEditable = true; this.editorDiv.contentEditable = true;
this._isDraggable = false; this._isDraggable = false;
@ -322,16 +315,16 @@ class FreeTextEditor extends AnnotationEditor {
this.editorDiv.addEventListener("paste", this.editorDivPaste.bind(this), { this.editorDiv.addEventListener("paste", this.editorDivPaste.bind(this), {
signal, signal,
}); });
return true;
} }
/** @inheritdoc */ /** @inheritdoc */
disableEditMode() { disableEditMode() {
if (!this.isInEditMode()) { if (!super.disableEditMode()) {
return; return false;
} }
this.parent.setEditingState(true);
super.disableEditMode();
this.overlayDiv.classList.add("enabled"); this.overlayDiv.classList.add("enabled");
this.editorDiv.contentEditable = false; this.editorDiv.contentEditable = false;
this.div.setAttribute("aria-activedescendant", this.#editorDivId); this.div.setAttribute("aria-activedescendant", this.#editorDivId);
@ -349,6 +342,8 @@ class FreeTextEditor extends AnnotationEditor {
// In case the blur callback hasn't been called. // In case the blur callback hasn't been called.
this.isEditing = false; this.isEditing = false;
this.parent.div.classList.add("freetextEditing"); this.parent.div.classList.add("freetextEditing");
return true;
} }
/** @inheritdoc */ /** @inheritdoc */
@ -498,18 +493,7 @@ class FreeTextEditor extends AnnotationEditor {
this.editorDiv.focus(); this.editorDiv.focus();
} }
/** /** @inheritdoc */
* ondblclick callback.
* @param {MouseEvent} event
*/
dblclick(event) {
this.enterInEditMode();
}
/**
* onkeydown callback.
* @param {KeyboardEvent} event
*/
keydown(event) { keydown(event) {
if (event.target === this.div && event.key === "Enter") { if (event.target === this.div && event.key === "Enter") {
this.enterInEditMode(); this.enterInEditMode();
@ -546,6 +530,11 @@ class FreeTextEditor extends AnnotationEditor {
this.editorDiv.setAttribute("aria-multiline", true); this.editorDiv.setAttribute("aria-multiline", true);
} }
/** @inheritdoc */
get canChangeContent() {
return true;
}
/** @inheritdoc */ /** @inheritdoc */
render() { render() {
if (this.div) { if (this.div) {
@ -579,8 +568,6 @@ class FreeTextEditor extends AnnotationEditor {
this.overlayDiv.classList.add("overlay", "enabled"); this.overlayDiv.classList.add("overlay", "enabled");
this.div.append(this.overlayDiv); this.div.append(this.overlayDiv);
bindEvents(this, this.div, ["dblclick", "keydown"]);
if (this._isCopy || this.annotationElementId) { if (this._isCopy || this.annotationElementId) {
// This editor was created in using copy (ctrl+c). // This editor was created in using copy (ctrl+c).
const [parentWidth, parentHeight] = this.parentDimensions; const [parentWidth, parentHeight] = this.parentDimensions;

View File

@ -71,9 +71,10 @@ class StampEditor extends AnnotationEditor {
/** @inheritdoc */ /** @inheritdoc */
static paste(item, parent) { static paste(item, parent) {
parent.pasteEditor(AnnotationEditorType.STAMP, { parent.pasteEditor(
bitmapFile: item.getAsFile(), { mode: AnnotationEditorType.STAMP },
}); { bitmapFile: item.getAsFile() }
);
} }
/** @inheritdoc */ /** @inheritdoc */

View File

@ -1711,7 +1711,7 @@ class AnnotationEditorUIManager {
} }
for (const editor of this.#allEditors.values()) { for (const editor of this.#allEditors.values()) {
if (editor.annotationElementId === editId) { if (editor.annotationElementId === editId || editor.id === editId) {
this.setSelected(editor); this.setSelected(editor);
editor.enterInEditMode(); editor.enterInEditMode();
} else { } else {
@ -1730,16 +1730,17 @@ class AnnotationEditorUIManager {
/** /**
* Update the toolbar if it's required to reflect the tool currently used. * Update the toolbar if it's required to reflect the tool currently used.
* @param {Object} options
* @param {number} mode * @param {number} mode
* @returns {undefined} * @returns {undefined}
*/ */
updateToolbar(mode) { updateToolbar(options) {
if (mode === this.#mode) { if (options.mode === this.#mode) {
return; return;
} }
this._eventBus.dispatch("switchannotationeditormode", { this._eventBus.dispatch("switchannotationeditormode", {
source: this, source: this,
mode, ...options,
}); });
} }

View File

@ -1229,3 +1229,65 @@ describe("The pen-drawn shape must maintain correct curvature regardless of the
); );
}); });
}); });
describe("Should switch from an editor and mode to others by double clicking", () => {
let pages;
beforeEach(async () => {
pages = await loadAndWait("empty.pdf", ".annotationEditorLayer");
});
afterEach(async () => {
await closePages(pages);
});
it("must check that the editor an the mode are correct", async () => {
await Promise.all(
pages.map(async ([browserName, page]) => {
await switchToInk(page);
const editorLayerRect = await getRect(page, ".annotationEditorLayer");
const drawStartX = editorLayerRect.x + 100;
const drawStartY = editorLayerRect.y + 100;
const inkSelector = getEditorSelector(0);
const clickHandle = await waitForPointerUp(page);
await page.mouse.move(drawStartX, drawStartY);
await page.mouse.down();
await page.mouse.move(drawStartX + 50, drawStartY + 50);
await page.mouse.up();
await awaitPromise(clickHandle);
await commit(page);
await switchToEditor("FreeText", page);
const freeTextSelector = getEditorSelector(1);
const data = "Hello PDF.js World !!";
await page.mouse.click(
editorLayerRect.x + 200,
editorLayerRect.y + 200
);
await page.waitForSelector(freeTextSelector, { visible: true });
await page.type(`${freeTextSelector} .internal`, data);
await page.keyboard.press("Escape");
await page.waitForSelector(
".freeTextEditor.selectedEditor .overlay.enabled"
);
await page.waitForSelector("#editorInkButton:not(.toggled)");
let modeChangedHandle = await waitForAnnotationModeChanged(page);
await selectEditor(page, inkSelector, 2);
await awaitPromise(modeChangedHandle);
await page.waitForSelector("#editorInkButton.toggled");
await waitForSelectedEditor(page, inkSelector);
await page.waitForSelector("#editorFreeTextButton:not(.toggled)");
modeChangedHandle = await waitForAnnotationModeChanged(page);
await selectEditor(page, freeTextSelector, 2);
await awaitPromise(modeChangedHandle);
await page.waitForSelector("#editorFreeTextButton.toggled");
await waitForSelectedEditor(page, freeTextSelector);
})
);
});
});

View File

@ -147,6 +147,11 @@
.annotationEditorLayer.disabled { .annotationEditorLayer.disabled {
pointer-events: none; pointer-events: none;
&.highlightEditing
:is(.freeTextEditor, .inkEditor, .stampEditor, .signatureEditor) {
pointer-events: auto;
}
} }
.annotationEditorLayer.freetextEditing { .annotationEditorLayer.freetextEditing {