Skip to content

Commit

Permalink
revert basePath changes, compare only offsets for MemoizedText decora…
Browse files Browse the repository at this point in the history
…tions
  • Loading branch information
dsvgit committed Feb 7, 2023
1 parent dfc4239 commit d1e14e6
Show file tree
Hide file tree
Showing 8 changed files with 54 additions and 48 deletions.
2 changes: 1 addition & 1 deletion .changeset/dull-eels-hammer.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
'slate-react': minor
---

Reduce re-renders caused by decorations updates if only the path field is modified. It is possible by adding the `basePath` field to the `Point` type.
If TextComponent decorations keep the same offsets and only paths are changed, prevent re-rendering because only decoration offsets matter when leaves are calculated.
4 changes: 2 additions & 2 deletions packages/slate-react/src/components/element.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {
NODE_TO_INDEX,
EDITOR_TO_KEY_TO_ELEMENT,
} from '../utils/weak-maps'
import { isDecoratorRangeListEqual } from '../utils/range-list'
import { isElementDecorationsEqual } from '../utils/range-list'
import {
RenderElementProps,
RenderLeafProps,
Expand Down Expand Up @@ -139,7 +139,7 @@ const MemoizedElement = React.memo(Element, (prev, next) => {
prev.element === next.element &&
prev.renderElement === next.renderElement &&
prev.renderLeaf === next.renderLeaf &&
isDecoratorRangeListEqual(prev.decorations, next.decorations) &&
isElementDecorationsEqual(prev.decorations, next.decorations) &&
(prev.selection === next.selection ||
(!!prev.selection &&
!!next.selection &&
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
Expand Up @@ -2,7 +2,7 @@ import React, { useRef } from 'react'
import { Element, Range, Text as SlateText } from 'slate'
import { ReactEditor, useSlateStatic } from '..'
import { useIsomorphicLayoutEffect } from '../hooks/use-isomorphic-layout-effect'
import { isDecoratorRangeListEqual } from '../utils/range-list'
import { isTextDecorationsEqual } from '../utils/range-list'
import {
EDITOR_TO_KEY_TO_ELEMENT,
ELEMENT_TO_NODE,
Expand Down Expand Up @@ -79,7 +79,7 @@ const MemoizedText = React.memo(Text, (prev, next) => {
next.isLast === prev.isLast &&
next.renderLeaf === prev.renderLeaf &&
next.text === prev.text &&
isDecoratorRangeListEqual(next.decorations, prev.decorations)
isTextDecorationsEqual(next.decorations, prev.decorations)
)
})

Expand Down
5 changes: 1 addition & 4 deletions packages/slate-react/src/custom-types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { BasePoint, BaseRange, BaseText, Path } from 'slate'
import { BaseRange, BaseText } from 'slate'
import { ReactEditor } from './plugin/react-editor'

declare module 'slate' {
Expand All @@ -10,8 +10,5 @@ declare module 'slate' {
Range: BaseRange & {
placeholder?: string
}
Point: BasePoint & {
basePath?: Path
}
}
}
30 changes: 3 additions & 27 deletions packages/slate-react/src/hooks/use-children.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React from 'react'
import { Editor, Range, Element, Ancestor, Descendant, Point } from 'slate'
import { Editor, Range, Element, Ancestor, Descendant } from 'slate'

import ElementComponent from '../components/element'
import TextComponent from '../components/text'
Expand Down Expand Up @@ -52,7 +52,7 @@ const useChildren = (props: {
const ds = decorate([n, p])

for (const dec of decorations) {
const d = Range.intersection(toAbsoluteRange(dec), range)
const d = Range.intersection(dec, range)

if (d) {
ds.push(d)
Expand All @@ -76,7 +76,7 @@ const useChildren = (props: {
} else {
children.push(
<TextComponent
decorations={ds.map(toAbsoluteRange)}
decorations={ds}
key={key.id}
isLast={isLeafBlock && i === node.children.length - 1}
parent={node}
Expand All @@ -94,28 +94,4 @@ const useChildren = (props: {
return children
}

const toAbsoluteRange = (range: Range): Range => {
const absAnchor = toAbsolutePoint(range.anchor)
const absFocus = toAbsolutePoint(range.focus)

if (absAnchor === range.anchor && absFocus === absFocus) {
return range
}

return {
...range,
anchor: toAbsolutePoint(range.anchor),
focus: toAbsolutePoint(range.focus),
}
}

const toAbsolutePoint = (point: Point) => {
return point.basePath
? {
path: [...point.basePath, ...point.path],
offset: point.offset,
}
: point
}

export default useChildren
48 changes: 42 additions & 6 deletions packages/slate-react/src/utils/range-list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,16 @@ export const shallowCompare = (obj1: {}, obj2: {}) =>
key => obj2.hasOwnProperty(key) && obj1[key] === obj2[key]
)

const isDecorationFlagsEqual = (range: Range, other: Range) => {
const { anchor: rangeAnchor, focus: rangeFocus, ...rangeOwnProps } = range
const { anchor: otherAnchor, focus: otherFocus, ...otherOwnProps } = other

return (
range[PLACEHOLDER_SYMBOL] === other[PLACEHOLDER_SYMBOL] &&
shallowCompare(rangeOwnProps, otherOwnProps)
)
}

/**
* Check if a list of decorator ranges are equal to another.
*
Expand All @@ -15,7 +25,7 @@ export const shallowCompare = (obj1: {}, obj2: {}) =>
* kept in order, and the odd case where they aren't is okay to re-render for.
*/

export const isDecoratorRangeListEqual = (
export const isElementDecorationsEqual = (
list: Range[],
another: Range[]
): boolean => {
Expand All @@ -27,13 +37,39 @@ export const isDecoratorRangeListEqual = (
const range = list[i]
const other = another[i]

const { anchor: rangeAnchor, focus: rangeFocus, ...rangeOwnProps } = range
const { anchor: otherAnchor, focus: otherFocus, ...otherOwnProps } = other
if (!Range.equals(range, other) || !isDecorationFlagsEqual(range, other)) {
return false
}
}

return true
}

/**
* Check if a list of decorator ranges are 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.
*/

export const isTextDecorationsEqual = (
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]

// compare only offsets because paths doesn't matter for text
if (
!Range.equals(range, other) ||
range[PLACEHOLDER_SYMBOL] !== other[PLACEHOLDER_SYMBOL] ||
!shallowCompare(rangeOwnProps, otherOwnProps)
range.anchor.offset !== other.anchor.offset ||
range.focus.offset !== other.focus.offset ||
!isDecorationFlagsEqual(range, other)
) {
return false
}
Expand Down
6 changes: 2 additions & 4 deletions site/examples/code-highlighting.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -292,13 +292,11 @@ const ExtractRanges = () => {
}

const anchor: Point = {
basePath: [index, 0],
path: [],
path: [index, 0],
offset: startOffset,
}
const focus: Point = {
basePath: [index, 0],
path: [],
path: [index, 0],
offset: endOffset,
}

Expand Down
3 changes: 1 addition & 2 deletions site/examples/custom-types.d.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Descendant, BaseEditor, BaseRange, BasePoint, Range } from 'slate'
import { Descendant, BaseEditor, BaseRange, Range } from 'slate'
import { ReactEditor } from 'slate-react'
import { HistoryEditor } from 'slate-history'

Expand Down Expand Up @@ -121,7 +121,6 @@ declare module 'slate' {
Editor: CustomEditor
Element: CustomElement
Text: CustomText | EmptyText
Point: BasePoint & { basePath?: number[] }
Range: BaseRange & {
[key: string]: unknown
}
Expand Down

0 comments on commit d1e14e6

Please sign in to comment.