From fd9243b8f52d157cdacc46c8c6f9e97b9bd82b4e Mon Sep 17 00:00:00 2001 From: Ian Storm Taylor Date: Tue, 7 Aug 2018 15:58:33 -0700 Subject: [PATCH] refactor slate-hotkeys, fix deleting at start of block (#2048) --- packages/slate-hotkeys/Changelog.md | 8 + packages/slate-hotkeys/package.json | 2 +- packages/slate-hotkeys/src/index.js | 168 ++++++++------------- packages/slate-react/src/plugins/after.js | 16 +- packages/slate-react/src/plugins/before.js | 23 ++- yarn.lock | 4 + 6 files changed, 101 insertions(+), 120 deletions(-) diff --git a/packages/slate-hotkeys/Changelog.md b/packages/slate-hotkeys/Changelog.md index c381fe992..296c5f9c6 100644 --- a/packages/slate-hotkeys/Changelog.md +++ b/packages/slate-hotkeys/Changelog.md @@ -4,6 +4,14 @@ This document maintains a list of changes to the `slate-hotkeys` package with ea --- +### `0.2.0` — August 7, 2018 + +###### BREAKING + +**Some hotkey checkers have changed or been removed.** Please check the source to see the changes, sorry for the hassle. This was needed to cleanup the behavior and get this package to not be leaky in terms of what checkers it exposed. + +--- + ### `0.1.0` — April 5, 2018 :tada: diff --git a/packages/slate-hotkeys/package.json b/packages/slate-hotkeys/package.json index 67e9c45ac..f9c37beab 100644 --- a/packages/slate-hotkeys/package.json +++ b/packages/slate-hotkeys/package.json @@ -13,7 +13,7 @@ "lib/" ], "dependencies": { - "is-hotkey": "^0.1.1", + "is-hotkey": "^0.1.3", "slate-dev-environment": "^0.1.4" }, "scripts": { diff --git a/packages/slate-hotkeys/src/index.js b/packages/slate-hotkeys/src/index.js index d56a3d0bb..b11573e99 100644 --- a/packages/slate-hotkeys/src/index.js +++ b/packages/slate-hotkeys/src/index.js @@ -2,104 +2,80 @@ import { isKeyHotkey } from 'is-hotkey' import { IS_IOS, IS_MAC } from 'slate-dev-environment' /** - * Is Apple? + * Hotkey mappings for each platform. * - * @type {Boolean} + * @type {Object} */ -const IS_APPLE = IS_IOS || IS_MAC +const HOTKEYS = { + bold: 'mod+b', + compose: ['down', 'left', 'right', 'up', 'backspace', 'enter'], + moveBackward: 'mod?+ctrl?+alt?+left', + moveForward: 'mod?+ctrl?+alt?+right', + deleteBackward: 'shift?+backspace', + deleteForward: 'shift?+delete', + extendBackward: 'shift+left', + extendForward: 'shift+right', + italic: 'mod+i', + splitBlock: 'shift?+enter', + undo: 'mod+z', +} + +const APPLE_HOTKEYS = { + moveLineBackward: 'opt+up', + moveLineForward: 'opt+down', + deleteBackward: ['ctrl+backspace', 'ctrl+h'], + deleteForward: ['ctrl+delete', 'ctrl+d'], + deleteLineBackward: 'cmd+shift?+backspace', + deleteLineForward: ['cmd+shift?+delete', 'ctrl+k'], + deleteWordBackward: 'opt+shift?+backspace', + deleteWordForward: 'opt+shift?+delete', + extendLineBackward: 'opt+shift+up', + extendLineForward: 'opt+shift+down', + redo: 'cmd+shift+z', + transposeCharacter: 'ctrl+t', +} + +const WINDOWS_HOTKEYS = { + deleteWordBackward: 'ctrl+shift?+backspace', + deleteWordForward: 'ctrl+shift?+delete', + redo: 'ctrl+y', +} /** * Hotkeys. * - * @type {Function} + * @type {Object} */ -const isBold = isKeyHotkey('mod+b') -const isItalic = isKeyHotkey('mod+i') +const Hotkeys = {} -const isEnter = isKeyHotkey('enter') -const isShiftEnter = isKeyHotkey('shift+enter') -const isSplitBlock = e => isEnter(e) || isShiftEnter(e) +const IS_APPLE = IS_IOS || IS_MAC +const IS_WINDOWS = !IS_APPLE +const KEYS = [] + .concat(Object.keys(HOTKEYS)) + .concat(Object.keys(APPLE_HOTKEYS)) + .concat(Object.keys(WINDOWS_HOTKEYS)) -const isBackspace = isKeyHotkey('backspace') -const isShiftBackspace = isKeyHotkey('shift+backspace') -const isDelete = isKeyHotkey('delete') -const isShiftDelete = isKeyHotkey('shift+delete') -const isDeleteBackward = e => isBackspace(e) || isShiftBackspace(e) -const isDeleteForward = e => isDelete(e) || isShiftDelete(e) +KEYS.forEach(key => { + const method = `is${key[0].toUpperCase()}${key.slice(1)}` + if (Hotkeys[method]) return -const isDeleteCharBackwardMac = isKeyHotkey('ctrl+h') -const isDeleteCharForwardMac = isKeyHotkey('ctrl+d') -const isDeleteCharBackward = e => - isDeleteBackward(e) || (IS_APPLE && isDeleteCharBackwardMac(e)) -const isDeleteCharForward = e => - isDeleteForward(e) || (IS_APPLE && isDeleteCharForwardMac(e)) + const generic = HOTKEYS[key] + const apple = APPLE_HOTKEYS[key] + const windows = WINDOWS_HOTKEYS[key] -const isDeleteLineBackwardMac = e => - isKeyHotkey('cmd+shift+backspace', e) || isKeyHotkey('cmd+backspace', e) -const isDeleteLineForwardMac = isKeyHotkey('ctrl+k') -const isDeleteLineBackward = e => IS_APPLE && isDeleteLineBackwardMac(e) -const isDeleteLineForward = e => IS_APPLE && isDeleteLineForwardMac(e) + const isGeneric = generic && isKeyHotkey(generic) + const isApple = apple && isKeyHotkey(apple) + const isWindows = windows && isKeyHotkey(windows) -const isDeleteWordBackwardMac = e => - isKeyHotkey('shift+option+backspace', e) || isKeyHotkey('option+backspace', e) -const isDeleteWordBackwardPC = isKeyHotkey('ctrl+backspace') -const isDeleteWordForwardMac = e => - isKeyHotkey('shift+option+delete', e) || isKeyHotkey('option+delete', e) -const isDeleteWordForwardPC = isKeyHotkey('ctrl+delete') -const isDeleteWordBackward = e => - IS_APPLE ? isDeleteWordBackwardMac(e) : isDeleteWordBackwardPC(e) -const isDeleteWordForward = e => - IS_APPLE ? isDeleteWordForwardMac(e) : isDeleteWordForwardPC(e) - -const isExtendCharForward = isKeyHotkey('shift+right') -const isExtendCharBackward = isKeyHotkey('shift+left') - -const isRightArrow = isKeyHotkey('right') -const isLeftArrow = isKeyHotkey('left') -const isCollapseCharForward = e => isRightArrow(e) && !isExtendCharForward(e) -const isCollapseCharBackward = e => isLeftArrow(e) && !isExtendCharBackward(e) - -const isCollapseLineBackwardMac = isKeyHotkey('option+up') -const isCollapseLineForwardMac = isKeyHotkey('option+down') -const isCollapseLineBackward = e => IS_APPLE && isCollapseLineBackwardMac(e) -const isCollapseLineForward = e => IS_APPLE && isCollapseLineForwardMac(e) - -const isExtendLineBackwardMac = isKeyHotkey('option+shift+up') -const isExtendLineForwardMac = isKeyHotkey('option+shift+down') -const isExtendLineBackward = e => IS_APPLE && isExtendLineBackwardMac(e) -const isExtendLineForward = e => IS_APPLE && isExtendLineForwardMac(e) - -const isUndo = isKeyHotkey('mod+z') -const isRedoMac = isKeyHotkey('mod+shift+z') -const isRedoPC = isKeyHotkey('mod+y') -const isRedo = e => (IS_APPLE ? isRedoMac(e) : isRedoPC(e)) - -const isTransposeCharacterMac = isKeyHotkey('ctrl+t') -const isTransposeCharacter = e => IS_APPLE && isTransposeCharacterMac(e) - -const isContentEditable = e => - isBold(e) || - isDeleteCharBackward(e) || - isDeleteCharForward(e) || - isDeleteLineBackward(e) || - isDeleteLineForward(e) || - isDeleteWordBackward(e) || - isDeleteWordForward(e) || - isItalic(e) || - isRedo(e) || - isSplitBlock(e) || - isTransposeCharacter(e) || - isUndo(e) - -const isComposing = e => - e.key == 'ArrowDown' || - e.key == 'ArrowLeft' || - e.key == 'ArrowRight' || - e.key == 'ArrowUp' || - e.key == 'Backspace' || - e.key == 'Enter' + Hotkeys[method] = event => { + if (isGeneric && isGeneric(event)) return true + if (IS_APPLE && isApple && isApple(event)) return true + if (IS_WINDOWS && isWindows && isWindows(event)) return true + return false + } +}) /** * Export. @@ -107,26 +83,4 @@ const isComposing = e => * @type {Object} */ -export default { - isBold, - isCollapseCharBackward, - isCollapseCharForward, - isCollapseLineBackward, - isCollapseLineForward, - isComposing, - isContentEditable, - isDeleteCharBackward, - isDeleteCharForward, - isDeleteLineBackward, - isDeleteLineForward, - isDeleteWordBackward, - isDeleteWordForward, - isExtendCharBackward, - isExtendCharForward, - isExtendLineBackward, - isExtendLineForward, - isItalic, - isRedo, - isSplitBlock, - isUndo, -} +export default Hotkeys diff --git a/packages/slate-react/src/plugins/after.js b/packages/slate-react/src/plugins/after.js index c17c99b9d..71d56b93d 100644 --- a/packages/slate-react/src/plugins/after.js +++ b/packages/slate-react/src/plugins/after.js @@ -377,11 +377,11 @@ function AfterPlugin() { : change.splitBlock() } - if (Hotkeys.isDeleteCharBackward(event) && !IS_IOS) { + if (Hotkeys.isDeleteBackward(event) && !IS_IOS) { return change.deleteCharBackward() } - if (Hotkeys.isDeleteCharForward(event) && !IS_IOS) { + if (Hotkeys.isDeleteForward(event) && !IS_IOS) { return change.deleteCharForward() } @@ -412,12 +412,12 @@ function AfterPlugin() { // COMPAT: Certain browsers don't handle the selection updates properly. In // Chrome, the selection isn't properly extended. And in Firefox, the // selection isn't properly collapsed. (2017/10/17) - if (Hotkeys.isCollapseLineBackward(event)) { + if (Hotkeys.isMoveLineBackward(event)) { event.preventDefault() return change.moveToStartOfBlock() } - if (Hotkeys.isCollapseLineForward(event)) { + if (Hotkeys.isMoveLineForward(event)) { event.preventDefault() return change.moveToEndOfBlock() } @@ -435,7 +435,7 @@ function AfterPlugin() { // COMPAT: If a void node is selected, or a zero-width text node adjacent to // an inline is selected, we need to handle these hotkeys manually because // browsers won't know what to do. - if (Hotkeys.isCollapseCharBackward(event)) { + if (Hotkeys.isMoveBackward(event)) { const { document, isInVoid, previousText, startText } = value const isPreviousInVoid = previousText && document.hasVoidParent(previousText.key) @@ -446,7 +446,7 @@ function AfterPlugin() { } } - if (Hotkeys.isCollapseCharForward(event)) { + if (Hotkeys.isMoveForward(event)) { const { document, isInVoid, nextText, startText } = value const isNextInVoid = nextText && document.hasVoidParent(nextText.key) @@ -456,7 +456,7 @@ function AfterPlugin() { } } - if (Hotkeys.isExtendCharBackward(event)) { + if (Hotkeys.isExtendBackward(event)) { const { document, isInVoid, previousText, startText } = value const isPreviousInVoid = previousText && document.hasVoidParent(previousText.key) @@ -467,7 +467,7 @@ function AfterPlugin() { } } - if (Hotkeys.isExtendCharForward(event)) { + if (Hotkeys.isExtendForward(event)) { const { document, isInVoid, nextText, startText } = value const isNextInVoid = nextText && document.hasVoidParent(nextText.key) diff --git a/packages/slate-react/src/plugins/before.js b/packages/slate-react/src/plugins/before.js index 3b5c084b2..5c79fc773 100644 --- a/packages/slate-react/src/plugins/before.js +++ b/packages/slate-react/src/plugins/before.js @@ -387,13 +387,28 @@ function BeforePlugin() { // typing. However, certain characters also move the selection before // we're able to handle it, so prevent their default behavior. if (isComposing) { - if (Hotkeys.isComposing(event)) event.preventDefault() + if (Hotkeys.isCompose(event)) event.preventDefault() return true } - // Certain hotkeys have native behavior in contenteditable elements which - // will cause our value to be out of sync, so prevent them. - if (Hotkeys.isContentEditable(event) && !IS_IOS) { + // Certain hotkeys have native editing behaviors in `contenteditable` + // elements which will change the DOM and cause our value to be out of sync, + // so they need to always be prevented. + if ( + !IS_IOS && + (Hotkeys.isBold(event) || + Hotkeys.isDeleteBackward(event) || + Hotkeys.isDeleteForward(event) || + Hotkeys.isDeleteLineBackward(event) || + Hotkeys.isDeleteLineForward(event) || + Hotkeys.isDeleteWordBackward(event) || + Hotkeys.isDeleteWordForward(event) || + Hotkeys.isItalic(event) || + Hotkeys.isRedo(event) || + Hotkeys.isSplitBlock(event) || + Hotkeys.isTransposeCharacter(event) || + Hotkeys.isUndo(event)) + ) { event.preventDefault() } diff --git a/yarn.lock b/yarn.lock index 819eae11c..bd55e1dce 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4706,6 +4706,10 @@ is-hotkey@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/is-hotkey/-/is-hotkey-0.1.1.tgz#b279a2fd108391be9aa93c6cb317f50357da549a" +is-hotkey@^0.1.3: + version "0.1.3" + resolved "https://registry.yarnpkg.com/is-hotkey/-/is-hotkey-0.1.3.tgz#8a129eec16f3941bd4f37191e02b9c3e91950549" + is-in-browser@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/is-in-browser/-/is-in-browser-1.1.3.tgz#56ff4db683a078c6082eb95dad7dc62e1d04f835"