mirror of
https://github.com/facebook/lexical.git
synced 2025-08-06 16:39:33 +08:00
[lexical-list] Revert PR 6912 (#6944)
This commit is contained in:
@ -7,6 +7,20 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import type {ListNode, ListType} from './';
|
import type {ListNode, ListType} from './';
|
||||||
|
import type {
|
||||||
|
BaseSelection,
|
||||||
|
DOMConversionMap,
|
||||||
|
DOMConversionOutput,
|
||||||
|
DOMExportOutput,
|
||||||
|
EditorConfig,
|
||||||
|
EditorThemeClasses,
|
||||||
|
LexicalNode,
|
||||||
|
NodeKey,
|
||||||
|
ParagraphNode,
|
||||||
|
RangeSelection,
|
||||||
|
SerializedElementNode,
|
||||||
|
Spread,
|
||||||
|
} from 'lexical';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
addClassNamesToElement,
|
addClassNamesToElement,
|
||||||
@ -15,24 +29,11 @@ import {
|
|||||||
import {
|
import {
|
||||||
$applyNodeReplacement,
|
$applyNodeReplacement,
|
||||||
$createParagraphNode,
|
$createParagraphNode,
|
||||||
$getSelection,
|
|
||||||
$isElementNode,
|
$isElementNode,
|
||||||
$isParagraphNode,
|
$isParagraphNode,
|
||||||
$isRangeSelection,
|
$isRangeSelection,
|
||||||
BaseSelection,
|
|
||||||
DOMConversionMap,
|
|
||||||
DOMConversionOutput,
|
|
||||||
DOMExportOutput,
|
|
||||||
EditorConfig,
|
|
||||||
EditorThemeClasses,
|
|
||||||
ElementNode,
|
ElementNode,
|
||||||
LexicalEditor,
|
LexicalEditor,
|
||||||
LexicalNode,
|
|
||||||
NodeKey,
|
|
||||||
ParagraphNode,
|
|
||||||
RangeSelection,
|
|
||||||
SerializedParagraphNode,
|
|
||||||
Spread,
|
|
||||||
} from 'lexical';
|
} from 'lexical';
|
||||||
import invariant from 'shared/invariant';
|
import invariant from 'shared/invariant';
|
||||||
import normalizeClassNames from 'shared/normalizeClassNames';
|
import normalizeClassNames from 'shared/normalizeClassNames';
|
||||||
@ -46,11 +47,11 @@ export type SerializedListItemNode = Spread<
|
|||||||
checked: boolean | undefined;
|
checked: boolean | undefined;
|
||||||
value: number;
|
value: number;
|
||||||
},
|
},
|
||||||
SerializedParagraphNode
|
SerializedElementNode
|
||||||
>;
|
>;
|
||||||
|
|
||||||
/** @noInheritDoc */
|
/** @noInheritDoc */
|
||||||
export class ListItemNode extends ParagraphNode {
|
export class ListItemNode extends ElementNode {
|
||||||
/** @internal */
|
/** @internal */
|
||||||
__value: number;
|
__value: number;
|
||||||
/** @internal */
|
/** @internal */
|
||||||
@ -80,11 +81,12 @@ export class ListItemNode extends ParagraphNode {
|
|||||||
$setListItemThemeClassNames(element, config.theme, this);
|
$setListItemThemeClassNames(element, config.theme, this);
|
||||||
return element;
|
return element;
|
||||||
}
|
}
|
||||||
updateDOM(prevNode: this, dom: HTMLElement, config: EditorConfig): boolean {
|
|
||||||
if (super.updateDOM(prevNode, dom, config)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
updateDOM(
|
||||||
|
prevNode: ListItemNode,
|
||||||
|
dom: HTMLElement,
|
||||||
|
config: EditorConfig,
|
||||||
|
): boolean {
|
||||||
const parent = this.getParent();
|
const parent = this.getParent();
|
||||||
if ($isListNode(parent) && parent.getListType() === 'check') {
|
if ($isListNode(parent) && parent.getListType() === 'check') {
|
||||||
updateListItemChecked(dom, this, prevNode, parent);
|
updateListItemChecked(dom, this, prevNode, parent);
|
||||||
@ -92,6 +94,7 @@ export class ListItemNode extends ParagraphNode {
|
|||||||
// @ts-expect-error - this is always HTMLListItemElement
|
// @ts-expect-error - this is always HTMLListItemElement
|
||||||
dom.value = this.__value;
|
dom.value = this.__value;
|
||||||
$setListItemThemeClassNames(dom, config.theme, this);
|
$setListItemThemeClassNames(dom, config.theme, this);
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -125,12 +128,6 @@ export class ListItemNode extends ParagraphNode {
|
|||||||
node.setValue(serializedNode.value);
|
node.setValue(serializedNode.value);
|
||||||
node.setFormat(serializedNode.format);
|
node.setFormat(serializedNode.format);
|
||||||
node.setDirection(serializedNode.direction);
|
node.setDirection(serializedNode.direction);
|
||||||
if (typeof serializedNode.textFormat === 'number') {
|
|
||||||
node.setTextFormat(serializedNode.textFormat);
|
|
||||||
}
|
|
||||||
if (typeof serializedNode.textStyle === 'string') {
|
|
||||||
node.setTextStyle(serializedNode.textStyle);
|
|
||||||
}
|
|
||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -227,11 +224,15 @@ export class ListItemNode extends ParagraphNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const siblings = this.getNextSiblings();
|
const siblings = this.getNextSiblings();
|
||||||
|
|
||||||
|
// Split the lists and insert the node in between them
|
||||||
listNode.insertAfter(node, restoreSelection);
|
listNode.insertAfter(node, restoreSelection);
|
||||||
|
|
||||||
if (siblings.length !== 0) {
|
if (siblings.length !== 0) {
|
||||||
const newListNode = $createListNode(listNode.getListType());
|
const newListNode = $createListNode(listNode.getListType());
|
||||||
|
|
||||||
siblings.forEach((sibling) => newListNode.append(sibling));
|
siblings.forEach((sibling) => newListNode.append(sibling));
|
||||||
|
|
||||||
node.insertAfter(newListNode, restoreSelection);
|
node.insertAfter(newListNode, restoreSelection);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -255,49 +256,51 @@ export class ListItemNode extends ParagraphNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
insertNewAfter(
|
insertNewAfter(
|
||||||
selection: RangeSelection,
|
_: RangeSelection,
|
||||||
restoreSelection = true,
|
restoreSelection = true,
|
||||||
): ListItemNode | ParagraphNode {
|
): ListItemNode | ParagraphNode {
|
||||||
const newElement = $createListItemNode(
|
const newElement = $createListItemNode(
|
||||||
this.__checked == null ? undefined : false,
|
this.__checked == null ? undefined : false,
|
||||||
);
|
);
|
||||||
|
|
||||||
const format = selection.format;
|
|
||||||
newElement.setTextFormat(format);
|
|
||||||
|
|
||||||
newElement.setFormat(this.getFormatType());
|
|
||||||
this.insertAfter(newElement, restoreSelection);
|
this.insertAfter(newElement, restoreSelection);
|
||||||
|
|
||||||
return newElement;
|
return newElement;
|
||||||
}
|
}
|
||||||
|
|
||||||
collapseAtStart(): boolean {
|
collapseAtStart(selection: RangeSelection): true {
|
||||||
const selection = $getSelection();
|
|
||||||
|
|
||||||
if (!$isRangeSelection(selection)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const paragraph = $createParagraphNode();
|
const paragraph = $createParagraphNode();
|
||||||
const children = this.getChildren();
|
const children = this.getChildren();
|
||||||
children.forEach((child) => paragraph.append(child));
|
children.forEach((child) => paragraph.append(child));
|
||||||
|
|
||||||
const listNode = this.getParentOrThrow();
|
const listNode = this.getParentOrThrow();
|
||||||
const listNodeParent = listNode.getParent();
|
const listNodeParent = listNode.getParentOrThrow();
|
||||||
|
const isIndented = $isListItemNode(listNodeParent);
|
||||||
if (!$isListNode(listNode)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (listNode.getChildrenSize() === 1) {
|
if (listNode.getChildrenSize() === 1) {
|
||||||
if ($isListItemNode(listNodeParent)) {
|
if (isIndented) {
|
||||||
|
// if the list node is nested, we just want to remove it,
|
||||||
|
// effectively unindenting it.
|
||||||
listNode.remove();
|
listNode.remove();
|
||||||
listNodeParent.select();
|
listNodeParent.select();
|
||||||
} else {
|
} else {
|
||||||
listNode.insertBefore(paragraph);
|
listNode.insertBefore(paragraph);
|
||||||
listNode.remove();
|
listNode.remove();
|
||||||
paragraph.select();
|
// If we have selection on the list item, we'll need to move it
|
||||||
|
// to the paragraph
|
||||||
|
const anchor = selection.anchor;
|
||||||
|
const focus = selection.focus;
|
||||||
|
const key = paragraph.getKey();
|
||||||
|
|
||||||
|
if (anchor.type === 'element' && anchor.getNode().is(this)) {
|
||||||
|
anchor.set(key, anchor.offset, 'element');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (focus.type === 'element' && focus.getNode().is(this)) {
|
||||||
|
focus.set(key, focus.offset, 'element');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
listNode.insertBefore(paragraph);
|
||||||
|
this.remove();
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
@ -242,6 +242,7 @@ export function removeList(editor: LexicalEditor): void {
|
|||||||
|
|
||||||
if ($isLeafNode(node)) {
|
if ($isLeafNode(node)) {
|
||||||
const listItemNode = $getNearestNodeOfType(node, ListItemNode);
|
const listItemNode = $getNearestNodeOfType(node, ListItemNode);
|
||||||
|
|
||||||
if (listItemNode != null) {
|
if (listItemNode != null) {
|
||||||
listNodes.add($getTopListNode(listItemNode));
|
listNodes.add($getTopListNode(listItemNode));
|
||||||
}
|
}
|
||||||
@ -477,13 +478,11 @@ export function $handleListInsertParagraph(): boolean {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
// Only run this code on empty list items
|
// Only run this code on empty list items
|
||||||
|
|
||||||
const anchor = selection.anchor.getNode();
|
const anchor = selection.anchor.getNode();
|
||||||
|
|
||||||
if (!$isListItemNode(anchor) || anchor.getChildrenSize() !== 0) {
|
if (!$isListItemNode(anchor) || anchor.getChildrenSize() !== 0) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const topListNode = $getTopListNode(anchor);
|
const topListNode = $getTopListNode(anchor);
|
||||||
const parent = anchor.getParent();
|
const parent = anchor.getParent();
|
||||||
|
|
||||||
@ -493,6 +492,7 @@ export function $handleListInsertParagraph(): boolean {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const grandparent = parent.getParent();
|
const grandparent = parent.getParent();
|
||||||
|
|
||||||
let replacementNode: ParagraphNode | ListItemNode;
|
let replacementNode: ParagraphNode | ListItemNode;
|
||||||
|
|
||||||
if ($isRootOrShadowRoot(grandparent)) {
|
if ($isRootOrShadowRoot(grandparent)) {
|
||||||
@ -506,10 +506,10 @@ export function $handleListInsertParagraph(): boolean {
|
|||||||
} else {
|
} else {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
replacementNode.select();
|
replacementNode.select();
|
||||||
|
|
||||||
const nextSiblings = anchor.getNextSiblings();
|
const nextSiblings = anchor.getNextSiblings();
|
||||||
|
|
||||||
if (nextSiblings.length > 0) {
|
if (nextSiblings.length > 0) {
|
||||||
const newList = $createListNode(parent.getListType());
|
const newList = $createListNode(parent.getListType());
|
||||||
if ($isListItemNode(replacementNode)) {
|
if ($isListItemNode(replacementNode)) {
|
||||||
@ -521,7 +521,9 @@ export function $handleListInsertParagraph(): boolean {
|
|||||||
}
|
}
|
||||||
newList.append(...nextSiblings);
|
newList.append(...nextSiblings);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Don't leave hanging nested empty lists
|
// Don't leave hanging nested empty lists
|
||||||
$removeHighestEmptyListParent(anchor);
|
$removeHighestEmptyListParent(anchor);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -6,8 +6,9 @@
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import type {LexicalNode, Spread} from 'lexical';
|
||||||
|
|
||||||
import {$findMatchingParent} from '@lexical/utils';
|
import {$findMatchingParent} from '@lexical/utils';
|
||||||
import {type LexicalNode, type Spread} from 'lexical';
|
|
||||||
import invariant from 'shared/invariant';
|
import invariant from 'shared/invariant';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
@ -1901,48 +1901,4 @@ test.describe.parallel('Nested List', () => {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
test('new list item should preserve format from previous list item even after new list item is indented', async ({
|
|
||||||
page,
|
|
||||||
}) => {
|
|
||||||
await focusEditor(page);
|
|
||||||
await toggleBulletList(page);
|
|
||||||
await toggleBold(page);
|
|
||||||
await page.keyboard.type('MLH Fellowship');
|
|
||||||
await page.keyboard.press('Enter');
|
|
||||||
await clickIndentButton(page);
|
|
||||||
await page.keyboard.type('Fall 2024');
|
|
||||||
await assertHTML(
|
|
||||||
page,
|
|
||||||
html`
|
|
||||||
<ul class="PlaygroundEditorTheme__ul">
|
|
||||||
<li
|
|
||||||
class="PlaygroundEditorTheme__listItem PlaygroundEditorTheme__ltr"
|
|
||||||
dir="ltr"
|
|
||||||
value="1">
|
|
||||||
<strong
|
|
||||||
class="PlaygroundEditorTheme__textBold"
|
|
||||||
data-lexical-text="true">
|
|
||||||
MLH Fellowship
|
|
||||||
</strong>
|
|
||||||
</li>
|
|
||||||
<li
|
|
||||||
class="PlaygroundEditorTheme__listItem PlaygroundEditorTheme__nestedListItem"
|
|
||||||
value="2">
|
|
||||||
<ul class="PlaygroundEditorTheme__ul">
|
|
||||||
<li
|
|
||||||
class="PlaygroundEditorTheme__listItem PlaygroundEditorTheme__ltr"
|
|
||||||
dir="ltr"
|
|
||||||
value="1">
|
|
||||||
<strong
|
|
||||||
class="PlaygroundEditorTheme__textBold"
|
|
||||||
data-lexical-text="true">
|
|
||||||
Fall 2024
|
|
||||||
</strong>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
`,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
@ -1026,7 +1026,7 @@ describe('LexicalEditor tests', () => {
|
|||||||
editable ? 'editable' : 'non-editable'
|
editable ? 'editable' : 'non-editable'
|
||||||
})`, async () => {
|
})`, async () => {
|
||||||
const JSON_EDITOR_STATE =
|
const JSON_EDITOR_STATE =
|
||||||
'{"root":{"children":[{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"123","type":"text","version":1}],"direction":null,"format":"","indent":0,"type":"paragraph","version":1,"textFormat":0,"textStyle":""}],"direction":null,"format":"","indent":0,"type":"root","version":1}}';
|
'{"root":{"children":[{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"123","type":"text","version":1}],"direction":null,"format":"","indent":0,"type":"paragraph","version":1,"textStyle":""}],"direction":null,"format":"","indent":0,"type":"root","version":1}}';
|
||||||
init();
|
init();
|
||||||
const contentEditable = editor.getRootElement();
|
const contentEditable = editor.getRootElement();
|
||||||
editor.setEditable(editable);
|
editor.setEditable(editable);
|
||||||
|
File diff suppressed because one or more lines are too long
@ -120,7 +120,11 @@ export class ParagraphNode extends ElementNode {
|
|||||||
}
|
}
|
||||||
return dom;
|
return dom;
|
||||||
}
|
}
|
||||||
updateDOM(prevNode: this, dom: HTMLElement, config: EditorConfig): boolean {
|
updateDOM(
|
||||||
|
prevNode: ParagraphNode,
|
||||||
|
dom: HTMLElement,
|
||||||
|
config: EditorConfig,
|
||||||
|
): boolean {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -160,9 +164,7 @@ export class ParagraphNode extends ElementNode {
|
|||||||
node.setFormat(serializedNode.format);
|
node.setFormat(serializedNode.format);
|
||||||
node.setIndent(serializedNode.indent);
|
node.setIndent(serializedNode.indent);
|
||||||
node.setDirection(serializedNode.direction);
|
node.setDirection(serializedNode.direction);
|
||||||
if (typeof serializedNode.textFormat === 'number') {
|
|
||||||
node.setTextFormat(serializedNode.textFormat);
|
node.setTextFormat(serializedNode.textFormat);
|
||||||
}
|
|
||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -188,8 +190,7 @@ export class ParagraphNode extends ElementNode {
|
|||||||
const direction = this.getDirection();
|
const direction = this.getDirection();
|
||||||
newElement.setDirection(direction);
|
newElement.setDirection(direction);
|
||||||
newElement.setFormat(this.getFormatType());
|
newElement.setFormat(this.getFormatType());
|
||||||
newElement.setStyle(this.getStyle());
|
newElement.setStyle(this.getTextStyle());
|
||||||
|
|
||||||
this.insertAfter(newElement, restoreSelection);
|
this.insertAfter(newElement, restoreSelection);
|
||||||
return newElement;
|
return newElement;
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user