Skip to content

Commit

Permalink
feat: add optional method to decoration object (#5776)
Browse files Browse the repository at this point in the history
  • Loading branch information
yf-yang authored Dec 7, 2024
1 parent 6bc5758 commit 5a1c728
Show file tree
Hide file tree
Showing 9 changed files with 101 additions and 19 deletions.
5 changes: 5 additions & 0 deletions .changeset/tiny-cheetahs-ring.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'slate': minor
---

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"
2 changes: 1 addition & 1 deletion docs/api/nodes/text.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ If a `props.text` property is passed in, it will be ignored.

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

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

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

Expand Down
5 changes: 3 additions & 2 deletions packages/slate-react/src/components/editable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
Range,
Text,
Transforms,
DecoratedRange,
} from 'slate'
import { useAndroidInputManager } from '../hooks/android-input-manager/use-android-input-manager'
import useChildren from '../hooks/use-children'
Expand Down Expand Up @@ -116,7 +117,7 @@ export interface RenderLeafProps {
*/

export type EditableProps = {
decorate?: (entry: NodeEntry) => Range[]
decorate?: (entry: NodeEntry) => DecoratedRange[]
onDOMBeforeInput?: (event: InputEvent) => void
placeholder?: string
readOnly?: boolean
Expand Down Expand Up @@ -1876,7 +1877,7 @@ export const DefaultPlaceholder = ({
* A default memoized decorate function.
*/

export const defaultDecorate: (entry: NodeEntry) => Range[] = () => []
export const defaultDecorate: (entry: NodeEntry) => DecoratedRange[] = () => []

/**
* A default implement to scroll dom range into view.
Expand Down
10 changes: 8 additions & 2 deletions packages/slate-react/src/components/element.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
import getDirection from 'direction'
import React, { useCallback } from 'react'
import { JSX } from 'react'
import { Editor, Element as SlateElement, Node, Range } from 'slate'
import {
Editor,
Element as SlateElement,
Node,
Range,
DecoratedRange,
} from 'slate'
import { ReactEditor, useReadOnly, useSlateStatic } from '..'
import useChildren from '../hooks/use-children'
import { isElementDecorationsEqual } from 'slate-dom'
Expand All @@ -25,7 +31,7 @@ import Text from './text'
*/

const Element = (props: {
decorations: Range[]
decorations: DecoratedRange[]
element: SlateElement
renderElement?: (props: RenderElementProps) => JSX.Element
renderPlaceholder: (props: RenderPlaceholderProps) => JSX.Element
Expand Down
4 changes: 2 additions & 2 deletions packages/slate-react/src/components/text.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React, { useCallback, useRef } from 'react'
import { Element, Range, Text as SlateText } from 'slate'
import { Element, DecoratedRange, Text as SlateText } from 'slate'
import { ReactEditor, useSlateStatic } from '..'
import { isTextDecorationsEqual } from 'slate-dom'
import {
Expand All @@ -15,7 +15,7 @@ import Leaf from './leaf'
*/

const Text = (props: {
decorations: Range[]
decorations: DecoratedRange[]
isLast: boolean
parent: Element
renderPlaceholder: (props: RenderPlaceholderProps) => JSX.Element
Expand Down
11 changes: 9 additions & 2 deletions packages/slate-react/src/hooks/use-children.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
import React from 'react'
import { Ancestor, Descendant, Editor, Element, Range } from 'slate'
import {
Ancestor,
Descendant,
Editor,
Element,
Range,
DecoratedRange,
} from 'slate'
import {
RenderElementProps,
RenderLeafProps,
Expand All @@ -19,7 +26,7 @@ import { useSlateStatic } from './use-slate-static'
*/

const useChildren = (props: {
decorations: Range[]
decorations: DecoratedRange[]
node: Ancestor
renderElement?: (props: RenderElementProps) => JSX.Element
renderPlaceholder: (props: RenderPlaceholderProps) => JSX.Element
Expand Down
10 changes: 5 additions & 5 deletions packages/slate-react/src/hooks/use-decorate.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
import { createContext, useContext } from 'react'
import { Range, NodeEntry } from 'slate'
import { DecoratedRange, NodeEntry } from 'slate'

/**
* A React context for sharing the `decorate` prop of the editable.
*/

export const DecorateContext = createContext<(entry: NodeEntry) => Range[]>(
() => []
)
export const DecorateContext = createContext<
(entry: NodeEntry) => DecoratedRange[]
>(() => [])

/**
* Get the current `decorate` prop of the editable.
*/

export const useDecorate = (): ((entry: NodeEntry) => Range[]) => {
export const useDecorate = (): ((entry: NodeEntry) => DecoratedRange[]) => {
return useContext(DecorateContext)
}
19 changes: 14 additions & 5 deletions packages/slate/src/interfaces/text.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,14 @@ export interface TextEqualsOptions {
loose?: boolean
}

export type DecoratedRange = Range & {
/**
* Customize how another decoration is merged into a text node. If not specified, `Object.assign` would be used.
* It is useful for overlapping decorations with the same key but different values.
*/
merge?: (leaf: Text, decoration: object) => void
}

export interface TextInterface {
/**
* Check if two text nodes are equal.
Expand Down Expand Up @@ -54,7 +62,7 @@ export interface TextInterface {
/**
* Get the leaves for a text node given decorations.
*/
decorations: (node: Text, decorations: Range[]) => Text[]
decorations: (node: Text, decorations: DecoratedRange[]) => Text[]
}

// eslint-disable-next-line no-redeclare
Expand Down Expand Up @@ -103,16 +111,17 @@ export const Text: TextInterface = {
return true
},

decorations(node: Text, decorations: Range[]): Text[] {
decorations(node: Text, decorations: DecoratedRange[]): Text[] {
let leaves: Text[] = [{ ...node }]

for (const dec of decorations) {
const { anchor, focus, ...rest } = dec
const { anchor, focus, merge: mergeDecoration, ...rest } = dec
const [start, end] = Range.edges(dec)
const next = []
let leafEnd = 0
const decorationStart = start.offset
const decorationEnd = end.offset
const merge = mergeDecoration ?? Object.assign

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

// If the range encompasses the entire leaf, add the range.
if (decorationStart <= leafStart && leafEnd <= decorationEnd) {
Object.assign(leaf, rest)
merge(leaf, rest)
next.push(leaf)
continue
}
Expand Down Expand Up @@ -157,7 +166,7 @@ export const Text: TextInterface = {
middle = { ...middle, text: middle.text.slice(off) }
}

Object.assign(middle, rest)
merge(middle, rest)

if (before) {
next.push(before)
Expand Down
54 changes: 54 additions & 0 deletions packages/slate/test/interfaces/Text/decorations/merge.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { Text } from 'slate'

const merge = (leaf: Text, dec: { decoration: number[] }) => {
const { decoration, ...rest } = dec
leaf.decoration = [...(leaf.decoration ?? []), ...decoration]
Object.assign(leaf, rest)
}

export const input = [
{
anchor: {
path: [0],
offset: 0,
},
focus: {
path: [0],
offset: 2,
},
merge,
decoration: [1, 2, 3],
},
{
anchor: {
path: [0],
offset: 1,
},
focus: {
path: [0],
offset: 3,
},
merge,
decoration: [4, 5, 6],
},
]
export const test = decorations => {
return Text.decorations({ text: 'abc', mark: 'mark' }, decorations)
}
export const output = [
{
text: 'a',
mark: 'mark',
decoration: [1, 2, 3],
},
{
text: 'b',
mark: 'mark',
decoration: [1, 2, 3, 4, 5, 6],
},
{
text: 'c',
mark: 'mark',
decoration: [4, 5, 6],
},
]

0 comments on commit 5a1c728

Please sign in to comment.