mirror of
https://github.com/ianstormtaylor/slate.git
synced 2025-08-28 17:39:57 +02:00
remove marks, in favor of text properties (#3235)
* remove marks, in favor of text properties * fix lint * fix more examples * update docs
This commit is contained in:
@@ -6,11 +6,7 @@ import TextComponent from './text'
|
||||
import { ReactEditor } from '..'
|
||||
import { useEditor } from '../hooks/use-editor'
|
||||
import { NODE_TO_INDEX, NODE_TO_PARENT } from '../utils/weak-maps'
|
||||
import {
|
||||
RenderDecorationProps,
|
||||
RenderElementProps,
|
||||
RenderMarkProps,
|
||||
} from './editable'
|
||||
import { RenderElementProps, RenderLeafProps } from './editable'
|
||||
|
||||
/**
|
||||
* Children.
|
||||
@@ -20,18 +16,16 @@ const Children = (props: {
|
||||
decorate: (entry: NodeEntry) => Range[]
|
||||
decorations: Range[]
|
||||
node: Ancestor
|
||||
renderDecoration?: (props: RenderDecorationProps) => JSX.Element
|
||||
renderElement?: (props: RenderElementProps) => JSX.Element
|
||||
renderMark?: (props: RenderMarkProps) => JSX.Element
|
||||
renderLeaf?: (props: RenderLeafProps) => JSX.Element
|
||||
selection: Range | null
|
||||
}) => {
|
||||
const {
|
||||
decorate,
|
||||
decorations,
|
||||
node,
|
||||
renderDecoration,
|
||||
renderElement,
|
||||
renderMark,
|
||||
renderLeaf,
|
||||
selection,
|
||||
} = props
|
||||
const editor = useEditor()
|
||||
@@ -65,9 +59,8 @@ const Children = (props: {
|
||||
decorations={ds}
|
||||
element={n}
|
||||
key={key.id}
|
||||
renderDecoration={renderDecoration}
|
||||
renderElement={renderElement}
|
||||
renderMark={renderMark}
|
||||
renderLeaf={renderLeaf}
|
||||
selection={sel}
|
||||
/>
|
||||
)
|
||||
@@ -78,8 +71,7 @@ const Children = (props: {
|
||||
key={key.id}
|
||||
isLast={isLeafBlock && i === node.children.length}
|
||||
parent={node}
|
||||
renderDecoration={renderDecoration}
|
||||
renderMark={renderMark}
|
||||
renderLeaf={renderLeaf}
|
||||
text={n}
|
||||
/>
|
||||
)
|
||||
|
@@ -5,7 +5,7 @@ import React, {
|
||||
useMemo,
|
||||
useCallback,
|
||||
} from 'react'
|
||||
import { Editor, Element, NodeEntry, Node, Range, Text, Mark } from 'slate'
|
||||
import { Editor, Element, NodeEntry, Node, Range, Text } from 'slate'
|
||||
import debounce from 'debounce'
|
||||
import scrollIntoView from 'scroll-into-view-if-needed'
|
||||
|
||||
@@ -15,7 +15,6 @@ import { IS_FIREFOX, IS_SAFARI } from '../utils/environment'
|
||||
import { ReactEditor } from '..'
|
||||
import { ReadOnlyContext } from '../hooks/use-read-only'
|
||||
import { useSlate } from '../hooks/use-slate'
|
||||
import { Leaf } from '../utils/leaf'
|
||||
import {
|
||||
DOMElement,
|
||||
DOMNode,
|
||||
@@ -34,20 +33,6 @@ import {
|
||||
PLACEHOLDER_SYMBOL,
|
||||
} from '../utils/weak-maps'
|
||||
|
||||
/**
|
||||
* `RenderDecorationProps` are passed to the `renderDecoration` handler.
|
||||
*/
|
||||
|
||||
export interface RenderDecorationProps {
|
||||
children: any
|
||||
decoration: Range
|
||||
leaf: Leaf
|
||||
text: Text
|
||||
attributes: {
|
||||
'data-slate-decoration': true
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* `RenderElementProps` are passed to the `renderElement` handler.
|
||||
*/
|
||||
@@ -56,8 +41,8 @@ export interface RenderElementProps {
|
||||
children: any
|
||||
element: Element
|
||||
attributes: {
|
||||
'data-slate-inline'?: true
|
||||
'data-slate-node': 'element'
|
||||
'data-slate-inline'?: true
|
||||
'data-slate-void'?: true
|
||||
dir?: 'rtl'
|
||||
ref: any
|
||||
@@ -65,16 +50,15 @@ export interface RenderElementProps {
|
||||
}
|
||||
|
||||
/**
|
||||
* `RenderMarkProps` are passed to the `renderMark` handler.
|
||||
* `RenderLeafProps` are passed to the `renderLeaf` handler.
|
||||
*/
|
||||
|
||||
export interface RenderMarkProps {
|
||||
export interface RenderLeafProps {
|
||||
children: any
|
||||
mark: Mark
|
||||
leaf: Leaf
|
||||
leaf: Text
|
||||
text: Text
|
||||
attributes: {
|
||||
'data-slate-mark': true
|
||||
'data-slate-leaf': true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,21 +74,19 @@ export const Editable = (
|
||||
readOnly?: boolean
|
||||
role?: string
|
||||
style?: React.CSSProperties
|
||||
renderDecoration?: (props: RenderDecorationProps) => JSX.Element
|
||||
renderElement?: (props: RenderElementProps) => JSX.Element
|
||||
renderMark?: (props: RenderMarkProps) => JSX.Element
|
||||
renderLeaf?: (props: RenderLeafProps) => JSX.Element
|
||||
} & React.TextareaHTMLAttributes<HTMLDivElement>
|
||||
) => {
|
||||
const {
|
||||
autoFocus,
|
||||
decorate = defaultDecorate,
|
||||
onDOMBeforeInput: propsOnDOMBeforeInput,
|
||||
placeholder,
|
||||
readOnly = false,
|
||||
renderDecoration,
|
||||
renderElement,
|
||||
renderMark,
|
||||
autoFocus,
|
||||
renderLeaf,
|
||||
style = {},
|
||||
onDOMBeforeInput: propsOnDOMBeforeInput,
|
||||
...attributes
|
||||
} = props
|
||||
const editor = useSlate()
|
||||
@@ -906,9 +888,8 @@ export const Editable = (
|
||||
decorate={decorate}
|
||||
decorations={decorations}
|
||||
node={editor}
|
||||
renderDecoration={renderDecoration}
|
||||
renderElement={renderElement}
|
||||
renderMark={renderMark}
|
||||
renderLeaf={renderLeaf}
|
||||
selection={editor.selection}
|
||||
/>
|
||||
</div>
|
||||
|
@@ -13,12 +13,7 @@ import {
|
||||
NODE_TO_INDEX,
|
||||
KEY_TO_ELEMENT,
|
||||
} from '../utils/weak-maps'
|
||||
import {
|
||||
RenderDecorationProps,
|
||||
RenderElementProps,
|
||||
RenderMarkProps,
|
||||
} from './editable'
|
||||
import { isRangeListEqual } from '../utils/leaf'
|
||||
import { RenderElementProps, RenderLeafProps } from './editable'
|
||||
|
||||
/**
|
||||
* Element.
|
||||
@@ -28,18 +23,16 @@ const Element = (props: {
|
||||
decorate: (entry: NodeEntry) => Range[]
|
||||
decorations: Range[]
|
||||
element: SlateElement
|
||||
renderDecoration?: (props: RenderDecorationProps) => JSX.Element
|
||||
renderElement?: (props: RenderElementProps) => JSX.Element
|
||||
renderMark?: (props: RenderMarkProps) => JSX.Element
|
||||
renderLeaf?: (props: RenderLeafProps) => JSX.Element
|
||||
selection: Range | null
|
||||
}) => {
|
||||
const {
|
||||
decorate,
|
||||
decorations,
|
||||
element,
|
||||
renderDecoration,
|
||||
renderElement = (p: RenderElementProps) => <DefaultElement {...p} />,
|
||||
renderMark,
|
||||
renderLeaf,
|
||||
selection,
|
||||
} = props
|
||||
const ref = useRef<HTMLElement>(null)
|
||||
@@ -53,9 +46,8 @@ const Element = (props: {
|
||||
decorate={decorate}
|
||||
decorations={decorations}
|
||||
node={element}
|
||||
renderDecoration={renderDecoration}
|
||||
renderElement={renderElement}
|
||||
renderMark={renderMark}
|
||||
renderLeaf={renderLeaf}
|
||||
selection={selection}
|
||||
/>
|
||||
)
|
||||
@@ -141,9 +133,8 @@ const MemoizedElement = React.memo(Element, (prev, next) => {
|
||||
return (
|
||||
prev.decorate === next.decorate &&
|
||||
prev.element === next.element &&
|
||||
prev.renderDecoration === next.renderDecoration &&
|
||||
prev.renderElement === next.renderElement &&
|
||||
prev.renderMark === next.renderMark &&
|
||||
prev.renderLeaf === next.renderLeaf &&
|
||||
isRangeListEqual(prev.decorations, next.decorations) &&
|
||||
(prev.selection === next.selection ||
|
||||
(!!prev.selection &&
|
||||
@@ -167,4 +158,29 @@ export const DefaultElement = (props: RenderElementProps) => {
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a list of ranges is equal to another.
|
||||
*
|
||||
* PERF: this requires the two lists to also have the ranges inside them in the
|
||||
* same order, but this is an okay constraint for us since decorations are
|
||||
* kept in order, and the odd case where they aren't is okay to re-render for.
|
||||
*/
|
||||
|
||||
const isRangeListEqual = (list: Range[], another: Range[]): boolean => {
|
||||
if (list.length !== another.length) {
|
||||
return false
|
||||
}
|
||||
|
||||
for (let i = 0; i < list.length; i++) {
|
||||
const range = list[i]
|
||||
const other = another[i]
|
||||
|
||||
if (!Range.equals(range, other)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
export default MemoizedElement
|
||||
|
@@ -2,9 +2,8 @@ import React from 'react'
|
||||
import { Text, Element } from 'slate'
|
||||
|
||||
import String from './string'
|
||||
import { Leaf as SlateLeaf } from '../utils/leaf'
|
||||
import { PLACEHOLDER_SYMBOL } from '../utils/weak-maps'
|
||||
import { RenderDecorationProps, RenderMarkProps } from './editable'
|
||||
import { RenderLeafProps } from './editable'
|
||||
|
||||
/**
|
||||
* Individual leaves in a text node with unique formatting.
|
||||
@@ -12,10 +11,9 @@ import { RenderDecorationProps, RenderMarkProps } from './editable'
|
||||
|
||||
const Leaf = (props: {
|
||||
isLast: boolean
|
||||
leaf: SlateLeaf
|
||||
leaf: Text
|
||||
parent: Element
|
||||
renderDecoration?: (props: RenderDecorationProps) => JSX.Element
|
||||
renderMark?: (props: RenderMarkProps) => JSX.Element
|
||||
renderLeaf?: (props: RenderLeafProps) => JSX.Element
|
||||
text: Text
|
||||
}) => {
|
||||
const {
|
||||
@@ -23,117 +21,64 @@ const Leaf = (props: {
|
||||
isLast,
|
||||
text,
|
||||
parent,
|
||||
renderDecoration = (props: RenderDecorationProps) => (
|
||||
<DefaultDecoration {...props} />
|
||||
),
|
||||
renderMark = (props: RenderMarkProps) => <DefaultMark {...props} />,
|
||||
renderLeaf = (props: RenderLeafProps) => <DefaultLeaf {...props} />,
|
||||
} = props
|
||||
|
||||
let children = (
|
||||
<String isLast={isLast} leaf={leaf} parent={parent} text={text} />
|
||||
)
|
||||
|
||||
if (leaf[PLACEHOLDER_SYMBOL]) {
|
||||
children = (
|
||||
<React.Fragment>
|
||||
<span
|
||||
contentEditable={false}
|
||||
style={{
|
||||
pointerEvents: 'none',
|
||||
display: 'inline-block',
|
||||
verticalAlign: 'text-top',
|
||||
width: '0',
|
||||
maxWidth: '100%',
|
||||
whiteSpace: 'nowrap',
|
||||
opacity: '0.333',
|
||||
}}
|
||||
>
|
||||
{leaf.placeholder}
|
||||
</span>
|
||||
{children}
|
||||
</React.Fragment>
|
||||
)
|
||||
}
|
||||
|
||||
// COMPAT: Having the `data-` attributes on these leaf elements ensures that
|
||||
// in certain misbehaving browsers they aren't weirdly cloned/destroyed by
|
||||
// contenteditable behaviors. (2019/05/08)
|
||||
for (const mark of leaf.marks) {
|
||||
const ret = renderMark({
|
||||
children,
|
||||
leaf,
|
||||
mark,
|
||||
text,
|
||||
attributes: {
|
||||
'data-slate-mark': true,
|
||||
},
|
||||
})
|
||||
|
||||
if (ret) {
|
||||
children = ret
|
||||
}
|
||||
const attributes: {
|
||||
'data-slate-leaf': true
|
||||
} = {
|
||||
'data-slate-leaf': true,
|
||||
}
|
||||
|
||||
for (const decoration of leaf.decorations) {
|
||||
const p = {
|
||||
children,
|
||||
decoration,
|
||||
leaf,
|
||||
text,
|
||||
attributes: {
|
||||
'data-slate-decoration': true,
|
||||
},
|
||||
}
|
||||
|
||||
if (PLACEHOLDER_SYMBOL in decoration) {
|
||||
// @ts-ignore
|
||||
children = <PlaceholderDecoration {...p} />
|
||||
} else {
|
||||
// @ts-ignore
|
||||
const ret = renderDecoration(p)
|
||||
|
||||
if (ret) {
|
||||
children = ret
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return <span data-slate-leaf>{children}</span>
|
||||
return renderLeaf({ attributes, children, leaf, text })
|
||||
}
|
||||
|
||||
const MemoizedLeaf = React.memo(Leaf, (prev, next) => {
|
||||
return (
|
||||
next.parent === prev.parent &&
|
||||
next.isLast === prev.isLast &&
|
||||
next.renderDecoration === prev.renderDecoration &&
|
||||
next.renderMark === prev.renderMark &&
|
||||
next.renderLeaf === prev.renderLeaf &&
|
||||
next.text === prev.text &&
|
||||
SlateLeaf.equals(next.leaf, prev.leaf)
|
||||
Text.matches(next.leaf, prev.leaf)
|
||||
)
|
||||
})
|
||||
|
||||
/**
|
||||
* The default custom decoration renderer.
|
||||
* The default custom leaf renderer.
|
||||
*/
|
||||
|
||||
export const DefaultDecoration = (props: RenderDecorationProps) => {
|
||||
export const DefaultLeaf = (props: RenderLeafProps) => {
|
||||
const { attributes, children } = props
|
||||
return <span {...attributes}>{children}</span>
|
||||
}
|
||||
|
||||
/**
|
||||
* The default custom mark renderer.
|
||||
*/
|
||||
|
||||
export const DefaultMark = (props: RenderMarkProps) => {
|
||||
const { attributes, children } = props
|
||||
return <span {...attributes}>{children}</span>
|
||||
}
|
||||
|
||||
/**
|
||||
* A custom decoration for the default placeholder behavior.
|
||||
*/
|
||||
|
||||
const PlaceholderDecoration = (props: RenderDecorationProps) => {
|
||||
const { decoration, attributes, children } = props
|
||||
const { placeholder } = decoration
|
||||
return (
|
||||
<span {...attributes}>
|
||||
<span
|
||||
contentEditable={false}
|
||||
style={{
|
||||
pointerEvents: 'none',
|
||||
display: 'inline-block',
|
||||
verticalAlign: 'text-top',
|
||||
width: '0',
|
||||
maxWidth: '100%',
|
||||
whiteSpace: 'nowrap',
|
||||
opacity: '0.333',
|
||||
}}
|
||||
>
|
||||
{placeholder}
|
||||
</span>
|
||||
{children}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
export default MemoizedLeaf
|
||||
|
@@ -2,7 +2,6 @@ import React from 'react'
|
||||
import { Editor, Text, Path, Element, Node } from 'slate'
|
||||
|
||||
import { ReactEditor, useEditor } from '..'
|
||||
import { Leaf } from '../utils/leaf'
|
||||
|
||||
/**
|
||||
* Leaf content strings.
|
||||
@@ -10,7 +9,7 @@ import { Leaf } from '../utils/leaf'
|
||||
|
||||
const String = (props: {
|
||||
isLast: boolean
|
||||
leaf: Leaf
|
||||
leaf: Text
|
||||
parent: Element
|
||||
text: Text
|
||||
}) => {
|
||||
|
@@ -2,9 +2,8 @@ import React, { useLayoutEffect, useRef } from 'react'
|
||||
import { Range, Element, Text as SlateText } from 'slate'
|
||||
|
||||
import Leaf from './leaf'
|
||||
import { Leaf as SlateLeaf } from '../utils/leaf'
|
||||
import { ReactEditor, useEditor } from '..'
|
||||
import { RenderDecorationProps, RenderMarkProps } from './editable'
|
||||
import { RenderLeafProps } from './editable'
|
||||
import {
|
||||
KEY_TO_ELEMENT,
|
||||
NODE_TO_ELEMENT,
|
||||
@@ -19,18 +18,10 @@ const Text = (props: {
|
||||
decorations: Range[]
|
||||
isLast: boolean
|
||||
parent: Element
|
||||
renderDecoration?: (props: RenderDecorationProps) => JSX.Element
|
||||
renderMark?: (props: RenderMarkProps) => JSX.Element
|
||||
renderLeaf?: (props: RenderLeafProps) => JSX.Element
|
||||
text: SlateText
|
||||
}) => {
|
||||
const {
|
||||
decorations,
|
||||
isLast,
|
||||
parent,
|
||||
renderDecoration,
|
||||
renderMark,
|
||||
text,
|
||||
} = props
|
||||
const { decorations, isLast, parent, renderLeaf, text } = props
|
||||
const editor = useEditor()
|
||||
const ref = useRef<HTMLSpanElement>(null)
|
||||
const leaves = getLeaves(text, decorations)
|
||||
@@ -47,8 +38,7 @@ const Text = (props: {
|
||||
leaf={leaf}
|
||||
text={text}
|
||||
parent={parent}
|
||||
renderDecoration={renderDecoration}
|
||||
renderMark={renderMark}
|
||||
renderLeaf={renderLeaf}
|
||||
/>
|
||||
)
|
||||
}
|
||||
@@ -76,12 +66,12 @@ const Text = (props: {
|
||||
* Get the leaves for a text node given decorations.
|
||||
*/
|
||||
|
||||
const getLeaves = (node: SlateText, decorations: Range[]): SlateLeaf[] => {
|
||||
const { text, marks } = node
|
||||
let leaves: SlateLeaf[] = [{ text, marks, decorations: [] }]
|
||||
const getLeaves = (node: SlateText, decorations: Range[]): SlateText[] => {
|
||||
let leaves: SlateText[] = [{ ...node }]
|
||||
|
||||
const compile = (range: Range, key?: string) => {
|
||||
const [start, end] = Range.edges(range)
|
||||
for (const dec of decorations) {
|
||||
const { anchor, focus, ...rest } = dec
|
||||
const [start, end] = Range.edges(dec)
|
||||
const next = []
|
||||
let o = 0
|
||||
|
||||
@@ -92,7 +82,7 @@ const getLeaves = (node: SlateText, decorations: Range[]): SlateLeaf[] => {
|
||||
|
||||
// If the range encompases the entire leaf, add the range.
|
||||
if (start.offset <= offset && end.offset >= offset + length) {
|
||||
leaf.decorations.push(range)
|
||||
Object.assign(leaf, rest)
|
||||
next.push(leaf)
|
||||
continue
|
||||
}
|
||||
@@ -115,14 +105,18 @@ const getLeaves = (node: SlateText, decorations: Range[]): SlateLeaf[] => {
|
||||
let after
|
||||
|
||||
if (end.offset < offset + length) {
|
||||
;[middle, after] = SlateLeaf.split(middle, end.offset - offset)
|
||||
const off = end.offset - offset
|
||||
after = { ...middle, text: middle.text.slice(off) }
|
||||
middle = { ...middle, text: middle.text.slice(0, off) }
|
||||
}
|
||||
|
||||
if (start.offset > offset) {
|
||||
;[before, middle] = SlateLeaf.split(middle, start.offset - offset)
|
||||
const off = start.offset - offset
|
||||
before = { ...middle, text: middle.text.slice(0, off) }
|
||||
middle = { ...middle, text: middle.text.slice(off) }
|
||||
}
|
||||
|
||||
middle.decorations.push(range)
|
||||
Object.assign(middle, rest)
|
||||
|
||||
if (before) {
|
||||
next.push(before)
|
||||
@@ -138,28 +132,16 @@ const getLeaves = (node: SlateText, decorations: Range[]): SlateLeaf[] => {
|
||||
leaves = next
|
||||
}
|
||||
|
||||
for (const range of decorations) {
|
||||
compile(range)
|
||||
}
|
||||
|
||||
return leaves
|
||||
}
|
||||
|
||||
const MemoizedText = React.memo(Text, (prev, next) => {
|
||||
if (
|
||||
return (
|
||||
next.parent === prev.parent &&
|
||||
next.isLast === prev.isLast &&
|
||||
next.renderDecoration === prev.renderDecoration &&
|
||||
next.renderMark === prev.renderMark &&
|
||||
next.renderLeaf === prev.renderLeaf &&
|
||||
next.text === prev.text
|
||||
) {
|
||||
return SlateLeaf.equals(
|
||||
{ ...next.text, decorations: next.decorations },
|
||||
{ ...prev.text, decorations: prev.decorations }
|
||||
)
|
||||
}
|
||||
|
||||
return false
|
||||
)
|
||||
})
|
||||
|
||||
export default MemoizedText
|
||||
|
@@ -1,6 +1,6 @@
|
||||
export * from './components/editable'
|
||||
export { DefaultElement } from './components/element'
|
||||
export { DefaultMark, DefaultDecoration } from './components/leaf'
|
||||
export { DefaultLeaf } from './components/leaf'
|
||||
export * from './hooks/use-editor'
|
||||
export * from './hooks/use-focused'
|
||||
export * from './hooks/use-read-only'
|
||||
|
@@ -1,113 +0,0 @@
|
||||
import isPlainObject from 'is-plain-object'
|
||||
import { Range, Mark } from 'slate'
|
||||
|
||||
/**
|
||||
* The `Leaf` interface represents the individual leaves inside a text node,
|
||||
* once decorations have been applied.
|
||||
*/
|
||||
|
||||
interface Leaf {
|
||||
decorations: Range[]
|
||||
marks: Mark[]
|
||||
text: string
|
||||
}
|
||||
|
||||
namespace Leaf {
|
||||
/**
|
||||
* Check if two leaves are equal.
|
||||
*/
|
||||
|
||||
export const equals = (leaf: Leaf, another: Leaf): boolean => {
|
||||
return (
|
||||
leaf.text === another.text &&
|
||||
leaf.decorations.length === another.decorations.length &&
|
||||
leaf.marks.length === another.marks.length &&
|
||||
leaf.marks.every(m => Mark.exists(m, another.marks)) &&
|
||||
another.marks.every(m => Mark.exists(m, leaf.marks)) &&
|
||||
isRangeListEqual(leaf.decorations, another.decorations)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a value is a `Leaf` object.
|
||||
*/
|
||||
|
||||
export const isLeaf = (value: any): value is Leaf => {
|
||||
return (
|
||||
isPlainObject(value) &&
|
||||
typeof value.text === 'string' &&
|
||||
Mark.isMarkSet(value.marks) &&
|
||||
Range.isRangeList(value.decorations)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Split a leaf into two at an offset.
|
||||
*/
|
||||
|
||||
export const split = (leaf: Leaf, offset: number): [Leaf, Leaf] => {
|
||||
return [
|
||||
{
|
||||
text: leaf.text.slice(0, offset),
|
||||
marks: leaf.marks,
|
||||
decorations: [...leaf.decorations],
|
||||
},
|
||||
{
|
||||
text: leaf.text.slice(offset),
|
||||
marks: leaf.marks,
|
||||
decorations: [...leaf.decorations],
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a list of ranges is equal to another.
|
||||
*
|
||||
* PERF: this requires the two lists to also have the ranges inside them in the
|
||||
* same order, but this is an okay constraint for us since decorations are
|
||||
* kept in order, and the odd case where they aren't is okay to re-render for.
|
||||
*/
|
||||
|
||||
const isRangeListEqual = (list: Range[], another: Range[]): boolean => {
|
||||
if (list.length !== another.length) {
|
||||
return false
|
||||
}
|
||||
|
||||
for (let i = 0; i < list.length; i++) {
|
||||
const range = list[i]
|
||||
const other = another[i]
|
||||
|
||||
if (!Range.equals(range, other)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a map of ranges is equal to another.
|
||||
*/
|
||||
|
||||
const isRangeMapEqual = (
|
||||
map: Record<string, Range>,
|
||||
another: Record<string, Range>
|
||||
): boolean => {
|
||||
if (Object.keys(map).length !== Object.keys(another).length) {
|
||||
return false
|
||||
}
|
||||
|
||||
for (const key in map) {
|
||||
const range = map[key]
|
||||
const other = another[key]
|
||||
|
||||
if (!Range.equals(range, other)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
export { Leaf, isRangeListEqual, isRangeMapEqual }
|
@@ -1,4 +1,4 @@
|
||||
import { Node, Ancestor, Editor } from 'slate'
|
||||
import { Node, Ancestor, Editor, Text } from 'slate'
|
||||
|
||||
import { Key } from './key'
|
||||
|
||||
@@ -16,10 +16,11 @@ export const NODE_TO_PARENT: WeakMap<Node, Ancestor> = new WeakMap()
|
||||
*/
|
||||
|
||||
export const EDITOR_TO_ELEMENT: WeakMap<Editor, HTMLElement> = new WeakMap()
|
||||
export const NODE_TO_ELEMENT: WeakMap<Node, HTMLElement> = new WeakMap()
|
||||
export const EDITOR_TO_PLACEHOLDER: WeakMap<Editor, string> = new WeakMap()
|
||||
export const ELEMENT_TO_NODE: WeakMap<HTMLElement, Node> = new WeakMap()
|
||||
export const NODE_TO_KEY: WeakMap<Node, Key> = new WeakMap()
|
||||
export const KEY_TO_ELEMENT: WeakMap<Key, HTMLElement> = new WeakMap()
|
||||
export const NODE_TO_ELEMENT: WeakMap<Node, HTMLElement> = new WeakMap()
|
||||
export const NODE_TO_KEY: WeakMap<Node, Key> = new WeakMap()
|
||||
|
||||
/**
|
||||
* Weak maps for storing editor-related state.
|
||||
@@ -30,8 +31,4 @@ export const IS_FOCUSED: WeakMap<Editor, boolean> = new WeakMap()
|
||||
export const IS_DRAGGING: WeakMap<Editor, boolean> = new WeakMap()
|
||||
export const IS_CLICKING: WeakMap<Editor, boolean> = new WeakMap()
|
||||
|
||||
/**
|
||||
* Symbols.
|
||||
*/
|
||||
|
||||
export const PLACEHOLDER_SYMBOL = Symbol('placeholder')
|
||||
export const PLACEHOLDER_SYMBOL = (Symbol('placeholder') as unknown) as string
|
||||
|
@@ -16,11 +16,8 @@ export const withReact = (editor: Editor): Editor => {
|
||||
const matches: [Path, Key][] = []
|
||||
|
||||
switch (op.type) {
|
||||
case 'add_mark':
|
||||
case 'insert_text':
|
||||
case 'remove_mark':
|
||||
case 'remove_text':
|
||||
case 'set_mark':
|
||||
case 'set_node': {
|
||||
for (const [node, path] of Editor.levels(editor, { at: op.path })) {
|
||||
const key = ReactEditor.findKey(editor, node)
|
||||
|
Reference in New Issue
Block a user