Changelog
This is a list of changes to Slate with each new release. Until 1.0 is released, breaking changes will be added as minor version bumps, and smaller, patch-level changes won't be noted since the library is moving quickly while in beta.
⚠️ Until https://github.com/atlassian/changesets/issues/264 is solved, each package will maintain its own individual changelog, which you can find here:

0.61 — March 29, 2021

BREAKING

New CustomTypes for Editor, Element, Text and other implementation specific objects. Improved typing with TypeScript lets you add CustomTypes for the Slate Editor. This change requires you to set up your types at the start. It's a new concept so please read the new TypeScript documentation here: https://docs.slatejs.org/concepts/11-typescript

0.60 — November 24, 2020

BREAKING

Introduced new customizable TypeScript typings. You can override the built-in types to extend them for your own editor's domain model. However the changes to make this possible likely resulted in some changes to the existing type contracts.
The useEditor hook was renamed to useSlateStatic. This was done to better differentiate between the useSlate hook and to make it clear that the static version will not re-render when changes occur.

0.59 — September 24, 2020

There were no breaking changes or new additions in this release.

0.58 — May 5th, 2020

BREAKING

User properties on Elements and Texts now have an unknown type instead of any. Previously, the arbitrary user defined keys on the Text and Element interface had a type of any which effectively removed any potential type checking on those properties. Now these have a type of unknown so that type checking can be done by consumers of the API when they are applying their own custom properties to the Texts and Elements.

0.57 — December 18, 2019

BREAKING

Overridable commands now live directly on the editor object. Previously the Command concept was implemented as an interface that was passed into the editor.exec function, allowing the "core" commands to be overridden in one place. But this introduced a lot of Redux-like indirection when implementing custom commands that wasn't necessary because they are never overridden. Instead, now the core actions that can be overridden are implemented as individual functions on the editor (eg. editor.insertText) and they can be overridden just like any other function (eg. isVoid).
Previously to override a command you'd do:
1
const withPlugin = editor => {
2
const { exec } = editor
3
4
editor.exec = command => {
5
if (command.type === 'insert_text') {
6
const { text } = command
7
8
if (myCustomLogic) {
9
// ...
10
return
11
}
12
}
13
14
exec(command)
15
}
16
17
return editor
18
}
Copied!
Now, you'd override the specific function directly:
1
const withPlugin = editor => {
2
const { insertText } = editor
3
4
editor.insertText = text => {
5
if (myCustomLogic) {
6
// ...
7
return
8
}
9
10
insertText(text)
11
}
12
13
return editor
14
}
Copied!
You shouldn't ever need to call these functions directly! They are there for plugins to tap into, but there are higher level helpers for you to call whenever you actually need to invoke them. Read on…
Transforms now live in a separate namespace of helpers. Previously the document and selection transformation helpers were available directly on the Editor interface as Editor.*. But these helpers are fairly low level, and not something that you'd use in your own codebase all over the place, usually only inside specific custom helpers of your own. To make room for custom userland commands, these helpers have been moved to a new Transforms namespace.
Previously you'd write:
1
Editor.unwrapNodes(editor, ...)
Copied!
Now you'd write:
1
Transforms.unwrapNodes(editor, ...)
Copied!
The Command interfaces were removed. As part of those changes, the existing Command, CoreCommand, HistoryCommand, and ReactCommand interfaces were all removed. You no longer need to define these "command objects", because you can just call the functions directly. Plugins can still define their own overridable commands by extending the Editor interface with new functions. The slate-react plugin does this with insertData and the slate-history plugin does this with undo and redo.

NEW

User action helpers now live directly on the Editor.* interface. These are taking the place of the existing Transforms.* helpers that were moved. These helpers are equivalent to user actions, and they always operate on the existing selection. There are some defined by core, but you are likely to define your own custom helpers that are specific to your domain as well.
For example, here are some of the built-in actions:
1
Editor.insertText(editor, 'a string of text')
2
Editor.deleteForward(editor)
3
Editor.deleteBackward(editor, { unit: 'word' })
4
Editor.addMark(editor, 'bold', true)
5
Editor.insertBreak(editor)
6
...
Copied!
Every one of the old "core commands" has an equivalent Editor.* helper exposed now. However, you can easily define your own custom helpers and place them in a namespace as well:
1
const MyEditor = {
2
...Editor,
3
insertParagraph(editor) { ... },
4
toggleBoldMark(editor) { ... },
5
formatLink(editor, url) { ... },
6
...
7
}
Copied!
Whatever makes sense for your specific use case!

0.56 — December 17, 2019

BREAKING

The format_text command is split into add_mark and remove_mark. Although the goal is to keep the number of commands in core to a minimum, having this as a combined command made it very hard to write logic that wanted to guarantee to only ever add or remove a mark from a text node. Now you can be guaranteed that the add_mark command will only ever add custom properties to text nodes, and the remove_mark command will only ever remove them.
Previously you would write:
1
editor.exec({
2
type: 'format_text',
3
properties: { bold: true },
4
})
Copied!
Now you would write:
1
if (isActive) {
2
editor.exec({ type: 'remove_mark', key: 'bold' })
3
} else {
4
editor.exec({ type: 'add_mark', key: 'bold', value: true })
5
}
Copied!
🤖 Note that the "mark" term does not mean what it meant in 0.47 and earlier. It simply means formatting that is applied at the text level—bold, italic, etc. We need a term for it because it's such a common pattern in richtext editors, and "mark" is often the term that is used. For example the <mark> tag in HTML.
The Node.text helper was renamed to Node.string. This was simply to reduce the confusion between "the text string" and "text nodes". The helper still just returns the concatenated string content of a node.

0.55 — December 15, 2019

BREAKING

The match option must now be a function. Previously there were a few shorthands, like passing in a plain object. This behavior was removed because it made it harder to reason about exactly what was being matched, it made debugging harder, and it made it hard to type well. Now the match option must be a function that receives the Node object to match. If you're using TypeScript, and the function you pass in is a type guard, that will be taken into account in the return value!
Previously you might write:
1
Editor.nodes(editor, {
2
at: range,
3
match: 'text',
4
})
5
6
Editor.nodes(editor, {
7
at: range,
8
match: { type: 'paragraph' },
9
})
Copied!
Now you'd write:
1
Editor.nodes(editor, {
2
at: range,
3
match: Text.isText,
4
})
5
6
Editor.nodes(editor, {
7
at: range,
8
match: node => node.type === 'paragraph',
9
})
Copied!
The mode option now defaults to 'lowest'. Previously the default varied depending on where in the codebase it was used. Now it defaults to 'lowest' everywhere, and you can always pass in 'highest' to change the behavior. The one exception is the Editor.nodes helper which defaults to 'all' since that's the expected behavior most of the time.
The Editor.match helper was renamed to Editor.above. This was just to make it clear how it searched in the tree—it looks through all of the nodes directly above a location in the document.
The Editor.above/previous/next helpers now take all options in a dictionary. Previously their APIs did not exactly match the Editor.nodes helper which they are shorthand for, but now this is no longer the case. The at, match and mode options are all passed in the options argument.
Previously you would use:
1
Editor.previous(editor, path, n => Text.isText(n), {
2
mode: 'lowest',
3
})
Copied!
Now you'd use:
1
Editor.previous(editor, {
2
at: path,
3
match: n => Text.isText(n),
4
mode: 'lowest',
5
...
6
})
Copied!
The Editor.elements and Editor.texts helpers were removed. These were simple convenience helpers that were rarely used. You can now achieve the same thing by using the Editor.nodes helper directly along with the match option. For example:
1
Editor.nodes(editor, {
2
at: range,
3
match: Element.isElement,
4
})
Copied!

0.54 — December 12, 2019

BREAKING

The <Slate> onChange handler no longer receives the selection argument. Previously it received (value, selection), now it receives simply (value). Instead, you can access any property of the editor directly (including the value as editor.children). The value/onChange convention is provided purely for form-related use cases that expect it. This is along with the change to how extra props are "controlled". By default they are uncontrolled, but you can pass in any of the other top-level editor properties to take control of them.
The Command and CoreCommand interfaces have been split apart. Previously you could access Command.isCoreCommand, however now this helper lives directly on the core command interface as CoreCommand.isCoreCommand. This makes it more symmetrical with userland commands.
Command checkers have been simplified. Previously Slate exposed command-checking helpers like Command.isInsertTextCommand. However these were verbose and not useful most of the time. Instead, you can now check for CoreCommand.isCoreCommand and then use the command.type property to narrow further. This keeps core more symmetrical with how userland will implement custom commands.

NEW

The <Slate> component is now pseudo-controlled. It requires a value= prop to be passed in which is controlled. However, the selection, marks, history, or any other props are not required to be controlled. They default to being uncontrolled. If your use case requires controlling these extra props you can pass them in and they will start being controlled again. This change was made to make using Slate easier, while still allowing for more complex state to be controlled by core or plugins going forward—state that users don't need to concern themselves with most of time.
The Editor now has a marks property. This property represents text-level formatting that will be applied to the next character that is inserted. This is a common richtext editor behavior, where pressing a Bold button with a collapsed selection turns on "bold" formatting mode, and then typing a character becomes bold. This state isn't stored in the document, and is instead stored as an extra property on the editor itself.

0.53 — December 10, 2019

BREAKING

The slate-schema package has been removed! This decision was made because with the new helpers on the Editor.* interface, and with the changes to normalizeNode in the latest version of Slate, adding constraints using normalizeNode actually leads to more maintainable code than using slate-schema. Previously it was required to keep things from getting too unreadable, but that always came at a large cost of indirection and learning additional APIs. Everything you could do with slate-schema you can do with normalizeNode, and more.
Node matching functions now receive just a Node. Previously they received a NodeEntry tuple, which consisted of [node, path]. However now they receive only a node argument, which makes it easier to write one-off node-checking helpers and pass them in directly as arguments. If you need to ensure a path, lookup the node first.
A few unnecessary helpers were removed. There were a handful of leftovers helpers that were not used anywhere in Slate's core logic, and were very unlikely to be used in userland, so they've been removed to reduce bundle size. You are always free to re-implement them if you truly need them. The list of helpers removed is:
    Editor.ancestor
    Node.closest
    Node.furthest
    Range.exists
    Range.isRangeList
    Range.isRangeMap

0.52 — December 5, 2019

BREAKING

The slate-schema package now exports a factory. Previously you imported the withSchema function directly from the package, and passed in your schema rules when you called it. However, now you import the defineSchema factory instead which takes your schema rules and returns a custom withSchema plugin function. This way you can still use helpers like compose with the plugin, while pre-defining your custom rules.
The properties validation in the schema is now exhaustive. Previously a properties validation would check any properties you defined, and leave any unknown ones as is. This made it hard to be certain about which properties would end up on a node. Now any non-defined properties are considered invalid. And using an empty {} validation would ensure that there are no custom properties at all.

NEW

The leaves schema validation ensures text-level formatting. You can use it from any higher up element node in the tree, to guarantee that it only contains certain types of text-level formatting on its inner text nodes. For example you could use it to ensure that a code block doesn't allow any of its text to be bolded or italicized.

0.51 — December 5, 2019

BREAKING

The Mark interface has been removed! Previously text-level formatting was stored in an array of unique marks. Now that same formatting is stored directly on the Text nodes themselves. For example instead of:
1
{
2
text: 'A line of text.',
3
marks: [{ type: 'bold' }],
4
}
Copied!
You now have:
1
{
2
text: 'A line of text.',
3
bold: true,
4
}
Copied!
And the marks are added and removed from the text nodes using the same Editor.setNodes transform that you use for toggling formatting on block and inline nodes. This greatly simplifies things and makes Slate's core even smaller.
The <Slate> component is now a "controlled" component. This makes things a bit more React-ish, and makes it easier to update the editor's value when new data is received after the initial render. To arrive at the previous "uncontrolled" behavior you'll need to implement it in userland using React's built-in hooks.
Whereas previously you would do:
1
<Slate defaultValue={initialValue}>...</Slate>
Copied!
Now you must manage the value and selection yourself, like:
1
const [value, setValue] = useState(initialValue)
2
const [selection, setSelection] = useState(null)
3
4
<Slate
5
value={value}
6
selection={selection}
7
onChange={(value, selection) => {
8
setValue(value)
9
setSelection(selection)
10
}}
11
>
12
...
13
</Slate>
Copied!

0.50 — November 27, 2019

BREAKING

A complete overhaul. The Slate codebase has had a complete overhaul and many pieces of its core architecture have been reconsidered from the ground up. There are lots of changes. We recommend re-reading the Walkthroughs and Concepts documentation and the Examples to get a sense for everything that has changed. As well as the Migration writeup for what the major changes are.
Warning: Changes past this point refer to the older Slate architecture, based on Immutable.js and without TypeScript. Many things are different in the older architecture and may not apply to the newer one.

0.47 — May 8, 2019

NEW

Introducing the Annotation model. This is very similar to what used to be stored in value.decorations, except they also contain a unique "key" to be identified by. They can be used for things like comments, suggestions, collaborative cursors, etc.
1
{
2
object: 'annotation',
3
key: String,
4
type: String,
5
data: Map,
6
anchor: Point,
7
focus: Point,
8
}
Copied!
There are three new *_annotation operations. The set of operations now includes add_annotation, remove_annotation and set_annotation. They are similar to the existing *_mark operations.
Introducing "iterable" model methods. This introduces several iteratable-producing methods on the Element interface, which Document, Block and Inline all implement. There are iterables for traversing the entire tree:
1
element.blocks(options)
2
element.descendants(options)
3
element.inlines(options)
4
element.texts(options)
5
6
element.ancestors(path, options)
7
element.siblings(path, options)
Copied!
You can use them just like the native JavaScript iterables. For example, you can loop through the text nodes after a specific node:
1
for (const next of document.texts({ path: start.path })) {
2
const [node, path] = next
3
// do something with the text node or its path
4
}
Copied!
Or you can traverse all of the "leaf" blocks:
1
for (const [block] of document.blocks({ onlyLeaves: true })) {
2
// ...
3
}
Copied!
And because these iterations use native for/of loops, you can easily break or return out of the loops directly—a much nicer DX than remembering to return false.

BREAKING

The value.decorations property is now value.annotations. Following with the split of decorations into annotations, this property was also renamed. They must now contain unique key properties, as they are stored as a Map instead of a List. This allows for much more performant updates.
The Decoration model no longer has a nested mark property. Previously a real Mark object was used as a property on decorations, but now the type and data properties are first class properties instead.
1
{
2
object: 'decoration',
3
type: String,
4
data: Map,
5
anchor: Point,
6
focus: Point,
7
}
Copied!

0.46 — May 1, 2019

BREAKING

Mark operations no longer have offset or length properties. Since text nodes now contain a unique set of marks, it wouldn't make sense for a single mark-related operation to result in a splitting of nodes. Instead, when a mark is added to only part of a text node, it will result in a split_node operation as well as an add_mark operation.
Text operations no longer have a marks property. Previously it was used to add text with a specific set of marks. However this is no longer necessary, and when text is added with marks it will result in an insert_text operation as well as an add_mark operation.
Using Text.create or Text.createList with a leaves property will error. Now that text nodes no longer have leaves, you will need to pass in the text string and marks directly when creating a new text node. (However, you can still create entire values using Value.create in a backwards compatible way for convenience while migrating.)
1
// This works, although deprecated, which is the common case...
2
Value.create(oldValueJson)
3
4
// ...but this will error!
5
Text.create(oldTextJson)
Copied!
Value.toJSON returns the new data model format, without leaves. Although Value.fromJSON and Value.create allow the old format in deprecated mode, calling Value.toJSON will return the new data format. If you still need the old one you'll need to iterate the document tree converting text nodes yourself.
The low-level Value.* and Node.* mutation methods have changed. These changes follow the operation signature changes, since the methods take the same arguments as the operations themselves. For example:
1
// Previously...
2
value.addMark(path, offset, length, mark)
3
4
// ...is now:
5
value.addMark(path, mark)
Copied!
These are low-level methods, so this change shouldn't affect the majority of use cases.

DEPRECATED

Initializing editors with Text nodes with a leaves property is deprecated. In this new version of Slate, creating a new value with Value.create with the old leaf data model is still allowed for convenience in migration, but it will be removed in a coming version. (However, using the low-level Text.create will throw an error!)
1
// This works, although deprecated, which is the common case...
2
Value.create(oldValueJson)
3
4
// ...but this will error!
5
Text.create(oldTextJson)
Copied!

0.45 — April 2, 2019

BREAKING

A few properties of Operation objects have changed. In an effort to standardize and streamline operations, their properties have changed. This won't affect 90% of use cases, since operations are usually low-level concerns. However, if you are using operational transform or some other low-level parts of Slate, this may affect you. The value, selection, node, and mark properties—which contained references to Immutable.js objects—have all been removed. In their place, we have standardized a properties and newProperties pair. This will greatly reduce the size of operations stored in memory, and makes dealing with them easier when serialized as well.

0.44 — November 8, 2018

NEW

Introducing the child_min_invalid and child_max_invalid schema errors. These new schema errors map directly to the mix and max schema rule definitions, and make it easier to determine exactly what your normalization logic needs to do to fix the document.
Added new node retrieval methods. There are three new methdos for node retrieval. The first is getNodesAtRange which will retrieve all of the nodes in the tree in a given range. And the second two are getRootBlocksAtRange and getRootInlinesAtRange for retrieving the top-most blocks or inlines in a given range. These should be helpful in defining your own command logic.

BREAKING

Schema errors for min and max rules have changed. Previously they would result in errors of child_required, child_object_invalid, child_type_invalid and child_unknown. Now that we have the new child_min_invalid and child_max_invalid errors, these schema rules will return them instead, making it much easier to determine exactly which rule is causing a schema error.

DEPRECATED

The getBlocksAtRange and getInlinesAtRange methods have been renamed. To clear up confusion about which blocks and inlines are retrieve in the case of nesting, these two methods have been renamed to getLeafBlocksAtRange and getLeafInlinesAtRange to clarify that they retrieve the bottom-most nodes. And now there are two additional methods called getRootBlocksAtRange and getRootInlinesAtRange for cases where you want the top-most nodes instead.

0.43 — October 27, 2018

NEW

The editor.command and editor.query methods can take functions. Previously they only accepted a type string and would look up the command or query by type. Now, they also accept a custom function. This is helpful for plugin authors, who want to accept a "command option", since it gives users more flexibility to write one-off commands or queries. For example a plugin could be passed either:
1
Hotkey({
2
hotkey: 'cmd+b',
3
command: 'addBoldMark',
4
})
Copied!
Or a custom command function:
1
Hotkey({
2
hotkey: 'cmd+b',
3
command: editor => editor.addBoldMark().moveToEnd(),
4
})
Copied!

BREAKING

The Change object has been removed. The Change object as we know it previously has been removed, and all of its behaviors have been folded into the Editor controller. This includes the top-level commands and queries methods, as well as methods like applyOperation and normalize. All places that used to receive change now receive editor, which is API equivalent.
Changes are now flushed to onChange asynchronously. Previously this was done synchronously, which resulted in some strange race conditions in React environments. Now they will always be flushed asynchronously, just like setState.
The normalize* and validate* middleware signatures have changed! Previously the normalize* and validate* middleware was passed (node, next). However now, for consistency with the other middleware they are all passed (node, editor, next). This way, all middleware always receive editor and next as their final two arguments.
The editor.event method has been removed. Previously this is what you'd use when writing tests to simulate events being fired—which were slightly different to other running other middleware. With the simplification to the editor and to the newly-consistent middleware signatures, you can now use editor.run directly to simulate events:
1
editor.run('onKeyDown', { key: 'Tab', ... })
Copied!

DEPRECATED

The editor.change method is deprecated. With the removal of the Change object, there's no need anymore to create the small closures with editor.change(). Instead you can directly invoke commands on the editor in series, and all of the changes will be emitted asynchronously on the next tick.
1
editor
2
.insertText('word')
3
.moveFocusForward(10)
4
.addMark('bold')
Copied!
The applyOperations method is deprecated. Instead you can loop a set of operations and apply each one using applyOperation. This is to reduce the number of methods exposed on the Editor to keep it simpler.
The change.call method is deprecated. Previously this was used to call a one-off function as a change method. Now this behavior is equivalent to calling editor.command(fn) instead.

0.42 — October 9, 2018

NEW

Introducing the Editor controller. Previously there was a vague editor concept, that was the React component itself. This was helpful, but because it was tightly coupled to React and the browser, it didn't lend itself to non-browser use cases well. This meant that the line between "model" and "controller/view" was blurred, and some concepts lived in both places at once, in inconsistent ways.
A new Editor controller now makes this relationship clear. It borrows many of its behaviors from the React <Editor> component. And the component actually just instantiates its own plain JavaScript Editor under the covers to delegate the work to.
This new concept powers a lot of the thinking in this new version, unlocking a lot of changes that bring a clearer separation of responsibilities to Slate. It allows us to create editors in any environment, which makes server-side use cases easier, brings parity to testing, and even opens us up to supporting other view layers like React Native or Vue.js in the future.
It has a familiar API, based on the existing editor concept:
1
const editor = new Editor({ plugins, value, onChange })
2
3
editor.change(change => {
4
...
5
})
Copied!
However it also introduces imperative methods to make testing easier:
1
editor.run('renderNode', props)
2
3
editor.event('onKeyDown', event)
4
5
editor.command('addMark', 'bold')
6
7
editor.query('isVoid', node)
Copied!
I'm very excited about it, so I hope you like it!
Introducing the "commands" concept. Previously, "change methods" were treated in a first-class way, but plugins had no easy way to add their own change methods that were reusable elsewhere. And they had no way to override the built-in logic for certain commands, for example splitBlock or insertText. However, now this is all customizable by plugins, with the core Slate plugin providing all of the previous default commands.
1
const plugin = {
2
commands: {
3
wrapQuote(change) {
4
change.wrapBlock('quote')
5
},
6
},
7
}
Copied!
Those commands are then available directly on the change objects, which are now editor-specific:
1
change.wrapQuote()
Copied!
This allows you to define all of your commands in a single, easily-testable place. And then "behavioral" plugins can simply take command names as options, so that you have full control over the logic they trigger.
Introducing the "queries" concept. Similarly to the commands, queries allow plugins to define specific behaviors that the editor can be queried for in a reusable way, to be used when rendering buttons, or deciding on command behaviors, etc.
For example, you might define an getActiveList query:
1
const plugin = {
2
queries: {
3
getActiveList(editor) {},
4
},
5
}
Copied!
And then be able to re-use that logic easily in different places in your codebase, or pass in the query name to a plugin that can use your custom logic itself:
1
const list = change.getActiveList()
2
3
if (list) {
4
...
5
} else {
6
...
7
}
Copied!
Taken together, commands and queries offer a better way for plugins to manage their inter-dependencies. They can take in command or query names as options to change their behaviors, or they can export new commands and queries that you can reuse in your codebase.
The middleware stack is now deferrable. With the introduction of the Editor controller, the middleware stack in Slate has also been upgraded. Each middleware now receives a next function (similar to Express or Koa) that allows you to choose whether to iterating the stack or not.
1
// Previously, you'd return `undefined` to continue.
2
function onKeyDown(event, editor, next) {
3
if (event.key !== 'Enter') return
4
...
5
}
6
7
// Now, you call `next()` to continue...
8
function onKeyDown(event, editor, next) {
9
if (event.key !== 'Enter') return next()
10
...
11
}
Copied!
While that may seem inconvenient, it opens up an entire new behavior, which is deferring to the plugins later in the stack to see if they "handle" a specific case, and if not, handling it yourself:
1
function onKeyDown(event, editor, next) {
2
if (event.key === 'Enter') {
3
const handled = next()
4
if (handled) return handled
5
6
// Otherwise, handle `Enter` yourself...
7
}
8
}
Copied!
This is how all of the core logic in slate-react is now implemented, eliminating the need for a "before" and an "after" plugin that duplicate logic.
Under the covers, the schema, commands and queries concept are all implemented as plugins that attach varying middleware as well. For example, commands are processed using the onCommand middleware under the covers:
1
const plugin = {
2
onCommand(command, editor, next) {
3
...
4
}
5
}
Copied!
This allows you to actually listen in to all commands, and override individual behaviors if you choose to do so, without having to override the command itself. This is a very advanced feature, which most people won't need, but it shows the flexibility provided by migrating all of the previously custom internal logic to be based on the new middleware stack.
Plugins can now be defined in nested arrays. This is a small addition, but it means that you no longer need to differentiate between individual plugins and multiple plugins in an array. This allows plugins to be more easily composed up from multiple other plugins themselves, without the end user having to change how they use them. Small, but encourages reuse just a little bit more.

DEPRECATED

The slate-simulator is deprecated. Previously this was used as a pseudo-controller for testing purposes. However, now with the new Editor controller as a first-class concept, everything the simulator could do can now be done directly in the library. This should make testing in non-browser environments much easier to do.

BREAKING

The Value object is no longer tied to changes. Previously, you could create a new Change by calling value.change() and retrieve a new value. With the re-architecture to properly decouple the schema, commands, queries and plugins from the core Slate data models, this is no longer possible. Instead, changes are always created via an Editor instance, where those concepts live.
1
// Instead of...
2
const { value } = this.state
3
const change = value.change()
4
...
5
this.onChange(change)
6
7
// You now would do...
8
this.editor.change(change => {
9
const { value } = change
10
...
11
})
Copied!
Sometimes this means you will need to store the React ref of the editor to be able to access its editor.change method in your React components.
Remove the Stack "model", in favor of the new Editor. Previously there was a pseudo-model called the Stack that was very low level, and not really a model. This concept has now been rolled into the new Editor controller, which can be used in any environment because it's just plain JavaScript. There was almost no need to directly use a Stack instance previously, so this change shouldn't affect almost anyone.
Remove the Schema "model", in favor of the new Editor. Previously there was another pseudo-model called the Schema, that was used to contain validation logic. All of the same validation features are still available, but the old Schema model is now rolled into the Editor controller as well, in the form of an internal SchemaPlugin that isn't exposed.
Remove the schema.isVoid and schema.isAtomic in favor of queries. Previously these two methods were used to query the schema about the behavior of a specific node or decoration. Now these same queries as possible using the "queries" concept, and are available directly on the change object:
1
if (change.isVoid(node)) {
2
...
3
}
Copied!
The middleware stack must now be explicitly continued, using next. Previously returning undefined from a middleware would (usually) continue the stack onto the next middleware. Now, with middleware taking a next function argument you must explicitly decide to continue the stack by call next() yourself.
Remove the History model, in favor of commands. Previously there was a History model that stored the undo/redo stacks, and managing saving new operations to those stacks. All of this logic has been folded into the new "commands" concept, and the undo/redo stacks now live in value.data. This has the benefit of allowing the history behavior to be completely overridable by userland plugins, which was not an easy feat to manage before.
Values can no longer be normalized on creation. With the decoupling of the data model and the plugin layer, the schema rules are no longer available inside the Value model. This means that you can no longer receive a "normalized" value without having access to the Editor and its plugins.
1
// While previously you could attach a `schema` to a value...
2
const normalized = Value.create({ ..., schema })
3
4
// Now you'd need to do that with the `editor`...
5
const value = Value.create({ ... })
6
const editor = new Editor({ value, plugins: [{ schema }] })
7
const normalized = editor.value
Copied!
While this seems inconvenient, it makes the boundaries in the API much more clear, and keeps the immutable and mutable concepts separated. This specific code sample gets longer, but the complexities elsewhere in the library are removed.
The Change class is no longer exported. Changes are now editor-specific, so exporting the Change class no longer makes sense. Instead, you can use the editor.change() API to receive a new change object with the commands and queries specific to your editor's plugins.
The getClosestVoid, getDecorations and hasVoidParent method now take an editor. Previously these Node methods took a schema argument, but this has been replaced with the new editor controller instead now that the Schema model has been removed.

0.41 — September 21, 2018

DEPRECATED

The withoutNormalization helper has been renamed to withoutNormalizing. This is to stay consistent with the new helpers for withoutSaving and withoutMerging.

BREAKING

The the "operation flags" concept was removed. This was a confusing concept that was implemented in multiple different ways and led to the logic around normalizing, saving, and merging operations being more complex than it needed to be. These flags have been replaced with three simpler helper functions: withoutNormalizing, withoutSaving and withoutMerging.
1
change.withoutNormalizing(() => {
2
nodes.forEach(node => change.removeNodeByKey(node.key))
3
})
Copied!
1
change.withoutSaving(() => {
2
change.setValue({ decorations })
3
})
Copied!
This means that you no longer use the { normalize: false } or { save: false } options as arguments to individual change methods, and instead use these new helper methods to apply these behaviors to groups of changes at once.
The "normalize" change methods have been removed. Previously there were a handful of different normalization change methods like normalizeNodeByPath, normalizeParentByKey, etc. These were confusing because it put the onus on the implemented to know exact which nodes needed to be normalized. They have been removed, and implementers no longer ever need to worry about which specific nodes to normalize, as Slate will handle that for them.
The internal refindNode and refindPath methods were removed. These should never have been exposed in the first place, and are now no longer present on the Element interface. These were only used internally during the normalization process.

0.40 — August 22, 2018

BREAKING

Remove all previously deprecated code paths. This helps to reduce some of the complexity in Slate by not having to handle these code paths anymore. And it helps to reduce file size. When upgrading, it's highly recommended that you upgrade to the previous version first and ensure there are no deprecation warnings being logged, then upgrade to this version.

0.39 — August 22, 2018

NEW

Introducing the Range model and interface. Previously the "range" concept was used in multiple different places, for the selection, for decorations, and for acting on ranges of the document. This worked okay, but it was hiding the underlying system which is that Range is really an interface that other models can choose to implement. Now, we still use the Range model for referencing parts of the document, but it can also be implemented by other models that need to attach more semantic meaning...
Introducing the Decoration and Selection models. These two new models both implement the new Range interface. Where previously they had to mis-use the Range model itself with added semantics. This just cleans up some of the confusion around overlapping properties, and allows us to add even more domain-specific methods and properties in the future without trouble.

BREAKING

Decorations have changed! Previously, decorations piggybacked on the Range model, using the existing marks property, and introducing their own isAtomic property. However, they have now been split out into their own Decoration model with a single mark and with the isAtomic property controlled by the schema. What previously would have looked like:
1
Range.create({
2
anchor: { ... },
3
focus: { ... },
4
marks: [{ type: 'highlight' }],
5
isAtomic: true,
6
})
Copied!
Is now:
1
Decoration.create({
2
anchor: { ... },
3
focus: { ... },
4
mark: { type: 'highlight' },
5
})
Copied!
Each decoration maps to a single mark object. And the atomicity of the mark controlled in the schema instead, for example:
1
const schema = {
2
marks: {
3
highlight: {
4
isAtomic: true,
5
},
6
},
7
}
Copied!
The Range model has reduced semantics. Previously, since all decorations and selections were ranges, you could create ranges with an isFocused, isAtomic or marks properties. Now Range objects are much simpler, offering only an anchor and a focus, and can be extended by other models implementing the range interface. However, this means that using Range.create or document.createRange might not be what you want anymore. For example, for creating a new selection, you used to use:
1
const selection = document.createRange({
2
isFocused: true,
3
anchor: { ... },
4
focus: { ... },
5
})
Copied!
But now, you'll need to use document.createSelection instead:
1
const selection = document.createSelection({
2
isFocused: true,
3
anchor: { ... },
4
focus: { ... },
5
})
Copied!
The value.decorations property is no longer nullable. Previously when no decorations were applied to the value, the decorations property would be set to null. Now it will be an empty List object, so that the interface is more consistent.

DEPRECATED

The Node.createChildren static method is deprecated. This was just an alias for Node.createList and wasn't necessary. You can use Node.createList going forward for the same effect.
The renderPortal property of plugins is deprecated. This allows slate-react to be slightly slimmer, since this behavior can be handled in React 16 with the new <React.Fragment> using the renderEditor property instead, in a way that offers more control over the portal behavior.
The data property of plugins is deprecated. This property wasn't well designed and circumvented the core tenet that all changes to the value object will flow through operations inside Change objects. It was mostly used for view-layer state which should be handled with React-specific conventions for state management instead.

0.38 — August 21, 2018

DEPRECATED

Node.isVoid access is deprecated. Previously the "voidness" of a node was hardcoded in the data model. Soon it will be determined at runtime based on your editor's schema. This deprecation just ensures that you aren't using the node.isVoid property which will not work in future verisons. What previously would have been:
1
if (node.isVoid) {
2
...
3
}
Copied!
Now becomes:
1
if (schema.isVoid(node)) {
2
...
3
}
Copied!
This requires you to have a reference to the schema object, which can be access as value.schema.
Value.isFocused/isBlurred and Value.hasUndos/hasRedos are deprecated. These properties are easily available via value.selection and value.history instead, and are now deprecated to reduce the complexity and number of different ways of doing things.

0.37 — August 3, 2018

NEW

Introducing the Point model. Ranges are now built up of two Point models—an anchor and a focus—instead of having the properties set directly on the range itself. This makes the "point" concept first-class in Slate and better API's can be built around point objects.
1
Point.create({
2
key: 'a',