mirror of
https://github.com/livebook-dev/livebook.git
synced 2025-02-21 21:36:17 +08:00
155 lines
4.2 KiB
JavaScript
155 lines
4.2 KiB
JavaScript
import { getAttributeOrThrow, parseBoolean } from "../lib/attribute";
|
|
import { cancelEvent, isEditableElement, isMacOS } from "../lib/utils";
|
|
|
|
/**
|
|
* A hook for ControlComponent to handle user keyboard interactions.
|
|
*
|
|
* ## Configuration
|
|
*
|
|
* * `data-cell-id` - id of the cell in which the control is rendered
|
|
*
|
|
* * `data-default-handlers` - whether keyboard events should be
|
|
* intercepted and canceled, disabling session shortcuts. Must be
|
|
* one of "off", "on", or "disable_only"
|
|
*
|
|
* * `data-keydown-enabled` - whether keydown events should be listened to
|
|
*
|
|
* * `data-keyup-enabled` - whether keyup events should be listened to
|
|
*
|
|
* * `data-target` - the target to send live events to
|
|
*/
|
|
const KeyboardControl = {
|
|
mounted() {
|
|
this.props = this.getProps();
|
|
|
|
this._handleDocumentKeyDown = this.handleDocumentKeyDown.bind(this);
|
|
this._handleDocumentKeyUp = this.handleDocumentKeyUp.bind(this);
|
|
this._handleDocumentFocus = this.handleDocumentFocus.bind(this);
|
|
|
|
// We intentionally register on window rather than document, to
|
|
// intercept events as early on as possible, even before the
|
|
// session shortcuts
|
|
window.addEventListener("keydown", this._handleDocumentKeyDown, true);
|
|
window.addEventListener("keyup", this._handleDocumentKeyUp, true);
|
|
// Note: the focus event doesn't bubble, so we register for the
|
|
// capture phase
|
|
window.addEventListener("focus", this._handleDocumentFocus, true);
|
|
},
|
|
|
|
updated() {
|
|
this.props = this.getProps();
|
|
},
|
|
|
|
destroyed() {
|
|
window.removeEventListener("keydown", this._handleDocumentKeyDown, true);
|
|
window.removeEventListener("keyup", this._handleDocumentKeyUp, true);
|
|
window.removeEventListener("focus", this._handleDocumentFocus, true);
|
|
},
|
|
|
|
getProps() {
|
|
return {
|
|
cellId: getAttributeOrThrow(this.el, "data-cell-id"),
|
|
defaultHandlers: getAttributeOrThrow(this.el, "data-default-handlers"),
|
|
isKeydownEnabled: getAttributeOrThrow(
|
|
this.el,
|
|
"data-keydown-enabled",
|
|
parseBoolean
|
|
),
|
|
isKeyupEnabled: getAttributeOrThrow(
|
|
this.el,
|
|
"data-keyup-enabled",
|
|
parseBoolean
|
|
),
|
|
target: getAttributeOrThrow(this.el, "data-target"),
|
|
};
|
|
},
|
|
|
|
handleDocumentKeyDown(event) {
|
|
if (
|
|
this.isKeyboardToggle(event) &&
|
|
!isEditableElement(document.activeElement)
|
|
) {
|
|
cancelEvent(event);
|
|
this.keyboardEnabled() ? this.disableKeyboard() : this.enableKeyboard();
|
|
return;
|
|
}
|
|
|
|
if (this.keyboardEnabled()) {
|
|
if (this.props.defaultHandlers !== "on") {
|
|
cancelEvent(event);
|
|
}
|
|
|
|
if (event.repeat) {
|
|
return;
|
|
}
|
|
|
|
if (this.props.isKeydownEnabled) {
|
|
const { key } = event;
|
|
this.pushEventTo(this.props.target, "keydown", { key });
|
|
}
|
|
}
|
|
},
|
|
|
|
handleDocumentKeyUp(event) {
|
|
if (this.keyboardEnabled()) {
|
|
if (this.props.defaultHandlers !== "on") {
|
|
cancelEvent(event);
|
|
}
|
|
|
|
if (this.props.isKeyupEnabled) {
|
|
const { key } = event;
|
|
this.pushEventTo(this.props.target, "keyup", { key });
|
|
}
|
|
}
|
|
},
|
|
|
|
handleDocumentFocus(event) {
|
|
if (this.props.isKeydownEnabled && isEditableElement(event.target)) {
|
|
this.disableKeyboard();
|
|
}
|
|
},
|
|
|
|
enableKeyboard() {
|
|
if (!this.keyboardEnabled()) {
|
|
this.pushEventTo(this.props.target, "enable_keyboard", {});
|
|
}
|
|
},
|
|
|
|
disableKeyboard() {
|
|
if (this.keyboardEnabled()) {
|
|
this.pushEventTo(this.props.target, "disable_keyboard", {});
|
|
}
|
|
},
|
|
|
|
keyboardEnabled() {
|
|
return this.props.isKeydownEnabled || this.props.isKeyupEnabled;
|
|
},
|
|
|
|
isKeyboardToggle(event) {
|
|
if (event.repeat) {
|
|
return false;
|
|
}
|
|
|
|
const { metaKey, ctrlKey, key } = event;
|
|
const cmd = isMacOS() ? metaKey : ctrlKey;
|
|
|
|
if (cmd && key === "k" && this.isCellFocused()) {
|
|
return (
|
|
!this.keyboardEnabled() ||
|
|
["on", "disable_only"].includes(this.props.defaultHandlers)
|
|
);
|
|
} else {
|
|
return false;
|
|
}
|
|
},
|
|
|
|
isCellFocused() {
|
|
const sessionEl = this.el.closest("[data-el-session]");
|
|
return (
|
|
sessionEl &&
|
|
sessionEl.getAttribute("data-js-focused-id") === this.props.cellId
|
|
);
|
|
},
|
|
};
|
|
|
|
export default KeyboardControl;
|