mirror of
https://github.com/ianstormtaylor/slate.git
synced 2025-08-07 07:46:32 +02:00
Custom TypeScript Types (#3835)
This PR adds better TypeScript types into Slate and is based on the proposal here: https://github.com/ianstormtaylor/slate/issues/3725
* Extend Slate's types like Element and Text
* Supports type discrimination (ie. if an element has type === "table" then we get a reduced set of properties)
* added custom types
* files
* more extensions
* files
* changed fixtures
* changes eslint file
* changed element.children to descendant
* updated types
* more type changes
* changed a lot of typing, still getting building errors
* extended text type in slate-react
* removed type assertions
* Clean up of custom types and a couple uneeded comments.
* Rename headingElement-true.tsx.tsx to headingElement-true.tsx
* moved basetext and baselement
* Update packages/slate/src/interfaces/text.ts
Co-authored-by: Brent Farese <25846953+BrentFarese@users.noreply.github.com>
* Fix some type issues with core functions.
* Clean up text and element files.
* Convert other types to extended types.
* Change the type of editor.marks to the appropriate type.
* Add version 100.0.0 to package.json
* Revert "Add version 100.0.0 to package.json"
This reverts commit 329e44e43d
.
* added custom types
* files
* more extensions
* files
* changed fixtures
* changes eslint file
* changed element.children to descendant
* updated types
* more type changes
* changed a lot of typing, still getting building errors
* extended text type in slate-react
* removed type assertions
* Clean up of custom types and a couple uneeded comments.
* Rename headingElement-true.tsx.tsx to headingElement-true.tsx
* moved basetext and baselement
* Update packages/slate/src/interfaces/text.ts
Co-authored-by: Brent Farese <25846953+BrentFarese@users.noreply.github.com>
* Fix some type issues with core functions.
* Clean up text and element files.
* Convert other types to extended types.
* Change the type of editor.marks to the appropriate type.
* Run linter.
* Remove key:string uknown from the base types.
* Clean up types after removing key:string unknown.
* Lint and prettier fixes.
* Implement custom-types
Co-authored-by: mdmjg <mdj308@nyu.edu>
* added custom types to examples
* reset yarn lock
* added ts to fixtures
* examples custom types
* Working fix
* ts-thesunny-try
* Extract interface types.
* Fix minor return type in create-editor.
* Fix the typing issue with Location having compile time CustomTypes
* Extract types for Transforms.
* Update README.
* Fix dependency on slate-history in slate-react
Co-authored-by: mdmjg <mdj308@nyu.edu>
Co-authored-by: Brent Farese <brentfarese@gmail.com>
Co-authored-by: Brent Farese <25846953+BrentFarese@users.noreply.github.com>
Co-authored-by: Tim Buckley <timothypbuckley@gmail.com>
This commit is contained in:
74
.eslintrc
74
.eslintrc
@@ -5,7 +5,12 @@
|
||||
"prettier/@typescript-eslint",
|
||||
"prettier/react"
|
||||
],
|
||||
"plugins": ["@typescript-eslint", "import", "react", "prettier"],
|
||||
"plugins": [
|
||||
"@typescript-eslint",
|
||||
"import",
|
||||
"react",
|
||||
"prettier"
|
||||
],
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"parserOptions": {
|
||||
"sourceType": "module",
|
||||
@@ -16,7 +21,12 @@
|
||||
},
|
||||
"ignorePatterns": ["**/next-env.d.ts"],
|
||||
"settings": {
|
||||
"import/extensions": [".js", ".ts", ".jsx", ".tsx"],
|
||||
"import/extensions": [
|
||||
".js",
|
||||
".ts",
|
||||
".jsx",
|
||||
".tsx"
|
||||
],
|
||||
"react": {
|
||||
"version": "detect"
|
||||
}
|
||||
@@ -29,8 +39,16 @@
|
||||
},
|
||||
"rules": {
|
||||
"constructor-super": "error",
|
||||
"dot-notation": ["error", { "allowKeywords": true }],
|
||||
"eqeqeq": ["error", "smart"],
|
||||
"dot-notation": [
|
||||
"error",
|
||||
{
|
||||
"allowKeywords": true
|
||||
}
|
||||
],
|
||||
"eqeqeq": [
|
||||
"error",
|
||||
"smart"
|
||||
],
|
||||
"import/default": "error",
|
||||
"import/export": "error",
|
||||
"import/first": "error",
|
||||
@@ -40,7 +58,9 @@
|
||||
"import/no-deprecated": "error",
|
||||
"import/no-extraneous-dependencies": [
|
||||
"error",
|
||||
{ "peerDependencies": true }
|
||||
{
|
||||
"peerDependencies": true
|
||||
}
|
||||
],
|
||||
"import/no-mutable-exports": "error",
|
||||
"import/no-named-as-default": "error",
|
||||
@@ -86,18 +106,27 @@
|
||||
"no-var": "error",
|
||||
"no-void": "error",
|
||||
"no-with": "error",
|
||||
"object-shorthand": ["error", "always"],
|
||||
"object-shorthand": [
|
||||
"error",
|
||||
"always"
|
||||
],
|
||||
"prefer-arrow-callback": "error",
|
||||
"prefer-const": [
|
||||
"error",
|
||||
{ "destructuring": "all", "ignoreReadBeforeAssign": true }
|
||||
{
|
||||
"destructuring": "all",
|
||||
"ignoreReadBeforeAssign": true
|
||||
}
|
||||
],
|
||||
"prefer-rest-params": "error",
|
||||
"prefer-spread": "error",
|
||||
"prefer-template": "error",
|
||||
"prettier/prettier": "error",
|
||||
"radix": "error",
|
||||
"react/jsx-boolean-value": ["error", "never"],
|
||||
"react/jsx-boolean-value": [
|
||||
"error",
|
||||
"never"
|
||||
],
|
||||
"react/jsx-no-duplicate-props": "error",
|
||||
"react/jsx-no-target-blank": "error",
|
||||
"react/jsx-no-undef": "error",
|
||||
@@ -112,15 +141,34 @@
|
||||
"react/react-in-jsx-scope": "error",
|
||||
"react/self-closing-comp": "error",
|
||||
"react/sort-prop-types": "error",
|
||||
"spaced-comment": ["error", "always", { "exceptions": ["-"] }],
|
||||
"spaced-comment": [
|
||||
"error",
|
||||
"always",
|
||||
{
|
||||
"exceptions": [
|
||||
"-"
|
||||
]
|
||||
}
|
||||
],
|
||||
"use-isnan": "error",
|
||||
"valid-jsdoc": [
|
||||
"error",
|
||||
{ "prefer": { "return": "returns" }, "requireReturn": false }
|
||||
{
|
||||
"prefer": {
|
||||
"return": "returns"
|
||||
},
|
||||
"requireReturn": false
|
||||
}
|
||||
],
|
||||
"valid-typeof": "error",
|
||||
"yield-star-spacing": ["error", "after"],
|
||||
"yoda": ["error", "never"]
|
||||
"yield-star-spacing": [
|
||||
"error",
|
||||
"after"
|
||||
],
|
||||
"yoda": [
|
||||
"error",
|
||||
"never"
|
||||
]
|
||||
},
|
||||
"overrides": [
|
||||
{
|
||||
@@ -131,4 +179,4 @@
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
@@ -1,6 +1,6 @@
|
||||
# Location
|
||||
|
||||
The `Location` interface is a union of the ways to refer to a specific location in a Slate document: paths, points or ranges. Methods will often accept a `Location` instead of requiring only a `Path`, `Point` or `Range`.
|
||||
The `Location` interface is a union of the ways to refer to a specific location in a Slate document: paths, points or ranges. Methods will often accept a `Location` instead of requiring only a `Path`, `Point` or `Range`.
|
||||
|
||||
```typescript
|
||||
type Location = Path | Point | Range
|
||||
@@ -26,9 +26,9 @@ type Path = number[]
|
||||
|
||||
```typescript
|
||||
interface Point {
|
||||
path: Path
|
||||
offset: number
|
||||
[key: string]: unknown
|
||||
path: Path
|
||||
offset: number
|
||||
[key: string]: unknown
|
||||
}
|
||||
```
|
||||
|
||||
@@ -66,9 +66,9 @@ Options: `{affinity?: 'forward' | 'backward' | null}`
|
||||
|
||||
```typescript
|
||||
interface Range {
|
||||
anchor: Point
|
||||
focus: Point
|
||||
[key: string]: unknown
|
||||
anchor: Point
|
||||
focus: Point
|
||||
[key: string]: unknown
|
||||
}
|
||||
```
|
||||
|
||||
@@ -96,7 +96,7 @@ Get the intersection of one `range` with `another`.
|
||||
|
||||
###### `Range.isBackward(range: Range): boolean`
|
||||
|
||||
Check if a `range` is backward, meaning that its anchor point appears *after* its focus point in the document.
|
||||
Check if a `range` is backward, meaning that its anchor point appears _after_ its focus point in the document.
|
||||
|
||||
###### `Range.isCollapsed(range: Range): boolean`
|
||||
|
||||
@@ -126,5 +126,4 @@ Get the start point of a `range`
|
||||
|
||||
Transform a `range` by an `op`.
|
||||
|
||||
Options: `{affinity: 'forward' | 'backward' |
|
||||
'outward' | 'inward' | null}`
|
||||
Options: `{affinity: 'forward' | 'backward' | 'outward' | 'inward' | null}`
|
||||
|
@@ -1,6 +1,6 @@
|
||||
# Node
|
||||
|
||||
The `Node` union type represents all of the different types of nodes that occur in a Slate document tree.
|
||||
The `Node` union type represents all of the different types of nodes that occur in a Slate document tree.
|
||||
|
||||
```typescript
|
||||
type Node = Editor | Element | Text
|
||||
@@ -57,7 +57,7 @@ Get the first node entry in a root node from a `path`.
|
||||
|
||||
###### `Node.fragment(root: Node, range: Range): Descendant[]`
|
||||
|
||||
Get the sliced fragment represented by the `range`.
|
||||
Get the sliced fragment represented by the `range`.
|
||||
|
||||
###### `Node.get(root: Node, path: Path): Node`
|
||||
|
||||
@@ -85,7 +85,7 @@ Get the node at a specific `path`, ensuring it's a leaf text node. If the node i
|
||||
|
||||
###### `Node.levels(root: Node, path: Path, options?): Generator<NodeEntry>`
|
||||
|
||||
Return a generator of the nodes in a branch of the tree, from a specific `path`. By default, the order is top-down, from the lowest to the highest node in the tree, but you can pass the `reverse: true` option to go bottom-up.
|
||||
Return a generator of the nodes in a branch of the tree, from a specific `path`. By default, the order is top-down, from the lowest to the highest node in the tree, but you can pass the `reverse: true` option to go bottom-up.
|
||||
|
||||
Options: `{reverse?: boolean}`
|
||||
|
||||
@@ -236,8 +236,8 @@ Check if an element matches a set of `props`. Note: This checks custom propertie
|
||||
|
||||
```typescript
|
||||
interface Text {
|
||||
text: string,
|
||||
[key: string]: unknown
|
||||
text: string
|
||||
[key: string]: unknown
|
||||
}
|
||||
```
|
||||
|
||||
|
@@ -8,9 +8,9 @@
|
||||
|
||||
```typescript
|
||||
interface PointRef {
|
||||
current: Point | null
|
||||
affinity: 'forward' | 'backward' | null
|
||||
unref(): Point | null
|
||||
current: Point | null
|
||||
affinity: 'forward' | 'backward' | null
|
||||
unref(): Point | null
|
||||
}
|
||||
```
|
||||
|
||||
@@ -26,9 +26,9 @@ Transform the point refs current value by an `op`.
|
||||
|
||||
```typescript
|
||||
interface RangeRef {
|
||||
current: Range | null
|
||||
affinity: 'forward' | 'backward' | 'outward' | 'inward' | null
|
||||
unref(): Range | null
|
||||
current: Range | null
|
||||
affinity: 'forward' | 'backward' | 'outward' | 'inward' | null
|
||||
unref(): Range | null
|
||||
}
|
||||
```
|
||||
|
||||
|
@@ -8,28 +8,28 @@ Transforms that operate on nodes.
|
||||
|
||||
###### NodeOptions
|
||||
|
||||
All transforms listed below support a parameter `options`. This includes options specific to the transform, and general `NodeOptions` to specify the place in the document that the transform is applied to.
|
||||
All transforms listed below support a parameter `options`. This includes options specific to the transform, and general `NodeOptions` to specify the place in the document that the transform is applied to.
|
||||
|
||||
```typescript
|
||||
interface NodeOptions {
|
||||
at?: Location
|
||||
match?: (node: Node) => boolean
|
||||
mode?: 'highest' | 'lowest'
|
||||
voids?: boolean
|
||||
at?: Location
|
||||
match?: (node: Node) => boolean
|
||||
mode?: 'highest' | 'lowest'
|
||||
voids?: boolean
|
||||
}
|
||||
```
|
||||
|
||||
###### `Transforms.insertNodes(editor: Editor, nodes: Node | Node[], options?)`
|
||||
|
||||
Insert `nodes` at the specified location in the document. If no location is specified, insert at the current selection. If there is no selection, insert at the end of the document.
|
||||
Insert `nodes` at the specified location in the document. If no location is specified, insert at the current selection. If there is no selection, insert at the end of the document.
|
||||
|
||||
Options supported: `NodeOptions & {hanging?: boolean, select?: boolean}`.
|
||||
Options supported: `NodeOptions & {hanging?: boolean, select?: boolean}`.
|
||||
|
||||
###### `Transforms.removeNodes(editor: Editor, options?)`
|
||||
|
||||
Remove nodes at the specified location in the document. If no location is specified, remove the nodes in the selection.
|
||||
Remove nodes at the specified location in the document. If no location is specified, remove the nodes in the selection.
|
||||
|
||||
Options supported: `NodeOptions & {hanging?: boolean}`
|
||||
Options supported: `NodeOptions & {hanging?: boolean}`
|
||||
|
||||
###### `Transforms.mergeNodes(editor: Editor, options?)`
|
||||
|
||||
@@ -41,7 +41,7 @@ Options supported: `NodeOptions & {hanging?: boolean}`
|
||||
|
||||
Split nodes at the specified location. If no location is specified, split the selection.
|
||||
|
||||
Options supported: `NodeOptions & {height?: number, always?: boolean}`
|
||||
Options supported: `NodeOptions & {height?: number, always?: boolean}`
|
||||
|
||||
###### `Transforms.wrapNodes(editor: Editor, element: Element, options?)`
|
||||
|
||||
@@ -75,7 +75,7 @@ Options supported: `NodeOptions`. For `options.mode`, `'all'` is also supported.
|
||||
|
||||
###### `Transforms.moveNodes(editor: Editor, options)`
|
||||
|
||||
Move the nodes from an origin to a destination. A destination must be specified in the `options`. If no origin is specified, move the selection.
|
||||
Move the nodes from an origin to a destination. A destination must be specified in the `options`. If no origin is specified, move the selection.
|
||||
|
||||
Options supported: `NodeOptions & {to: Path}`. For `options.mode`, `'all'` is also supported.
|
||||
|
||||
@@ -87,7 +87,7 @@ Transforms that operate on the document's selection.
|
||||
|
||||
Collapse the selection to a single point.
|
||||
|
||||
Options: `{edge?: 'anchor' | 'focus' | 'start' | 'end'}`
|
||||
Options: `{edge?: 'anchor' | 'focus' | 'start' | 'end'}`
|
||||
|
||||
###### `Transforms.select(editor: Editor, target: Location)`
|
||||
|
||||
|
@@ -1,3 +1,3 @@
|
||||
# Slate Hyperscript
|
||||
|
||||
This package contains a hyperscript helper for creating Slate documents with JSX!
|
||||
This package contains a hyperscript helper for creating Slate documents with JSX!
|
||||
|
@@ -16,10 +16,9 @@ _Note, if you'd rather use a pre-bundled version of Slate, you can `yarn add sla
|
||||
|
||||
Once you've installed Slate, you'll need to import it.
|
||||
|
||||
|
||||
```jsx
|
||||
// Import React dependencies.
|
||||
import React, { useEffect, useMemo, useState } from "react";
|
||||
import React, { useEffect, useMemo, useState } from 'react'
|
||||
// Import the Slate editor factory.
|
||||
import { createEditor } from 'slate'
|
||||
|
||||
@@ -70,7 +69,11 @@ const App = () => {
|
||||
const [value, setValue] = useState([])
|
||||
// Render the Slate context.
|
||||
return (
|
||||
<Slate editor={editor} value={value} onChange={newValue => setValue(newValue)} />
|
||||
<Slate
|
||||
editor={editor}
|
||||
value={value}
|
||||
onChange={newValue => setValue(newValue)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
```
|
||||
@@ -89,7 +92,11 @@ const App = () => {
|
||||
const [value, setValue] = useState([])
|
||||
return (
|
||||
// Add the editable component inside the context.
|
||||
<Slate editor={editor} value={value} onChange={newValue => setValue(newValue)}>
|
||||
<Slate
|
||||
editor={editor}
|
||||
value={value}
|
||||
onChange={newValue => setValue(newValue)}
|
||||
>
|
||||
<Editable />
|
||||
</Slate>
|
||||
)
|
||||
@@ -114,7 +121,11 @@ const App = () => {
|
||||
])
|
||||
|
||||
return (
|
||||
<Slate editor={editor} value={value} onChange={newValue => setValue(newValue)}>
|
||||
<Slate
|
||||
editor={editor}
|
||||
value={value}
|
||||
onChange={newValue => setValue(newValue)}
|
||||
>
|
||||
<Editable />
|
||||
</Slate>
|
||||
)
|
||||
|
@@ -75,7 +75,7 @@ const App = () => {
|
||||
// Prevent the ampersand character from being inserted.
|
||||
event.preventDefault()
|
||||
// Execute the `insertText` method when the event occurs.
|
||||
editor.insertText("and")
|
||||
editor.insertText('and')
|
||||
}
|
||||
}}
|
||||
/>
|
||||
|
@@ -22,7 +22,7 @@ const App = () => {
|
||||
onKeyDown={event => {
|
||||
if (event.key === '&') {
|
||||
event.preventDefault()
|
||||
editor.insertText("and")
|
||||
editor.insertText('and')
|
||||
}
|
||||
}}
|
||||
/>
|
||||
@@ -93,7 +93,7 @@ const App = () => {
|
||||
onKeyDown={event => {
|
||||
if (event.key === '&') {
|
||||
event.preventDefault()
|
||||
editor.insertText("and")
|
||||
editor.insertText('and')
|
||||
}
|
||||
}}
|
||||
/>
|
||||
|
@@ -20,6 +20,7 @@
|
||||
"serve": "cd ./site && next",
|
||||
"start": "npm-run-all --parallel --print-label watch serve",
|
||||
"test": "mocha --require ./config/babel/register.cjs ./packages/*/test/index.js",
|
||||
"test:custom": "mocha --require ./config/babel/register.cjs ./packages/slate/test/index.js",
|
||||
"test:inspect": "yarn test --inspect-brk",
|
||||
"watch": "yarn build:rollup --watch"
|
||||
},
|
||||
@@ -87,4 +88,4 @@
|
||||
"source-map-loader": "^0.2.4",
|
||||
"typescript": "^3.7.2"
|
||||
}
|
||||
}
|
||||
}
|
@@ -13,7 +13,7 @@ export const MERGING = new WeakMap<Editor, boolean | undefined>()
|
||||
* `HistoryEditor` contains helpers for history-enabled editors.
|
||||
*/
|
||||
|
||||
export interface HistoryEditor extends Editor {
|
||||
export type HistoryEditor = Editor & {
|
||||
history: History
|
||||
undo: () => void
|
||||
redo: () => void
|
||||
@@ -25,7 +25,7 @@ export const HistoryEditor = {
|
||||
*/
|
||||
|
||||
isHistoryEditor(value: any): value is HistoryEditor {
|
||||
return Editor.isEditor(value) && History.isHistory(value.history)
|
||||
return History.isHistory(value.history) && Editor.isEditor(value)
|
||||
},
|
||||
|
||||
/**
|
||||
|
6
packages/slate-history/test/jsx.d.ts
vendored
Normal file
6
packages/slate-history/test/jsx.d.ts
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
// This allows tests to include Slate Nodes written in JSX without TypeScript complaining.
|
||||
declare namespace jsx.JSX {
|
||||
interface IntrinsicElements {
|
||||
[elemName: string]: any // eslint-disable-line
|
||||
}
|
||||
}
|
@@ -237,7 +237,7 @@ export function createEditor(
|
||||
const selection: Partial<Range> = {}
|
||||
const editor = makeEditor()
|
||||
Object.assign(editor, attributes)
|
||||
editor.children = descendants
|
||||
editor.children = descendants as Element[]
|
||||
|
||||
// Search the document's texts to see if any of them have tokens associated
|
||||
// that need incorporated into the selection.
|
||||
|
6
packages/slate-hyperscript/test/jsx.d.ts
vendored
Normal file
6
packages/slate-hyperscript/test/jsx.d.ts
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
// This allows tests to include Slate Nodes written in JSX without TypeScript complaining.
|
||||
declare namespace jsx.JSX {
|
||||
interface IntrinsicElements {
|
||||
[elemName: string]: any // eslint-disable-line
|
||||
}
|
||||
}
|
@@ -24,6 +24,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"slate": "^0.59.0",
|
||||
"slate-history": "^0.59.0",
|
||||
"slate-hyperscript": "^0.59.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
|
@@ -9,6 +9,7 @@ import {
|
||||
Transforms,
|
||||
Path,
|
||||
} from 'slate'
|
||||
import { HistoryEditor } from 'slate-history'
|
||||
import throttle from 'lodash/throttle'
|
||||
import scrollIntoView from 'scroll-into-view-if-needed'
|
||||
|
||||
@@ -766,7 +767,7 @@ export const Editable = (props: EditableProps) => {
|
||||
if (Hotkeys.isRedo(nativeEvent)) {
|
||||
event.preventDefault()
|
||||
|
||||
if (typeof editor.redo === 'function') {
|
||||
if (HistoryEditor.isHistoryEditor(editor)) {
|
||||
editor.redo()
|
||||
}
|
||||
|
||||
@@ -776,7 +777,7 @@ export const Editable = (props: EditableProps) => {
|
||||
if (Hotkeys.isUndo(nativeEvent)) {
|
||||
event.preventDefault()
|
||||
|
||||
if (typeof editor.undo === 'function') {
|
||||
if (HistoryEditor.isHistoryEditor(editor)) {
|
||||
editor.undo()
|
||||
}
|
||||
|
||||
|
@@ -1,6 +1,5 @@
|
||||
import React from 'react'
|
||||
import { Text, Element } from 'slate'
|
||||
|
||||
import { Element, Text } from 'slate'
|
||||
import String from './string'
|
||||
import { PLACEHOLDER_SYMBOL } from '../utils/weak-maps'
|
||||
import { RenderLeafProps } from './editable'
|
||||
@@ -46,7 +45,7 @@ const Leaf = (props: {
|
||||
textDecoration: 'none',
|
||||
}}
|
||||
>
|
||||
{leaf.placeholder as React.ReactNode}
|
||||
{leaf.placeholder}
|
||||
</span>
|
||||
{children}
|
||||
</React.Fragment>
|
||||
@@ -75,10 +74,6 @@ const MemoizedLeaf = React.memo(Leaf, (prev, next) => {
|
||||
)
|
||||
})
|
||||
|
||||
/**
|
||||
* The default custom leaf renderer.
|
||||
*/
|
||||
|
||||
export const DefaultLeaf = (props: RenderLeafProps) => {
|
||||
const { attributes, children } = props
|
||||
return <span {...attributes}>{children}</span>
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import React, { useMemo, useState, useCallback, useEffect } from 'react'
|
||||
import { Node } from 'slate'
|
||||
import { Node, Element, Descendant } from 'slate'
|
||||
|
||||
import { ReactEditor } from '../plugin/react-editor'
|
||||
import { FocusedContext } from '../hooks/use-focused'
|
||||
@@ -14,10 +14,9 @@ import { EDITOR_TO_ON_CHANGE } from '../utils/weak-maps'
|
||||
|
||||
export const Slate = (props: {
|
||||
editor: ReactEditor
|
||||
value: Node[]
|
||||
value: Descendant[]
|
||||
children: React.ReactNode
|
||||
onChange: (value: Node[]) => void
|
||||
[key: string]: unknown
|
||||
onChange: (value: Descendant[]) => void
|
||||
}) => {
|
||||
const { editor, children, onChange, value, ...rest } = props
|
||||
const [key, setKey] = useState(0)
|
||||
|
12
packages/slate-react/src/custom-types.ts
Normal file
12
packages/slate-react/src/custom-types.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { CustomTypes } from 'slate'
|
||||
|
||||
declare module 'slate' {
|
||||
interface CustomTypes {
|
||||
Text: {
|
||||
placeholder: string
|
||||
}
|
||||
Range: {
|
||||
placeholder?: string
|
||||
}
|
||||
}
|
||||
}
|
@@ -12,6 +12,7 @@ import DOMText = globalThis.Text
|
||||
import DOMRange = globalThis.Range
|
||||
import DOMSelection = globalThis.Selection
|
||||
import DOMStaticRange = globalThis.StaticRange
|
||||
|
||||
export {
|
||||
DOMNode,
|
||||
DOMComment,
|
||||
|
@@ -1 +1,3 @@
|
||||
This package contains the core logic of Slate. Feel free to poke around to learn more!
|
||||
|
||||
Note: A number of source files contain extracted types for `Interfaces` or `Transforms`. This is done currently to enable custom type extensions as found in `packages/src/interfaces/custom-types.ts`.
|
||||
|
@@ -307,7 +307,7 @@ export const createEditor = (): Editor => {
|
||||
* Get the "dirty" paths generated from an operation.
|
||||
*/
|
||||
|
||||
const getDirtyPaths = (op: Operation) => {
|
||||
const getDirtyPaths = (op: Operation): Path[] => {
|
||||
switch (op.type) {
|
||||
case 'insert_text':
|
||||
case 'remove_text':
|
||||
|
@@ -11,4 +11,5 @@ export * from './interfaces/point-ref'
|
||||
export * from './interfaces/range'
|
||||
export * from './interfaces/range-ref'
|
||||
export * from './interfaces/text'
|
||||
export * from './interfaces/custom-types'
|
||||
export * from './transforms'
|
||||
|
11
packages/slate/src/interfaces/custom-types.ts
Normal file
11
packages/slate/src/interfaces/custom-types.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
/**
|
||||
* Extendable Custom Types Interface
|
||||
*/
|
||||
|
||||
export interface CustomTypes {
|
||||
[key: string]: unknown
|
||||
}
|
||||
|
||||
export type ExtendedType<K extends string, B> = unknown extends CustomTypes[K]
|
||||
? B
|
||||
: B & CustomTypes[K]
|
@@ -4,8 +4,7 @@ import { reverse as reverseText } from 'esrever'
|
||||
|
||||
import {
|
||||
Ancestor,
|
||||
Descendant,
|
||||
Element,
|
||||
ExtendedType,
|
||||
Location,
|
||||
Node,
|
||||
NodeEntry,
|
||||
@@ -27,18 +26,23 @@ import {
|
||||
RANGE_REFS,
|
||||
} from '../utils/weak-maps'
|
||||
import { getWordDistance, getCharacterDistance } from '../utils/string'
|
||||
import { Descendant } from './node'
|
||||
import { Element } from './element'
|
||||
|
||||
export type BaseSelection = Range | null
|
||||
|
||||
export type Selection = ExtendedType<'Selection', BaseSelection>
|
||||
|
||||
/**
|
||||
* The `Editor` interface stores all the state of a Slate editor. It is extended
|
||||
* by plugins that wish to add their own helpers and implement new behaviors.
|
||||
*/
|
||||
|
||||
export interface Editor {
|
||||
children: Node[]
|
||||
selection: Range | null
|
||||
export interface BaseEditor {
|
||||
children: Descendant[]
|
||||
selection: Selection
|
||||
operations: Operation[]
|
||||
marks: Record<string, any> | null
|
||||
[key: string]: unknown
|
||||
marks: Omit<Text, 'text'> | null
|
||||
|
||||
// Schema-specific node behaviors.
|
||||
isInline: (element: Element) => boolean
|
||||
@@ -60,7 +64,208 @@ export interface Editor {
|
||||
removeMark: (key: string) => void
|
||||
}
|
||||
|
||||
export const Editor = {
|
||||
export type Editor = ExtendedType<'Editor', BaseEditor>
|
||||
|
||||
export interface EditorInterface {
|
||||
above: <T extends Ancestor>(
|
||||
editor: Editor,
|
||||
options?: {
|
||||
at?: Location
|
||||
match?: NodeMatch<T>
|
||||
mode?: 'highest' | 'lowest'
|
||||
voids?: boolean
|
||||
}
|
||||
) => NodeEntry<T> | undefined
|
||||
addMark: (editor: Editor, key: string, value: any) => void
|
||||
after: (
|
||||
editor: Editor,
|
||||
at: Location,
|
||||
options?: {
|
||||
distance?: number
|
||||
unit?: 'offset' | 'character' | 'word' | 'line' | 'block'
|
||||
}
|
||||
) => Point | undefined
|
||||
before: (
|
||||
editor: Editor,
|
||||
at: Location,
|
||||
options?: {
|
||||
distance?: number
|
||||
unit?: 'offset' | 'character' | 'word' | 'line' | 'block'
|
||||
}
|
||||
) => Point | undefined
|
||||
deleteBackward: (
|
||||
editor: Editor,
|
||||
options?: {
|
||||
unit?: 'character' | 'word' | 'line' | 'block'
|
||||
}
|
||||
) => void
|
||||
deleteForward: (
|
||||
editor: Editor,
|
||||
options?: {
|
||||
unit?: 'character' | 'word' | 'line' | 'block'
|
||||
}
|
||||
) => void
|
||||
deleteFragment: (editor: Editor) => void
|
||||
edges: (editor: Editor, at: Location) => [Point, Point]
|
||||
end: (editor: Editor, at: Location) => Point
|
||||
first: (editor: Editor, at: Location) => NodeEntry
|
||||
fragment: (editor: Editor, at: Location) => Descendant[]
|
||||
hasBlocks: (editor: Editor, element: Element) => boolean
|
||||
hasInlines: (editor: Editor, element: Element) => boolean
|
||||
hasTexts: (editor: Editor, element: Element) => boolean
|
||||
insertBreak: (editor: Editor) => void
|
||||
insertFragment: (editor: Editor, fragment: Node[]) => void
|
||||
insertNode: (editor: Editor, node: Node) => void
|
||||
insertText: (editor: Editor, text: string) => void
|
||||
isBlock: (editor: Editor, value: any) => value is Element
|
||||
isEditor: (value: any) => value is Editor
|
||||
isEnd: (editor: Editor, point: Point, at: Location) => boolean
|
||||
isEdge: (editor: Editor, point: Point, at: Location) => boolean
|
||||
isEmpty: (editor: Editor, element: Element) => boolean
|
||||
isInline: (editor: Editor, value: any) => value is Element
|
||||
isNormalizing: (editor: Editor) => boolean
|
||||
isStart: (editor: Editor, point: Point, at: Location) => boolean
|
||||
isVoid: (editor: Editor, value: any) => value is Element
|
||||
last: (editor: Editor, at: Location) => NodeEntry
|
||||
leaf: (
|
||||
editor: Editor,
|
||||
at: Location,
|
||||
options?: {
|
||||
depth?: number
|
||||
edge?: 'start' | 'end'
|
||||
}
|
||||
) => NodeEntry<Text>
|
||||
levels: <T extends Node>(
|
||||
editor: Editor,
|
||||
options?: {
|
||||
at?: Location
|
||||
match?: NodeMatch<T>
|
||||
reverse?: boolean
|
||||
voids?: boolean
|
||||
}
|
||||
) => Generator<NodeEntry<T>, void, undefined>
|
||||
marks: (editor: Editor) => Omit<Text, 'text'> | null
|
||||
next: <T extends Descendant>(
|
||||
editor: Editor,
|
||||
options?: {
|
||||
at?: Location
|
||||
match?: NodeMatch<T>
|
||||
mode?: 'all' | 'highest' | 'lowest'
|
||||
voids?: boolean
|
||||
}
|
||||
) => NodeEntry<T> | undefined
|
||||
node: (
|
||||
editor: Editor,
|
||||
at: Location,
|
||||
options?: {
|
||||
depth?: number
|
||||
edge?: 'start' | 'end'
|
||||
}
|
||||
) => NodeEntry
|
||||
nodes: <T extends Node>(
|
||||
editor: Editor,
|
||||
options?: {
|
||||
at?: Location | Span
|
||||
match?: NodeMatch<T>
|
||||
mode?: 'all' | 'highest' | 'lowest'
|
||||
universal?: boolean
|
||||
reverse?: boolean
|
||||
voids?: boolean
|
||||
}
|
||||
) => Generator<NodeEntry<T>, void, undefined>
|
||||
normalize: (
|
||||
editor: Editor,
|
||||
options?: {
|
||||
force?: boolean
|
||||
}
|
||||
) => void
|
||||
parent: (
|
||||
editor: Editor,
|
||||
at: Location,
|
||||
options?: {
|
||||
depth?: number
|
||||
edge?: 'start' | 'end'
|
||||
}
|
||||
) => NodeEntry<Ancestor>
|
||||
path: (
|
||||
editor: Editor,
|
||||
at: Location,
|
||||
options?: {
|
||||
depth?: number
|
||||
edge?: 'start' | 'end'
|
||||
}
|
||||
) => Path
|
||||
pathRef: (
|
||||
editor: Editor,
|
||||
path: Path,
|
||||
options?: {
|
||||
affinity?: 'backward' | 'forward' | null
|
||||
}
|
||||
) => PathRef
|
||||
pathRefs: (editor: Editor) => Set<PathRef>
|
||||
point: (
|
||||
editor: Editor,
|
||||
at: Location,
|
||||
options?: {
|
||||
edge?: 'start' | 'end'
|
||||
}
|
||||
) => Point
|
||||
pointRef: (
|
||||
editor: Editor,
|
||||
point: Point,
|
||||
options?: {
|
||||
affinity?: 'backward' | 'forward' | null
|
||||
}
|
||||
) => PointRef
|
||||
pointRefs: (editor: Editor) => Set<PointRef>
|
||||
positions: (
|
||||
editor: Editor,
|
||||
options?: {
|
||||
at?: Location
|
||||
unit?: 'offset' | 'character' | 'word' | 'line' | 'block'
|
||||
reverse?: boolean
|
||||
}
|
||||
) => Generator<Point, void, undefined>
|
||||
previous: <T extends Node>(
|
||||
editor: Editor,
|
||||
options?: {
|
||||
at?: Location
|
||||
match?: NodeMatch<T>
|
||||
mode?: 'all' | 'highest' | 'lowest'
|
||||
voids?: boolean
|
||||
}
|
||||
) => NodeEntry<T> | undefined
|
||||
range: (editor: Editor, at: Location, to?: Location) => Range
|
||||
rangeRef: (
|
||||
editor: Editor,
|
||||
range: Range,
|
||||
options?: {
|
||||
affinity?: 'backward' | 'forward' | 'outward' | 'inward' | null
|
||||
}
|
||||
) => RangeRef
|
||||
rangeRefs: (editor: Editor) => Set<RangeRef>
|
||||
removeMark: (editor: Editor, key: string) => void
|
||||
start: (editor: Editor, at: Location) => Point
|
||||
string: (editor: Editor, at: Location) => string
|
||||
unhangRange: (
|
||||
editor: Editor,
|
||||
range: Range,
|
||||
options?: {
|
||||
voids?: boolean
|
||||
}
|
||||
) => Range
|
||||
void: (
|
||||
editor: Editor,
|
||||
options?: {
|
||||
at?: Location
|
||||
mode?: 'highest' | 'lowest'
|
||||
voids?: boolean
|
||||
}
|
||||
) => NodeEntry<Element> | undefined
|
||||
withoutNormalizing: (editor: Editor, fn: () => void) => void
|
||||
}
|
||||
|
||||
export const Editor: EditorInterface = {
|
||||
/**
|
||||
* Get the ancestor above a location in the document.
|
||||
*/
|
||||
@@ -508,7 +713,7 @@ export const Editor = {
|
||||
* Get the marks that would be added to text at the current selection.
|
||||
*/
|
||||
|
||||
marks(editor: Editor): Record<string, any> | null {
|
||||
marks(editor: Editor): Omit<Text, 'text'> | null {
|
||||
const { marks, selection } = editor
|
||||
|
||||
if (!selection) {
|
||||
@@ -559,7 +764,7 @@ export const Editor = {
|
||||
* Get the matching node in the branch of the document after a location.
|
||||
*/
|
||||
|
||||
next<T extends Node>(
|
||||
next<T extends Descendant>(
|
||||
editor: Editor,
|
||||
options: {
|
||||
at?: Location
|
||||
@@ -736,7 +941,7 @@ export const Editor = {
|
||||
options: {
|
||||
force?: boolean
|
||||
} = {}
|
||||
) {
|
||||
): void {
|
||||
const { force = false } = options
|
||||
const getDirtyPaths = (editor: Editor) => {
|
||||
return DIRTY_PATHS.get(editor) || []
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import isPlainObject from 'is-plain-object'
|
||||
import { Editor, Node, Path } from '..'
|
||||
import { Editor, Node, Path, Descendant, ExtendedType, Ancestor } from '..'
|
||||
|
||||
/**
|
||||
* `Element` objects are a type of node in a Slate document that contain other
|
||||
@@ -7,12 +7,29 @@ import { Editor, Node, Path } from '..'
|
||||
* depending on the Slate editor's configuration.
|
||||
*/
|
||||
|
||||
export interface Element {
|
||||
children: Node[]
|
||||
[key: string]: unknown
|
||||
export interface BaseElement {
|
||||
children: Descendant[]
|
||||
}
|
||||
|
||||
export const Element = {
|
||||
export type Element = ExtendedType<'Element', BaseElement>
|
||||
|
||||
export interface ElementInterface {
|
||||
isAncestor: (value: any) => value is Ancestor
|
||||
isElement: (value: any) => value is Element
|
||||
isElementList: (value: any) => value is Element[]
|
||||
isElementProps: (props: any) => props is Partial<Element>
|
||||
matches: (element: Element, props: Partial<Element>) => boolean
|
||||
}
|
||||
|
||||
export const Element: ElementInterface = {
|
||||
/**
|
||||
* Check if a value implements the 'Ancestor' interface.
|
||||
*/
|
||||
|
||||
isAncestor(value: any): value is Ancestor {
|
||||
return isPlainObject(value) && Node.isNodeList(value.children)
|
||||
},
|
||||
|
||||
/**
|
||||
* Check if a value implements the `Element` interface.
|
||||
*/
|
||||
@@ -36,6 +53,14 @@ export const Element = {
|
||||
)
|
||||
},
|
||||
|
||||
/**
|
||||
* Check if a set of props is a partial of Element.
|
||||
*/
|
||||
|
||||
isElementProps(props: any): props is Partial<Element> {
|
||||
return (props as Partial<Element>).children !== undefined
|
||||
},
|
||||
|
||||
/**
|
||||
* Check if an element matches set of properties.
|
||||
*
|
||||
|
@@ -11,7 +11,11 @@ import { Path, Point, Range } from '..'
|
||||
|
||||
export type Location = Path | Point | Range
|
||||
|
||||
export const Location = {
|
||||
export interface LocationInterface {
|
||||
isLocation: (value: any) => value is Location
|
||||
}
|
||||
|
||||
export const Location: LocationInterface = {
|
||||
/**
|
||||
* Check if a value implements the `Location` interface.
|
||||
*/
|
||||
@@ -28,7 +32,11 @@ export const Location = {
|
||||
|
||||
export type Span = [Path, Path]
|
||||
|
||||
export const Span = {
|
||||
export interface SpanInterface {
|
||||
isSpan: (value: any) => value is Span
|
||||
}
|
||||
|
||||
export const Span: SpanInterface = {
|
||||
/**
|
||||
* Check if a value implements the `Span` interface.
|
||||
*/
|
||||
|
@@ -1,14 +1,93 @@
|
||||
import { produce } from 'immer'
|
||||
import { Editor, Element, ElementEntry, Path, Range, Text } from '..'
|
||||
import { Editor, Path, Range, Text } from '..'
|
||||
import { Element, ElementEntry } from './element'
|
||||
import { ExtendedType } from './custom-types'
|
||||
|
||||
/**
|
||||
* The `Node` union type represents all of the different types of nodes that
|
||||
* occur in a Slate document tree.
|
||||
*/
|
||||
|
||||
export type Node = Editor | Element | Text
|
||||
export type BaseNode = Editor | Element | Text
|
||||
export type Node = ExtendedType<'Node', BaseNode>
|
||||
|
||||
export const Node = {
|
||||
export interface NodeInterface {
|
||||
ancestor: (root: Node, path: Path) => Ancestor
|
||||
ancestors: (
|
||||
root: Node,
|
||||
path: Path,
|
||||
options?: {
|
||||
reverse?: boolean
|
||||
}
|
||||
) => Generator<NodeEntry<Ancestor>, void, undefined>
|
||||
child: (root: Node, index: number) => Descendant
|
||||
children: (
|
||||
root: Node,
|
||||
path: Path,
|
||||
options?: {
|
||||
reverse?: boolean
|
||||
}
|
||||
) => Generator<NodeEntry<Descendant>, void, undefined>
|
||||
common: (root: Node, path: Path, another: Path) => NodeEntry
|
||||
descendant: (root: Node, path: Path) => Descendant
|
||||
descendants: (
|
||||
root: Node,
|
||||
options?: {
|
||||
from?: Path
|
||||
to?: Path
|
||||
reverse?: boolean
|
||||
pass?: (node: NodeEntry) => boolean
|
||||
}
|
||||
) => Generator<NodeEntry<Descendant>, void, undefined>
|
||||
elements: (
|
||||
root: Node,
|
||||
options?: {
|
||||
from?: Path
|
||||
to?: Path
|
||||
reverse?: boolean
|
||||
pass?: (node: NodeEntry) => boolean
|
||||
}
|
||||
) => Generator<ElementEntry, void, undefined>
|
||||
extractProps: (node: Node) => NodeProps
|
||||
first: (root: Node, path: Path) => NodeEntry
|
||||
fragment: (root: Node, range: Range) => Descendant[]
|
||||
get: (root: Node, path: Path) => Node
|
||||
has: (root: Node, path: Path) => boolean
|
||||
isNode: (value: any) => value is Node
|
||||
isNodeList: (value: any) => value is Node[]
|
||||
last: (root: Node, path: Path) => NodeEntry
|
||||
leaf: (root: Node, path: Path) => Text
|
||||
levels: (
|
||||
root: Node,
|
||||
path: Path,
|
||||
options?: {
|
||||
reverse?: boolean
|
||||
}
|
||||
) => Generator<NodeEntry, void, undefined>
|
||||
matches: (node: Node, props: Partial<Node>) => boolean
|
||||
nodes: (
|
||||
root: Node,
|
||||
options?: {
|
||||
from?: Path
|
||||
to?: Path
|
||||
reverse?: boolean
|
||||
pass?: (entry: NodeEntry) => boolean
|
||||
}
|
||||
) => Generator<NodeEntry, void, undefined>
|
||||
parent: (root: Node, path: Path) => Ancestor
|
||||
string: (node: Node) => string
|
||||
texts: (
|
||||
root: Node,
|
||||
options?: {
|
||||
from?: Path
|
||||
to?: Path
|
||||
reverse?: boolean
|
||||
pass?: (node: NodeEntry) => boolean
|
||||
}
|
||||
) => Generator<NodeEntry<Text>, void, undefined>
|
||||
}
|
||||
|
||||
export const Node: NodeInterface = {
|
||||
/**
|
||||
* Get the node at a specific path, asserting that it's an ancestor node.
|
||||
*/
|
||||
@@ -164,6 +243,22 @@ export const Node = {
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Extract props from a Node.
|
||||
*/
|
||||
|
||||
extractProps(node: Node): NodeProps {
|
||||
if (Element.isAncestor(node)) {
|
||||
const { children, ...properties } = node
|
||||
|
||||
return properties
|
||||
} else {
|
||||
const { text, ...properties } = node
|
||||
|
||||
return properties
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the first node entry in a root node from a path.
|
||||
*/
|
||||
@@ -222,7 +317,7 @@ export const Node = {
|
||||
}
|
||||
}
|
||||
|
||||
delete r.selection
|
||||
if (Editor.isEditor(r)) delete r.selection
|
||||
})
|
||||
|
||||
return newRoot.children
|
||||
@@ -354,8 +449,12 @@ export const Node = {
|
||||
|
||||
matches(node: Node, props: Partial<Node>): boolean {
|
||||
return (
|
||||
(Element.isElement(node) && Element.matches(node, props)) ||
|
||||
(Text.isText(node) && Text.matches(node, props))
|
||||
(Element.isElement(node) &&
|
||||
Element.isElementProps(props) &&
|
||||
Element.matches(node, props)) ||
|
||||
(Text.isText(node) &&
|
||||
Text.isTextProps(props) &&
|
||||
Text.matches(node, props))
|
||||
)
|
||||
},
|
||||
|
||||
@@ -516,3 +615,11 @@ export type Ancestor = Editor | Element
|
||||
*/
|
||||
|
||||
export type NodeEntry<T extends Node = Node> = [T, Path]
|
||||
|
||||
/**
|
||||
* Convenience type for returning the props of a node.
|
||||
*/
|
||||
export type NodeProps =
|
||||
| Omit<Editor, 'children'>
|
||||
| Omit<Element, 'children'>
|
||||
| Omit<Text, 'text'>
|
||||
|
@@ -1,87 +1,121 @@
|
||||
import { Node, Path, Range } from '..'
|
||||
import { ExtendedType, Node, Path, Range } from '..'
|
||||
import isPlainObject from 'is-plain-object'
|
||||
|
||||
export type InsertNodeOperation = {
|
||||
export type BaseInsertNodeOperation = {
|
||||
type: 'insert_node'
|
||||
path: Path
|
||||
node: Node
|
||||
[key: string]: unknown
|
||||
}
|
||||
|
||||
export type InsertTextOperation = {
|
||||
export type InsertNodeOperation = ExtendedType<
|
||||
'InsertNodeOperation',
|
||||
BaseInsertNodeOperation
|
||||
>
|
||||
|
||||
export type BaseInsertTextOperation = {
|
||||
type: 'insert_text'
|
||||
path: Path
|
||||
offset: number
|
||||
text: string
|
||||
[key: string]: unknown
|
||||
}
|
||||
|
||||
export type MergeNodeOperation = {
|
||||
export type InsertTextOperation = ExtendedType<
|
||||
'InsertTextOperation',
|
||||
BaseInsertTextOperation
|
||||
>
|
||||
|
||||
export type BaseMergeNodeOperation = {
|
||||
type: 'merge_node'
|
||||
path: Path
|
||||
position: number
|
||||
properties: Partial<Node>
|
||||
[key: string]: unknown
|
||||
}
|
||||
|
||||
export type MoveNodeOperation = {
|
||||
export type MergeNodeOperation = ExtendedType<
|
||||
'MergeNodeOperation',
|
||||
BaseMergeNodeOperation
|
||||
>
|
||||
|
||||
export type BaseMoveNodeOperation = {
|
||||
type: 'move_node'
|
||||
path: Path
|
||||
newPath: Path
|
||||
[key: string]: unknown
|
||||
}
|
||||
|
||||
export type RemoveNodeOperation = {
|
||||
export type MoveNodeOperation = ExtendedType<
|
||||
'MoveNodeOperation',
|
||||
BaseMoveNodeOperation
|
||||
>
|
||||
|
||||
export type BaseRemoveNodeOperation = {
|
||||
type: 'remove_node'
|
||||
path: Path
|
||||
node: Node
|
||||
[key: string]: unknown
|
||||
}
|
||||
|
||||
export type RemoveTextOperation = {
|
||||
export type RemoveNodeOperation = ExtendedType<
|
||||
'RemoveNodeOperation',
|
||||
BaseRemoveNodeOperation
|
||||
>
|
||||
|
||||
export type BaseRemoveTextOperation = {
|
||||
type: 'remove_text'
|
||||
path: Path
|
||||
offset: number
|
||||
text: string
|
||||
[key: string]: unknown
|
||||
}
|
||||
|
||||
export type SetNodeOperation = {
|
||||
export type RemoveTextOperation = ExtendedType<
|
||||
'RemoveTextOperation',
|
||||
BaseRemoveTextOperation
|
||||
>
|
||||
|
||||
export type BaseSetNodeOperation = {
|
||||
type: 'set_node'
|
||||
path: Path
|
||||
properties: Partial<Node>
|
||||
newProperties: Partial<Node>
|
||||
[key: string]: unknown
|
||||
}
|
||||
|
||||
export type SetSelectionOperation =
|
||||
export type SetNodeOperation = ExtendedType<
|
||||
'SetNodeOperation',
|
||||
BaseSetNodeOperation
|
||||
>
|
||||
|
||||
export type BaseSetSelectionOperation =
|
||||
| {
|
||||
type: 'set_selection'
|
||||
[key: string]: unknown
|
||||
properties: null
|
||||
newProperties: Range
|
||||
}
|
||||
| {
|
||||
type: 'set_selection'
|
||||
[key: string]: unknown
|
||||
properties: Partial<Range>
|
||||
newProperties: Partial<Range>
|
||||
}
|
||||
| {
|
||||
type: 'set_selection'
|
||||
[key: string]: unknown
|
||||
properties: Range
|
||||
newProperties: null
|
||||
}
|
||||
|
||||
export type SplitNodeOperation = {
|
||||
export type SetSelectionOperation = ExtendedType<
|
||||
'SetSelectionOperation',
|
||||
BaseSetSelectionOperation
|
||||
>
|
||||
|
||||
export type BaseSplitNodeOperation = {
|
||||
type: 'split_node'
|
||||
path: Path
|
||||
position: number
|
||||
properties: Partial<Node>
|
||||
[key: string]: unknown
|
||||
}
|
||||
|
||||
export type SplitNodeOperation = ExtendedType<
|
||||
'SplitNodeOperation',
|
||||
BaseSplitNodeOperation
|
||||
>
|
||||
|
||||
export type NodeOperation =
|
||||
| InsertNodeOperation
|
||||
| MergeNodeOperation
|
||||
@@ -103,7 +137,16 @@ export type TextOperation = InsertTextOperation | RemoveTextOperation
|
||||
|
||||
export type Operation = NodeOperation | SelectionOperation | TextOperation
|
||||
|
||||
export const Operation = {
|
||||
export interface OperationInterface {
|
||||
isNodeOperation: (value: any) => value is NodeOperation
|
||||
isOperation: (value: any) => value is Operation
|
||||
isOperationList: (value: any) => value is Operation[]
|
||||
isSelectionOperation: (value: any) => value is SelectionOperation
|
||||
isTextOperation: (value: any) => value is TextOperation
|
||||
inverse: (op: Operation) => Operation
|
||||
}
|
||||
|
||||
export const Operation: OperationInterface = {
|
||||
/**
|
||||
* Check of a value is a `NodeOperation` object.
|
||||
*/
|
||||
|
@@ -12,7 +12,11 @@ export interface PathRef {
|
||||
unref(): Path | null
|
||||
}
|
||||
|
||||
export const PathRef = {
|
||||
export interface PathRefInterface {
|
||||
transform: (ref: PathRef, op: Operation) => void
|
||||
}
|
||||
|
||||
export const PathRef: PathRefInterface = {
|
||||
/**
|
||||
* Transform the path ref's current value by an operation.
|
||||
*/
|
||||
|
@@ -9,7 +9,41 @@ import { Operation } from '..'
|
||||
|
||||
export type Path = number[]
|
||||
|
||||
export const Path = {
|
||||
export interface PathInterface {
|
||||
ancestors: (path: Path, options?: { reverse?: boolean }) => Path[]
|
||||
common: (path: Path, another: Path) => Path
|
||||
compare: (path: Path, another: Path) => -1 | 0 | 1
|
||||
endsAfter: (path: Path, another: Path) => boolean
|
||||
endsAt: (path: Path, another: Path) => boolean
|
||||
endsBefore: (path: Path, another: Path) => boolean
|
||||
equals: (path: Path, another: Path) => boolean
|
||||
isAfter: (path: Path, another: Path) => boolean
|
||||
isAncestor: (path: Path, another: Path) => boolean
|
||||
isBefore: (path: Path, another: Path) => boolean
|
||||
isChild: (path: Path, another: Path) => boolean
|
||||
isCommon: (path: Path, another: Path) => boolean
|
||||
isDescendant: (path: Path, another: Path) => boolean
|
||||
isParent: (path: Path, another: Path) => boolean
|
||||
isPath: (value: any) => value is Path
|
||||
isSibling: (path: Path, another: Path) => boolean
|
||||
levels: (
|
||||
path: Path,
|
||||
options?: {
|
||||
reverse?: boolean
|
||||
}
|
||||
) => Path[]
|
||||
next: (path: Path) => Path
|
||||
parent: (path: Path) => Path
|
||||
previous: (path: Path) => Path
|
||||
relative: (path: Path, ancestor: Path) => Path
|
||||
transform: (
|
||||
path: Path,
|
||||
operation: Operation,
|
||||
options?: { affinity?: 'forward' | 'backward' | null }
|
||||
) => Path | null
|
||||
}
|
||||
|
||||
export const Path: PathInterface = {
|
||||
/**
|
||||
* Get a list of ancestor paths for a given path.
|
||||
*
|
||||
|
@@ -12,7 +12,11 @@ export interface PointRef {
|
||||
unref(): Point | null
|
||||
}
|
||||
|
||||
export const PointRef = {
|
||||
export interface PointRefInterface {
|
||||
transform: (ref: PointRef, op: Operation) => void
|
||||
}
|
||||
|
||||
export const PointRef: PointRefInterface = {
|
||||
/**
|
||||
* Transform the point ref's current value by an operation.
|
||||
*/
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import isPlainObject from 'is-plain-object'
|
||||
import { produce } from 'immer'
|
||||
import { Operation, Path } from '..'
|
||||
import { ExtendedType, Operation, Path } from '..'
|
||||
|
||||
/**
|
||||
* `Point` objects refer to a specific location in a text node in a Slate
|
||||
@@ -9,13 +9,27 @@ import { Operation, Path } from '..'
|
||||
* only refer to `Text` nodes.
|
||||
*/
|
||||
|
||||
export interface Point {
|
||||
export interface BasePoint {
|
||||
path: Path
|
||||
offset: number
|
||||
[key: string]: unknown
|
||||
}
|
||||
|
||||
export const Point = {
|
||||
export type Point = ExtendedType<'Point', BasePoint>
|
||||
|
||||
export interface PointInterface {
|
||||
compare: (point: Point, another: Point) => -1 | 0 | 1
|
||||
isAfter: (point: Point, another: Point) => boolean
|
||||
isBefore: (point: Point, another: Point) => boolean
|
||||
equals: (point: Point, another: Point) => boolean
|
||||
isPoint: (value: any) => value is Point
|
||||
transform: (
|
||||
point: Point,
|
||||
op: Operation,
|
||||
options?: { affinity?: 'forward' | 'backward' | null }
|
||||
) => Point | null
|
||||
}
|
||||
|
||||
export const Point: PointInterface = {
|
||||
/**
|
||||
* Compare a point to another, returning an integer indicating whether the
|
||||
* point was before, at, or after the other.
|
||||
|
@@ -12,7 +12,11 @@ export interface RangeRef {
|
||||
unref(): Range | null
|
||||
}
|
||||
|
||||
export const RangeRef = {
|
||||
export interface RangeRefInterface {
|
||||
transform: (ref: RangeRef, op: Operation) => void
|
||||
}
|
||||
|
||||
export const RangeRef: RangeRefInterface = {
|
||||
/**
|
||||
* Transform the range ref's current value by an operation.
|
||||
*/
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import { produce } from 'immer'
|
||||
import isPlainObject from 'is-plain-object'
|
||||
import { Operation, Path, Point, PointEntry } from '..'
|
||||
import { ExtendedType, Operation, Path, Point, PointEntry } from '..'
|
||||
|
||||
/**
|
||||
* `Range` objects are a set of points that refer to a specific span of a Slate
|
||||
@@ -8,13 +8,41 @@ import { Operation, Path, Point, PointEntry } from '..'
|
||||
* multiple nodes.
|
||||
*/
|
||||
|
||||
export interface Range {
|
||||
export interface BaseRange {
|
||||
anchor: Point
|
||||
focus: Point
|
||||
[key: string]: unknown
|
||||
}
|
||||
|
||||
export const Range = {
|
||||
export type Range = ExtendedType<'Range', BaseRange>
|
||||
|
||||
export interface RangeInterface {
|
||||
edges: (
|
||||
range: Range,
|
||||
options?: {
|
||||
reverse?: boolean
|
||||
}
|
||||
) => [Point, Point]
|
||||
end: (range: Range) => Point
|
||||
equals: (range: Range, another: Range) => boolean
|
||||
includes: (range: Range, target: Path | Point | Range) => boolean
|
||||
intersection: (range: Range, another: Range) => Range | null
|
||||
isBackward: (range: Range) => boolean
|
||||
isCollapsed: (range: Range) => boolean
|
||||
isExpanded: (range: Range) => boolean
|
||||
isForward: (range: Range) => boolean
|
||||
isRange: (value: any) => value is Range
|
||||
points: (range: Range) => Generator<PointEntry, void, undefined>
|
||||
start: (range: Range) => Point
|
||||
transform: (
|
||||
range: Range,
|
||||
op: Operation,
|
||||
options?: {
|
||||
affinity?: 'forward' | 'backward' | 'outward' | 'inward' | null
|
||||
}
|
||||
) => Range | null
|
||||
}
|
||||
|
||||
export const Range: RangeInterface = {
|
||||
/**
|
||||
* Get the start and end points of a range, in the order in which they appear
|
||||
* in the document.
|
||||
|
@@ -1,5 +1,6 @@
|
||||
import isPlainObject from 'is-plain-object'
|
||||
import { Range } from '..'
|
||||
import { ExtendedType } from './custom-types'
|
||||
|
||||
/**
|
||||
* `Text` objects represent the nodes that contain the actual text content of a
|
||||
@@ -7,12 +8,22 @@ import { Range } from '..'
|
||||
* nodes in the document tree as they cannot contain any children.
|
||||
*/
|
||||
|
||||
export interface Text {
|
||||
export interface BaseText {
|
||||
text: string
|
||||
[key: string]: unknown
|
||||
}
|
||||
|
||||
export const Text = {
|
||||
export type Text = ExtendedType<'Text', BaseText>
|
||||
|
||||
export interface TextInterface {
|
||||
equals: (text: Text, another: Text, options?: { loose?: boolean }) => boolean
|
||||
isText: (value: any) => value is Text
|
||||
isTextList: (value: any) => value is Text[]
|
||||
isTextProps: (props: any) => props is Partial<Text>
|
||||
matches: (text: Text, props: Partial<Text>) => boolean
|
||||
decorations: (node: Text, decorations: Range[]) => Text[]
|
||||
}
|
||||
|
||||
export const Text: TextInterface = {
|
||||
/**
|
||||
* Check if two text nodes are equal.
|
||||
*/
|
||||
@@ -63,6 +74,14 @@ export const Text = {
|
||||
return Array.isArray(value) && (value.length === 0 || Text.isText(value[0]))
|
||||
},
|
||||
|
||||
/**
|
||||
* Check if some props are a partial of Text.
|
||||
*/
|
||||
|
||||
isTextProps(props: any): props is Partial<Text> {
|
||||
return (props as Partial<Text>).text !== undefined
|
||||
},
|
||||
|
||||
/**
|
||||
* Check if an text matches set of properties.
|
||||
*
|
||||
|
@@ -13,12 +13,16 @@ import {
|
||||
Ancestor,
|
||||
} from '..'
|
||||
|
||||
export const GeneralTransforms = {
|
||||
export interface GeneralTransforms {
|
||||
transform: (editor: Editor, op: Operation) => void
|
||||
}
|
||||
|
||||
export const GeneralTransforms: GeneralTransforms = {
|
||||
/**
|
||||
* Transform the editor by an operation.
|
||||
*/
|
||||
|
||||
transform(editor: Editor, op: Operation) {
|
||||
transform(editor: Editor, op: Operation): void {
|
||||
editor.children = createDraft(editor.children)
|
||||
let selection = editor.selection && createDraft(editor.selection)
|
||||
|
||||
@@ -272,7 +276,7 @@ export const GeneralTransforms = {
|
||||
}
|
||||
}
|
||||
|
||||
editor.children = finishDraft(editor.children) as Node[]
|
||||
editor.children = finishDraft(editor.children)
|
||||
|
||||
if (selection) {
|
||||
editor.selection = isDraft(selection)
|
||||
|
@@ -12,7 +12,116 @@ import {
|
||||
Ancestor,
|
||||
} from '..'
|
||||
|
||||
export const NodeTransforms = {
|
||||
export interface NodeTransforms {
|
||||
insertNodes: (
|
||||
editor: Editor,
|
||||
nodes: Node | Node[],
|
||||
options?: {
|
||||
at?: Location
|
||||
match?: (node: Node) => boolean
|
||||
mode?: 'highest' | 'lowest'
|
||||
hanging?: boolean
|
||||
select?: boolean
|
||||
voids?: boolean
|
||||
}
|
||||
) => void
|
||||
liftNodes: (
|
||||
editor: Editor,
|
||||
options?: {
|
||||
at?: Location
|
||||
match?: (node: Node) => boolean
|
||||
mode?: 'all' | 'highest' | 'lowest'
|
||||
voids?: boolean
|
||||
}
|
||||
) => void
|
||||
mergeNodes: (
|
||||
editor: Editor,
|
||||
options?: {
|
||||
at?: Location
|
||||
match?: (node: Node) => boolean
|
||||
mode?: 'highest' | 'lowest'
|
||||
hanging?: boolean
|
||||
voids?: boolean
|
||||
}
|
||||
) => void
|
||||
moveNodes: (
|
||||
editor: Editor,
|
||||
options: {
|
||||
at?: Location
|
||||
match?: (node: Node) => boolean
|
||||
mode?: 'all' | 'highest' | 'lowest'
|
||||
to: Path
|
||||
voids?: boolean
|
||||
}
|
||||
) => void
|
||||
removeNodes: (
|
||||
editor: Editor,
|
||||
options?: {
|
||||
at?: Location
|
||||
match?: (node: Node) => boolean
|
||||
mode?: 'highest' | 'lowest'
|
||||
hanging?: boolean
|
||||
voids?: boolean
|
||||
}
|
||||
) => void
|
||||
setNodes: (
|
||||
editor: Editor,
|
||||
props: Partial<Node>,
|
||||
options?: {
|
||||
at?: Location
|
||||
match?: (node: Node) => boolean
|
||||
mode?: 'all' | 'highest' | 'lowest'
|
||||
hanging?: boolean
|
||||
split?: boolean
|
||||
voids?: boolean
|
||||
}
|
||||
) => void
|
||||
splitNodes: (
|
||||
editor: Editor,
|
||||
options?: {
|
||||
at?: Location
|
||||
match?: (node: Node) => boolean
|
||||
mode?: 'highest' | 'lowest'
|
||||
always?: boolean
|
||||
height?: number
|
||||
voids?: boolean
|
||||
}
|
||||
) => void
|
||||
unsetNodes: (
|
||||
editor: Editor,
|
||||
props: string | string[],
|
||||
options?: {
|
||||
at?: Location
|
||||
match?: (node: Node) => boolean
|
||||
mode?: 'all' | 'highest' | 'lowest'
|
||||
split?: boolean
|
||||
voids?: boolean
|
||||
}
|
||||
) => void
|
||||
unwrapNodes: (
|
||||
editor: Editor,
|
||||
options?: {
|
||||
at?: Location
|
||||
match?: (node: Node) => boolean
|
||||
mode?: 'all' | 'highest' | 'lowest'
|
||||
split?: boolean
|
||||
voids?: boolean
|
||||
}
|
||||
) => void
|
||||
wrapNodes: (
|
||||
editor: Editor,
|
||||
element: Element,
|
||||
options?: {
|
||||
at?: Location
|
||||
match?: (node: Node) => boolean
|
||||
mode?: 'all' | 'highest' | 'lowest'
|
||||
split?: boolean
|
||||
voids?: boolean
|
||||
}
|
||||
) => void
|
||||
}
|
||||
|
||||
export const NodeTransforms: NodeTransforms = {
|
||||
/**
|
||||
* Insert nodes at a specific location in the Editor.
|
||||
*/
|
||||
@@ -28,7 +137,7 @@ export const NodeTransforms = {
|
||||
select?: boolean
|
||||
voids?: boolean
|
||||
} = {}
|
||||
) {
|
||||
): void {
|
||||
Editor.withoutNormalizing(editor, () => {
|
||||
const { hanging = false, voids = false, mode = 'lowest' } = options
|
||||
let { at, match, select } = options
|
||||
@@ -143,7 +252,7 @@ export const NodeTransforms = {
|
||||
mode?: 'all' | 'highest' | 'lowest'
|
||||
voids?: boolean
|
||||
} = {}
|
||||
) {
|
||||
): void {
|
||||
Editor.withoutNormalizing(editor, () => {
|
||||
const { at = editor.selection, mode = 'lowest', voids = false } = options
|
||||
let { match } = options
|
||||
@@ -208,7 +317,7 @@ export const NodeTransforms = {
|
||||
hanging?: boolean
|
||||
voids?: boolean
|
||||
} = {}
|
||||
) {
|
||||
): void {
|
||||
Editor.withoutNormalizing(editor, () => {
|
||||
let { match, at = editor.selection } = options
|
||||
const { hanging = false, voids = false, mode = 'lowest' } = options
|
||||
@@ -346,7 +455,7 @@ export const NodeTransforms = {
|
||||
to: Path
|
||||
voids?: boolean
|
||||
}
|
||||
) {
|
||||
): void {
|
||||
Editor.withoutNormalizing(editor, () => {
|
||||
const {
|
||||
to,
|
||||
@@ -396,7 +505,7 @@ export const NodeTransforms = {
|
||||
hanging?: boolean
|
||||
voids?: boolean
|
||||
} = {}
|
||||
) {
|
||||
): void {
|
||||
Editor.withoutNormalizing(editor, () => {
|
||||
const { hanging = false, voids = false, mode = 'lowest' } = options
|
||||
let { at = editor.selection, match } = options
|
||||
@@ -444,7 +553,7 @@ export const NodeTransforms = {
|
||||
split?: boolean
|
||||
voids?: boolean
|
||||
} = {}
|
||||
) {
|
||||
): void {
|
||||
Editor.withoutNormalizing(editor, () => {
|
||||
let { match, at = editor.selection } = options
|
||||
const {
|
||||
@@ -542,7 +651,7 @@ export const NodeTransforms = {
|
||||
height?: number
|
||||
voids?: boolean
|
||||
} = {}
|
||||
) {
|
||||
): void {
|
||||
Editor.withoutNormalizing(editor, () => {
|
||||
const { mode = 'lowest', voids = false } = options
|
||||
let { match, at = editor.selection, height = 0, always = false } = options
|
||||
@@ -631,7 +740,7 @@ export const NodeTransforms = {
|
||||
|
||||
if (always || !beforeRef || !Editor.isEdge(editor, point, path)) {
|
||||
split = true
|
||||
const { text, children, ...properties } = node
|
||||
const properties = Node.extractProps(node)
|
||||
editor.apply({
|
||||
type: 'split_node',
|
||||
path,
|
||||
@@ -667,7 +776,7 @@ export const NodeTransforms = {
|
||||
split?: boolean
|
||||
voids?: boolean
|
||||
} = {}
|
||||
) {
|
||||
): void {
|
||||
if (!Array.isArray(props)) {
|
||||
props = [props]
|
||||
}
|
||||
@@ -694,8 +803,8 @@ export const NodeTransforms = {
|
||||
mode?: 'all' | 'highest' | 'lowest'
|
||||
split?: boolean
|
||||
voids?: boolean
|
||||
}
|
||||
) {
|
||||
} = {}
|
||||
): void {
|
||||
Editor.withoutNormalizing(editor, () => {
|
||||
const { mode = 'lowest', split = false, voids = false } = options
|
||||
let { at = editor.selection, match } = options
|
||||
@@ -720,7 +829,7 @@ export const NodeTransforms = {
|
||||
|
||||
for (const pathRef of pathRefs) {
|
||||
const path = pathRef.unref()!
|
||||
const [node] = Editor.node(editor, path) as NodeEntry<Ancestor>
|
||||
const [node] = Editor.node(editor, path)
|
||||
let range = Editor.range(editor, path)
|
||||
|
||||
if (split && rangeRef) {
|
||||
@@ -729,7 +838,7 @@ export const NodeTransforms = {
|
||||
|
||||
Transforms.liftNodes(editor, {
|
||||
at: range,
|
||||
match: n => node.children.includes(n),
|
||||
match: n => Element.isAncestor(node) && node.children.includes(n),
|
||||
voids,
|
||||
})
|
||||
}
|
||||
@@ -755,7 +864,7 @@ export const NodeTransforms = {
|
||||
split?: boolean
|
||||
voids?: boolean
|
||||
} = {}
|
||||
) {
|
||||
): void {
|
||||
Editor.withoutNormalizing(editor, () => {
|
||||
const { mode = 'lowest', split = false, voids = false } = options
|
||||
let { match, at = editor.selection } = options
|
||||
@@ -823,7 +932,7 @@ export const NodeTransforms = {
|
||||
|
||||
const range = Editor.range(editor, firstPath, lastPath)
|
||||
const commonNodeEntry = Editor.node(editor, commonPath)
|
||||
const [commonNode] = commonNodeEntry as NodeEntry<Ancestor>
|
||||
const [commonNode] = commonNodeEntry
|
||||
const depth = commonPath.length + 1
|
||||
const wrapperPath = Path.next(lastPath.slice(0, depth))
|
||||
const wrapper = { ...element, children: [] }
|
||||
@@ -831,7 +940,8 @@ export const NodeTransforms = {
|
||||
|
||||
Transforms.moveNodes(editor, {
|
||||
at: range,
|
||||
match: n => commonNode.children.includes(n),
|
||||
match: n =>
|
||||
Element.isAncestor(commonNode) && commonNode.children.includes(n),
|
||||
to: wrapperPath.concat(0),
|
||||
voids,
|
||||
})
|
||||
|
@@ -1,6 +1,34 @@
|
||||
import { Editor, Location, Point, Range, Transforms } from '..'
|
||||
|
||||
export const SelectionTransforms = {
|
||||
export interface SelectionTransforms {
|
||||
collapse: (
|
||||
editor: Editor,
|
||||
options?: {
|
||||
edge?: 'anchor' | 'focus' | 'start' | 'end'
|
||||
}
|
||||
) => void
|
||||
deselect: (editor: Editor) => void
|
||||
move: (
|
||||
editor: Editor,
|
||||
options?: {
|
||||
distance?: number
|
||||
unit?: 'offset' | 'character' | 'word' | 'line'
|
||||
reverse?: boolean
|
||||
edge?: 'anchor' | 'focus' | 'start' | 'end'
|
||||
}
|
||||
) => void
|
||||
select: (editor: Editor, target: Location) => void
|
||||
setPoint: (
|
||||
editor: Editor,
|
||||
props: Partial<Point>,
|
||||
options?: {
|
||||
edge?: 'anchor' | 'focus' | 'start' | 'end'
|
||||
}
|
||||
) => void
|
||||
setSelection: (editor: Editor, props: Partial<Range>) => void
|
||||
}
|
||||
|
||||
export const SelectionTransforms: SelectionTransforms = {
|
||||
/**
|
||||
* Collapse the selection.
|
||||
*/
|
||||
@@ -10,7 +38,7 @@ export const SelectionTransforms = {
|
||||
options: {
|
||||
edge?: 'anchor' | 'focus' | 'start' | 'end'
|
||||
} = {}
|
||||
) {
|
||||
): void {
|
||||
const { edge = 'anchor' } = options
|
||||
const { selection } = editor
|
||||
|
||||
@@ -33,7 +61,7 @@ export const SelectionTransforms = {
|
||||
* Unset the selection.
|
||||
*/
|
||||
|
||||
deselect(editor: Editor) {
|
||||
deselect(editor: Editor): void {
|
||||
const { selection } = editor
|
||||
|
||||
if (selection) {
|
||||
@@ -57,7 +85,7 @@ export const SelectionTransforms = {
|
||||
reverse?: boolean
|
||||
edge?: 'anchor' | 'focus' | 'start' | 'end'
|
||||
} = {}
|
||||
) {
|
||||
): void {
|
||||
const { selection } = editor
|
||||
const { distance = 1, unit = 'character', reverse = false } = options
|
||||
let { edge = null } = options
|
||||
@@ -105,7 +133,7 @@ export const SelectionTransforms = {
|
||||
* Set the selection to a new value.
|
||||
*/
|
||||
|
||||
select(editor: Editor, target: Location) {
|
||||
select(editor: Editor, target: Location): void {
|
||||
const { selection } = editor
|
||||
target = Editor.range(editor, target)
|
||||
|
||||
@@ -138,8 +166,8 @@ export const SelectionTransforms = {
|
||||
props: Partial<Point>,
|
||||
options: {
|
||||
edge?: 'anchor' | 'focus' | 'start' | 'end'
|
||||
}
|
||||
) {
|
||||
} = {}
|
||||
): void {
|
||||
const { selection } = editor
|
||||
let { edge = 'both' } = options
|
||||
|
||||
@@ -167,7 +195,7 @@ export const SelectionTransforms = {
|
||||
* Set new properties on the selection.
|
||||
*/
|
||||
|
||||
setSelection(editor: Editor, props: Partial<Range>) {
|
||||
setSelection(editor: Editor, props: Partial<Range>): void {
|
||||
const { selection } = editor
|
||||
const oldProps: Partial<Range> | null = {}
|
||||
const newProps: Partial<Range> = {}
|
||||
|
@@ -11,7 +11,38 @@ import {
|
||||
Transforms,
|
||||
} from '..'
|
||||
|
||||
export const TextTransforms = {
|
||||
export interface TextTransforms {
|
||||
delete: (
|
||||
editor: Editor,
|
||||
options?: {
|
||||
at?: Location
|
||||
distance?: number
|
||||
unit?: 'character' | 'word' | 'line' | 'block'
|
||||
reverse?: boolean
|
||||
hanging?: boolean
|
||||
voids?: boolean
|
||||
}
|
||||
) => void
|
||||
insertFragment: (
|
||||
editor: Editor,
|
||||
fragment: Node[],
|
||||
options?: {
|
||||
at?: Location
|
||||
hanging?: boolean
|
||||
voids?: boolean
|
||||
}
|
||||
) => void
|
||||
insertText: (
|
||||
editor: Editor,
|
||||
text: string,
|
||||
options?: {
|
||||
at?: Location
|
||||
voids?: boolean
|
||||
}
|
||||
) => void
|
||||
}
|
||||
|
||||
export const TextTransforms: TextTransforms = {
|
||||
/**
|
||||
* Delete content in the editor.
|
||||
*/
|
||||
@@ -26,7 +57,7 @@ export const TextTransforms = {
|
||||
hanging?: boolean
|
||||
voids?: boolean
|
||||
} = {}
|
||||
) {
|
||||
): void {
|
||||
Editor.withoutNormalizing(editor, () => {
|
||||
const {
|
||||
reverse = false,
|
||||
@@ -196,7 +227,7 @@ export const TextTransforms = {
|
||||
hanging?: boolean
|
||||
voids?: boolean
|
||||
} = {}
|
||||
) {
|
||||
): void {
|
||||
Editor.withoutNormalizing(editor, () => {
|
||||
const { hanging = false, voids = false } = options
|
||||
let { at = editor.selection } = options
|
||||
@@ -410,7 +441,7 @@ export const TextTransforms = {
|
||||
at?: Location
|
||||
voids?: boolean
|
||||
} = {}
|
||||
) {
|
||||
): void {
|
||||
Editor.withoutNormalizing(editor, () => {
|
||||
const { voids = false } = options
|
||||
let { at = editor.selection } = options
|
||||
|
@@ -0,0 +1,11 @@
|
||||
import { Text } from 'slate'
|
||||
import { isBoldText } from './type-guards'
|
||||
|
||||
export const input: Text = {
|
||||
placeholder: 'heading',
|
||||
text: 'mytext',
|
||||
}
|
||||
|
||||
export const test = isBoldText
|
||||
|
||||
export const output = false
|
12
packages/slate/test/interfaces/CustomTypes/boldText-true.tsx
Normal file
12
packages/slate/test/interfaces/CustomTypes/boldText-true.tsx
Normal file
@@ -0,0 +1,12 @@
|
||||
// show that regular methods that are imported work as expected
|
||||
import { Text } from 'slate'
|
||||
import { isBoldText } from './type-guards'
|
||||
|
||||
export const input: Text = {
|
||||
bold: true,
|
||||
text: 'mytext',
|
||||
}
|
||||
|
||||
export const test = isBoldText
|
||||
|
||||
export const output = true
|
30
packages/slate/test/interfaces/CustomTypes/custom-types.ts
Normal file
30
packages/slate/test/interfaces/CustomTypes/custom-types.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import { Descendant, Element, Text, CustomTypes } from 'slate'
|
||||
|
||||
export interface HeadingElement {
|
||||
type: 'heading'
|
||||
level: number
|
||||
children: Descendant[]
|
||||
}
|
||||
|
||||
export interface ListItemElement {
|
||||
type: 'list-item'
|
||||
depth: number
|
||||
children: Descendant[]
|
||||
}
|
||||
|
||||
export interface CustomText {
|
||||
placeholder: string
|
||||
text: string
|
||||
}
|
||||
|
||||
export interface BoldCustomText {
|
||||
bold: boolean
|
||||
text: string
|
||||
}
|
||||
|
||||
declare module 'slate' {
|
||||
interface CustomTypes {
|
||||
Element: HeadingElement | ListItemElement
|
||||
Text: CustomText | BoldCustomText
|
||||
}
|
||||
}
|
@@ -0,0 +1,11 @@
|
||||
import { Text } from 'slate'
|
||||
import { isCustomText } from './type-guards'
|
||||
|
||||
export const input: Text = {
|
||||
bold: true,
|
||||
text: 'mytext',
|
||||
}
|
||||
|
||||
export const test = isCustomText
|
||||
|
||||
export const output = false
|
@@ -0,0 +1,11 @@
|
||||
import { Text } from 'slate'
|
||||
import { isCustomText } from './type-guards'
|
||||
|
||||
export const input: Text = {
|
||||
placeholder: 'mystring',
|
||||
text: 'mytext',
|
||||
}
|
||||
|
||||
export const test = isCustomText
|
||||
|
||||
export const output = true
|
@@ -0,0 +1,12 @@
|
||||
import { Element } from 'slate'
|
||||
import { isHeadingElement } from './type-guards'
|
||||
|
||||
export const input: Element = {
|
||||
type: 'list-item',
|
||||
depth: 5,
|
||||
children: [],
|
||||
}
|
||||
|
||||
export const test = isHeadingElement
|
||||
|
||||
export const output = false
|
@@ -0,0 +1,12 @@
|
||||
import { Element } from 'slate'
|
||||
import { isHeadingElement } from './type-guards'
|
||||
|
||||
export const input: Element = {
|
||||
type: 'heading',
|
||||
level: 5,
|
||||
children: [],
|
||||
}
|
||||
|
||||
export const test = isHeadingElement
|
||||
|
||||
export const output = true
|
11
packages/slate/test/interfaces/CustomTypes/type-guards.ts
Normal file
11
packages/slate/test/interfaces/CustomTypes/type-guards.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { Element, Text } from 'slate'
|
||||
import { BoldCustomText, CustomText, HeadingElement } from './custom-types'
|
||||
|
||||
export const isBoldText = (text: Text): text is BoldCustomText =>
|
||||
!!(text as BoldCustomText).bold
|
||||
|
||||
export const isCustomText = (text: Text): text is CustomText =>
|
||||
!!(text as CustomText).placeholder
|
||||
|
||||
export const isHeadingElement = (element: Element): element is HeadingElement =>
|
||||
element.type === 'heading'
|
@@ -4,4 +4,5 @@ export const input = true
|
||||
export const test = value => {
|
||||
return Element.isElement(value)
|
||||
}
|
||||
|
||||
export const output = false
|
||||
|
6
packages/slate/test/jsx.d.ts
vendored
Normal file
6
packages/slate/test/jsx.d.ts
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
// This allows tests to include Slate Nodes written in JSX without TypeScript complaining.
|
||||
declare namespace jsx.JSX {
|
||||
interface IntrinsicElements {
|
||||
[elemName: string]: any // eslint-disable-line
|
||||
}
|
||||
}
|
@@ -7,12 +7,67 @@ import {
|
||||
useReadOnly,
|
||||
ReactEditor,
|
||||
} from 'slate-react'
|
||||
import { Node, Editor, Transforms, Range, Point, createEditor } from 'slate'
|
||||
import {
|
||||
Node,
|
||||
Editor,
|
||||
Transforms,
|
||||
Range,
|
||||
Point,
|
||||
createEditor,
|
||||
Descendant,
|
||||
Element as SlateElement,
|
||||
} from 'slate'
|
||||
import { css } from 'emotion'
|
||||
import { withHistory } from 'slate-history'
|
||||
|
||||
const initialValue: Descendant[] = [
|
||||
{
|
||||
type: 'paragraph',
|
||||
children: [
|
||||
{
|
||||
text:
|
||||
'With Slate you can build complex block types that have their own embedded content and behaviors, like rendering checkboxes inside check list items!',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'check-list-item',
|
||||
checked: true,
|
||||
children: [{ text: 'Slide to the left.' }],
|
||||
},
|
||||
{
|
||||
type: 'check-list-item',
|
||||
checked: true,
|
||||
children: [{ text: 'Slide to the right.' }],
|
||||
},
|
||||
{
|
||||
type: 'check-list-item',
|
||||
checked: false,
|
||||
children: [{ text: 'Criss-cross.' }],
|
||||
},
|
||||
{
|
||||
type: 'check-list-item',
|
||||
checked: true,
|
||||
children: [{ text: 'Criss-cross!' }],
|
||||
},
|
||||
{
|
||||
type: 'check-list-item',
|
||||
checked: false,
|
||||
children: [{ text: 'Cha cha real smooth…' }],
|
||||
},
|
||||
{
|
||||
type: 'check-list-item',
|
||||
checked: false,
|
||||
children: [{ text: "Let's go to work!" }],
|
||||
},
|
||||
{
|
||||
type: 'paragraph',
|
||||
children: [{ text: 'Try it out for yourself!' }],
|
||||
},
|
||||
]
|
||||
|
||||
const CheckListsExample = () => {
|
||||
const [value, setValue] = useState<Node[]>(initialValue)
|
||||
const [value, setValue] = useState<Descendant[]>(initialValue)
|
||||
const renderElement = useCallback(props => <Element {...props} />, [])
|
||||
const editor = useMemo(
|
||||
() => withChecklists(withHistory(withReact(createEditor()))),
|
||||
@@ -39,7 +94,10 @@ const withChecklists = editor => {
|
||||
|
||||
if (selection && Range.isCollapsed(selection)) {
|
||||
const [match] = Editor.nodes(editor, {
|
||||
match: n => n.type === 'check-list-item',
|
||||
match: n =>
|
||||
!Editor.isEditor(n) &&
|
||||
SlateElement.isElement(n) &&
|
||||
n.type === 'check-list-item',
|
||||
})
|
||||
|
||||
if (match) {
|
||||
@@ -47,11 +105,15 @@ const withChecklists = editor => {
|
||||
const start = Editor.start(editor, path)
|
||||
|
||||
if (Point.equals(selection.anchor, start)) {
|
||||
Transforms.setNodes(
|
||||
editor,
|
||||
{ type: 'paragraph' },
|
||||
{ match: n => n.type === 'check-list-item' }
|
||||
)
|
||||
const newProperties: Partial<SlateElement> = {
|
||||
type: 'paragraph',
|
||||
}
|
||||
Transforms.setNodes(editor, newProperties, {
|
||||
match: n =>
|
||||
!Editor.isEditor(n) &&
|
||||
SlateElement.isElement(n) &&
|
||||
n.type === 'check-list-item',
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -102,11 +164,10 @@ const CheckListItemElement = ({ attributes, children, element }) => {
|
||||
checked={checked}
|
||||
onChange={event => {
|
||||
const path = ReactEditor.findPath(editor, element)
|
||||
Transforms.setNodes(
|
||||
editor,
|
||||
{ checked: event.target.checked },
|
||||
{ at: path }
|
||||
)
|
||||
const newProperties: Partial<SlateElement> = {
|
||||
checked: event.target.checked,
|
||||
}
|
||||
Transforms.setNodes(editor, newProperties, { at: path })
|
||||
}}
|
||||
/>
|
||||
</span>
|
||||
@@ -129,48 +190,4 @@ const CheckListItemElement = ({ attributes, children, element }) => {
|
||||
)
|
||||
}
|
||||
|
||||
const initialValue = [
|
||||
{
|
||||
children: [
|
||||
{
|
||||
text:
|
||||
'With Slate you can build complex block types that have their own embedded content and behaviors, like rendering checkboxes inside check list items!',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'check-list-item',
|
||||
checked: true,
|
||||
children: [{ text: 'Slide to the left.' }],
|
||||
},
|
||||
{
|
||||
type: 'check-list-item',
|
||||
checked: true,
|
||||
children: [{ text: 'Slide to the right.' }],
|
||||
},
|
||||
{
|
||||
type: 'check-list-item',
|
||||
checked: false,
|
||||
children: [{ text: 'Criss-cross.' }],
|
||||
},
|
||||
{
|
||||
type: 'check-list-item',
|
||||
checked: true,
|
||||
children: [{ text: 'Criss-cross!' }],
|
||||
},
|
||||
{
|
||||
type: 'check-list-item',
|
||||
checked: false,
|
||||
children: [{ text: 'Cha cha real smooth…' }],
|
||||
},
|
||||
{
|
||||
type: 'check-list-item',
|
||||
checked: false,
|
||||
children: [{ text: "Let's go to work!" }],
|
||||
},
|
||||
{
|
||||
children: [{ text: 'Try it out for yourself!' }],
|
||||
},
|
||||
]
|
||||
|
||||
export default CheckListsExample
|
||||
|
@@ -5,7 +5,14 @@ import 'prismjs/components/prism-sql'
|
||||
import 'prismjs/components/prism-java'
|
||||
import React, { useState, useCallback, useMemo } from 'react'
|
||||
import { Slate, Editable, withReact } from 'slate-react'
|
||||
import { Text, createEditor, Node } from 'slate'
|
||||
import {
|
||||
Text,
|
||||
createEditor,
|
||||
Node,
|
||||
Element as SlateElement,
|
||||
BaseEditor,
|
||||
Descendant,
|
||||
} from 'slate'
|
||||
import { withHistory } from 'slate-history'
|
||||
import { css } from 'emotion'
|
||||
|
||||
|
18
site/examples/custom-types.d.ts
vendored
Normal file
18
site/examples/custom-types.d.ts
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
import { Text, createEditor, Node, Element, Editor, Descendant } from 'slate'
|
||||
|
||||
declare module 'slate' {
|
||||
interface CustomTypes {
|
||||
Element: CustomElement
|
||||
|
||||
Node: CustomNode
|
||||
}
|
||||
}
|
||||
|
||||
interface CustomElement {
|
||||
type?: string
|
||||
checked?: boolean
|
||||
url?: string
|
||||
children: Descendant[]
|
||||
}
|
||||
|
||||
type CustomNode = Editor | CustomElement | Text
|
@@ -1,5 +1,5 @@
|
||||
import React, { useState, useMemo } from 'react'
|
||||
import { Transforms, createEditor, Node } from 'slate'
|
||||
import { Transforms, createEditor, Node, Element as SlateElement } from 'slate'
|
||||
import {
|
||||
Slate,
|
||||
Editable,
|
||||
@@ -67,7 +67,10 @@ const VideoElement = ({ attributes, children, element }) => {
|
||||
url={url}
|
||||
onChange={val => {
|
||||
const path = ReactEditor.findPath(editor, element)
|
||||
Transforms.setNodes(editor, { url: val }, { at: path })
|
||||
const newProperties: Partial<SlateElement> = {
|
||||
url: val,
|
||||
}
|
||||
Transforms.setNodes(editor, newProperties, { at: path })
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import React, { useState, useCallback, useMemo } from 'react'
|
||||
import { Slate, Editable, withReact } from 'slate-react'
|
||||
import { Transforms, createEditor, Node } from 'slate'
|
||||
import { Transforms, createEditor, Node, Element as SlateElement } from 'slate'
|
||||
import { withHistory } from 'slate-history'
|
||||
|
||||
const withLayout = editor => {
|
||||
@@ -21,8 +21,9 @@ const withLayout = editor => {
|
||||
for (const [child, childPath] of Node.children(editor, path)) {
|
||||
const type = childPath[0] === 0 ? 'title' : 'paragraph'
|
||||
|
||||
if (child.type !== type) {
|
||||
Transforms.setNodes(editor, { type }, { at: childPath })
|
||||
if (SlateElement.isElement(child) && child.type !== type) {
|
||||
const newProperties: Partial<SlateElement> = { type }
|
||||
Transforms.setNodes(editor, newProperties, { at: childPath })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,7 +1,14 @@
|
||||
import React, { useState, useMemo } from 'react'
|
||||
import isUrl from 'is-url'
|
||||
import { Slate, Editable, withReact, useSlate } from 'slate-react'
|
||||
import { Node, Transforms, Editor, Range, createEditor } from 'slate'
|
||||
import {
|
||||
Node,
|
||||
Transforms,
|
||||
Editor,
|
||||
Range,
|
||||
createEditor,
|
||||
Element as SlateElement,
|
||||
} from 'slate'
|
||||
import { withHistory } from 'slate-history'
|
||||
|
||||
import { Button, Icon, Toolbar } from '../components'
|
||||
@@ -61,12 +68,18 @@ const insertLink = (editor, url) => {
|
||||
}
|
||||
|
||||
const isLinkActive = editor => {
|
||||
const [link] = Editor.nodes(editor, { match: n => n.type === 'link' })
|
||||
const [link] = Editor.nodes(editor, {
|
||||
match: n =>
|
||||
!Editor.isEditor(n) && SlateElement.isElement(n) && n.type === 'link',
|
||||
})
|
||||
return !!link
|
||||
}
|
||||
|
||||
const unwrapLink = editor => {
|
||||
Transforms.unwrapNodes(editor, { match: n => n.type === 'link' })
|
||||
Transforms.unwrapNodes(editor, {
|
||||
match: n =>
|
||||
!Editor.isEditor(n) && SlateElement.isElement(n) && n.type === 'link',
|
||||
})
|
||||
}
|
||||
|
||||
const wrapLink = (editor, url) => {
|
||||
|
@@ -1,6 +1,14 @@
|
||||
import React, { useState, useCallback, useMemo } from 'react'
|
||||
import { Slate, Editable, withReact } from 'slate-react'
|
||||
import { Node, Editor, Transforms, Range, Point, createEditor } from 'slate'
|
||||
import {
|
||||
Node,
|
||||
Editor,
|
||||
Transforms,
|
||||
Range,
|
||||
Point,
|
||||
createEditor,
|
||||
Element as SlateElement,
|
||||
} from 'slate'
|
||||
import { withHistory } from 'slate-history'
|
||||
|
||||
const SHORTCUTS = {
|
||||
@@ -55,16 +63,20 @@ const withShortcuts = editor => {
|
||||
if (type) {
|
||||
Transforms.select(editor, range)
|
||||
Transforms.delete(editor)
|
||||
Transforms.setNodes(
|
||||
editor,
|
||||
{ type },
|
||||
{ match: n => Editor.isBlock(editor, n) }
|
||||
)
|
||||
const newProperties: Partial<SlateElement> = {
|
||||
type,
|
||||
}
|
||||
Transforms.setNodes(editor, newProperties, {
|
||||
match: n => Editor.isBlock(editor, n),
|
||||
})
|
||||
|
||||
if (type === 'list-item') {
|
||||
const list = { type: 'bulleted-list', children: [] }
|
||||
Transforms.wrapNodes(editor, list, {
|
||||
match: n => n.type === 'list-item',
|
||||
match: n =>
|
||||
!Editor.isEditor(n) &&
|
||||
SlateElement.isElement(n) &&
|
||||
n.type === 'list-item',
|
||||
})
|
||||
}
|
||||
|
||||
@@ -88,14 +100,22 @@ const withShortcuts = editor => {
|
||||
const start = Editor.start(editor, path)
|
||||
|
||||
if (
|
||||
!Editor.isEditor(block) &&
|
||||
SlateElement.isElement(block) &&
|
||||
block.type !== 'paragraph' &&
|
||||
Point.equals(selection.anchor, start)
|
||||
) {
|
||||
Transforms.setNodes(editor, { type: 'paragraph' })
|
||||
const newProperties: Partial<SlateElement> = {
|
||||
type: 'paragraph',
|
||||
}
|
||||
Transforms.setNodes(editor, newProperties)
|
||||
|
||||
if (block.type === 'list-item') {
|
||||
Transforms.unwrapNodes(editor, {
|
||||
match: n => n.type === 'bulleted-list',
|
||||
match: n =>
|
||||
!Editor.isEditor(n) &&
|
||||
SlateElement.isElement(n) &&
|
||||
n.type === 'bulleted-list',
|
||||
split: true,
|
||||
})
|
||||
}
|
||||
|
@@ -1,7 +1,13 @@
|
||||
import React, { useCallback, useMemo, useState } from 'react'
|
||||
import isHotkey from 'is-hotkey'
|
||||
import { Editable, withReact, useSlate, Slate } from 'slate-react'
|
||||
import { Editor, Transforms, createEditor, Node } from 'slate'
|
||||
import {
|
||||
Editor,
|
||||
Transforms,
|
||||
createEditor,
|
||||
Node,
|
||||
Element as SlateElement,
|
||||
} from 'slate'
|
||||
import { withHistory } from 'slate-history'
|
||||
|
||||
import { Button, Icon, Toolbar } from '../components'
|
||||
@@ -59,13 +65,16 @@ const toggleBlock = (editor, format) => {
|
||||
const isList = LIST_TYPES.includes(format)
|
||||
|
||||
Transforms.unwrapNodes(editor, {
|
||||
match: n => LIST_TYPES.includes(n.type as string),
|
||||
match: n =>
|
||||
LIST_TYPES.includes(
|
||||
!Editor.isEditor(n) && SlateElement.isElement(n) && n.type
|
||||
),
|
||||
split: true,
|
||||
})
|
||||
|
||||
Transforms.setNodes(editor, {
|
||||
const newProperties: Partial<SlateElement> = {
|
||||
type: isActive ? 'paragraph' : isList ? 'list-item' : format,
|
||||
})
|
||||
}
|
||||
Transforms.setNodes(editor, newProperties)
|
||||
|
||||
if (!isActive && isList) {
|
||||
const block = { type: format, children: [] }
|
||||
@@ -85,7 +94,8 @@ const toggleMark = (editor, format) => {
|
||||
|
||||
const isBlockActive = (editor, format) => {
|
||||
const [match] = Editor.nodes(editor, {
|
||||
match: n => n.type === format,
|
||||
match: n =>
|
||||
!Editor.isEditor(n) && SlateElement.isElement(n) && n.type === format,
|
||||
})
|
||||
|
||||
return !!match
|
||||
|
@@ -1,6 +1,13 @@
|
||||
import React, { useState, useCallback, useMemo } from 'react'
|
||||
import { Slate, Editable, withReact } from 'slate-react'
|
||||
import { Editor, Range, Point, Node, createEditor } from 'slate'
|
||||
import {
|
||||
Editor,
|
||||
Range,
|
||||
Point,
|
||||
Node,
|
||||
createEditor,
|
||||
Element as SlateElement,
|
||||
} from 'slate'
|
||||
import { withHistory } from 'slate-history'
|
||||
|
||||
const TablesExample = () => {
|
||||
@@ -26,7 +33,10 @@ const withTables = editor => {
|
||||
|
||||
if (selection && Range.isCollapsed(selection)) {
|
||||
const [cell] = Editor.nodes(editor, {
|
||||
match: n => n.type === 'table-cell',
|
||||
match: n =>
|
||||
!Editor.isEditor(n) &&
|
||||
SlateElement.isElement(n) &&
|
||||
n.type === 'table-cell',
|
||||
})
|
||||
|
||||
if (cell) {
|
||||
@@ -47,7 +57,10 @@ const withTables = editor => {
|
||||
|
||||
if (selection && Range.isCollapsed(selection)) {
|
||||
const [cell] = Editor.nodes(editor, {
|
||||
match: n => n.type === 'table-cell',
|
||||
match: n =>
|
||||
!Editor.isEditor(n) &&
|
||||
SlateElement.isElement(n) &&
|
||||
n.type === 'table-cell',
|
||||
})
|
||||
|
||||
if (cell) {
|
||||
@@ -67,7 +80,12 @@ const withTables = editor => {
|
||||
const { selection } = editor
|
||||
|
||||
if (selection) {
|
||||
const [table] = Editor.nodes(editor, { match: n => n.type === 'table' })
|
||||
const [table] = Editor.nodes(editor, {
|
||||
match: n =>
|
||||
!Editor.isEditor(n) &&
|
||||
SlateElement.isElement(n) &&
|
||||
n.type === 'table',
|
||||
})
|
||||
|
||||
if (table) {
|
||||
return
|
||||
|
@@ -25,7 +25,11 @@ export const fixtures = (...args) => {
|
||||
}
|
||||
if (
|
||||
stat.isFile() &&
|
||||
(file.endsWith('.js') || file.endsWith('.ts') || file.endsWith('.tsx')) &&
|
||||
(file.endsWith('.js') ||
|
||||
file.endsWith('.tsx') ||
|
||||
file.endsWith('.ts')) &&
|
||||
!file.endsWith('custom-types.ts') &&
|
||||
!file.endsWith('type-guards.ts') &&
|
||||
!file.startsWith('.') &&
|
||||
// Ignoring `index.js` files allows us to use the fixtures directly
|
||||
// from the top-level directory itself, instead of only children.
|
||||
@@ -34,7 +38,7 @@ export const fixtures = (...args) => {
|
||||
const name = basename(file, extname(file))
|
||||
|
||||
// This needs to be a non-arrow function to use `this.skip()`.
|
||||
it(`${name} `, function () {
|
||||
it(`${name} `, function() {
|
||||
const module = require(p)
|
||||
|
||||
if (module.skip) {
|
||||
|
Reference in New Issue
Block a user