-
Notifications
You must be signed in to change notification settings - Fork 3.2k
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
Generic typescript #4177
base: main
Are you sure you want to change the base?
Generic typescript #4177
Conversation
🦋 Changeset detectedLatest commit: b207bd8 The changes in this PR will be included in the next version bump. This PR includes changesets to release 4 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
/release:next |
A new release has been made for this pull request. You can install it with |
@@ -24,39 +24,39 @@ export const HistoryEditor = { | |||
* Check if a value is a `HistoryEditor` object. | |||
*/ | |||
|
|||
isHistoryEditor(value: any): value is HistoryEditor { | |||
isHistoryEditor(value: any): value is HistoryEditor<Value> { |
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.
Missed a V extends Value
type parameter?
isHistoryEditor(value: any): value is HistoryEditor<Value> { | |
isHistoryEditor<V extends Value>(value: any): value is HistoryEditor<V> { |
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.
Good question. I'm not really sure how to handle the checking functions in general (like Element.isElement
, or this one). I'm not sure there's a way to accurately infer V
in that case without the user passing it in? And even if they pass it in, the function itself isn't verifying that it objects any sort of value?
Excited about this branch, it makes a lot more sense than overwriting type definitions. Is there a rough ETA before a @next release? |
Also pretty excited about this. Thanks for exploring this! |
We would really need this as well, are there any status updates? |
options: { | ||
at?: Location | Span | ||
match?: NodeMatch<T> | ||
match?: NodeMatch<T & NodeOf<Editor<V>>> |
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.
match?: NodeMatch<T & NodeOf<Editor<V>>> | |
match?: NodeMatch<NodeOf<Editor<V>>> |
We often know what type of nodes we want to get, so it makes sense to use T
as a return type.
I would remove it from match
option as we generally iterate through all types of nodes
Transforms.insertFragment(editor, fragment) | ||
}, | ||
|
||
insertNode: (node: Node) => { | ||
// @ts-ignore |
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.
This would create issues like udecode/plate#936
insertNode: ( | ||
node: | ||
| ElementOf<Editor<V>> | ||
| TextOf<Editor<V>> | ||
| Array<ElementOf<Editor<V>> | TextOf<Editor<V>>> | ||
) => { |
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.
Using Editor
or Value
here will introduce errors: circular dependency or value type not matching, I found only this looser typing working:
insertNode: ( | |
node: | |
| ElementOf<Editor<V>> | |
| TextOf<Editor<V>> | |
| Array<ElementOf<Editor<V>> | TextOf<Editor<V>>> | |
) => { | |
insertNode: <N extends Descendant>( | |
node: N | N[] | |
) => { |
@@ -55,246 +58,28 @@ export interface BaseEditor { | |||
deleteBackward: (unit: 'character' | 'word' | 'line' | 'block') => void | |||
deleteForward: (unit: 'character' | 'word' | 'line' | 'block') => void | |||
deleteFragment: (direction?: 'forward' | 'backward') => void | |||
getFragment: () => Descendant[] | |||
getFragment: () => Array<Element | Text> |
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.
getFragment: () => Array<Element | Text> | |
getFragment: <N extends Descendant>() => N[]; |
insertFragment: (fragment: Array<Element | Text>) => void | ||
insertNode: (node: Element | Text | Array<Element | Text>) => void |
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.
insertFragment: (fragment: Array<Element | Text>) => void | |
insertNode: (node: Element | Text | Array<Element | Text>) => void | |
insertFragment: <N extends Descendant>(fragment: N[]) => void; | |
insertNode: <N extends Descendant>(node: N | N[]) => void; |
I unfortunately don't have the time to complete that PR, so I've forked that PR into Plate. If you're looking to use these types, see Plate 11. |
Is there someone kind and brave enough to lift this PR up and continue the work on it? Or is the official approach against this? |
The official approach is that we definitely want to finish this, but no one has had time to do so. Plate seems to be doing pretty well with bounties attached to work they've not had time to finish, but I haven't wanted to go that route with Slate. At this point, a good path would probably be to look at what Plate has done, see if it makes sense to wrap this up, and if so land it. More likely there are some things to improve that were uncovered in that work. After we land this, a decent test suite for slate-react, and some optimizations for Reach 18 and 19, I'm ready to call it 1.0. (Just in time to start thinking about switching to EditContext API if everyone supports it (currently in Chrome, waiting for indication from others): https://w3c.github.io/edit-context/ (this is a substantial change)) |
Description
This is an attempt at making Slate work with TypeScript generics.
Issue
Fixes: #3725
Fixes: #3416
Example
The best example is looking at the new TypeScript example on this branch. (Other examples have been purposefully simplified to have very minimal type annotations, to keep them easier to understand.)
Context
This works by changing the
Editor
type to beEditor<V>
whereV
represents the "value" being edited by Slate. In the most generic editor,V
would be equivalent toElement[]
(since that is what is accepted as children of the editor). But in a custom editor, you might haveEditor<Array<Paragraph | Quote>>
.Other
Editor
-and-Node
-related methods have been also made generic, so for example if you useEditor.leaf(editor, path)
it knows that the return value is aText
node. But more specifically, it knows that it is the text node of the type you've defined in your custom elements (with any marks you've defined).This replaces the declaration merging approach, and provides some benefits. One of the drawbacks to declaration merging was that it was impossible to know whether you were dealing with an "unknown" or "known" element, since the underlying type was changed. Similarly, having two editors on the page with different schemas wasn't possible to represent. Hopefully this approach with generics will be able to smoothly replace the declaration merging approach. (While being easy to migrate to, since you can pass those same custom element definitions into
Editor
still.)It's still a work in progress.
Checks
yarn test
.yarn lint
. (Fix errors withyarn fix
.)yarn start
.)yarn changeset add
.)