Skip to content

Commit 5a1c728

Browse files
authoredDec 7, 2024··
feat: add optional method to decoration object (#5776)
1 parent 6bc5758 commit 5a1c728

File tree

9 files changed

+101
-19
lines changed

9 files changed

+101
-19
lines changed
 

‎.changeset/tiny-cheetahs-ring.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'slate': minor
3+
---
4+
5+
Add `merge` optional function to decorations and change related type signatures to `DecoratedRange`. Now developers can specify how two decoration object with the same key but different value are merged together if they overlap"

‎docs/api/nodes/text.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ If a `props.text` property is passed in, it will be ignored.
2626

2727
If there are properties in `text` that are not in `props`, those will be ignored when it comes to testing for a match.
2828

29-
#### `Text.decorations(node: Text, decorations: Range[]) => Text[]`
29+
#### `Text.decorations(node: Text, decorations: DecoratedRange[]) => Text[]`
3030

3131
Get the leaves for a text node, given `decorations`.
3232

‎packages/slate-react/src/components/editable.tsx

+3-2
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import {
2222
Range,
2323
Text,
2424
Transforms,
25+
DecoratedRange,
2526
} from 'slate'
2627
import { useAndroidInputManager } from '../hooks/android-input-manager/use-android-input-manager'
2728
import useChildren from '../hooks/use-children'
@@ -116,7 +117,7 @@ export interface RenderLeafProps {
116117
*/
117118

118119
export type EditableProps = {
119-
decorate?: (entry: NodeEntry) => Range[]
120+
decorate?: (entry: NodeEntry) => DecoratedRange[]
120121
onDOMBeforeInput?: (event: InputEvent) => void
121122
placeholder?: string
122123
readOnly?: boolean
@@ -1876,7 +1877,7 @@ export const DefaultPlaceholder = ({
18761877
* A default memoized decorate function.
18771878
*/
18781879

1879-
export const defaultDecorate: (entry: NodeEntry) => Range[] = () => []
1880+
export const defaultDecorate: (entry: NodeEntry) => DecoratedRange[] = () => []
18801881

18811882
/**
18821883
* A default implement to scroll dom range into view.

‎packages/slate-react/src/components/element.tsx

+8-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
11
import getDirection from 'direction'
22
import React, { useCallback } from 'react'
33
import { JSX } from 'react'
4-
import { Editor, Element as SlateElement, Node, Range } from 'slate'
4+
import {
5+
Editor,
6+
Element as SlateElement,
7+
Node,
8+
Range,
9+
DecoratedRange,
10+
} from 'slate'
511
import { ReactEditor, useReadOnly, useSlateStatic } from '..'
612
import useChildren from '../hooks/use-children'
713
import { isElementDecorationsEqual } from 'slate-dom'
@@ -25,7 +31,7 @@ import Text from './text'
2531
*/
2632

2733
const Element = (props: {
28-
decorations: Range[]
34+
decorations: DecoratedRange[]
2935
element: SlateElement
3036
renderElement?: (props: RenderElementProps) => JSX.Element
3137
renderPlaceholder: (props: RenderPlaceholderProps) => JSX.Element

‎packages/slate-react/src/components/text.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import React, { useCallback, useRef } from 'react'
2-
import { Element, Range, Text as SlateText } from 'slate'
2+
import { Element, DecoratedRange, Text as SlateText } from 'slate'
33
import { ReactEditor, useSlateStatic } from '..'
44
import { isTextDecorationsEqual } from 'slate-dom'
55
import {
@@ -15,7 +15,7 @@ import Leaf from './leaf'
1515
*/
1616

1717
const Text = (props: {
18-
decorations: Range[]
18+
decorations: DecoratedRange[]
1919
isLast: boolean
2020
parent: Element
2121
renderPlaceholder: (props: RenderPlaceholderProps) => JSX.Element

‎packages/slate-react/src/hooks/use-children.tsx

+9-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
import React from 'react'
2-
import { Ancestor, Descendant, Editor, Element, Range } from 'slate'
2+
import {
3+
Ancestor,
4+
Descendant,
5+
Editor,
6+
Element,
7+
Range,
8+
DecoratedRange,
9+
} from 'slate'
310
import {
411
RenderElementProps,
512
RenderLeafProps,
@@ -19,7 +26,7 @@ import { useSlateStatic } from './use-slate-static'
1926
*/
2027

2128
const useChildren = (props: {
22-
decorations: Range[]
29+
decorations: DecoratedRange[]
2330
node: Ancestor
2431
renderElement?: (props: RenderElementProps) => JSX.Element
2532
renderPlaceholder: (props: RenderPlaceholderProps) => JSX.Element
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,18 @@
11
import { createContext, useContext } from 'react'
2-
import { Range, NodeEntry } from 'slate'
2+
import { DecoratedRange, NodeEntry } from 'slate'
33

44
/**
55
* A React context for sharing the `decorate` prop of the editable.
66
*/
77

8-
export const DecorateContext = createContext<(entry: NodeEntry) => Range[]>(
9-
() => []
10-
)
8+
export const DecorateContext = createContext<
9+
(entry: NodeEntry) => DecoratedRange[]
10+
>(() => [])
1111

1212
/**
1313
* Get the current `decorate` prop of the editable.
1414
*/
1515

16-
export const useDecorate = (): ((entry: NodeEntry) => Range[]) => {
16+
export const useDecorate = (): ((entry: NodeEntry) => DecoratedRange[]) => {
1717
return useContext(DecorateContext)
1818
}

‎packages/slate/src/interfaces/text.ts

+14-5
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,14 @@ export interface TextEqualsOptions {
1919
loose?: boolean
2020
}
2121

22+
export type DecoratedRange = Range & {
23+
/**
24+
* Customize how another decoration is merged into a text node. If not specified, `Object.assign` would be used.
25+
* It is useful for overlapping decorations with the same key but different values.
26+
*/
27+
merge?: (leaf: Text, decoration: object) => void
28+
}
29+
2230
export interface TextInterface {
2331
/**
2432
* Check if two text nodes are equal.
@@ -54,7 +62,7 @@ export interface TextInterface {
5462
/**
5563
* Get the leaves for a text node given decorations.
5664
*/
57-
decorations: (node: Text, decorations: Range[]) => Text[]
65+
decorations: (node: Text, decorations: DecoratedRange[]) => Text[]
5866
}
5967

6068
// eslint-disable-next-line no-redeclare
@@ -103,16 +111,17 @@ export const Text: TextInterface = {
103111
return true
104112
},
105113

106-
decorations(node: Text, decorations: Range[]): Text[] {
114+
decorations(node: Text, decorations: DecoratedRange[]): Text[] {
107115
let leaves: Text[] = [{ ...node }]
108116

109117
for (const dec of decorations) {
110-
const { anchor, focus, ...rest } = dec
118+
const { anchor, focus, merge: mergeDecoration, ...rest } = dec
111119
const [start, end] = Range.edges(dec)
112120
const next = []
113121
let leafEnd = 0
114122
const decorationStart = start.offset
115123
const decorationEnd = end.offset
124+
const merge = mergeDecoration ?? Object.assign
116125

117126
for (const leaf of leaves) {
118127
const { length } = leaf.text
@@ -121,7 +130,7 @@ export const Text: TextInterface = {
121130

122131
// If the range encompasses the entire leaf, add the range.
123132
if (decorationStart <= leafStart && leafEnd <= decorationEnd) {
124-
Object.assign(leaf, rest)
133+
merge(leaf, rest)
125134
next.push(leaf)
126135
continue
127136
}
@@ -157,7 +166,7 @@ export const Text: TextInterface = {
157166
middle = { ...middle, text: middle.text.slice(off) }
158167
}
159168

160-
Object.assign(middle, rest)
169+
merge(middle, rest)
161170

162171
if (before) {
163172
next.push(before)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import { Text } from 'slate'
2+
3+
const merge = (leaf: Text, dec: { decoration: number[] }) => {
4+
const { decoration, ...rest } = dec
5+
leaf.decoration = [...(leaf.decoration ?? []), ...decoration]
6+
Object.assign(leaf, rest)
7+
}
8+
9+
export const input = [
10+
{
11+
anchor: {
12+
path: [0],
13+
offset: 0,
14+
},
15+
focus: {
16+
path: [0],
17+
offset: 2,
18+
},
19+
merge,
20+
decoration: [1, 2, 3],
21+
},
22+
{
23+
anchor: {
24+
path: [0],
25+
offset: 1,
26+
},
27+
focus: {
28+
path: [0],
29+
offset: 3,
30+
},
31+
merge,
32+
decoration: [4, 5, 6],
33+
},
34+
]
35+
export const test = decorations => {
36+
return Text.decorations({ text: 'abc', mark: 'mark' }, decorations)
37+
}
38+
export const output = [
39+
{
40+
text: 'a',
41+
mark: 'mark',
42+
decoration: [1, 2, 3],
43+
},
44+
{
45+
text: 'b',
46+
mark: 'mark',
47+
decoration: [1, 2, 3, 4, 5, 6],
48+
},
49+
{
50+
text: 'c',
51+
mark: 'mark',
52+
decoration: [4, 5, 6],
53+
},
54+
]

0 commit comments

Comments
 (0)
Please sign in to comment.