Skip to content
Draft
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .flowconfig
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ module.name_mapper='^@lexical/react/LexicalSelectionAlwaysOnDisplay$' -> '<PROJE
module.name_mapper='^@lexical/react/LexicalTabIndentationPlugin$' -> '<PROJECT_ROOT>/packages/lexical-react/flow/LexicalTabIndentationPlugin.js.flow'

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

also doesn't need to change

module.name_mapper='^@lexical/react/LexicalTableOfContentsPlugin$' -> '<PROJECT_ROOT>/packages/lexical-react/flow/LexicalTableOfContentsPlugin.js.flow'
module.name_mapper='^@lexical/react/LexicalTablePlugin$' -> '<PROJECT_ROOT>/packages/lexical-react/flow/LexicalTablePlugin.js.flow'
module.name_mapper='^@lexical/react/LexicalTouchIndentationPlugin$' -> '<PROJECT_ROOT>/packages/lexical-react/flow/LexicalTouchIndentationPlugin.js.flow'
module.name_mapper='^@lexical/react/LexicalTreeView$' -> '<PROJECT_ROOT>/packages/lexical-react/flow/LexicalTreeView.js.flow'
module.name_mapper='^@lexical/react/LexicalTypeaheadMenuPlugin$' -> '<PROJECT_ROOT>/packages/lexical-react/flow/LexicalTypeaheadMenuPlugin.js.flow'
module.name_mapper='^@lexical/react/ReactExtension$' -> '<PROJECT_ROOT>/packages/lexical-react/flow/LexicalReactExtension.js.flow'
Expand Down
3 changes: 3 additions & 0 deletions packages/lexical-devtools/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,9 @@
"@lexical/react/LexicalTablePlugin": [
"../lexical-react/src/LexicalTablePlugin.ts"
],
"@lexical/react/LexicalTouchIndentationPlugin": [
"../lexical-react/src/LexicalTouchIndentationPlugin.tsx"
],
"@lexical/react/LexicalTreeView": [
"../lexical-react/src/LexicalTreeView.tsx"
],
Expand Down
159 changes: 159 additions & 0 deletions packages/lexical-extension/src/TouchIndentationExtension.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*/

import type {LexicalEditor} from 'lexical';

import {$findMatchingParent} from '@lexical/utils';
import {
$getSelection,
$isElementNode,
$isRangeSelection,
defineExtension,
INDENT_CONTENT_COMMAND,
OUTDENT_CONTENT_COMMAND,
safeCast,
} from 'lexical';

import {namedSignals} from './namedSignals';
import {effect} from './signals';

const DEFAULT_SWIPE_THRESHOLD = 50;
const DEFAULT_VERTICAL_GUARD = 30;

function $isSelectionInListItem(): boolean {
const selection = $getSelection();
if (!$isRangeSelection(selection)) {
return false;
}
const node = selection.anchor.getNode();
const listItem = $findMatchingParent(
node,
parent => $isElementNode(parent) && parent.getType() === 'listitem',
);
return listItem != null;
}

export function registerTouchIndentation(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Suggested change
export function registerTouchIndentation(
function registerTouchIndentation(

editor: LexicalEditor,
swipeThreshold: number = DEFAULT_SWIPE_THRESHOLD,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This can use a signal and doesn't need to support defaults for legacy code

Suggested change
swipeThreshold: number = DEFAULT_SWIPE_THRESHOLD,
swipeThreshold: Signal<number>,

): () => void {
return editor.registerRootListener(rootElement => {
if (rootElement !== null) {
let startX = 0;
let startY = 0;
let isSwiping = false;
let isInListItem = false;

const handleTouchStart = (event: TouchEvent) => {
if (event.touches.length > 1) {
return;
}
const touch = event.touches[0];
if (touch != null && editor.isEditable()) {
startX = touch.clientX;
startY = touch.clientY;
isSwiping = false;
isInListItem = editor.read(() => $isSelectionInListItem());

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Suggested change
isInListItem = editor.read(() => $isSelectionInListItem());
isInListItem = editor.read($isSelectionInListItem);

}
};

const handleTouchMove = (event: TouchEvent) => {
if (!isInListItem || event.touches.length > 1) {
return;
}
const touch = event.touches[0];
if (touch != null) {
const deltaX = touch.clientX - startX;
const deltaY = touch.clientY - startY;
if (
Math.abs(deltaX) > swipeThreshold &&

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Suggested change
Math.abs(deltaX) > swipeThreshold &&
Math.abs(deltaX) > swipeThreshold.peek() &&

Math.abs(deltaY) < DEFAULT_VERTICAL_GUARD
) {
isSwiping = true;
event.preventDefault();
}
}
};

const handleTouchEnd = (event: TouchEvent) => {
if (!isSwiping) {
isSwiping = false;
isInListItem = false;
return;
}
const touch = event.changedTouches[0];
if (touch != null) {
const deltaX = touch.clientX - startX;
const deltaY = touch.clientY - startY;
if (
Math.abs(deltaX) > swipeThreshold &&

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Suggested change
Math.abs(deltaX) > swipeThreshold &&
Math.abs(deltaX) > swipeThreshold.peek() &&

Math.abs(deltaY) < DEFAULT_VERTICAL_GUARD
) {
event.preventDefault();
if (deltaX > 0) {
editor.dispatchCommand(INDENT_CONTENT_COMMAND, undefined);
} else {
editor.dispatchCommand(OUTDENT_CONTENT_COMMAND, undefined);
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Suggested change
if (deltaX > 0) {
editor.dispatchCommand(INDENT_CONTENT_COMMAND, undefined);
} else {
editor.dispatchCommand(OUTDENT_CONTENT_COMMAND, undefined);
}
editor.dispatchCommand(deltaX > 0 ? INDENT_CONTENT_COMMAND : OUTDENT_CONTENT_COMMAND, undefined);

}
}
isSwiping = false;
isInListItem = false;
};

rootElement.addEventListener('touchstart', handleTouchStart, {
capture: true,
passive: true,
});
rootElement.addEventListener('touchmove', handleTouchMove, {
capture: true,
passive: false,
});
rootElement.addEventListener('touchend', handleTouchEnd, {
capture: true,
passive: false,
});

return () => {
rootElement.removeEventListener('touchstart', handleTouchStart, {
capture: true,
});
rootElement.removeEventListener('touchmove', handleTouchMove, {
capture: true,
});
rootElement.removeEventListener('touchend', handleTouchEnd, {
capture: true,
});
};
}
});
}

export interface TouchIndentationConfig {
disabled: boolean;
swipeThreshold: number;
}

export const TouchIndentationExtension = defineExtension({
build(editor, config, state) {
return namedSignals(config);
},
config: safeCast<TouchIndentationConfig>({
disabled: false,
swipeThreshold: DEFAULT_SWIPE_THRESHOLD,
}),
name: '@lexical/extension/TouchIndentation',
register(editor, config, state) {
const {disabled, swipeThreshold} = state.getOutput();
return effect(() => {
if (!disabled.value) {
return registerTouchIndentation(editor, swipeThreshold.value);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Suggested change
return registerTouchIndentation(editor, swipeThreshold.value);
return registerTouchIndentation(editor, swipeThreshold);

}
});
},
});
Loading
Loading