From bb0dd7f78662aafc408646466d3efb492b0df0ca Mon Sep 17 00:00:00 2001 From: Zaki Date: Fri, 1 Jul 2022 07:08:25 -0700 Subject: [PATCH] Add ability to indent/un-indent multiple list items (#1642) * Add ability to indent/un-indent multiple list items * some improvement * fix using unhang; add more test cases * Adding changset for list indentation fix --- .changeset/lemon-queens-pretend.md | 5 + packages/nodes/list/src/onKeyDownList.ts | 33 +- .../nodes/list/src/onkeyDownList.spec.tsx | 394 ++++++++++++++++++ 3 files changed, 428 insertions(+), 4 deletions(-) create mode 100644 .changeset/lemon-queens-pretend.md create mode 100644 packages/nodes/list/src/onkeyDownList.spec.tsx diff --git a/.changeset/lemon-queens-pretend.md b/.changeset/lemon-queens-pretend.md new file mode 100644 index 0000000000..fab90d90af --- /dev/null +++ b/.changeset/lemon-queens-pretend.md @@ -0,0 +1,5 @@ +--- +'@udecode/plate-list': minor +--- + +Improved list item indentation when selection spans across different elements diff --git a/packages/nodes/list/src/onKeyDownList.ts b/packages/nodes/list/src/onKeyDownList.ts index d4e8bed8ab..6affa8f184 100644 --- a/packages/nodes/list/src/onKeyDownList.ts +++ b/packages/nodes/list/src/onKeyDownList.ts @@ -2,13 +2,17 @@ import { getAboveNode, HotkeyPlugin, Hotkeys, + isCollapsed, KeyboardHandlerReturnType, PlateEditor, + select, + unhangRange, Value, WithPlatePlugin, } from '@udecode/plate-core'; import isHotkey from 'is-hotkey'; import { castArray } from 'lodash'; +import { Range } from 'slate'; import { moveListItems, toggleList } from './transforms'; export const onKeyDownList = < @@ -20,16 +24,37 @@ export const onKeyDownList = < ): KeyboardHandlerReturnType => (e) => { const isTab = Hotkeys.isTab(editor, e); const isUntab = Hotkeys.isUntab(editor, e); + + let workRange = editor.selection; + if (editor.selection && (isTab || isUntab)) { + const { selection } = editor; + + // Unhang the expanded selection + if (!isCollapsed(editor.selection)) { + const { anchor, focus } = Range.isBackward(selection) + ? { anchor: selection.focus, focus: selection.anchor } + : { anchor: selection.anchor, focus: selection.focus }; + + // This is a workaround for a Slate bug + // See: https://github.com/ianstormtaylor/slate/pull/5039 + anchor.offset = 0; + const unHungRange = unhangRange(editor, { anchor, focus }); + if (unHungRange) { + workRange = unHungRange; + select(editor, unHungRange); + } + } + + // check if we're in a list context. const listSelected = getAboveNode(editor, { at: editor.selection, - match: { type }, }); - if (listSelected) { + if (workRange && listSelected) { e.preventDefault(); - moveListItems(editor, { increase: isTab }); - return; + moveListItems(editor, { at: workRange, increase: isTab }); + return true; } } diff --git a/packages/nodes/list/src/onkeyDownList.spec.tsx b/packages/nodes/list/src/onkeyDownList.spec.tsx new file mode 100644 index 0000000000..0440e4f9ca --- /dev/null +++ b/packages/nodes/list/src/onkeyDownList.spec.tsx @@ -0,0 +1,394 @@ +/** @jsx jsx */ + +import { getPlugin, HotkeyPlugin, Hotkeys } from '@udecode/plate-core'; +import { createListPlugin } from '@udecode/plate-list'; +import { jsx } from '@udecode/plate-test-utils'; +import { createPlateUIEditor } from '@udecode/plate-ui/src/utils/createPlateUIEditor'; +import * as isHotkey from 'is-hotkey'; +import { onKeyDownList } from './onKeyDownList'; + +jsx; +/* +input: +1. E1 +2. |E2 + +output: +1. E1 + 1. |E2 +*/ +it('should indent single list item (start of item)', () => { + const input = ( + + + + E1 + + + + + E2 + + + + + ) as any; + + const output = ( + + + + E1 + + + + + E2 + + + + + + + ) as any; + + const event = new KeyboardEvent('keydown', { key: 'Tab' }) as any; + const editor = createPlateUIEditor({ + editor: input, + plugins: [createListPlugin()], + }); + + onKeyDownList(editor, getPlugin(editor, 'ul'))(event as any); + expect(editor.children).toEqual(output.children); +}); + +/* +input: +1. E1 +2. E2| + +output: +1. E1 + 1. E2| +*/ +it('should indent single list item (end of item)', () => { + const input = ( + + + + E1 + + + + E2 + + + + + + ) as any; + + const output = ( + + + + E1 + + + + E2 + + + + + + + + ) as any; + + const event = new KeyboardEvent('keydown', { key: 'Tab' }) as any; + const editor = createPlateUIEditor({ + editor: input, + plugins: [createListPlugin()], + }); + + onKeyDownList(editor, getPlugin(editor, 'ul'))(event as any); + expect(editor.children).toEqual(output.children); +}); + +/* +input: +1. E1 +2. |E2 +3. E3| + +output: +1. E1 + 1. |E2 + 2. E3| +*/ +it('should indent multiple list items (start/end)', () => { + const input = ( + + + + E1 + + + + + E2 + + + + + E3 + + + + + + ) as any; + + const output = ( + + + + E1 + + + + + E2 + + + + + E3 + + + + + + + + ) as any; + + const event = new KeyboardEvent('keydown', { key: 'Tab' }) as any; + const editor = createPlateUIEditor({ + editor: input, + plugins: [createListPlugin()], + }); + + onKeyDownList(editor, getPlugin(editor, 'ul'))(event as any); + expect(editor.children).toEqual(output.children); +}); + +/* +input: +1. E1 + 1. |E2 + 2. E3| + +output: +1. E1 +2. |E2 +3. E3| +*/ +it('should un-indent multiple list items (start/end)', () => { + const input = ( + + + + E1 + + + + + E2 + + + + + E3 + + + + + + + + ) as any; + + const output = ( + + + + E1 + + + + + E2 + + + + + E3 + + + + + + ) as any; + + const event = new KeyboardEvent('keydown', { + shiftKey: true, + key: 'Tab', + }) as any; + const editor = createPlateUIEditor({ + editor: input, + plugins: [createListPlugin()], + }); + + onKeyDownList(editor, getPlugin(editor, 'list'))(event as any); + expect(editor.children).toEqual(output.children); +}); + +/* +input: +1. E1 + 1. |E2 + 2. E3 +| + +output: +1. E1 +2. |E2 +3. E3 +| +*/ +it('should un-indent multiple list items (start/out)', () => { + const input = ( + + + + E1 + + + + + E2 + + + + + E3 + + + + + + + + ) as any; + + const output = ( + + + + E1 + + + + + E2 + + + + + E3 + + + + + + ) as any; + + const event = new KeyboardEvent('keydown', { + shiftKey: true, + key: 'Tab', + }) as any; + const editor = createPlateUIEditor({ + editor: input, + plugins: [createListPlugin()], + }); + + onKeyDownList(editor, getPlugin(editor, 'list'))(event as any); + expect(editor.children).toEqual(output.children); +}); + +it('should unhang before indentation', () => { + const input = ( + + + + E1 + + + + + E2 + + + + E3 + + + + + + paragraph + + + + ) as any; + + const output = ( + + + + E1 + + + + + E2 + + + + E3 + + + + + + + + paragraph + + + + ) as any; + + const event = new KeyboardEvent('keydown', { + key: 'Tab', + }) as any; + const editor = createPlateUIEditor({ + editor: input, + plugins: [createListPlugin()], + }); + + onKeyDownList(editor, getPlugin(editor, 'list'))(event as any); + expect(editor.children).toEqual(output.children); +});