-
Notifications
You must be signed in to change notification settings - Fork 1.7k
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
[lexical] Feature: Implement Editor.read and EditorState.read with editor argument #6347
Changes from 5 commits
cb2b260
88ac5fc
876e3a0
bb8c31e
353e755
004ad87
71be817
7996209
2a8cb98
a744382
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -91,6 +91,10 @@ function exportNodeToJSON<SerializedNode extends SerializedLexicalNode>( | |
return serializedNode; | ||
} | ||
|
||
export interface EditorStateReadOptions { | ||
editor?: LexicalEditor | null; | ||
} | ||
|
||
export class EditorState { | ||
_nodeMap: NodeMap; | ||
_selection: null | BaseSelection; | ||
|
@@ -108,8 +112,12 @@ export class EditorState { | |
return this._nodeMap.size === 1 && this._selection === null; | ||
} | ||
|
||
read<V>(callbackFn: () => V): V { | ||
return readEditorState(this, callbackFn); | ||
read<V>(callbackFn: () => V, options?: EditorStateReadOptions): V { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not a fan of this option like I stated in #6346 (comment) While the intent is valid, I feel like it can easily lead to pitfalls where the editor is no longer compatible with the EditorState. For reference, @ivailop7 mentioned a real-use case around DOM (for tables) and this can potentially fail and be hard to debug with this API, whereas There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm totally open to leaving There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I only implemented it this way because it seemed to be the stated preference of @StyleT on the call and @fantactuka in #6346 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. My point was that we shouldn't have 2 ways of reading the state. But we may sugar coat some API. As mentioned in #6346 (comment) the idea is the following:
This allows to: |
||
return readEditorState( | ||
(options && options.editor) || null, | ||
this, | ||
callbackFn, | ||
); | ||
} | ||
|
||
clone(selection?: null | BaseSelection): EditorState { | ||
|
@@ -122,7 +130,7 @@ export class EditorState { | |
return editorState; | ||
} | ||
toJSON(): SerializedEditorState { | ||
return readEditorState(this, () => ({ | ||
return readEditorState(null, this, () => ({ | ||
root: exportNodeToJSON($getRoot()), | ||
})); | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -22,6 +22,7 @@ import { | |
$createNodeSelection, | ||
$createParagraphNode, | ||
$createTextNode, | ||
$getEditor, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could I add a small request to add a test, that checks if '$getNearestNodeFromDOMNode' can be called within the new "read" method. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sure, it does work, just added tests. Noticed that there's a small inconsistency in that the root node is never set in the It does only run the tests with the latest reconciled state (the current default) because that is really what probably makes the most sense for this use case. |
||
$getNodeByKey, | ||
$getRoot, | ||
$isTextNode, | ||
|
@@ -133,6 +134,78 @@ describe('LexicalEditor tests', () => { | |
return Promise.resolve().then(); | ||
} | ||
|
||
describe('read()', () => { | ||
it('Can read the editor state', async () => { | ||
init(); | ||
expect(editor.read(() => $getRoot().getTextContent())).toEqual(''); | ||
expect(editor.read(() => $getEditor())).toBe(editor); | ||
editor.update(() => { | ||
const root = $getRoot(); | ||
const paragraph = $createParagraphNode(); | ||
const text = $createTextNode('This works!'); | ||
root.append(paragraph); | ||
paragraph.append(text); | ||
}); | ||
expect(editor.read(() => $getRoot().getTextContent())).toEqual(''); | ||
expect(editor.read(() => $getRoot().getTextContent(), {})).toEqual(''); | ||
expect( | ||
editor.read(() => $getRoot().getTextContent(), {pending: false}), | ||
).toEqual(''); | ||
expect( | ||
editor.read(() => $getRoot().getTextContent(), {pending: true}), | ||
).toEqual('This works!'); | ||
await Promise.resolve().then(); | ||
editor.read(() => { | ||
$getRoot(); | ||
}); | ||
expect(editor.read(() => $getRoot().getTextContent())).toEqual( | ||
'This works!', | ||
); | ||
expect( | ||
editor.read(() => $getRoot().getTextContent(), {pending: true}), | ||
).toEqual('This works!'); | ||
}); | ||
|
||
it('Can be nested in an update or read', async () => { | ||
init(); | ||
editor.update(() => { | ||
const root = $getRoot(); | ||
const paragraph = $createParagraphNode(); | ||
const text = $createTextNode('This works!'); | ||
root.append(paragraph); | ||
paragraph.append(text); | ||
editor.read(() => { | ||
expect($getRoot().getTextContent()).toBe(''); | ||
}); | ||
editor.read( | ||
() => { | ||
expect($getRoot().getTextContent()).toBe('This works!'); | ||
}, | ||
{pending: true}, | ||
); | ||
// This works, although it is discouraged in the documentation. | ||
editor.read(() => { | ||
editor.update(() => { | ||
expect($getRoot().getTextContent()).toBe('This works!'); | ||
}); | ||
expect($getRoot().getTextContent()).toBe(''); | ||
editor.read( | ||
() => { | ||
expect($getRoot().getTextContent()).toBe('This works!'); | ||
}, | ||
{pending: true}, | ||
); | ||
}); | ||
}); | ||
await Promise.resolve().then(); | ||
editor.read(() => { | ||
editor.read(() => { | ||
expect($getRoot().getTextContent()).toBe('This works!'); | ||
}); | ||
}); | ||
}); | ||
}); | ||
|
||
it('Should create an editor with an initial editor state', async () => { | ||
const rootElement = document.createElement('div'); | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IMO this behavior is confusing,
.read
should always grab thepending
state just likeupdate
. You would not expect an update to be done on top of the reconciler, but rather on top of your previous changes, likewise forThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't hold any strong opinions on the API, so long as the option exists. I implemented it this way mostly based on the loudest opinions at the time 😆
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm with Gerard on this one.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm surprised that this would be your preference since the DOM use case would really be best handled with the latest reconciled editor state rather than the pending editor state since the DOM is not necessarily in sync with that yet.