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, 20210.60
— November 24, 2020useEditor
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, 20200.58
— May 5th, 2020Text
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 Text
s and Element
s.0.57
— December 18, 2019Command
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
).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.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
.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.Editor.*
helper exposed now. However, you can easily define your own custom helpers and place them in a namespace as well:0.56
— December 17, 2019format_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.🤖 Note that the "mark" term does not mean what it meant in0.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.
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, 2019match
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!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.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.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.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:0.54
— December 12, 2019<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.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.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.<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.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, 2019slate-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
. 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.Editor.ancestor
Node.closest
Node.furthest
Range.exists
Range.isRangeList
Range.isRangeMap
0.52
— December 5, 2019slate-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.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.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, 2019Mark
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: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.<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.0.50
— November 27, 2019⚠ 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, 2019Annotation
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.*_annotation
operations. The set of operations now includes add_annotation
, remove_annotation
and set_annotation
. They are similar to the existing *_mark
operations.Element
interface, which Document
, Block
and Inline
all implement. There are iterables for traversing the entire tree:for/of
loops, you can easily break
or return
out of the loops directly—a much nicer DX than remembering to return false
.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.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.0.46
— May 1, 2019offset
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.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.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.)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.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: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!)0.45
— April 2, 2019Operation
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, 2018child_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.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.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.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, 2018editor.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: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.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
.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.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: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.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.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, 2018Editor
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.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.editor
concept:splitBlock
or insertText
. However, now this is all customizable by plugins, with the core Slate plugin providing all of the previous default commands.change
objects, which are now editor-specific:getActiveList
query: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.slate-react
is now implemented, eliminating the need for a "before" and an "after" plugin that duplicate logic.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: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.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.ref
of the editor
to be able to access its editor.change
method in your React components.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.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.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: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.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.Value
model. This means that you can no longer receive a "normalized" value without having access to the Editor
and its plugins.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.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, 2018withoutNormalization
helper has been renamed to withoutNormalizing
. This is to stay consistent with the new helpers for withoutSaving
and withoutMerging
.withoutNormalizing
, withoutSaving
and withoutMerging
.{ 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.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.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, 20180.39
— August 22, 2018Range
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...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.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:mark
object. And the atomicity of the mark controlled in the schema instead, for example: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:document.createSelection
instead: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.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.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.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, 2018Node.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: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, 2018Point
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.Range
objects via the anchor
, focus
, start
and end
properties:anchorKey
, anchorOffset
, etc. properties.Document.createRange
creates a relative range. Previously you'd have to use Range.create
and make sure that you passed valid arguments, and ensure that you "normalized" the range to sync its keys and paths. This is no longer the case, since the createRange
method will do it for you.anchor
and focus
paths are set.Document.createPoint
creates a relative point. Just like the createRange
method, createPoint
will create a point that is guaranteed to be relative to the document itself. This is often a lot easier than using Point.create
directly.Range.focus
method was removed. (Not Change.focus
!) This was necessary to make way for the new range.focus
point property. Usually this would have been done in a migration-friendly way like the rest of the method changes in this release, but this was an exception. However the change.focus()
method is still available and works as expected.Range.set
and Range.merge
are dangerous. If you were previously using the super low-level Immutable.js methods range.set
or range.merge
with any of the now-removed properties of ranges, these invocations will fail. Instead, you should use the range.set*
helpers going forward which can be migrated with deprecations warnings instead of failing outright.offset
property of points defaults to null
. Previously it would default to 0
but that could be confusing because it made no distinction from a "set" or "unset" offset. Now they default to null
instead. This shouldn't really affect any real-world usage of Slate.Range.toJSON()
structure has changed. With the introduction of points, the range now returns its anchor
and focus
properties as nested point JSON objects instead of directly as properties. For example:Value
were deprecated. Previously you could access things like anchorKey
, startOffset
and isCollapsed
directly on Value
objects. This results in extra duplication that is hard to maintain over time, and hard for newcomers to understand, without much benefit. All of these properties are deprecated and should be accessed on the value.selection
object directly instead.Range
methods were standardized, with many deprecated. The methods on Range
objects had grown drastically in size. Many of them weren't consistently named, or overlapped in unnecessary ways. With the introduction of Point
objects a lot of these methods could be cleaned up and their logic delegated to the points directly. All of these methods remain available but will raise deprecation warnings, making it easier to upgrade.Range
method deprecations, the same confusion and poor naming choices existed in the Change
methods that dealt with selections. Many of them have been renamed for consistency, or deprecated when alternatives existed. All of these methods remain available but will raise deprecation warnings, making it easier to upgrade.