diff --git a/docs/reference/slate/schema.md b/docs/reference/slate/schema.md
index 5fc3a7c0b0d..49e397322e9 100644
--- a/docs/reference/slate/schema.md
+++ b/docs/reference/slate/schema.md
@@ -154,6 +154,24 @@ Will determine whether the node is treated as a "void" node or not, making its c
Will validate the last child node against a [`match`](#match).
+### `next`
+
+`Object|Array`
+
+```js
+{
+ next: { type: 'quote' },
+}
+```
+
+```js
+{
+ next: [{ type: 'quote' }, { type: 'paragraph' }],
+}
+```
+
+Will validate the next sibling node against a [`match`](#match).
+
### `nodes`
`Array`
@@ -231,6 +249,24 @@ For more information on the arguments passed to `normalize`, see the [Normalizin
Will validate a node's parent against a [`match`](#match).
+### `previous`
+
+`Object|Array`
+
+```js
+{
+ previous: { type: 'quote' },
+}
+```
+
+```js
+{
+ previous: [{ type: 'quote' }, { type: 'paragraph' }],
+}
+```
+
+Will validate the previous sibling node against a [`match`](#match).
+
### `text`
`RegExp|Function`
@@ -380,6 +416,30 @@ Raised when the `object` property of the last child node is invalid, when a spec
Raised when the `type` property of the last child node is invalid, when a specific `last` rule was defined in a schema.
+### `'next_sibling_object_invalid'`
+
+```js
+{
+ next: Node,
+ node: Node,
+ rule: Object,
+}
+```
+
+Raised when the `object` property of the next sibling node is invalid, when a specific `next` rule was defined in a schema.
+
+### `'next_sibling_type_invalid'`
+
+```js
+{
+ next: Node,
+ node: Node,
+ rule: Object,
+}
+```
+
+Raised when the `type` property of the next sibling node is invalid, when a specific `next` rule was defined in a schema.
+
### `'node_data_invalid'`
```js
@@ -451,3 +511,27 @@ Raised when the `object` property of the parent of a node is invalid, when a spe
```
Raised when the `type` property of the parent of a node is invalid, when a specific `parent` rule was defined in a schema.
+
+### `'previous_sibling_object_invalid'`
+
+```js
+{
+ previous: Node,
+ node: Node,
+ rule: Object,
+}
+```
+
+Raised when the `object` property of the previous sibling node is invalid, when a specific `previous` rule was defined in a schema.
+
+### `'previous_sibling_type_invalid'`
+
+```js
+{
+ previous: Node,
+ node: Node,
+ rule: Object,
+}
+```
+
+Raised when the `type` property of the previous sibling node is invalid, when a specific `previous` rule was defined in a schema.
diff --git a/packages/slate/src/models/schema.js b/packages/slate/src/models/schema.js
index 234e968e449..23d3a9cd5da 100644
--- a/packages/slate/src/models/schema.js
+++ b/packages/slate/src/models/schema.js
@@ -386,7 +386,7 @@ class Schema extends Record(DEFAULTS) {
*/
function defaultNormalize(change, error) {
- const { code, node, child, key, mark } = error
+ const { code, node, child, next, previous, key, mark } = error
switch (code) {
case 'child_object_invalid':
@@ -403,6 +403,24 @@ function defaultNormalize(change, error) {
: change.removeNodeByKey(child.key, { normalize: false })
}
+ case 'previous_sibling_object_invalid':
+ case 'previous_sibling_type_invalid': {
+ return previous.object === 'text' &&
+ node.object === 'block' &&
+ node.nodes.size === 1
+ ? change.removeNodeByKey(node.key, { normalize: false })
+ : change.removeNodeByKey(previous.key, { normalize: false })
+ }
+
+ case 'next_sibling_object_invalid':
+ case 'next_sibling_type_invalid': {
+ return next.object === 'text' &&
+ node.object === 'block' &&
+ node.nodes.size === 1
+ ? change.removeNodeByKey(node.key, { normalize: false })
+ : change.removeNodeByKey(next.key, { normalize: false })
+ }
+
case 'child_required':
case 'node_text_invalid':
case 'parent_object_invalid':
@@ -493,12 +511,14 @@ function validateRules(object, rule, rules, options = {}) {
function validateObject(node, rule) {
if (rule.object == null) return
if (rule.object === node.object) return
+ if (typeof rule.object === 'function' && rule.object(node.object)) return
return fail('node_object_invalid', { rule, node })
}
function validateType(node, rule) {
if (rule.type == null) return
if (rule.type === node.type) return
+ if (typeof rule.type === 'function' && rule.type(node.type)) return
return fail('node_type_invalid', { rule, node })
}
@@ -506,6 +526,11 @@ function validateData(node, rule) {
if (rule.data == null) return
if (node.data == null) return
+ if (typeof rule.data === 'function') {
+ if (rule.data(node.data)) return
+ return fail('node_data_invalid', { rule, node })
+ }
+
for (const key in rule.data) {
const fn = rule.data[key]
const value = node.data && node.data.get(key)
@@ -520,7 +545,12 @@ function validateMarks(node, rule) {
const marks = node.getMarks().toArray()
for (const mark of marks) {
- const valid = rule.marks.some(def => def.type === mark.type)
+ const valid = rule.marks.some(
+ def =>
+ typeof def.type === 'function'
+ ? def.type(mark.type)
+ : def.type === mark.type
+ )
if (valid) continue
return fail('node_mark_invalid', { rule, node, mark })
}
diff --git a/packages/slate/test/schema/custom/child-kind-invalid-function.js b/packages/slate/test/schema/custom/child-kind-invalid-function.js
new file mode 100644
index 00000000000..bc7270ecd20
--- /dev/null
+++ b/packages/slate/test/schema/custom/child-kind-invalid-function.js
@@ -0,0 +1,34 @@
+/** @jsx h */
+
+import h from '../../helpers/h'
+
+export const schema = {
+ blocks: {
+ paragraph: {},
+ quote: {
+ nodes: [
+ {
+ match: [{ object: o => ['inline', 'text'].includes(o) }],
+ },
+ ],
+ },
+ },
+}
+
+export const input = (
+
+
+
+ text
+
+
+
+)
+
+export const output = (
+
+
+
+
+
+)
diff --git a/packages/slate/test/schema/custom/child-type-invalid-function.js b/packages/slate/test/schema/custom/child-type-invalid-function.js
new file mode 100644
index 00000000000..96819ae64a2
--- /dev/null
+++ b/packages/slate/test/schema/custom/child-type-invalid-function.js
@@ -0,0 +1,32 @@
+/** @jsx h */
+
+import h from '../../helpers/h'
+
+export const schema = {
+ blocks: {
+ paragraph: {},
+ quote: {
+ nodes: [
+ {
+ match: [{ type: t => t === 'paragraph' }],
+ },
+ ],
+ },
+ },
+}
+
+export const input = (
+
+
+
+
+
+
+
+)
+
+export const output = (
+
+
+
+)
diff --git a/packages/slate/test/schema/custom/first-child-kind-invalid-function.js b/packages/slate/test/schema/custom/first-child-kind-invalid-function.js
new file mode 100644
index 00000000000..2ccaddc043c
--- /dev/null
+++ b/packages/slate/test/schema/custom/first-child-kind-invalid-function.js
@@ -0,0 +1,30 @@
+/** @jsx h */
+
+import h from '../../helpers/h'
+
+export const schema = {
+ blocks: {
+ paragraph: {},
+ quote: {
+ first: [{ object: o => o === 'text' }],
+ },
+ },
+}
+
+export const input = (
+
+
+
+
+
+
+
+)
+
+export const output = (
+
+
+
+
+
+)
diff --git a/packages/slate/test/schema/custom/first-child-type-invalid-function.js b/packages/slate/test/schema/custom/first-child-type-invalid-function.js
new file mode 100644
index 00000000000..884b6a56ce4
--- /dev/null
+++ b/packages/slate/test/schema/custom/first-child-type-invalid-function.js
@@ -0,0 +1,35 @@
+/** @jsx h */
+
+import h from '../../helpers/h'
+
+export const schema = {
+ blocks: {
+ paragraph: {},
+ quote: {
+ first: { type: t => t === 'paragraph' },
+ },
+ },
+}
+
+export const input = (
+
+
+
+
+
+
+
+
+
+)
+
+export const output = (
+
+
+
+
+
+
+
+
+)
diff --git a/packages/slate/test/schema/custom/last-child-kind-invalid-function.js b/packages/slate/test/schema/custom/last-child-kind-invalid-function.js
new file mode 100644
index 00000000000..4439cec0bbd
--- /dev/null
+++ b/packages/slate/test/schema/custom/last-child-kind-invalid-function.js
@@ -0,0 +1,30 @@
+/** @jsx h */
+
+import h from '../../helpers/h'
+
+export const schema = {
+ blocks: {
+ paragraph: {},
+ quote: {
+ last: [{ object: o => o === 'text' }],
+ },
+ },
+}
+
+export const input = (
+
+
+
+
+
+
+
+)
+
+export const output = (
+
+
+
+
+
+)
diff --git a/packages/slate/test/schema/custom/last-child-type-invalid-function.js b/packages/slate/test/schema/custom/last-child-type-invalid-function.js
new file mode 100644
index 00000000000..fe74d438c44
--- /dev/null
+++ b/packages/slate/test/schema/custom/last-child-type-invalid-function.js
@@ -0,0 +1,35 @@
+/** @jsx h */
+
+import h from '../../helpers/h'
+
+export const schema = {
+ blocks: {
+ paragraph: {},
+ quote: {
+ last: { type: t => t === 'paragraph' },
+ },
+ },
+}
+
+export const input = (
+
+
+
+
+
+
+
+
+
+)
+
+export const output = (
+
+
+
+
+
+
+
+
+)
diff --git a/packages/slate/test/schema/custom/next-kind-invalid-custom.js b/packages/slate/test/schema/custom/next-kind-invalid-custom.js
new file mode 100644
index 00000000000..3fced31c40a
--- /dev/null
+++ b/packages/slate/test/schema/custom/next-kind-invalid-custom.js
@@ -0,0 +1,38 @@
+/** @jsx h */
+
+import h from '../../helpers/h'
+
+export const schema = {
+ blocks: {
+ image: {
+ next: [{ object: 'inline' }, { object: 'text' }],
+ normalize: (change, { code, next }) => {
+ if (code == 'next_sibling_object_invalid') {
+ change.unwrapBlockByKey(next.key, 'paragraph')
+ }
+ },
+ },
+ },
+}
+
+export const input = (
+
+
+
+
+
+
+
+
+)
+
+export const output = (
+
+
+
+
+
+
+
+
+)
diff --git a/packages/slate/test/schema/custom/next-kind-invalid-default.js b/packages/slate/test/schema/custom/next-kind-invalid-default.js
new file mode 100644
index 00000000000..952c18b415c
--- /dev/null
+++ b/packages/slate/test/schema/custom/next-kind-invalid-default.js
@@ -0,0 +1,32 @@
+/** @jsx h */
+
+import h from '../../helpers/h'
+
+export const schema = {
+ blocks: {
+ image: {
+ next: [{ object: 'text' }],
+ },
+ },
+}
+
+export const input = (
+
+
+
+
+
+
+
+
+)
+
+export const output = (
+
+
+
+
+
+
+
+)
diff --git a/packages/slate/test/schema/custom/next-kind-invalid-function.js b/packages/slate/test/schema/custom/next-kind-invalid-function.js
new file mode 100644
index 00000000000..5d8d56e4c10
--- /dev/null
+++ b/packages/slate/test/schema/custom/next-kind-invalid-function.js
@@ -0,0 +1,32 @@
+/** @jsx h */
+
+import h from '../../helpers/h'
+
+export const schema = {
+ blocks: {
+ image: {
+ next: [{ object: o => o === 'text' }],
+ },
+ },
+}
+
+export const input = (
+
+
+
+
+
+
+
+
+)
+
+export const output = (
+
+
+
+
+
+
+
+)
diff --git a/packages/slate/test/schema/custom/next-type-invalid-custom.js b/packages/slate/test/schema/custom/next-type-invalid-custom.js
new file mode 100644
index 00000000000..da3f8a3978a
--- /dev/null
+++ b/packages/slate/test/schema/custom/next-type-invalid-custom.js
@@ -0,0 +1,36 @@
+/** @jsx h */
+
+import h from '../../helpers/h'
+
+export const schema = {
+ blocks: {
+ paragraph: {
+ next: [{ type: 'paragraph' }],
+ normalize: (change, { code, next }) => {
+ if (code == 'next_sibling_type_invalid') {
+ change.wrapBlockByKey(next.key, 'paragraph')
+ }
+ },
+ },
+ },
+}
+
+export const input = (
+
+
+
+
+
+
+)
+
+export const output = (
+
+
+
+
+
+
+
+
+)
diff --git a/packages/slate/test/schema/custom/next-type-invalid-default.js b/packages/slate/test/schema/custom/next-type-invalid-default.js
new file mode 100644
index 00000000000..d57f86ca0ed
--- /dev/null
+++ b/packages/slate/test/schema/custom/next-type-invalid-default.js
@@ -0,0 +1,28 @@
+/** @jsx h */
+
+import h from '../../helpers/h'
+
+export const schema = {
+ blocks: {
+ paragraph: {
+ next: [{ type: 'paragraph' }],
+ },
+ },
+}
+
+export const input = (
+
+
+
+
+
+
+)
+
+export const output = (
+
+
+
+
+
+)
diff --git a/packages/slate/test/schema/custom/next-type-invalid-function.js b/packages/slate/test/schema/custom/next-type-invalid-function.js
new file mode 100644
index 00000000000..9d3f40cc05a
--- /dev/null
+++ b/packages/slate/test/schema/custom/next-type-invalid-function.js
@@ -0,0 +1,28 @@
+/** @jsx h */
+
+import h from '../../helpers/h'
+
+export const schema = {
+ blocks: {
+ paragraph: {
+ next: [{ type: t => t === 'paragraph' }],
+ },
+ },
+}
+
+export const input = (
+
+
+
+
+
+
+)
+
+export const output = (
+
+
+
+
+
+)
diff --git a/packages/slate/test/schema/custom/node-data-invalid-function.js b/packages/slate/test/schema/custom/node-data-invalid-function.js
new file mode 100644
index 00000000000..1de89bcef5a
--- /dev/null
+++ b/packages/slate/test/schema/custom/node-data-invalid-function.js
@@ -0,0 +1,28 @@
+/** @jsx h */
+
+import h from '../../helpers/h'
+
+export const schema = {
+ blocks: {
+ paragraph: {
+ data: d => d.get('thing') === 'valid',
+ },
+ },
+}
+
+export const input = (
+
+
+
+
+
+
+)
+
+export const output = (
+
+
+
+
+
+)
diff --git a/packages/slate/test/schema/custom/node-mark-invalid-function.js b/packages/slate/test/schema/custom/node-mark-invalid-function.js
new file mode 100644
index 00000000000..fb38335de52
--- /dev/null
+++ b/packages/slate/test/schema/custom/node-mark-invalid-function.js
@@ -0,0 +1,31 @@
+/** @jsx h */
+
+import h from '../../helpers/h'
+
+export const schema = {
+ blocks: {
+ paragraph: {
+ marks: [{ type: t => ['bold', 'underline'].includes(t) }],
+ },
+ },
+}
+
+export const input = (
+
+
+
+ one two three
+
+
+
+)
+
+export const output = (
+
+
+
+ one two three
+
+
+
+)
diff --git a/packages/slate/test/schema/custom/node-object-invalid-function.js b/packages/slate/test/schema/custom/node-object-invalid-function.js
new file mode 100644
index 00000000000..e4df5b24ecb
--- /dev/null
+++ b/packages/slate/test/schema/custom/node-object-invalid-function.js
@@ -0,0 +1,25 @@
+/** @jsx h */
+
+import h from '../../helpers/h'
+
+export const schema = {
+ blocks: {
+ paragraph: {
+ object: o => o === 'inline',
+ },
+ },
+}
+
+export const input = (
+
+
+ invalid
+
+
+)
+
+export const output = (
+
+
+
+)
diff --git a/packages/slate/test/schema/custom/node-type-invalid-function.js b/packages/slate/test/schema/custom/node-type-invalid-function.js
new file mode 100644
index 00000000000..64c7e521c15
--- /dev/null
+++ b/packages/slate/test/schema/custom/node-type-invalid-function.js
@@ -0,0 +1,25 @@
+/** @jsx h */
+
+import h from '../../helpers/h'
+
+export const schema = {
+ blocks: {
+ paragraph: {
+ type: t => t === 'impossible',
+ },
+ },
+}
+
+export const input = (
+
+
+ invalid
+
+
+)
+
+export const output = (
+
+
+
+)
diff --git a/packages/slate/test/schema/custom/parent-kind-invalid-function.js b/packages/slate/test/schema/custom/parent-kind-invalid-function.js
new file mode 100644
index 00000000000..91974a364cb
--- /dev/null
+++ b/packages/slate/test/schema/custom/parent-kind-invalid-function.js
@@ -0,0 +1,33 @@
+/** @jsx h */
+
+import h from '../../helpers/h'
+
+export const schema = {
+ inlines: {
+ link: {
+ parent: { object: o => o === 'block' },
+ },
+ },
+}
+
+export const input = (
+
+
+
+
+ one
+
+
+
+
+)
+
+export const output = (
+
+
+
+
+
+
+
+)
diff --git a/packages/slate/test/schema/custom/parent-type-invalid-function.js b/packages/slate/test/schema/custom/parent-type-invalid-function.js
new file mode 100644
index 00000000000..f37dcb00f06
--- /dev/null
+++ b/packages/slate/test/schema/custom/parent-type-invalid-function.js
@@ -0,0 +1,30 @@
+/** @jsx h */
+
+import h from '../../helpers/h'
+
+export const schema = {
+ blocks: {
+ list: {},
+ item: {
+ parent: { type: t => t === 'list' },
+ },
+ },
+}
+
+export const input = (
+
+
+
+
+
+
+
+)
+
+export const output = (
+
+
+
+
+
+)
diff --git a/packages/slate/test/schema/custom/previous-kind-invalid-custom.js b/packages/slate/test/schema/custom/previous-kind-invalid-custom.js
new file mode 100644
index 00000000000..51806aeccec
--- /dev/null
+++ b/packages/slate/test/schema/custom/previous-kind-invalid-custom.js
@@ -0,0 +1,38 @@
+/** @jsx h */
+
+import h from '../../helpers/h'
+
+export const schema = {
+ blocks: {
+ image: {
+ previous: [{ object: 'inline' }, { object: 'text' }],
+ normalize: (change, { code, previous }) => {
+ if (code == 'previous_sibling_object_invalid') {
+ change.unwrapBlockByKey(previous.key, 'paragraph')
+ }
+ },
+ },
+ },
+}
+
+export const input = (
+
+
+
+
+
+
+
+
+)
+
+export const output = (
+
+
+
+
+
+
+
+
+)
diff --git a/packages/slate/test/schema/custom/previous-kind-invalid-default.js b/packages/slate/test/schema/custom/previous-kind-invalid-default.js
new file mode 100644
index 00000000000..9b89a6590f7
--- /dev/null
+++ b/packages/slate/test/schema/custom/previous-kind-invalid-default.js
@@ -0,0 +1,32 @@
+/** @jsx h */
+
+import h from '../../helpers/h'
+
+export const schema = {
+ blocks: {
+ image: {
+ previous: [{ object: 'text' }],
+ },
+ },
+}
+
+export const input = (
+
+
+
+
+
+
+
+
+)
+
+export const output = (
+
+
+
+
+
+
+
+)
diff --git a/packages/slate/test/schema/custom/previous-kind-invalid-function.js b/packages/slate/test/schema/custom/previous-kind-invalid-function.js
new file mode 100644
index 00000000000..6a17ea716b3
--- /dev/null
+++ b/packages/slate/test/schema/custom/previous-kind-invalid-function.js
@@ -0,0 +1,32 @@
+/** @jsx h */
+
+import h from '../../helpers/h'
+
+export const schema = {
+ blocks: {
+ image: {
+ previous: [{ object: o => o === 'text' }],
+ },
+ },
+}
+
+export const input = (
+
+
+
+
+
+
+
+
+)
+
+export const output = (
+
+
+
+
+
+
+
+)
diff --git a/packages/slate/test/schema/custom/previous-type-invalid-custom.js b/packages/slate/test/schema/custom/previous-type-invalid-custom.js
new file mode 100644
index 00000000000..1d018703061
--- /dev/null
+++ b/packages/slate/test/schema/custom/previous-type-invalid-custom.js
@@ -0,0 +1,36 @@
+/** @jsx h */
+
+import h from '../../helpers/h'
+
+export const schema = {
+ blocks: {
+ paragraph: {
+ previous: [{ type: 'paragraph' }],
+ normalize: (change, { code, previous }) => {
+ if (code == 'previous_sibling_type_invalid') {
+ change.wrapBlockByKey(previous.key, 'paragraph')
+ }
+ },
+ },
+ },
+}
+
+export const input = (
+
+
+
+
+
+
+)
+
+export const output = (
+
+
+
+
+
+
+
+
+)
diff --git a/packages/slate/test/schema/custom/previous-type-invalid-default.js b/packages/slate/test/schema/custom/previous-type-invalid-default.js
new file mode 100644
index 00000000000..0e40604dd51
--- /dev/null
+++ b/packages/slate/test/schema/custom/previous-type-invalid-default.js
@@ -0,0 +1,28 @@
+/** @jsx h */
+
+import h from '../../helpers/h'
+
+export const schema = {
+ blocks: {
+ paragraph: {
+ previous: [{ type: 'paragraph' }],
+ },
+ },
+}
+
+export const input = (
+
+
+
+
+
+
+)
+
+export const output = (
+
+
+
+
+
+)
diff --git a/packages/slate/test/schema/custom/previous-type-invalid-function.js b/packages/slate/test/schema/custom/previous-type-invalid-function.js
new file mode 100644
index 00000000000..dfa81340179
--- /dev/null
+++ b/packages/slate/test/schema/custom/previous-type-invalid-function.js
@@ -0,0 +1,28 @@
+/** @jsx h */
+
+import h from '../../helpers/h'
+
+export const schema = {
+ blocks: {
+ paragraph: {
+ previous: [{ type: t => t === 'paragraph' }],
+ },
+ },
+}
+
+export const input = (
+
+
+
+
+
+
+)
+
+export const output = (
+
+
+
+
+
+)