mirror of
https://github.com/mozilla/pdf.js.git
synced 2025-04-19 22:58:07 +02:00
[Editor] Move the alt text button in the editor toolbar
This commit is contained in:
parent
8e2507e0e8
commit
59112a7730
6 changed files with 372 additions and 422 deletions
180
src/display/editor/alt_text.js
Normal file
180
src/display/editor/alt_text.js
Normal file
|
@ -0,0 +1,180 @@
|
|||
/* Copyright 2023 Mozilla Foundation
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { noContextMenu } from "../display_utils.js";
|
||||
|
||||
class AltText {
|
||||
#altText = "";
|
||||
|
||||
#altTextDecorative = false;
|
||||
|
||||
#altTextButton = null;
|
||||
|
||||
#altTextTooltip = null;
|
||||
|
||||
#altTextTooltipTimeout = null;
|
||||
|
||||
#altTextWasFromKeyBoard = false;
|
||||
|
||||
#editor = null;
|
||||
|
||||
static _l10nPromise = null;
|
||||
|
||||
constructor(editor) {
|
||||
this.#editor = editor;
|
||||
}
|
||||
|
||||
static initialize(l10nPromise) {
|
||||
AltText._l10nPromise ||= l10nPromise;
|
||||
}
|
||||
|
||||
async render() {
|
||||
const altText = (this.#altTextButton = document.createElement("button"));
|
||||
altText.className = "altText";
|
||||
const msg = await AltText._l10nPromise.get(
|
||||
"pdfjs-editor-alt-text-button-label"
|
||||
);
|
||||
altText.textContent = msg;
|
||||
altText.setAttribute("aria-label", msg);
|
||||
altText.tabIndex = "0";
|
||||
altText.addEventListener("contextmenu", noContextMenu);
|
||||
altText.addEventListener("pointerdown", event => event.stopPropagation());
|
||||
|
||||
const onClick = event => {
|
||||
event.preventDefault();
|
||||
this.#editor._uiManager.editAltText(this.#editor);
|
||||
};
|
||||
altText.addEventListener("click", onClick, { capture: true });
|
||||
altText.addEventListener("keydown", event => {
|
||||
if (event.target === altText && event.key === "Enter") {
|
||||
this.#altTextWasFromKeyBoard = true;
|
||||
onClick(event);
|
||||
}
|
||||
});
|
||||
await this.#setState();
|
||||
|
||||
return altText;
|
||||
}
|
||||
|
||||
finish() {
|
||||
if (!this.#altTextButton) {
|
||||
return;
|
||||
}
|
||||
this.#altTextButton.focus({ focusVisible: this.#altTextWasFromKeyBoard });
|
||||
this.#altTextWasFromKeyBoard = false;
|
||||
}
|
||||
|
||||
get data() {
|
||||
return {
|
||||
altText: this.#altText,
|
||||
decorative: this.#altTextDecorative,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the alt text data.
|
||||
*/
|
||||
set data({ altText, decorative }) {
|
||||
if (this.#altText === altText && this.#altTextDecorative === decorative) {
|
||||
return;
|
||||
}
|
||||
this.#altText = altText;
|
||||
this.#altTextDecorative = decorative;
|
||||
this.#setState();
|
||||
}
|
||||
|
||||
toggle(enabled = false) {
|
||||
if (!this.#altTextButton) {
|
||||
return;
|
||||
}
|
||||
if (!enabled && this.#altTextTooltipTimeout) {
|
||||
clearTimeout(this.#altTextTooltipTimeout);
|
||||
this.#altTextTooltipTimeout = null;
|
||||
}
|
||||
this.#altTextButton.disabled = !enabled;
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.#altTextButton?.remove();
|
||||
this.#altTextButton = null;
|
||||
this.#altTextTooltip = null;
|
||||
}
|
||||
|
||||
async #setState() {
|
||||
const button = this.#altTextButton;
|
||||
if (!button) {
|
||||
return;
|
||||
}
|
||||
if (!this.#altText && !this.#altTextDecorative) {
|
||||
button.classList.remove("done");
|
||||
this.#altTextTooltip?.remove();
|
||||
return;
|
||||
}
|
||||
button.classList.add("done");
|
||||
|
||||
AltText._l10nPromise
|
||||
.get("pdfjs-editor-alt-text-edit-button-label")
|
||||
.then(msg => {
|
||||
button.setAttribute("aria-label", msg);
|
||||
});
|
||||
let tooltip = this.#altTextTooltip;
|
||||
if (!tooltip) {
|
||||
this.#altTextTooltip = tooltip = document.createElement("span");
|
||||
tooltip.className = "tooltip";
|
||||
tooltip.setAttribute("role", "tooltip");
|
||||
const id = (tooltip.id = `alt-text-tooltip-${this.#editor.id}`);
|
||||
button.setAttribute("aria-describedby", id);
|
||||
|
||||
const DELAY_TO_SHOW_TOOLTIP = 100;
|
||||
button.addEventListener("mouseenter", () => {
|
||||
this.#altTextTooltipTimeout = setTimeout(() => {
|
||||
this.#altTextTooltipTimeout = null;
|
||||
this.#altTextTooltip.classList.add("show");
|
||||
this.#editor._uiManager._eventBus.dispatch("reporttelemetry", {
|
||||
source: this,
|
||||
details: {
|
||||
type: "editing",
|
||||
subtype: this.editorType,
|
||||
data: {
|
||||
action: "alt_text_tooltip",
|
||||
},
|
||||
},
|
||||
});
|
||||
}, DELAY_TO_SHOW_TOOLTIP);
|
||||
});
|
||||
button.addEventListener("mouseleave", () => {
|
||||
if (this.#altTextTooltipTimeout) {
|
||||
clearTimeout(this.#altTextTooltipTimeout);
|
||||
this.#altTextTooltipTimeout = null;
|
||||
}
|
||||
this.#altTextTooltip?.classList.remove("show");
|
||||
});
|
||||
}
|
||||
tooltip.innerText = this.#altTextDecorative
|
||||
? await AltText._l10nPromise.get(
|
||||
"pdfjs-editor-alt-text-decorative-tooltip"
|
||||
)
|
||||
: this.#altText;
|
||||
|
||||
if (!tooltip.parentNode) {
|
||||
button.append(tooltip);
|
||||
}
|
||||
|
||||
const element = this.#editor.getImageForAltText();
|
||||
element?.setAttribute("aria-describedby", tooltip.id);
|
||||
}
|
||||
}
|
||||
|
||||
export { AltText };
|
|
@ -23,6 +23,7 @@ import {
|
|||
KeyboardManager,
|
||||
} from "./tools.js";
|
||||
import { FeatureTest, shadow, unreachable } from "../../shared/util.js";
|
||||
import { AltText } from "./alt_text.js";
|
||||
import { EditorToolbar } from "./toolbar.js";
|
||||
import { noContextMenu } from "../display_utils.js";
|
||||
|
||||
|
@ -41,17 +42,7 @@ import { noContextMenu } from "../display_utils.js";
|
|||
class AnnotationEditor {
|
||||
#allResizerDivs = null;
|
||||
|
||||
#altText = "";
|
||||
|
||||
#altTextDecorative = false;
|
||||
|
||||
#altTextButton = null;
|
||||
|
||||
#altTextTooltip = null;
|
||||
|
||||
#altTextTooltipTimeout = null;
|
||||
|
||||
#altTextWasFromKeyBoard = false;
|
||||
#altText = null;
|
||||
|
||||
#keepAspectRatio = false;
|
||||
|
||||
|
@ -95,10 +86,6 @@ class AnnotationEditor {
|
|||
|
||||
static _zIndex = 1;
|
||||
|
||||
// When one of the dimensions of an editor is smaller than this value, the
|
||||
// button to edit the alt text is visually moved outside of the editor.
|
||||
static SMALL_EDITOR_SIZE = 0;
|
||||
|
||||
static get _resizerKeyboardManager() {
|
||||
const resize = AnnotationEditor.prototype._resizeWithKeyboard;
|
||||
const small = AnnotationEditorUIManager.TRANSLATE_SMALL;
|
||||
|
@ -631,11 +618,6 @@ class AnnotationEditor {
|
|||
if (!this.#keepAspectRatio) {
|
||||
this.div.style.height = `${((100 * height) / parentHeight).toFixed(2)}%`;
|
||||
}
|
||||
this.#altTextButton?.classList.toggle(
|
||||
"small",
|
||||
width < AnnotationEditor.SMALL_EDITOR_SIZE ||
|
||||
height < AnnotationEditor.SMALL_EDITOR_SIZE
|
||||
);
|
||||
}
|
||||
|
||||
fixDims() {
|
||||
|
@ -709,7 +691,7 @@ class AnnotationEditor {
|
|||
return;
|
||||
}
|
||||
|
||||
this.#toggleAltTextButton(false);
|
||||
this.#altText?.toggle(false);
|
||||
|
||||
const boundResizerPointermove = this.#resizerPointermove.bind(this, name);
|
||||
const savedDraggable = this._isDraggable;
|
||||
|
@ -732,7 +714,7 @@ class AnnotationEditor {
|
|||
|
||||
const pointerUpCallback = () => {
|
||||
this.parent.togglePointerEvents(true);
|
||||
this.#toggleAltTextButton(true);
|
||||
this.#altText?.toggle(true);
|
||||
this._isDraggable = savedDraggable;
|
||||
window.removeEventListener("pointerup", pointerUpCallback);
|
||||
window.removeEventListener("blur", pointerUpCallback);
|
||||
|
@ -917,136 +899,19 @@ class AnnotationEditor {
|
|||
this.fixAndSetPosition();
|
||||
}
|
||||
|
||||
async addAltTextButton() {
|
||||
if (this.#altTextButton) {
|
||||
return;
|
||||
}
|
||||
const altText = (this.#altTextButton = document.createElement("button"));
|
||||
altText.className = "altText";
|
||||
const msg = await AnnotationEditor._l10nPromise.get(
|
||||
"pdfjs-editor-alt-text-button-label"
|
||||
);
|
||||
altText.textContent = msg;
|
||||
altText.setAttribute("aria-label", msg);
|
||||
altText.tabIndex = "0";
|
||||
altText.addEventListener("contextmenu", noContextMenu);
|
||||
altText.addEventListener("pointerdown", event => event.stopPropagation());
|
||||
|
||||
const onClick = event => {
|
||||
this.#altTextButton.hidden = true;
|
||||
event.preventDefault();
|
||||
this._uiManager.editAltText(this);
|
||||
};
|
||||
altText.addEventListener("click", onClick, { capture: true });
|
||||
altText.addEventListener("keydown", event => {
|
||||
if (event.target === altText && event.key === "Enter") {
|
||||
this.#altTextWasFromKeyBoard = true;
|
||||
onClick(event);
|
||||
}
|
||||
});
|
||||
this.#setAltTextButtonState();
|
||||
this.div.append(altText);
|
||||
if (!AnnotationEditor.SMALL_EDITOR_SIZE) {
|
||||
// We take the width of the alt text button and we add 40% to it to be
|
||||
// sure to have enough space for it.
|
||||
const PERCENT = 40;
|
||||
AnnotationEditor.SMALL_EDITOR_SIZE = Math.min(
|
||||
128,
|
||||
Math.round(altText.getBoundingClientRect().width * (1 + PERCENT / 100))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async #setAltTextButtonState() {
|
||||
const button = this.#altTextButton;
|
||||
if (!button) {
|
||||
return;
|
||||
}
|
||||
if (!this.#altText && !this.#altTextDecorative) {
|
||||
button.classList.remove("done");
|
||||
this.#altTextTooltip?.remove();
|
||||
return;
|
||||
}
|
||||
button.classList.add("done");
|
||||
|
||||
AnnotationEditor._l10nPromise
|
||||
.get("pdfjs-editor-alt-text-edit-button-label")
|
||||
.then(msg => {
|
||||
button.setAttribute("aria-label", msg);
|
||||
});
|
||||
let tooltip = this.#altTextTooltip;
|
||||
if (!tooltip) {
|
||||
this.#altTextTooltip = tooltip = document.createElement("span");
|
||||
tooltip.className = "tooltip";
|
||||
tooltip.setAttribute("role", "tooltip");
|
||||
const id = (tooltip.id = `alt-text-tooltip-${this.id}`);
|
||||
button.setAttribute("aria-describedby", id);
|
||||
|
||||
const DELAY_TO_SHOW_TOOLTIP = 100;
|
||||
button.addEventListener("mouseenter", () => {
|
||||
this.#altTextTooltipTimeout = setTimeout(() => {
|
||||
this.#altTextTooltipTimeout = null;
|
||||
this.#altTextTooltip.classList.add("show");
|
||||
this._uiManager._eventBus.dispatch("reporttelemetry", {
|
||||
source: this,
|
||||
details: {
|
||||
type: "editing",
|
||||
subtype: this.editorType,
|
||||
data: {
|
||||
action: "alt_text_tooltip",
|
||||
},
|
||||
},
|
||||
});
|
||||
}, DELAY_TO_SHOW_TOOLTIP);
|
||||
});
|
||||
button.addEventListener("mouseleave", () => {
|
||||
if (this.#altTextTooltipTimeout) {
|
||||
clearTimeout(this.#altTextTooltipTimeout);
|
||||
this.#altTextTooltipTimeout = null;
|
||||
}
|
||||
this.#altTextTooltip?.classList.remove("show");
|
||||
});
|
||||
}
|
||||
tooltip.innerText = this.#altTextDecorative
|
||||
? await AnnotationEditor._l10nPromise.get(
|
||||
"pdfjs-editor-alt-text-decorative-tooltip"
|
||||
)
|
||||
: this.#altText;
|
||||
|
||||
if (!tooltip.parentNode) {
|
||||
button.append(tooltip);
|
||||
}
|
||||
|
||||
const element = this.getImageForAltText();
|
||||
element?.setAttribute("aria-describedby", tooltip.id);
|
||||
}
|
||||
|
||||
#toggleAltTextButton(enabled = false) {
|
||||
if (!this.#altTextButton) {
|
||||
return;
|
||||
}
|
||||
if (!enabled && this.#altTextTooltipTimeout) {
|
||||
clearTimeout(this.#altTextTooltipTimeout);
|
||||
this.#altTextTooltipTimeout = null;
|
||||
}
|
||||
this.#altTextButton.disabled = !enabled;
|
||||
}
|
||||
|
||||
altTextFinish() {
|
||||
if (!this.#altTextButton) {
|
||||
return;
|
||||
}
|
||||
this.#altTextButton.hidden = false;
|
||||
this.#altTextButton.focus({ focusVisible: this.#altTextWasFromKeyBoard });
|
||||
this.#altTextWasFromKeyBoard = false;
|
||||
this.#altText?.finish();
|
||||
}
|
||||
|
||||
addEditToolbar() {
|
||||
async addEditToolbar() {
|
||||
if (this.#editToolbar || this.#isInEditMode) {
|
||||
return;
|
||||
}
|
||||
this.#editToolbar = new EditorToolbar(this);
|
||||
this.div.append(this.#editToolbar.render());
|
||||
if (this.#altText) {
|
||||
this.#editToolbar.addAltTextButton(await this.#altText.render());
|
||||
}
|
||||
}
|
||||
|
||||
removeEditToolbar() {
|
||||
|
@ -1055,29 +920,37 @@ class AnnotationEditor {
|
|||
}
|
||||
this.#editToolbar.remove();
|
||||
this.#editToolbar = null;
|
||||
|
||||
// We destroy the alt text but we don't null it because we want to be able
|
||||
// to restore it in case the user undoes the deletion.
|
||||
this.#altText?.destroy();
|
||||
}
|
||||
|
||||
getClientDimensions() {
|
||||
return this.div.getBoundingClientRect();
|
||||
}
|
||||
|
||||
async addAltTextButton() {
|
||||
if (this.#altText) {
|
||||
return;
|
||||
}
|
||||
AltText.initialize(AnnotationEditor._l10nPromise);
|
||||
this.#altText = new AltText(this);
|
||||
await this.addEditToolbar();
|
||||
}
|
||||
|
||||
get altTextData() {
|
||||
return {
|
||||
altText: this.#altText,
|
||||
decorative: this.#altTextDecorative,
|
||||
};
|
||||
return this.#altText?.data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the alt text data.
|
||||
*/
|
||||
set altTextData({ altText, decorative }) {
|
||||
if (this.#altText === altText && this.#altTextDecorative === decorative) {
|
||||
set altTextData(data) {
|
||||
if (!this.#altText) {
|
||||
return;
|
||||
}
|
||||
this.#altText = altText;
|
||||
this.#altTextDecorative = decorative;
|
||||
this.#setAltTextButtonState();
|
||||
this.#altText.data = data;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1413,11 +1286,6 @@ class AnnotationEditor {
|
|||
this._uiManager.removeEditor(this);
|
||||
}
|
||||
|
||||
// The editor is removed so we can remove the alt text button and if it's
|
||||
// restored then it's up to the subclass to add it back.
|
||||
this.#altTextButton?.remove();
|
||||
this.#altTextButton = null;
|
||||
this.#altTextTooltip = null;
|
||||
if (this.#moveInDOMTimeout) {
|
||||
clearTimeout(this.#moveInDOMTimeout);
|
||||
this.#moveInDOMTimeout = null;
|
||||
|
@ -1585,7 +1453,17 @@ class AnnotationEditor {
|
|||
select() {
|
||||
this.makeResizable();
|
||||
this.div?.classList.add("selectedEditor");
|
||||
this.addEditToolbar();
|
||||
if (!this.#editToolbar) {
|
||||
this.addEditToolbar().then(() => {
|
||||
if (this.div?.classList.contains("selectedEditor")) {
|
||||
// The editor can have been unselected while we were waiting for the
|
||||
// edit toolbar to be created, hence we want to be sure that this
|
||||
// editor is still selected.
|
||||
this.#editToolbar?.show();
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
this.#editToolbar?.show();
|
||||
}
|
||||
|
||||
|
@ -1614,21 +1492,13 @@ class AnnotationEditor {
|
|||
* When the user disables the editing mode some editors can change some of
|
||||
* their properties.
|
||||
*/
|
||||
disableEditing() {
|
||||
if (this.#altTextButton) {
|
||||
this.#altTextButton.hidden = true;
|
||||
}
|
||||
}
|
||||
disableEditing() {}
|
||||
|
||||
/**
|
||||
* When the user enables the editing mode some editors can change some of
|
||||
* their properties.
|
||||
*/
|
||||
enableEditing() {
|
||||
if (this.#altTextButton) {
|
||||
this.#altTextButton.hidden = false;
|
||||
}
|
||||
}
|
||||
enableEditing() {}
|
||||
|
||||
/**
|
||||
* The editor is about to be edited.
|
||||
|
|
|
@ -95,7 +95,10 @@ class EditorToolbar {
|
|||
const button = document.createElement("button");
|
||||
button.className = "delete";
|
||||
button.tabIndex = 0;
|
||||
button.setAttribute("data-l10n-id", "pdfjs-editor-remove-button");
|
||||
button.setAttribute(
|
||||
"data-l10n-id",
|
||||
`pdfjs-editor-remove-${this.#editor.editorType}-button`
|
||||
);
|
||||
this.#addListenersToElement(button);
|
||||
button.addEventListener("click", e => {
|
||||
this.#editor._uiManager.delete();
|
||||
|
@ -103,6 +106,17 @@ class EditorToolbar {
|
|||
this.#buttons.append(button);
|
||||
}
|
||||
|
||||
addAltTextButton(button) {
|
||||
this.#addListenersToElement(button);
|
||||
this.#buttons.prepend(button, this.#divider);
|
||||
}
|
||||
|
||||
get #divider() {
|
||||
const divider = document.createElement("div");
|
||||
divider.className = "divider";
|
||||
return divider;
|
||||
}
|
||||
|
||||
remove() {
|
||||
this.#toolbar.remove();
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue