Skip to content

Commit

Permalink
make sure we get arrow key events in jupyter notebooks
Browse files Browse the repository at this point in the history
  • Loading branch information
jeromedockes committed Sep 16, 2024
1 parent 338f235 commit 26cc22d
Showing 1 changed file with 78 additions and 0 deletions.
78 changes: 78 additions & 0 deletions skrub/_reporting/_data/templates/report.js
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,9 @@ if (customElements.get('skrub-table-report') === undefined) {
this.activate();
event.preventDefault();
});
// See forwardKeyboardEvent for details about captureKeys
this.elem.dataset.captureKeys =
"ArrowRight ArrowLeft ArrowUp ArrowDown Escape";
this.elem.setAttribute("tabindex", -1);
this.elem.oncopy = (event) => this.copyCell(event);
}
Expand Down Expand Up @@ -318,6 +321,8 @@ if (customElements.get('skrub-table-report') === undefined) {
this.nTailRows = this.elem.dataset.nTailRows;
this.nCols = this.elem.dataset.nCols;
this.elem.addEventListener('keydown', (e) => this.onKeyDown(e));
this.elem.addEventListener('skrub-keydown', (e) => this.onKeyDown(
unwrapSkrubKeyDown(e)));
}

onKeyDown(event) {
Expand Down Expand Up @@ -493,8 +498,13 @@ if (customElements.get('skrub-table-report') === undefined) {
}
this.lastTab = tab;
tab.addEventListener("click", () => this.selectTab(tab));
// See forwardKeyboardEvent for details about captureKeys
tab.dataset.captureKeys = "ArrowRight ArrowLeft";
tab.addEventListener("keydown", (event) => this.onKeyDown(
event));
tab.addEventListener("skrub-keydown", (event) => this
.onKeyDown(
unwrapSkrubKeyDown(event)));
});
this.selectTab(this.firstTab, false);
}
Expand Down Expand Up @@ -671,4 +681,72 @@ if (customElements.get('skrub-table-report') === undefined) {
function hasModifier(event) {
return event.ctrlKey || event.metaKey || event.shiftKey || event.altKey;
}

/* Jupyter notebooks and vscode stop the propagation of some keyboard events
during the capture phase to implement their keyboard shortcuts etc. When an
element inside the TableReport has the keyboard focus and wants to react on
that event (eg in the sample table arrow keys allow selecting a neighbouring
cell) we need to override that behavior.
We do that by adding an event listener on the window that is triggered
during the capture phase. If it can make sure the key press is for a report
element that will react to it, we stop its propagation (to avoid eg the
notebook jumping to the next jupyter code cell) and dispatch an event on the
targeted element. To make sure it does not get handled again by the listener
on the window and cause infinite recursion, we dispatch a custom event
instead of a KeyDown event.
This capture is only enabled if we detect the report is inserted in a page
where it is needed, by checking if there are elements in the page with class
names that are used by jupyter or vscode.
*/
function forwardKeyboardEvent(e) {
if (e.eventPhase !== 1) {
return;
}
if (e.target.tagName !== "SKRUB-TABLE-REPORT") {
return;
}
if (hasModifier(e)) {
return;
}
const target = e.target.shadowRoot.activeElement;
// only capture the event if the element lists the key in the keys it
// wants to capture ie in captureKeys
const wantsKey = target?.dataset.captureKeys?.split(/\s+/).includes(e.key);
if (!wantsKey) {
return;
}
const newEvent = new CustomEvent('skrub-keydown', {
bubbles: true,
cancelable: true,
detail: {
key: e.key,
code: e.code,
shiftKey: e.shiftKey,
altKey: e.altKey,
ctrlKey: e.ctrlKey,
metaKey: e.metaKey,
}
});
target.dispatchEvent(newEvent);
e.stopImmediatePropagation();
e.preventDefault();
}

/* Helper to unpack the custom event (see forwardKeyboardEvent above) and
make it look like a regular KeyDown event. */
function unwrapSkrubKeyDown(e) {
return {
preventDefault: () => {},
stopPropagation: () => {},
target: e.target,
...e.detail
};
}

if (document.querySelector(".jp-Cell, .widgetarea")) {
window.addEventListener("keydown", forwardKeyboardEvent, true);
}

}

0 comments on commit 26cc22d

Please sign in to comment.