Skip to content

Commit

Permalink
Fix behaviour of inputs editing in block settings (#1123)
Browse files Browse the repository at this point in the history
  • Loading branch information
gohabereg authored Jun 2, 2020
1 parent e1500f7 commit 26b19a3
Show file tree
Hide file tree
Showing 12 changed files with 241 additions and 142 deletions.
2 changes: 1 addition & 1 deletion dist/editor.js

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions docs/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
- `Improvements` - Add code of conduct
- `Improvements` - Disabled useCapture flag for a block keydown handling. That will allow plugins to override keydown and stop event propagation, for example, to make own Tab behavior.
- `Improvements` - All modules now might have `destroy` method called on Editor.js destroy
- `Improvements` - Block settings can contain text inputs, focus will be restored after settings closed [#1090](https://github.com/codex-team/editor.js/issues/1090)
- `Fix` - Editor's styles won't be appended to the `<head>` when another instance have already do that [#1079](https://github.com/codex-team/editor.js/issues/1079)
- `Fix` - Fixed wrong toolbar icon centering in Firefox [#1120](https://github.com/codex-team/editor.js/pull/1120)
- `Fix` - Toolbox: Tool's order in Toolbox now saved in accordance with `tools` object keys order [#1073](https://github.com/codex-team/editor.js/issues/1073)
Expand Down
19 changes: 1 addition & 18 deletions src/components/block/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -243,24 +243,7 @@ export default class Block {
return this.cachedInputs;
}

const content = this.holder;
const allowedInputTypes = ['text', 'password', 'email', 'number', 'search', 'tel', 'url'];

const selector = '[contenteditable], textarea, input:not([type]), ' +
allowedInputTypes.map((type) => `input[type="${type}"]`).join(', ');

let inputs = _.array(content.querySelectorAll(selector));

/**
* If contenteditable element contains block elements, treat them as inputs.
*/
inputs = inputs.reduce((result, input) => {
if ($.isNativeInput(input) || $.containsOnlyInlineElements(input)) {
return [...result, input];
}

return [...result, ...$.getDeepestBlockElements(input)];
}, []);
const inputs = $.findAllInputs(this.holder);

/**
* If inputs amount was changed we need to check if input index is bigger then inputs array length
Expand Down
31 changes: 31 additions & 0 deletions src/components/dom.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import * as _ from './utils';

/**
* DOM manipulations helper
*/
Expand Down Expand Up @@ -194,6 +196,35 @@ export default class Dom {
return el.querySelectorAll(selector);
}

/**
* Returns CSS selector for all text inputs
*/
public static get allInputsSelector(): string {
const allowedInputTypes = ['text', 'password', 'email', 'number', 'search', 'tel', 'url'];

return '[contenteditable], textarea, input:not([type]), ' +
allowedInputTypes.map((type) => `input[type="${type}"]`).join(', ');
}

/**
* Find all contendeditable, textarea and editable input elements passed holder contains
*
* @param holder - element where to find inputs
*/
public static findAllInputs(holder: Element): HTMLElement[] {
return _.array(holder.querySelectorAll(Dom.allInputsSelector))
/**
* If contenteditable element contains block elements, treat them as inputs.
*/
.reduce((result, input) => {
if (Dom.isNativeInput(input) || Dom.containsOnlyInlineElements(input)) {
return [...result, input];
}

return [...result, ...Dom.getDeepestBlockElements(input)];
}, []);
}

/**
* Search for deepest node which is Leaf.
* Leaf is the vertex that doesn't have any child nodes
Expand Down
8 changes: 5 additions & 3 deletions src/components/domIterator.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import Dom from './dom';
import * as _ from './utils';
import SelectionUtils from './selection';

/**
* Iterator above passed Elements list.
Expand Down Expand Up @@ -156,11 +158,11 @@ export default class DomIterator {
focusedButtonIndex = (this.items.length + focusedButtonIndex - 1) % this.items.length;
}

if (Dom.isNativeInput(this.items[focusedButtonIndex])) {
if (Dom.canSetCaret(this.items[focusedButtonIndex])) {
/**
* Focus input
* Focus input with micro-delay to ensure DOM is updated
*/
this.items[focusedButtonIndex].focus();
_.delay(() => SelectionUtils.setCursor(this.items[focusedButtonIndex]), 50)();
}

/**
Expand Down
129 changes: 68 additions & 61 deletions src/components/flipper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export interface FlipperOptions {
/**
* Optional callback for button click
*/
activateCallback?: () => void;
activateCallback?: (item: HTMLElement) => void;
}

/**
Expand Down Expand Up @@ -62,7 +62,7 @@ export default class Flipper {
/**
* Call back for button click/enter
*/
private readonly activateCallback: () => void;
private readonly activateCallback: (item: HTMLElement) => void;

/**
* @class
Expand All @@ -73,44 +73,6 @@ export default class Flipper {
this.allowArrows = typeof options.allowArrows === 'boolean' ? options.allowArrows : true;
this.iterator = new DomIterator(options.items, options.focusedItemClass);
this.activateCallback = options.activateCallback;

/**
* Listening all keydowns on document and react on TAB/Enter press
* TAB will leaf iterator items
* ENTER will click the focused item
*/
document.addEventListener('keydown', (event) => {
const isReady = this.isEventReadyForHandling(event);

if (!isReady) {
return;
}

/**
* Prevent only used keys default behaviour
* (allows to navigate by ARROW DOWN, for example)
*/
if (Flipper.usedKeys.includes(event.keyCode)) {
event.preventDefault();
}

switch (event.keyCode) {
case _.keyCodes.TAB:
this.handleTabPress(event);
break;
case _.keyCodes.LEFT:
case _.keyCodes.UP:
this.flipLeft();
break;
case _.keyCodes.RIGHT:
case _.keyCodes.DOWN:
this.flipRight();
break;
case _.keyCodes.ENTER:
this.handleEnterPress(event);
break;
}
}, false);
}

/**
Expand Down Expand Up @@ -141,6 +103,13 @@ export default class Flipper {
if (items) {
this.iterator.setItems(items);
}

/**
* Listening all keydowns on document and react on TAB/Enter press
* TAB will leaf iterator items
* ENTER will click the focused item
*/
document.addEventListener('keydown', this.onKeyDown);
}

/**
Expand All @@ -149,6 +118,8 @@ export default class Flipper {
public deactivate(): void {
this.activated = false;
this.dropCursor();

document.removeEventListener('keydown', this.onKeyDown);
}

/**
Expand All @@ -168,6 +139,20 @@ export default class Flipper {
this.flipRight();
}

/**
* Focuses previous flipper iterator item
*/
public flipLeft(): void {
this.iterator.previous();
}

/**
* Focuses next flipper iterator item
*/
public flipRight(): void {
this.iterator.next();
}

/**
* Drops flipper's iterator cursor
*
Expand All @@ -177,6 +162,44 @@ export default class Flipper {
this.iterator.dropCursor();
}

/**
* KeyDown event handler
*
* @param event - keydown event
*/
private onKeyDown = (event): void => {
const isReady = this.isEventReadyForHandling(event);

if (!isReady) {
return;
}

/**
* Prevent only used keys default behaviour
* (allows to navigate by ARROW DOWN, for example)
*/
if (Flipper.usedKeys.includes(event.keyCode)) {
event.preventDefault();
}

switch (event.keyCode) {
case _.keyCodes.TAB:
this.handleTabPress(event);
break;
case _.keyCodes.LEFT:
case _.keyCodes.UP:
this.flipLeft();
break;
case _.keyCodes.RIGHT:
case _.keyCodes.DOWN:
this.flipRight();
break;
case _.keyCodes.ENTER:
this.handleEnterPress(event);
break;
}
};

/**
* This function is fired before handling flipper keycodes
* The result of this function defines if it is need to be handled or not
Expand All @@ -190,7 +213,9 @@ export default class Flipper {
_.keyCodes.ENTER,
];

if (this.allowArrows) {
const isCurrentItemIsFocusedInput = this.iterator.currentItem == document.activeElement;

if (this.allowArrows && !isCurrentItemIsFocusedInput) {
handlingKeyCodeList.push(
_.keyCodes.LEFT,
_.keyCodes.RIGHT,
Expand All @@ -199,11 +224,7 @@ export default class Flipper {
);
}

if (!this.activated || handlingKeyCodeList.indexOf(event.keyCode) === -1) {
return false;
}

return true;
return this.activated && handlingKeyCodeList.indexOf(event.keyCode) !== -1;
}

/**
Expand All @@ -226,20 +247,6 @@ export default class Flipper {
}
}

/**
* Focuses previous flipper iterator item
*/
private flipLeft(): void {
this.iterator.previous();
}

/**
* Focuses next flipper iterator item
*/
private flipRight(): void {
this.iterator.next();
}

/**
* Enter press will click current item if flipper is activated
*
Expand All @@ -255,7 +262,7 @@ export default class Flipper {
}

if (typeof this.activateCallback === 'function') {
this.activateCallback();
this.activateCallback(this.iterator.currentItem);
}

event.preventDefault();
Expand Down
29 changes: 0 additions & 29 deletions src/components/modules/blockEvents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,6 @@ export default class BlockEvents extends Module {
case _.keyCodes.TAB:
this.tabPressed(event);
break;

case _.keyCodes.ESC:
this.escapePressed(event);
break;
}
}

Expand Down Expand Up @@ -158,31 +154,6 @@ export default class BlockEvents extends Module {
}
}

/**
* Escape pressed
* If some of Toolbar components are opened, then close it otherwise close Toolbar
*
* @param {Event} event - escape keydown event
*/
public escapePressed(event): void {
/**
* Clear blocks selection by ESC
*/
this.Editor.BlockSelection.clearSelection(event);

if (this.Editor.Toolbox.opened) {
this.Editor.Toolbox.close();
} else if (this.Editor.BlockSettings.opened) {
this.Editor.BlockSettings.close();
} else if (this.Editor.ConversionToolbar.opened) {
this.Editor.ConversionToolbar.close();
} else if (this.Editor.InlineToolbar.opened) {
this.Editor.InlineToolbar.close();
} else {
this.Editor.Toolbar.close();
}
}

/**
* Add drop target styles
*
Expand Down
7 changes: 5 additions & 2 deletions src/components/modules/blockSelection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -188,17 +188,20 @@ export default class BlockSelection extends Module {
this.nativeInputSelected = false;
this.readyToBlockSelection = false;

const isKeyboard = reason && (reason instanceof KeyboardEvent);
const isPrintableKey = isKeyboard && _.isPrintableKey((reason as KeyboardEvent).keyCode);

/**
* If reason caused clear of the selection was printable key and any block is selected,
* remove selected blocks and insert pressed key
*/
if (this.anyBlockSelected && reason && reason instanceof KeyboardEvent && _.isPrintableKey(reason.keyCode)) {
if (this.anyBlockSelected && isKeyboard && isPrintableKey && !SelectionUtils.isSelectionExists) {
const indexToInsert = BlockManager.removeSelectedBlocks();

BlockManager.insertInitialBlockAtIndex(indexToInsert, true);
Caret.setToBlock(BlockManager.currentBlock);
_.delay(() => {
Caret.insertContentAtCaretPosition(reason.key);
Caret.insertContentAtCaretPosition((reason as KeyboardEvent).key);
}, 20)();
}

Expand Down
Loading

0 comments on commit 26b19a3

Please sign in to comment.