Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow Shift-Alt-<Left|Right> to indent/outdent list #1444

Merged
merged 10 commits into from
Dec 7, 2022
Original file line number Diff line number Diff line change
@@ -24,6 +24,7 @@ import {
createNumberDefinition,
getMetadata,
findClosestElementAncestor,
getComputedStyle,
} from 'roosterjs-editor-dom';
import {
BuildInEditFeature,
@@ -68,30 +69,50 @@ const ListStyleDefinitionMetadata = createObjectDefinition<ListStyleMetadata>(
true /** allowNull */
);

const shouldHandleIndentationEvent = (indenting: boolean) => (
event: PluginKeyboardEvent,
editor: IEditor
) => {
const { keyCode, altKey, shiftKey, ctrlKey, metaKey } = event.rawEvent;
return (
!ctrlKey &&
!metaKey &&
(keyCode === Keys.TAB
? !altKey && shiftKey === !indenting
: shiftKey && altKey && keyCode === (indenting ? Keys.RIGHT : Keys.LEFT)) &&
cacheGetListElement(event, editor)
);
};

const handleIndentationEvent = (indenting: boolean) => (
event: PluginKeyboardEvent,
editor: IEditor
) => {
const isRTL =
event.rawEvent.keyCode !== Keys.TAB &&
getComputedStyle(editor.getElementAtCursor(), 'direction') == 'rtl';
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getComputedStyle() is kind of heavy. So we should only call it when we really need to.

In this case, it means when we know that it is LEFT/RIGHT key with SHIFT key pressed, and there is list, then finally we can check if it is RTL. So that this function is only called when really need.

Copy link
Member Author

@ianeli1 ianeli1 Dec 2, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shift key check is handled by shouldHandleIndentationEvent, getComputedStyle doesn't get called if keycode is TAB, and this function is only called when shouldHandleIndentationEvent returns true, which already verifies if cursor is inside a list

setIndentation(editor, isRTL == indenting ? Indentation.Decrease : Indentation.Increase);
event.rawEvent.preventDefault();
};

/**
* IndentWhenTab edit feature, provides the ability to indent current list when user press TAB
*/
const IndentWhenTab: BuildInEditFeature<PluginKeyboardEvent> = {
keys: [Keys.TAB],
shouldHandleEvent: (event, editor) =>
!event.rawEvent.shiftKey && cacheGetListElement(event, editor),
handleEvent: (event, editor) => {
setIndentation(editor, Indentation.Increase);
event.rawEvent.preventDefault();
},
keys: [Keys.TAB, Keys.RIGHT],
ianeli1 marked this conversation as resolved.
Show resolved Hide resolved
shouldHandleEvent: shouldHandleIndentationEvent(true),
handleEvent: handleIndentationEvent(true),
allowFunctionKeys: true,
};

/**
* OutdentWhenShiftTab edit feature, provides the ability to outdent current list when user press Shift+TAB
*/
const OutdentWhenShiftTab: BuildInEditFeature<PluginKeyboardEvent> = {
keys: [Keys.TAB],
shouldHandleEvent: (event, editor) =>
event.rawEvent.shiftKey && cacheGetListElement(event, editor),
handleEvent: (event, editor) => {
setIndentation(editor, Indentation.Decrease);
event.rawEvent.preventDefault();
},
keys: [Keys.TAB, Keys.LEFT],
shouldHandleEvent: shouldHandleIndentationEvent(false),
handleEvent: handleIndentationEvent(false),
allowFunctionKeys: true,
};

/**
Original file line number Diff line number Diff line change
@@ -2,7 +2,13 @@ import * as blockFormat from 'roosterjs-editor-api/lib/utils/blockFormat';
import * as setIndentation from 'roosterjs-editor-api/lib/format/setIndentation';
import * as TestHelper from '../../../../roosterjs-editor-api/test/TestHelper';
import * as toggleListType from 'roosterjs-editor-api/lib/utils/toggleListType';
import { IEditor, Indentation, PluginEventType, PluginKeyboardEvent } from 'roosterjs-editor-types';
import {
IEditor,
Indentation,
PluginEventType,
PluginKeyboardEvent,
Keys,
} from 'roosterjs-editor-types';
import { ListFeatures } from '../../../lib/plugins/ContentEdit/features/listFeatures';
import { Position, PositionContentSearcher } from 'roosterjs-editor-dom';

@@ -148,11 +154,18 @@ describe('listFeatures | IndentWhenTab | OutdentWhenShiftTab', () => {
let editor: IEditor;
const TEST_ID = 'listFeatureTests';
let setIndentationFn: jasmine.Spy;
const getKeyboardEvent = (shiftKey: boolean) =>
const getKeyboardEvent = (keysPressed: (keyof KeyboardEventInit)[], keyCode: number) =>
new KeyboardEvent('keydown', {
shiftKey,
altKey: false,
ctrlKey: false,
keyCode,
...keysPressed.reduce(
(obj, cv) => ({
...obj,
[cv]: true,
}),
{}
),
});
let list: HTMLOListElement;

@@ -174,12 +187,13 @@ describe('listFeatures | IndentWhenTab | OutdentWhenShiftTab', () => {

function runTestShouldHandleEvent(
indent: boolean,
shiftKeyPressed: boolean,
keysPressed: (keyof KeyboardEventInit)[],
keyCode: number,
shouldHandle: boolean
) {
const keyboardEvent: PluginKeyboardEvent = {
eventType: PluginEventType.KeyDown,
rawEvent: getKeyboardEvent(shiftKeyPressed),
rawEvent: getKeyboardEvent(keysPressed, keyCode),
};
let triggered: boolean;
if (indent) {
@@ -198,16 +212,20 @@ describe('listFeatures | IndentWhenTab | OutdentWhenShiftTab', () => {
expect(triggered).toBe(shouldHandle);
}

function runTestHandleEvent(shiftKeyPressed: boolean) {
function runTestHandleEvent(
keysPressed: (keyof KeyboardEventInit)[],
keyCode: number,
indent: boolean
) {
const range = document.createRange();
range.setStart(list, 0);
range.setEnd(list, 1);
editor.select(range);
const keyboardEvent: PluginKeyboardEvent = {
eventType: PluginEventType.KeyDown,
rawEvent: getKeyboardEvent(shiftKeyPressed),
rawEvent: getKeyboardEvent(keysPressed, keyCode),
};
if (shiftKeyPressed) {
if (!indent) {
ListFeatures.outdentWhenShiftTab.handleEvent(keyboardEvent, editor);
} else {
ListFeatures.indentWhenTab.handleEvent(keyboardEvent, editor);
@@ -216,32 +234,42 @@ describe('listFeatures | IndentWhenTab | OutdentWhenShiftTab', () => {
expect(setIndentationFn).toHaveBeenCalled();
expect(setIndentationFn).toHaveBeenCalledWith(
editor,
shiftKeyPressed ? Indentation.Decrease : Indentation.Increase
indent ? Indentation.Increase : Indentation.Decrease
);
}

it('should not handle event | indent', () => {
runTestShouldHandleEvent(true, true, false);
runTestShouldHandleEvent(true, ['shiftKey'], Keys.TAB, false);
runTestShouldHandleEvent(true, ['shiftKey', 'altKey'], Keys.LEFT, false);
runTestShouldHandleEvent(true, ['shiftKey'], Keys.RIGHT, false);
runTestShouldHandleEvent(true, ['altKey'], Keys.RIGHT, false);
});

it('should handle event | indent', () => {
runTestShouldHandleEvent(true, false, true);
runTestShouldHandleEvent(true, [], Keys.TAB, true);
runTestShouldHandleEvent(true, ['shiftKey', 'altKey'], Keys.RIGHT, true);
});

it('should not handle event | outdent', () => {
runTestShouldHandleEvent(false, true, true);
runTestShouldHandleEvent(false, [], Keys.TAB, false);
runTestShouldHandleEvent(false, ['shiftKey', 'altKey'], Keys.RIGHT, false);
runTestShouldHandleEvent(false, ['shiftKey'], Keys.LEFT, false);
runTestShouldHandleEvent(false, ['altKey'], Keys.LEFT, false);
});

it('should handle event | outdent', () => {
runTestShouldHandleEvent(false, false, false);
runTestShouldHandleEvent(false, ['shiftKey'], Keys.TAB, true);
runTestShouldHandleEvent(false, ['shiftKey', 'altKey'], Keys.LEFT, true);
});

it('should handle indent | indent', () => {
runTestHandleEvent(false);
it('handle indent | indent', () => {
runTestHandleEvent([], Keys.TAB, true);
runTestHandleEvent(['shiftKey', 'altKey'], Keys.RIGHT, true);
});

it('should handle indent | outdent', () => {
runTestHandleEvent(true);
it('handle indent | outdent', () => {
runTestHandleEvent(['shiftKey'], Keys.TAB, false);
runTestHandleEvent(['shiftKey', 'altKey'], Keys.LEFT, false);
});
});