[Layout Engine] PC friendly element resize modifier keys; stuck key fix (#25935)
* keyboard fixes * add handling for the ctrl key (PC) * don't transmit modifier keys (no need; confuses PC)
This commit is contained in:
parent
9ea7720b50
commit
2b6769a088
|
@ -31,35 +31,35 @@ const setupHandler = (commit, target) => {
|
|||
const canvasPage = ancestorElement(target);
|
||||
if (!canvasPage) return;
|
||||
const canvasOrigin = canvasPage.getBoundingClientRect();
|
||||
window.onmousemove = ({ clientX, clientY, altKey, metaKey, shiftKey }) => {
|
||||
window.onmousemove = ({ clientX, clientY, altKey, metaKey, shiftKey, ctrlKey }) => {
|
||||
const { x, y } = localMousePosition(canvasOrigin, clientX, clientY);
|
||||
commit('cursorPosition', { x, y, altKey, metaKey, shiftKey });
|
||||
commit('cursorPosition', { x, y, altKey, metaKey, shiftKey, ctrlKey });
|
||||
};
|
||||
window.onmouseup = e => {
|
||||
e.stopPropagation();
|
||||
const { clientX, clientY, altKey, metaKey, shiftKey } = e;
|
||||
const { clientX, clientY, altKey, metaKey, shiftKey, ctrlKey } = e;
|
||||
const { x, y } = localMousePosition(canvasOrigin, clientX, clientY);
|
||||
commit('mouseEvent', { event: 'mouseUp', x, y, altKey, metaKey, shiftKey });
|
||||
commit('mouseEvent', { event: 'mouseUp', x, y, altKey, metaKey, shiftKey, ctrlKey });
|
||||
resetHandler();
|
||||
};
|
||||
};
|
||||
|
||||
const handleMouseMove = (
|
||||
commit,
|
||||
{ target, clientX, clientY, altKey, metaKey, shiftKey },
|
||||
{ target, clientX, clientY, altKey, metaKey, shiftKey, ctrlKey },
|
||||
isEditable
|
||||
) => {
|
||||
// mouse move must be handled even before an initial click
|
||||
if (!window.onmousemove && isEditable) {
|
||||
const { x, y } = localMousePosition(target, clientX, clientY);
|
||||
setupHandler(commit, target);
|
||||
commit('cursorPosition', { x, y, altKey, metaKey, shiftKey });
|
||||
commit('cursorPosition', { x, y, altKey, metaKey, shiftKey, ctrlKey });
|
||||
}
|
||||
};
|
||||
|
||||
const handleMouseDown = (commit, e, isEditable) => {
|
||||
e.stopPropagation();
|
||||
const { target, clientX, clientY, button, altKey, metaKey, shiftKey } = e;
|
||||
const { target, clientX, clientY, button, altKey, metaKey, shiftKey, ctrlKey } = e;
|
||||
if (button !== 0 || !isEditable) {
|
||||
resetHandler();
|
||||
return; // left-click and edit mode only
|
||||
|
@ -68,7 +68,7 @@ const handleMouseDown = (commit, e, isEditable) => {
|
|||
if (!ancestor) return;
|
||||
const { x, y } = localMousePosition(ancestor, clientX, clientY);
|
||||
setupHandler(commit, ancestor);
|
||||
commit('mouseEvent', { event: 'mouseDown', x, y, altKey, metaKey, shiftKey });
|
||||
commit('mouseEvent', { event: 'mouseDown', x, y, altKey, metaKey, shiftKey, ctrlKey });
|
||||
};
|
||||
|
||||
const keyCode = key => (key === 'Meta' ? 'MetaLeft' : 'Key' + key.toUpperCase());
|
||||
|
@ -97,6 +97,8 @@ const isNotTextInput = ({ tagName, type }) => {
|
|||
}
|
||||
};
|
||||
|
||||
const modifierKey = key => ['KeyALT', 'KeyCONTROL'].indexOf(keyCode(key)) > -1;
|
||||
|
||||
const handleKeyDown = (commit, e, isEditable, remove) => {
|
||||
const { key, target } = e;
|
||||
|
||||
|
@ -104,7 +106,7 @@ const handleKeyDown = (commit, e, isEditable, remove) => {
|
|||
if (isNotTextInput(target) && (key === 'Backspace' || key === 'Delete')) {
|
||||
e.preventDefault();
|
||||
remove();
|
||||
} else {
|
||||
} else if (!modifierKey(key)) {
|
||||
commit('keyboardEvent', {
|
||||
event: 'keyDown',
|
||||
code: keyCode(key), // convert to standard event code
|
||||
|
@ -114,7 +116,7 @@ const handleKeyDown = (commit, e, isEditable, remove) => {
|
|||
};
|
||||
|
||||
const handleKeyUp = (commit, { key }, isEditable) => {
|
||||
if (isEditable) {
|
||||
if (isEditable && !modifierKey(key)) {
|
||||
commit('keyboardEvent', {
|
||||
event: 'keyUp',
|
||||
code: keyCode(key), // convert to standard event code
|
||||
|
|
|
@ -6,6 +6,18 @@
|
|||
|
||||
const { select, selectReduce } = require('./state');
|
||||
|
||||
// Only needed to shuffle some modifier keys for Apple keyboards as per vector editing software conventions,
|
||||
// so it's OK that user agent strings are not reliable; in case it's spoofed, it'll just work with a slightly
|
||||
// different modifier key map (also, there aren't a lot of alternatives for OS / hw / keyboard detection).
|
||||
// It shouldn't fail in testing environments (node.js) either, where it can just return false, no need for
|
||||
// actually getting the OS on the server side.
|
||||
const appleKeyboard = Boolean(
|
||||
window &&
|
||||
window.navigator &&
|
||||
window.navigator.userAgent &&
|
||||
window.navigator.userAgent.match('Macintosh|iPhone|iPad')
|
||||
);
|
||||
|
||||
/**
|
||||
* Selectors directly from a state object
|
||||
*
|
||||
|
@ -27,81 +39,18 @@ const mouseButtonEvent = select(action => (action.type === 'mouseEvent' ? action
|
|||
primaryUpdate
|
||||
);
|
||||
|
||||
const keyboardEvent = select(action => (action.type === 'keyboardEvent' ? action.payload : null))(
|
||||
primaryUpdate
|
||||
);
|
||||
|
||||
const keyInfoFromMouseEvents = select(
|
||||
({ type, payload: { altKey, metaKey, shiftKey } }) =>
|
||||
type === 'cursorPosition' || type === 'mouseEvent' ? { altKey, metaKey, shiftKey } : null
|
||||
const keyFromMouse = select(
|
||||
({ type, payload: { altKey, metaKey, shiftKey, ctrlKey } }) =>
|
||||
type === 'cursorPosition' || type === 'mouseEvent' ? { altKey, metaKey, shiftKey, ctrlKey } : {}
|
||||
)(primaryUpdate);
|
||||
|
||||
const altTest = key => key.slice(0, 3).toLowerCase() === 'alt' || key === 'KeyALT';
|
||||
const metaTest = key => key.slice(0, 4).toLowerCase() === 'meta';
|
||||
const shiftTest = key => key === 'KeySHIFT' || key.slice(0, 5) === 'Shift';
|
||||
const deadKey1 = 'KeyDEAD';
|
||||
const deadKey2 = 'Key†';
|
||||
const metaHeld = select(appleKeyboard ? e => e.metaKey : e => e.altKey)(keyFromMouse);
|
||||
const optionHeld = select(appleKeyboard ? e => e.altKey : e => e.ctrlKey)(keyFromMouse);
|
||||
const shiftHeld = select(e => e.shiftKey)(keyFromMouse);
|
||||
|
||||
// Key states (up vs down) from keyboard events are trivially only captured if there's a keyboard event, and that only
|
||||
// happens if the user is interacting with the browser, and specifically, with the DOM subset that captures the keyboard
|
||||
// event. It's also clear that all keys, and importantly, modifier keys (alt, meta etc.) can alter state while the user
|
||||
// is not sending keyboard DOM events to the browser, eg. while using another tab or application. Similarly, an alt-tab
|
||||
// switch away from the browser will cause the registration of an `Alt down`, but not an `Alt up`, because that happens
|
||||
// in the switched-to application (https://github.com/elastic/kibana-canvas/issues/901).
|
||||
//
|
||||
// The solution is to also harvest modifier key (and in the future, maybe other key) statuses from mouse events, as these
|
||||
// modifier keys typically alter behavior while a pointer gesture is going on, in this case now, relaxing or tightening
|
||||
// snapping behavior. So we simply toggle the current key set up/down status (`lookup`) opportunistically.
|
||||
//
|
||||
// This function destructively modifies lookup, but could be made to work on immutable structures in the future.
|
||||
const updateKeyLookupFromMouseEvent = (lookup, keyInfoFromMouseEvent) => {
|
||||
Object.entries(keyInfoFromMouseEvent).forEach(([key, value]) => {
|
||||
if (metaTest(key)) {
|
||||
if (value) lookup.meta = true;
|
||||
else delete lookup.meta;
|
||||
}
|
||||
if (altTest(key)) {
|
||||
if (value) lookup.alt = true;
|
||||
else delete lookup.alt;
|
||||
}
|
||||
if (shiftTest(key)) {
|
||||
if (value) lookup.shift = true;
|
||||
else delete lookup.shift;
|
||||
}
|
||||
});
|
||||
return lookup;
|
||||
};
|
||||
|
||||
const pressedKeys = selectReduce((prevLookup, next, keyInfoFromMouseEvent) => {
|
||||
const lookup = keyInfoFromMouseEvent
|
||||
? updateKeyLookupFromMouseEvent(prevLookup, keyInfoFromMouseEvent)
|
||||
: prevLookup;
|
||||
// these weird things get in when we alt-tab (or similar) etc. away and get back later:
|
||||
delete lookup[deadKey1];
|
||||
delete lookup[deadKey2];
|
||||
if (!next) return { ...lookup };
|
||||
|
||||
let code = next.code;
|
||||
if (altTest(next.code)) code = 'alt';
|
||||
|
||||
if (metaTest(next.code)) code = 'meta';
|
||||
|
||||
if (shiftTest(next.code)) code = 'shift';
|
||||
|
||||
if (next.event === 'keyDown') {
|
||||
return { ...lookup, [code]: true };
|
||||
} else {
|
||||
/*eslint no-unused-vars: ["error", { "ignoreRestSiblings": true }]*/
|
||||
const { [code]: ignore, ...rest } = lookup;
|
||||
return rest;
|
||||
}
|
||||
}, {})(keyboardEvent, keyInfoFromMouseEvents);
|
||||
|
||||
const keyUp = select(keys => Object.keys(keys).length === 0)(pressedKeys);
|
||||
|
||||
const metaHeld = select(lookup => Boolean(lookup.meta))(pressedKeys);
|
||||
const optionHeld = select(lookup => Boolean(lookup.alt))(pressedKeys);
|
||||
const shiftHeld = select(lookup => Boolean(lookup.shift))(pressedKeys);
|
||||
// retaining this for now to avoid removing dependent inactive code `keyTransformGesture` from layout.js
|
||||
// todo remove this, and `keyTransformGesture` from layout.js and do accessibility outside the layout engine
|
||||
const pressedKeys = () => ({});
|
||||
|
||||
const cursorPosition = selectReduce((previous, position) => position || previous, { x: 0, y: 0 })(
|
||||
rawCursorPosition
|
||||
|
@ -122,18 +71,7 @@ const mouseIsDown = selectReduce(
|
|||
false
|
||||
)(mouseButtonEvent);
|
||||
|
||||
const gestureEnd = selectReduce(
|
||||
(prev, keyUp, mouseIsDown) => {
|
||||
const inAction = !keyUp || mouseIsDown;
|
||||
const ended = !inAction && prev.inAction;
|
||||
return { ended, inAction };
|
||||
},
|
||||
{
|
||||
ended: false,
|
||||
inAction: false,
|
||||
},
|
||||
d => d.ended
|
||||
)(keyUp, mouseIsDown);
|
||||
const gestureEnd = select(next => next && next.event === 'mouseUp')(mouseButtonEvent);
|
||||
|
||||
/**
|
||||
* mouseButtonStateTransitions
|
||||
|
|
Loading…
Reference in a new issue