Skip to content

Commit e8c3774

Browse files
committed
Change ListItemNode style based on transforms and selection
1 parent f9f3cc2 commit e8c3774

File tree

4 files changed

+73
-40
lines changed

4 files changed

+73
-40
lines changed

packages/lexical-list/src/LexicalListItemNode.ts

+6-1
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,9 @@ export class ListItemNode extends ElementNode {
8080
}
8181
element.value = this.__value;
8282
$setListItemThemeClassNames(element, config.theme, this);
83+
if (this.__style !== '') {
84+
element.style.cssText = this.__style;
85+
}
8386
return element;
8487
}
8588

@@ -95,7 +98,9 @@ export class ListItemNode extends ElementNode {
9598
// @ts-expect-error - this is always HTMLListItemElement
9699
dom.value = this.__value;
97100
$setListItemThemeClassNames(dom, config.theme, this);
98-
101+
if (prevNode.__style !== this.__style) {
102+
dom.style.cssText = this.__style;
103+
}
99104
return false;
100105
}
101106

packages/lexical-list/src/index.ts

+33-26
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,18 @@
88

99
import type {SerializedListItemNode} from './LexicalListItemNode';
1010
import type {ListType, SerializedListNode} from './LexicalListNode';
11-
import type {LexicalCommand, LexicalEditor, LexicalNode} from 'lexical';
11+
import type {LexicalCommand, LexicalEditor} from 'lexical';
1212

1313
import {mergeRegister} from '@lexical/utils';
1414
import {
1515
$getSelection,
1616
$isRangeSelection,
17+
$isTextNode,
18+
COMMAND_PRIORITY_EDITOR,
1719
COMMAND_PRIORITY_LOW,
1820
createCommand,
1921
INSERT_PARAGRAPH_COMMAND,
22+
SELECTION_CHANGE_COMMAND,
2023
TextNode,
2124
} from 'lexical';
2225

@@ -61,6 +64,21 @@ export const REMOVE_LIST_COMMAND: LexicalCommand<void> = createCommand(
6164
'REMOVE_LIST_COMMAND',
6265
);
6366

67+
function $checkSelectionListener(): boolean {
68+
const selection = $getSelection();
69+
if ($isRangeSelection(selection) && selection.isCollapsed()) {
70+
const node = selection.anchor.getNode();
71+
if (
72+
$isListItemNode(node) &&
73+
node.isEmpty() &&
74+
selection.style !== node.getStyle()
75+
) {
76+
node.setStyle(selection.style);
77+
}
78+
}
79+
return false;
80+
}
81+
6482
export function registerList(editor: LexicalEditor): () => void {
6583
const removeListener = mergeRegister(
6684
editor.registerCommand(
@@ -100,37 +118,26 @@ export function registerList(editor: LexicalEditor): () => void {
100118
},
101119
COMMAND_PRIORITY_LOW,
102120
),
121+
editor.registerCommand(
122+
SELECTION_CHANGE_COMMAND,
123+
$checkSelectionListener,
124+
COMMAND_PRIORITY_EDITOR,
125+
),
103126
editor.registerNodeTransform(ListItemNode, (node) => {
104-
const listItemElement = editor.getElementByKey(node.__key);
105-
if (node && listItemElement) {
106-
const firstChild = node.getFirstChild<LexicalNode>();
107-
if (firstChild) {
108-
const textElement = editor.getElementByKey(firstChild.getKey());
109-
if (
110-
textElement &&
111-
textElement.style.cssText &&
112-
textElement.style.cssText !== node.getStyle()
113-
) {
114-
listItemElement.setAttribute('style', textElement.style.cssText);
115-
node.markDirty();
116-
}
117-
} else {
118-
const selection = $getSelection();
119-
if (
120-
$isRangeSelection(selection) &&
121-
selection.isCollapsed() &&
122-
selection.style &&
123-
selection.style !== node.getStyle()
124-
) {
125-
listItemElement.setAttribute('style', selection.style);
126-
node.markDirty();
127-
}
127+
const firstChild = node.getFirstChild();
128+
if (firstChild && $isTextNode(firstChild)) {
129+
const style = firstChild.getStyle();
130+
if (node.getStyle() !== style) {
131+
node.setStyle(style);
128132
}
129133
}
130134
}),
131135
editor.registerNodeTransform(TextNode, (node) => {
132136
const listItemParentNode = node.getParent();
133-
if ($isListItemNode(listItemParentNode)) {
137+
if (
138+
$isListItemNode(listItemParentNode) &&
139+
node.is(listItemParentNode.getFirstChild())
140+
) {
134141
listItemParentNode.markDirty();
135142
}
136143
}),

packages/lexical-selection/src/lexical-node.ts

+8-3
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import {
1717
$isTextNode,
1818
$isTokenOrSegmented,
1919
BaseSelection,
20+
ElementNode,
2021
LexicalEditor,
2122
LexicalNode,
2223
Point,
@@ -241,7 +242,7 @@ export function $addNodeStyle(node: TextNode): void {
241242
}
242243

243244
function $patchStyle(
244-
target: TextNode | RangeSelection,
245+
target: TextNode | RangeSelection | ElementNode,
245246
patch: Record<
246247
string,
247248
| string
@@ -263,7 +264,7 @@ function $patchStyle(
263264
}
264265
return styles;
265266
},
266-
{...prevStyles} || {},
267+
{...prevStyles},
267268
);
268269
const newCSSText = getCSSFromStyleObject(newStyles);
269270
target.setStyle(newCSSText);
@@ -285,12 +286,16 @@ export function $patchStyleText(
285286
| null
286287
| ((
287288
currentStyleValue: string | null,
288-
target: TextNode | RangeSelection,
289+
target: TextNode | RangeSelection | ElementNode,
289290
) => string)
290291
>,
291292
): void {
292293
if (selection.isCollapsed() && $isRangeSelection(selection)) {
293294
$patchStyle(selection, patch);
295+
const emptyNode = selection.anchor.getNode();
296+
if ($isElementNode(emptyNode) && emptyNode.isEmpty()) {
297+
$patchStyle(emptyNode, patch);
298+
}
294299
} else {
295300
$forEachSelectedTextNode((textNode) => {
296301
$patchStyle(textNode, patch);

packages/lexical/src/LexicalEvents.ts

+26-10
Original file line numberDiff line numberDiff line change
@@ -337,31 +337,27 @@ function onSelectionChange(
337337
anchor.offset === lastOffset &&
338338
anchor.key === lastKey
339339
) {
340-
selection.format = lastFormat;
341-
selection.style = lastStyle;
340+
$updateSelectionFormatStyle(selection, lastFormat, lastStyle);
342341
} else {
343342
if (anchor.type === 'text') {
344343
invariant(
345344
$isTextNode(anchorNode),
346345
'Point.getNode() must return TextNode when type is text',
347346
);
348-
selection.format = anchorNode.getFormat();
349-
selection.style = anchorNode.getStyle();
347+
$updateSelectionFormatStyleFromNode(selection, anchorNode);
350348
} else if (anchor.type === 'element' && !isRootTextContentEmpty) {
351349
invariant(
352350
$isElementNode(anchorNode),
353351
'Point.getNode() must return ElementNode when type is element',
354352
);
355353
const lastNode = anchor.getNode();
356-
selection.style = '';
357354
if (
358355
// This previously applied to all ParagraphNode
359356
lastNode.isEmpty()
360357
) {
361-
selection.format = lastNode.getTextFormat();
362-
selection.style = lastNode.getTextStyle();
358+
$updateSelectionFormatStyleFromNode(selection, lastNode);
363359
} else {
364-
selection.format = 0;
360+
$updateSelectionFormatStyle(selection, 0, '');
365361
}
366362
}
367363
}
@@ -411,6 +407,27 @@ function onSelectionChange(
411407
});
412408
}
413409

410+
function $updateSelectionFormatStyle(
411+
selection: RangeSelection,
412+
format: number,
413+
style: string,
414+
) {
415+
if (selection.format !== format || selection.style !== style) {
416+
selection.format = format;
417+
selection.style = style;
418+
selection.dirty = true;
419+
}
420+
}
421+
422+
function $updateSelectionFormatStyleFromNode(
423+
selection: RangeSelection,
424+
node: TextNode | ElementNode,
425+
) {
426+
const format = node.getFormat();
427+
const style = node.getStyle();
428+
$updateSelectionFormatStyle(selection, format, style);
429+
}
430+
414431
// This is a work-around is mainly Chrome specific bug where if you select
415432
// the contents of an empty block, you cannot easily unselect anything.
416433
// This results in a tiny selection box that looks buggy/broken. This can
@@ -578,12 +595,11 @@ function onBeforeInput(event: InputEvent, editor: LexicalEditor): void {
578595
if ($isRangeSelection(selection)) {
579596
const anchorNode = selection.anchor.getNode();
580597
anchorNode.markDirty();
581-
selection.format = anchorNode.getFormat();
582598
invariant(
583599
$isTextNode(anchorNode),
584600
'Anchor node must be a TextNode',
585601
);
586-
selection.style = anchorNode.getStyle();
602+
$updateSelectionFormatStyleFromNode(selection, anchorNode);
587603
}
588604
} else {
589605
$setCompositionKey(null);

0 commit comments

Comments
 (0)