@portabletext/editor 2.7.0 → 2.7.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,5 @@
1
1
  import { Behavior, Editor, EditorEmittedEvent, EditorSchema } from "../_chunks-dts/behavior.types.action.cjs";
2
- import * as react22 from "react";
2
+ import * as react12 from "react";
3
3
  import React from "react";
4
4
  /**
5
5
  * @beta
@@ -181,7 +181,7 @@ type MarkdownPluginConfig = MarkdownBehaviorsConfig & {
181
181
  */
182
182
  declare function MarkdownPlugin(props: {
183
183
  config: MarkdownPluginConfig;
184
- }): react22.JSX.Element;
184
+ }): react12.JSX.Element;
185
185
  /**
186
186
  * @beta
187
187
  * Restrict the editor to one line. The plugin takes care of blocking
@@ -192,5 +192,5 @@ declare function MarkdownPlugin(props: {
192
192
  *
193
193
  * @deprecated Install the plugin from `@portabletext/plugin-one-line`
194
194
  */
195
- declare function OneLinePlugin(): react22.JSX.Element;
195
+ declare function OneLinePlugin(): react12.JSX.Element;
196
196
  export { BehaviorPlugin, DecoratorShortcutPlugin, EditorRefPlugin, EventListenerPlugin, MarkdownPlugin, type MarkdownPluginConfig, OneLinePlugin };
@@ -1,5 +1,5 @@
1
1
  import { Behavior, Editor, EditorEmittedEvent, EditorSchema } from "../_chunks-dts/behavior.types.action.js";
2
- import * as react21 from "react";
2
+ import * as react12 from "react";
3
3
  import React from "react";
4
4
  /**
5
5
  * @beta
@@ -181,7 +181,7 @@ type MarkdownPluginConfig = MarkdownBehaviorsConfig & {
181
181
  */
182
182
  declare function MarkdownPlugin(props: {
183
183
  config: MarkdownPluginConfig;
184
- }): react21.JSX.Element;
184
+ }): react12.JSX.Element;
185
185
  /**
186
186
  * @beta
187
187
  * Restrict the editor to one line. The plugin takes care of blocking
@@ -192,5 +192,5 @@ declare function MarkdownPlugin(props: {
192
192
  *
193
193
  * @deprecated Install the plugin from `@portabletext/plugin-one-line`
194
194
  */
195
- declare function OneLinePlugin(): react21.JSX.Element;
195
+ declare function OneLinePlugin(): react12.JSX.Element;
196
196
  export { BehaviorPlugin, DecoratorShortcutPlugin, EditorRefPlugin, EventListenerPlugin, MarkdownPlugin, type MarkdownPluginConfig, OneLinePlugin };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@portabletext/editor",
3
- "version": "2.7.0",
3
+ "version": "2.7.2",
4
4
  "description": "Portable Text Editor made in React",
5
5
  "keywords": [
6
6
  "sanity",
@@ -79,10 +79,10 @@
79
79
  "slate-dom": "^0.118.1",
80
80
  "slate-react": "0.117.4",
81
81
  "xstate": "^5.21.0",
82
- "@portabletext/block-tools": "^3.5.1",
82
+ "@portabletext/block-tools": "^3.5.2",
83
+ "@portabletext/schema": "^1.2.0",
83
84
  "@portabletext/keyboard-shortcuts": "^1.1.1",
84
- "@portabletext/patches": "^1.1.8",
85
- "@portabletext/schema": "^1.2.0"
85
+ "@portabletext/patches": "^1.1.8"
86
86
  },
87
87
  "devDependencies": {
88
88
  "@sanity/diff-match-patch": "^3.2.0",
@@ -112,8 +112,8 @@
112
112
  "vitest": "^3.2.4",
113
113
  "vitest-browser-react": "^1.0.1",
114
114
  "@portabletext/sanity-bridge": "1.1.7",
115
- "@portabletext/test": "^0.0.0",
116
- "racejar": "1.2.14"
115
+ "racejar": "1.2.14",
116
+ "@portabletext/test": "^0.0.0"
117
117
  },
118
118
  "peerDependencies": {
119
119
  "@portabletext/sanity-bridge": "^1.1.7",
@@ -73,16 +73,17 @@ export function RenderBlockObject(props: {
73
73
  draggable={!props.readOnly}
74
74
  >
75
75
  {props.renderBlock && legacySchemaType ? (
76
- props.renderBlock({
77
- children: <RenderDefaultBlockObject blockObject={blockObject} />,
78
- editorElementRef: blockObjectRef,
79
- focused,
80
- path: [{_key: props.element._key}],
81
- schemaType: legacySchemaType,
82
- selected,
83
- type: legacySchemaType,
84
- value: blockObject,
85
- })
76
+ <props.renderBlock
77
+ editorElementRef={blockObjectRef}
78
+ focused={focused}
79
+ path={[{_key: props.element._key}]}
80
+ schemaType={legacySchemaType}
81
+ selected={selected}
82
+ type={legacySchemaType}
83
+ value={blockObject}
84
+ >
85
+ <RenderDefaultBlockObject blockObject={blockObject} />
86
+ </props.renderBlock>
86
87
  ) : (
87
88
  <RenderDefaultBlockObject blockObject={blockObject} />
88
89
  )}
@@ -79,17 +79,18 @@ export function RenderInlineObject(props: {
79
79
  {props.children}
80
80
  <span ref={inlineObjectRef} style={{display: 'inline-block'}}>
81
81
  {props.renderChild && block && legacySchemaType ? (
82
- props.renderChild({
83
- annotations: [],
84
- children: <RenderDefaultInlineObject inlineObject={inlineObject} />,
85
- editorElementRef: inlineObjectRef,
86
- selected,
87
- focused,
88
- path: [{_key: block._key}, 'children', {_key: props.element._key}],
89
- schemaType: legacySchemaType,
90
- value: inlineObject,
91
- type: legacySchemaType,
92
- })
82
+ <props.renderChild
83
+ annotations={[]}
84
+ editorElementRef={inlineObjectRef}
85
+ selected={selected}
86
+ focused={focused}
87
+ path={[{_key: block._key}, 'children', {_key: props.element._key}]}
88
+ schemaType={legacySchemaType}
89
+ value={inlineObject}
90
+ type={legacySchemaType}
91
+ >
92
+ <RenderDefaultInlineObject inlineObject={inlineObject} />
93
+ </props.renderChild>
93
94
  ) : (
94
95
  <RenderDefaultInlineObject inlineObject={inlineObject} />
95
96
  )}
@@ -1,24 +1,21 @@
1
+ import {isTextBlock} from '@portabletext/schema'
1
2
  import {useSelector} from '@xstate/react'
2
- import {isEqual, uniq} from 'lodash'
3
+ import {uniq} from 'lodash'
4
+ import {useContext, useMemo, useRef, type ReactElement} from 'react'
5
+ import {useSlateStatic, type RenderLeafProps} from 'slate-react'
3
6
  import {
4
- startTransition,
5
- useCallback,
6
- useContext,
7
- useEffect,
8
- useMemo,
9
- useRef,
10
- useState,
11
- type ReactElement,
12
- } from 'react'
13
- import {useSelected, useSlateStatic, type RenderLeafProps} from 'slate-react'
7
+ getFocusSpan,
8
+ isOverlappingSelection,
9
+ isSelectionCollapsed,
10
+ } from '../../selectors'
14
11
  import type {
12
+ EditorSelection,
15
13
  RenderAnnotationFunction,
16
14
  RenderChildFunction,
17
15
  RenderDecoratorFunction,
18
16
  } from '../../types/editor'
19
17
  import {EditorActorContext} from '../editor-actor-context'
20
- import {usePortableTextEditor} from '../hooks/usePortableTextEditor'
21
- import {PortableTextEditor} from '../PortableTextEditor'
18
+ import {getEditorSnapshot} from '../editor-selector'
22
19
 
23
20
  export interface RenderSpanProps extends RenderLeafProps {
24
21
  children: ReactElement<any>
@@ -35,10 +32,66 @@ export function RenderSpan(props: RenderSpanProps) {
35
32
  s.context.getLegacySchema(),
36
33
  )
37
34
  const spanRef = useRef<HTMLElement>(null)
38
- const portableTextEditor = usePortableTextEditor()
39
- const blockSelected = useSelected()
40
- const [focused, setFocused] = useState(false)
41
- const [selected, setSelected] = useState(false)
35
+
36
+ /**
37
+ * A span is considered focused if the selection is collapsed and the caret
38
+ * is inside the span.
39
+ */
40
+ const focused = useSelector(editorActor, (editorActorSnapshot) => {
41
+ const snapshot = getEditorSnapshot({
42
+ editorActorSnapshot,
43
+ slateEditorInstance: slateEditor,
44
+ })
45
+
46
+ if (!snapshot.context.selection) {
47
+ return false
48
+ }
49
+
50
+ if (!isSelectionCollapsed(snapshot)) {
51
+ return false
52
+ }
53
+
54
+ const focusedSpan = getFocusSpan(snapshot)
55
+
56
+ if (!focusedSpan) {
57
+ return false
58
+ }
59
+
60
+ return focusedSpan.node._key === props.leaf._key
61
+ })
62
+
63
+ /**
64
+ * A span is considered selected if editor selection is overlapping with the
65
+ * span selection points.
66
+ */
67
+ const selected = useSelector(editorActor, (editorActorSnapshot) => {
68
+ const snapshot = getEditorSnapshot({
69
+ editorActorSnapshot,
70
+ slateEditorInstance: slateEditor,
71
+ })
72
+
73
+ if (!snapshot.context.selection) {
74
+ return false
75
+ }
76
+
77
+ const parent = props.children.props.parent
78
+ const block =
79
+ parent && isTextBlock(snapshot.context, parent) ? parent : undefined
80
+ const spanSelection: EditorSelection = block
81
+ ? {
82
+ anchor: {
83
+ path: [{_key: block._key}, 'children', {_key: props.leaf._key}],
84
+ offset: 0,
85
+ },
86
+ focus: {
87
+ path: [{_key: block._key}, 'children', {_key: props.leaf._key}],
88
+ offset: props.leaf.text.length,
89
+ },
90
+ }
91
+ : null
92
+
93
+ return isOverlappingSelection(spanSelection)(snapshot)
94
+ })
42
95
 
43
96
  const parent = props.children.props.parent
44
97
  const block = parent && slateEditor.isTextBlock(parent) ? parent : undefined
@@ -75,106 +128,6 @@ export function RenderSpan(props: RenderSpanProps) {
75
128
  return []
76
129
  })
77
130
 
78
- const shouldTrackSelectionAndFocus =
79
- annotationMarkDefs.length > 0 && blockSelected
80
-
81
- useEffect(() => {
82
- if (!shouldTrackSelectionAndFocus) {
83
- setFocused(false)
84
- return
85
- }
86
-
87
- const sel = PortableTextEditor.getSelection(portableTextEditor)
88
-
89
- if (
90
- sel &&
91
- isEqual(sel.focus.path, path) &&
92
- PortableTextEditor.isCollapsedSelection(portableTextEditor)
93
- ) {
94
- startTransition(() => {
95
- setFocused(true)
96
- })
97
- }
98
- }, [shouldTrackSelectionAndFocus, path, portableTextEditor])
99
-
100
- // Function to check if this leaf is currently inside the user's text selection
101
- const setSelectedFromRange = useCallback(() => {
102
- if (!shouldTrackSelectionAndFocus) {
103
- return
104
- }
105
-
106
- const winSelection = window.getSelection()
107
-
108
- if (!winSelection) {
109
- setSelected(false)
110
- return
111
- }
112
-
113
- if (winSelection && winSelection.rangeCount > 0) {
114
- const range = winSelection.getRangeAt(0)
115
-
116
- if (spanRef.current && range.intersectsNode(spanRef.current)) {
117
- setSelected(true)
118
- } else {
119
- setSelected(false)
120
- }
121
- } else {
122
- setSelected(false)
123
- }
124
- }, [shouldTrackSelectionAndFocus])
125
-
126
- useEffect(() => {
127
- if (!shouldTrackSelectionAndFocus) {
128
- return undefined
129
- }
130
-
131
- const onBlur = editorActor.on('blurred', () => {
132
- setFocused(false)
133
- setSelected(false)
134
- })
135
-
136
- const onFocus = editorActor.on('focused', () => {
137
- const sel = PortableTextEditor.getSelection(portableTextEditor)
138
-
139
- if (
140
- sel &&
141
- isEqual(sel.focus.path, path) &&
142
- PortableTextEditor.isCollapsedSelection(portableTextEditor)
143
- ) {
144
- setFocused(true)
145
- }
146
-
147
- setSelectedFromRange()
148
- })
149
-
150
- const onSelection = editorActor.on('selection', (event) => {
151
- if (
152
- event.selection &&
153
- isEqual(event.selection.focus.path, path) &&
154
- PortableTextEditor.isCollapsedSelection(portableTextEditor)
155
- ) {
156
- setFocused(true)
157
- } else {
158
- setFocused(false)
159
- }
160
- setSelectedFromRange()
161
- })
162
-
163
- return () => {
164
- onBlur.unsubscribe()
165
- onFocus.unsubscribe()
166
- onSelection.unsubscribe()
167
- }
168
- }, [
169
- editorActor,
170
- path,
171
- portableTextEditor,
172
- setSelectedFromRange,
173
- shouldTrackSelectionAndFocus,
174
- ])
175
-
176
- useEffect(() => setSelectedFromRange(), [setSelectedFromRange])
177
-
178
131
  let children = props.children
179
132
 
180
133
  /**
@@ -186,16 +139,19 @@ export function RenderSpan(props: RenderSpanProps) {
186
139
  )
187
140
 
188
141
  if (path && legacyDecoratorSchemaType && props.renderDecorator) {
189
- children = props.renderDecorator({
190
- children: children,
191
- editorElementRef: spanRef,
192
- focused,
193
- path,
194
- selected,
195
- schemaType: legacyDecoratorSchemaType,
196
- value: mark,
197
- type: legacyDecoratorSchemaType,
198
- })
142
+ children = (
143
+ <props.renderDecorator
144
+ editorElementRef={spanRef}
145
+ focused={focused}
146
+ path={path}
147
+ selected={selected}
148
+ schemaType={legacyDecoratorSchemaType}
149
+ value={mark}
150
+ type={legacyDecoratorSchemaType}
151
+ >
152
+ {children}
153
+ </props.renderDecorator>
154
+ )
199
155
  }
200
156
  }
201
157
 
@@ -210,17 +166,18 @@ export function RenderSpan(props: RenderSpanProps) {
210
166
  if (block && path && props.renderAnnotation) {
211
167
  children = (
212
168
  <span ref={spanRef}>
213
- {props.renderAnnotation({
214
- block,
215
- children: children,
216
- editorElementRef: spanRef,
217
- focused,
218
- path,
219
- selected,
220
- schemaType: legacyAnnotationSchemaType,
221
- value: annotationMarkDef,
222
- type: legacyAnnotationSchemaType,
223
- })}
169
+ <props.renderAnnotation
170
+ block={block}
171
+ editorElementRef={spanRef}
172
+ focused={focused}
173
+ path={path}
174
+ selected={selected}
175
+ schemaType={legacyAnnotationSchemaType}
176
+ value={annotationMarkDef}
177
+ type={legacyAnnotationSchemaType}
178
+ >
179
+ {children}
180
+ </props.renderAnnotation>
224
181
  </span>
225
182
  )
226
183
  } else {
@@ -238,17 +195,20 @@ export function RenderSpan(props: RenderSpanProps) {
238
195
  ) // Ensure object equality
239
196
 
240
197
  if (child) {
241
- children = props.renderChild({
242
- annotations: annotationMarkDefs,
243
- children: children,
244
- editorElementRef: spanRef,
245
- focused,
246
- path,
247
- schemaType: legacySchema.span,
248
- selected,
249
- value: child,
250
- type: legacySchema.span,
251
- })
198
+ children = (
199
+ <props.renderChild
200
+ annotations={annotationMarkDefs}
201
+ editorElementRef={spanRef}
202
+ focused={focused}
203
+ path={path}
204
+ schemaType={legacySchema.span}
205
+ selected={selected}
206
+ value={child}
207
+ type={legacySchema.span}
208
+ >
209
+ {children}
210
+ </props.renderChild>
211
+ )
252
212
  }
253
213
  }
254
214
 
@@ -60,16 +60,19 @@ export function RenderTextBlock(props: {
60
60
  : undefined
61
61
 
62
62
  if (legacyStyleSchemaType) {
63
- children = props.renderStyle({
64
- block: props.textBlock,
65
- children,
66
- editorElementRef: blockRef,
67
- focused,
68
- path: [{_key: props.textBlock._key}],
69
- schemaType: legacyStyleSchemaType,
70
- selected,
71
- value: props.textBlock.style,
72
- })
63
+ children = (
64
+ <props.renderStyle
65
+ block={props.textBlock}
66
+ editorElementRef={blockRef}
67
+ focused={focused}
68
+ path={[{_key: props.textBlock._key}]}
69
+ schemaType={legacyStyleSchemaType}
70
+ selected={selected}
71
+ value={props.textBlock.style}
72
+ >
73
+ {children}
74
+ </props.renderStyle>
75
+ )
73
76
  } else {
74
77
  console.error(
75
78
  `Unable to find Schema type for text block style ${props.textBlock.style}`,
@@ -83,17 +86,20 @@ export function RenderTextBlock(props: {
83
86
  )
84
87
 
85
88
  if (legacyListItemSchemaType) {
86
- children = props.renderListItem({
87
- block: props.textBlock,
88
- children,
89
- editorElementRef: blockRef,
90
- focused,
91
- level: props.textBlock.level ?? 1,
92
- path: [{_key: props.textBlock._key}],
93
- selected,
94
- value: props.textBlock.listItem,
95
- schemaType: legacyListItemSchemaType,
96
- })
89
+ children = (
90
+ <props.renderListItem
91
+ block={props.textBlock}
92
+ editorElementRef={blockRef}
93
+ focused={focused}
94
+ level={props.textBlock.level ?? 1}
95
+ path={[{_key: props.textBlock._key}]}
96
+ selected={selected}
97
+ value={props.textBlock.listItem}
98
+ schemaType={legacyListItemSchemaType}
99
+ >
100
+ {children}
101
+ </props.renderListItem>
102
+ )
97
103
  } else {
98
104
  console.error(
99
105
  `Unable to find Schema type for text block list item ${props.textBlock.listItem}`,
@@ -145,21 +151,24 @@ export function RenderTextBlock(props: {
145
151
  >
146
152
  {dragPositionBlock === 'start' ? <DropIndicator /> : null}
147
153
  <div ref={blockRef}>
148
- {props.renderBlock
149
- ? props.renderBlock({
150
- children,
151
- editorElementRef: blockRef,
152
- focused,
153
- level: props.textBlock.level,
154
- listItem: props.textBlock.listItem,
155
- path: [{_key: props.textBlock._key}],
156
- selected,
157
- schemaType: props.legacySchema.block,
158
- style: props.textBlock.style,
159
- type: props.legacySchema.block,
160
- value: props.textBlock,
161
- })
162
- : children}
154
+ {props.renderBlock ? (
155
+ <props.renderBlock
156
+ editorElementRef={blockRef}
157
+ focused={focused}
158
+ level={props.textBlock.level}
159
+ listItem={props.textBlock.listItem}
160
+ path={[{_key: props.textBlock._key}]}
161
+ selected={selected}
162
+ schemaType={props.legacySchema.block}
163
+ style={props.textBlock.style}
164
+ type={props.legacySchema.block}
165
+ value={props.textBlock}
166
+ >
167
+ {children}
168
+ </props.renderBlock>
169
+ ) : (
170
+ children
171
+ )}
163
172
  </div>
164
173
  {dragPositionBlock === 'end' ? <DropIndicator /> : null}
165
174
  </div>