Skip to content

Commit

Permalink
fix: gutter hover tooltip a11y improvements
Browse files Browse the repository at this point in the history
  • Loading branch information
babalaui committed Feb 19, 2025
1 parent 09fba2e commit 8fe9581
Show file tree
Hide file tree
Showing 3 changed files with 116 additions and 49 deletions.
8 changes: 4 additions & 4 deletions src/keyboard/gutter_handler_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -149,11 +149,11 @@ module.exports = {

// Click annotation.
emit(keys["enter"]);

setTimeout(function() {
// Check annotation is rendered.
editor.renderer.$loop._flush();
var tooltip = editor.container.querySelector(".ace_tooltip");
var tooltip = editor.container.querySelector(".ace_gutter-tooltip");
assert.ok(/error test/.test(tooltip.textContent));

// Press escape to dismiss the tooltip.
Expand Down Expand Up @@ -198,7 +198,7 @@ module.exports = {
setTimeout(function() {
// Check annotation is rendered.
editor.renderer.$loop._flush();
var tooltip = editor.container.querySelector(".ace_tooltip");
var tooltip = editor.container.querySelector(".ace_gutter-tooltip");
assert.ok(/error test/.test(tooltip.textContent));

// Press escape to dismiss the tooltip.
Expand All @@ -214,7 +214,7 @@ module.exports = {
setTimeout(function() {
// Check annotation is rendered.
editor.renderer.$loop._flush();
var tooltip = editor.container.querySelector(".ace_tooltip");
var tooltip = editor.container.querySelector(".ace_gutter-tooltip");
assert.ok(/warning test/.test(tooltip.textContent));

// Press escape to dismiss the tooltip.
Expand Down
53 changes: 40 additions & 13 deletions src/mouse/default_gutter_handler.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,12 @@ var dom = require("../lib/dom");
var event = require("../lib/event");
var Tooltip = require("../tooltip").Tooltip;
var nls = require("../config").nls;
var lang = require("../lib/lang");

const GUTTER_TOOLTIP_LEFT_OFFSET = 5;
const GUTTER_TOOLTIP_TOP_OFFSET = 3;
exports.GUTTER_TOOLTIP_LEFT_OFFSET = GUTTER_TOOLTIP_LEFT_OFFSET;
exports.GUTTER_TOOLTIP_TOP_OFFSET = GUTTER_TOOLTIP_TOP_OFFSET;


/**
* @param {MouseHandler} mouseHandler
Expand All @@ -15,7 +20,7 @@ var lang = require("../lib/lang");
function GutterHandler(mouseHandler) {
var editor = mouseHandler.editor;
var gutter = editor.renderer.$gutterLayer;
var tooltip = new GutterTooltip(editor);
var tooltip = new GutterTooltip(editor, true);

mouseHandler.editor.setDefaultHandler("guttermousedown", function(e) {
if (!editor.isFocused() || e.getButton() != 0)
Expand Down Expand Up @@ -61,6 +66,8 @@ function GutterHandler(mouseHandler) {
return;

editor.on("mousewheel", hideTooltip);
editor.on("changeSession", hideTooltip);
window.addEventListener("keydown", hideTooltip, true);

if (mouseHandler.$tooltipFollowsMouse) {
moveTooltip(mouseEvent);
Expand All @@ -71,20 +78,28 @@ function GutterHandler(mouseHandler) {
var gutterElement = gutterCell.element.querySelector(".ace_gutter_annotation");
var rect = gutterElement.getBoundingClientRect();
var style = tooltip.getElement().style;
style.left = rect.right + "px";
style.top = rect.bottom + "px";
style.left = (rect.right - GUTTER_TOOLTIP_LEFT_OFFSET) + "px";
style.top = (rect.bottom - GUTTER_TOOLTIP_TOP_OFFSET) + "px";
} else {
moveTooltip(mouseEvent);
}
}
}

function hideTooltip() {
function hideTooltip(e) {
// dont close tooltip in case the user wants to copy text from it (Ctrl/Meta + C)
if (e && e.type === "keydown" && (e.ctrlKey || e.metaKey))
return;
// in case mouse moved but is still on the tooltip, dont close it
if (e && e.type === "mouseout" && (!e.relatedTarget || tooltip.getElement().contains(e.relatedTarget)))
return;

Check warning on line 95 in src/mouse/default_gutter_handler.js

View check run for this annotation

Codecov / codecov/patch

src/mouse/default_gutter_handler.js#L95

Added line #L95 was not covered by tests
if (tooltipTimeout)
tooltipTimeout = clearTimeout(tooltipTimeout);
if (tooltip.isOpen) {
tooltip.hideTooltip();
editor.off("mousewheel", hideTooltip);
editor.off("changeSession", hideTooltip);
window.removeEventListener("keydown", hideTooltip, true);
}
}

Expand All @@ -107,34 +122,46 @@ function GutterHandler(mouseHandler) {
tooltipTimeout = null;
if (mouseEvent && !mouseHandler.isMousePressed)
showTooltip();
else
hideTooltip();
}, 50);
});

event.addListener(editor.renderer.$gutter, "mouseout", function(e) {
mouseEvent = null;
if (!tooltip.isOpen || tooltipTimeout)
if (!tooltip.isOpen)

Check warning on line 130 in src/mouse/default_gutter_handler.js

View check run for this annotation

Codecov / codecov/patch

src/mouse/default_gutter_handler.js#L130

Added line #L130 was not covered by tests
return;

tooltipTimeout = setTimeout(function() {
tooltipTimeout = null;
hideTooltip();
hideTooltip(e);

Check warning on line 135 in src/mouse/default_gutter_handler.js

View check run for this annotation

Codecov / codecov/patch

src/mouse/default_gutter_handler.js#L135

Added line #L135 was not covered by tests
}, 50);
}, editor);

editor.on("changeSession", hideTooltip);
editor.on("input", hideTooltip);
}

exports.GutterHandler = GutterHandler;

class GutterTooltip extends Tooltip {
constructor(editor) {
constructor(editor, isHover = false) {
super(editor.container);
this.editor = editor;
/**@type {Number | Undefined}*/
this.visibleTooltipRow;
var el = this.getElement();
el.setAttribute("role", "tooltip");
el.style.pointerEvents = "auto";
if (isHover) {
this.onMouseOut = this.onMouseOut.bind(this);
el.addEventListener("mouseout", this.onMouseOut);
}
}

// handler needed to hide tooltip after mouse hovers from tooltip to editor
onMouseOut(e) {
if (!this.isOpen) return;

Check warning on line 159 in src/mouse/default_gutter_handler.js

View check run for this annotation

Codecov / codecov/patch

src/mouse/default_gutter_handler.js#L158-L159

Added lines #L158 - L159 were not covered by tests

if (!e.relatedTarget || this.getElement().contains(e.relatedTarget)) return;

Check warning on line 161 in src/mouse/default_gutter_handler.js

View check run for this annotation

Codecov / codecov/patch

src/mouse/default_gutter_handler.js#L161

Added line #L161 was not covered by tests

if (e && e.currentTarget.contains(e.relatedTarget)) return;
this.hideTooltip();

Check warning on line 164 in src/mouse/default_gutter_handler.js

View check run for this annotation

Codecov / codecov/patch

src/mouse/default_gutter_handler.js#L163-L164

Added lines #L163 - L164 were not covered by tests
}

setPosition(x, y) {
Expand Down
104 changes: 72 additions & 32 deletions src/mouse/default_gutter_handler_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ var Editor = require("../editor").Editor;
var Mode = require("../mode/java").Mode;
var VirtualRenderer = require("../virtual_renderer").VirtualRenderer;
var assert = require("../test/assertions");
var user = require("../test/user");
const {GUTTER_TOOLTIP_LEFT_OFFSET, GUTTER_TOOLTIP_TOP_OFFSET} = require("./default_gutter_handler");
var MouseEvent = function(type, opts){
var e = document.createEvent("MouseEvents");
e.initMouseEvent(/click|wheel/.test(type) ? type : "mouse" + type,
Expand All @@ -36,8 +38,7 @@ module.exports = {
editor = this.editor;
next();
},

"test: gutter error tooltip" : function() {
"test: gutter error tooltip" : function(done) {
var editor = this.editor;
var value = "";

Expand All @@ -56,11 +57,12 @@ module.exports = {
// Wait for the tooltip to appear after its timeout.
setTimeout(function() {
editor.renderer.$loop._flush();
var tooltip = editor.container.querySelector(".ace_tooltip");
var tooltip = editor.container.querySelector(".ace_gutter-tooltip");
assert.ok(/error test/.test(tooltip.textContent));
}, 100);
done();
}, 100);
},
"test: gutter security tooltip" : function() {
"test: gutter security tooltip" : function(done) {
var editor = this.editor;
var value = "";

Expand All @@ -79,11 +81,12 @@ module.exports = {
// Wait for the tooltip to appear after its timeout.
setTimeout(function() {
editor.renderer.$loop._flush();
var tooltip = editor.container.querySelector(".ace_tooltip");
var tooltip = editor.container.querySelector(".ace_gutter-tooltip");
assert.ok(/security finding test/.test(tooltip.textContent));
}, 100);
done();
}, 100);
},
"test: gutter warning tooltip" : function() {
"test: gutter warning tooltip" : function(done) {
var editor = this.editor;
var value = "";

Expand All @@ -102,11 +105,12 @@ module.exports = {
// Wait for the tooltip to appear after its timeout.
setTimeout(function() {
editor.renderer.$loop._flush();
var tooltip = editor.container.querySelector(".ace_tooltip");
var tooltip = editor.container.querySelector(".ace_gutter-tooltip");
assert.ok(/warning test/.test(tooltip.textContent));
}, 100);
done();
}, 100);
},
"test: gutter info tooltip" : function() {
"test: gutter info tooltip" : function(done) {
var editor = this.editor;
var value = "";

Expand All @@ -125,11 +129,12 @@ module.exports = {
// Wait for the tooltip to appear after its timeout.
setTimeout(function() {
editor.renderer.$loop._flush();
var tooltip = editor.container.querySelector(".ace_tooltip");
var tooltip = editor.container.querySelector(".ace_gutter-tooltip");
assert.ok(/info test/.test(tooltip.textContent));
}, 100);
done();
}, 100);
},
"test: gutter hint tooltip" : function() {
"test: gutter hint tooltip" : function(done) {
var editor = this.editor;
var value = "";

Expand All @@ -148,9 +153,10 @@ module.exports = {
// Wait for the tooltip to appear after its timeout.
setTimeout(function() {
editor.renderer.$loop._flush();
var tooltip = editor.container.querySelector(".ace_tooltip");
var tooltip = editor.container.querySelector(".ace_gutter-tooltip");
assert.ok(/suggestion test/.test(tooltip.textContent));
}, 100);
done();
}, 100);
},
"test: gutter svg icons" : function() {
var editor = this.editor;
Expand All @@ -169,7 +175,7 @@ module.exports = {
var annotation = line.children[2].firstChild;
assert.ok(/ace_icon_svg/.test(annotation.className));
},
"test: error show up in fold" : function() {
"test: error show up in fold" : function(done) {
var editor = this.editor;
var value = "x {" + "\n".repeat(50) + "}";
value = value.repeat(50);
Expand Down Expand Up @@ -200,11 +206,12 @@ module.exports = {
// Wait for the tooltip to appear after its timeout.
setTimeout(function() {
editor.renderer.$loop._flush();
var tooltip = editor.container.querySelector(".ace_tooltip");
var tooltip = editor.container.querySelector(".ace_gutter-tooltip");
assert.ok(/error in folded/.test(tooltip.textContent));
}, 100);
done();
}, 50);
},
"test: security show up in fold" : function() {
"test: security show up in fold" : function(done) {
var editor = this.editor;
var value = "x {" + "\n".repeat(50) + "}";
value = value.repeat(50);
Expand Down Expand Up @@ -235,11 +242,12 @@ module.exports = {
// Wait for the tooltip to appear after its timeout.
setTimeout(function() {
editor.renderer.$loop._flush();
var tooltip = editor.container.querySelector(".ace_tooltip");
var tooltip = editor.container.querySelector(".ace_gutter-tooltip");
assert.ok(/security finding in folded/.test(tooltip.textContent));
}, 100);
done();
}, 100);
},
"test: warning show up in fold" : function() {
"test: warning show up in fold" : function(done) {
var editor = this.editor;
var value = "x {" + "\n".repeat(50) + "}";
value = value.repeat(50);
Expand Down Expand Up @@ -270,9 +278,10 @@ module.exports = {
// Wait for the tooltip to appear after its timeout.
setTimeout(function() {
editor.renderer.$loop._flush();
var tooltip = editor.container.querySelector(".ace_tooltip");
var tooltip = editor.container.querySelector(".ace_gutter-tooltip");
assert.ok(/warning in folded/.test(tooltip.textContent));
}, 100);
done();
}, 100);
},
"test: info not show up in fold" : function() {
var editor = this.editor;
Expand Down Expand Up @@ -396,12 +405,12 @@ module.exports = {
// Wait for the tooltip to appear after its timeout.
setTimeout(function() {
editor.renderer.$loop._flush();
var tooltip = editor.container.querySelector(".ace_tooltip");
var tooltip = editor.container.querySelector(".ace_gutter-tooltip");
assert.ok(/error test/.test(tooltip.textContent));
assert.equal(tooltip.style.left, `${rect.right}px`);
assert.equal(tooltip.style.top, `${rect.bottom}px`);
assert.equal(tooltip.style.left, `${rect.right - GUTTER_TOOLTIP_LEFT_OFFSET}px`);
assert.equal(tooltip.style.top, `${rect.bottom - GUTTER_TOOLTIP_TOP_OFFSET}px`);
done();
}, 100);
}, 100);
},
"test: gutter tooltip should properly display special characters (\" ' & <)" : function(done) {
var editor = this.editor;
Expand All @@ -422,12 +431,43 @@ module.exports = {
// Wait for the tooltip to appear after its timeout.
setTimeout(function() {
editor.renderer.$loop._flush();
var tooltip = editor.container.querySelector(".ace_tooltip");
var tooltip = editor.container.querySelector(".ace_gutter-tooltip");
assert.ok(/special characters " ' & </.test(tooltip.textContent));
done();
}, 100);
}, 100);
},
"test: gutter hover tooltip should remain open when pressing ctrl key combination" : function(done) {
var editor = this.editor;
var value = "";

editor.session.setMode(new Mode());
editor.setValue(value, -1);
editor.session.setAnnotations([{row: 0, column: 0, text: "error test", type: "error"}]);
editor.renderer.$loop._flush();

var lines = editor.renderer.$gutterLayer.$lines;
var annotation = lines.cells[0].element;
assert.ok(/ace_error/.test(annotation.className));

var rect = annotation.getBoundingClientRect();
annotation.dispatchEvent(new MouseEvent("move", {x: rect.left, y: rect.top}));

// Wait for the tooltip to appear after its timeout.
setTimeout(function () {
editor.renderer.$loop._flush();
var tooltip = editor.container.querySelector(".ace_gutter-tooltip");
assert.ok(/error test/.test(tooltip.textContent));
user.type("Ctrl-C");
tooltip = editor.container.querySelector(".ace_gutter-tooltip");
assert.ok(/error test/.test(tooltip.textContent));
// also verify if it closes when presses another key
user.type("Escape");
tooltip = editor.container.querySelector(".ace_gutter-tooltip");
assert.strictEqual(tooltip, undefined);
done();
}, 100);
},

tearDown : function() {
this.editor.destroy();
document.body.removeChild(this.editor.container);
Expand Down

0 comments on commit 8fe9581

Please sign in to comment.