@portabletext/editor 1.24.0 → 1.26.0

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.
Files changed (89) hide show
  1. package/lib/_chunks-cjs/behavior.core.cjs +283 -64
  2. package/lib/_chunks-cjs/behavior.core.cjs.map +1 -1
  3. package/lib/_chunks-cjs/selector.get-text-before.cjs +8 -8
  4. package/lib/_chunks-cjs/selector.get-text-before.cjs.map +1 -1
  5. package/lib/_chunks-cjs/selector.is-at-the-start-of-block.cjs +412 -0
  6. package/lib/_chunks-cjs/selector.is-at-the-start-of-block.cjs.map +1 -0
  7. package/lib/_chunks-cjs/util.is-empty-text-block.cjs +2 -2
  8. package/lib/_chunks-cjs/util.is-empty-text-block.cjs.map +1 -1
  9. package/lib/_chunks-cjs/util.is-equal-selection-points.cjs +46 -0
  10. package/lib/_chunks-cjs/util.is-equal-selection-points.cjs.map +1 -0
  11. package/lib/_chunks-cjs/util.reverse-selection.cjs +0 -16
  12. package/lib/_chunks-cjs/util.reverse-selection.cjs.map +1 -1
  13. package/lib/_chunks-es/behavior.core.js +259 -40
  14. package/lib/_chunks-es/behavior.core.js.map +1 -1
  15. package/lib/_chunks-es/selector.get-text-before.js +2 -2
  16. package/lib/_chunks-es/selector.is-at-the-start-of-block.js +414 -0
  17. package/lib/_chunks-es/selector.is-at-the-start-of-block.js.map +1 -0
  18. package/lib/_chunks-es/util.is-empty-text-block.js +1 -1
  19. package/lib/_chunks-es/util.is-equal-selection-points.js +47 -0
  20. package/lib/_chunks-es/util.is-equal-selection-points.js.map +1 -0
  21. package/lib/_chunks-es/util.reverse-selection.js +0 -16
  22. package/lib/_chunks-es/util.reverse-selection.js.map +1 -1
  23. package/lib/behaviors/index.cjs +27 -27
  24. package/lib/behaviors/index.cjs.map +1 -1
  25. package/lib/behaviors/index.d.cts +2208 -171
  26. package/lib/behaviors/index.d.ts +2208 -171
  27. package/lib/behaviors/index.js +1 -1
  28. package/lib/index.cjs +306 -298
  29. package/lib/index.cjs.map +1 -1
  30. package/lib/index.d.cts +10499 -521
  31. package/lib/index.d.ts +10499 -521
  32. package/lib/index.js +302 -294
  33. package/lib/index.js.map +1 -1
  34. package/lib/selectors/index.cjs +26 -171
  35. package/lib/selectors/index.cjs.map +1 -1
  36. package/lib/selectors/index.d.cts +16 -0
  37. package/lib/selectors/index.d.ts +16 -0
  38. package/lib/selectors/index.js +5 -151
  39. package/lib/selectors/index.js.map +1 -1
  40. package/lib/utils/index.cjs +5 -3
  41. package/lib/utils/index.cjs.map +1 -1
  42. package/lib/utils/index.d.cts +19 -0
  43. package/lib/utils/index.d.ts +19 -0
  44. package/lib/utils/index.js +4 -2
  45. package/package.json +6 -6
  46. package/src/behavior-actions/behavior.action-utils.insert-block.ts +3 -3
  47. package/src/behavior-actions/behavior.action.block.set.ts +23 -0
  48. package/src/behavior-actions/behavior.action.block.unset.ts +21 -0
  49. package/src/behavior-actions/behavior.action.insert-break.ts +2 -69
  50. package/src/behavior-actions/behavior.action.insert.block.ts +29 -0
  51. package/src/behavior-actions/behavior.actions.ts +116 -96
  52. package/src/behaviors/behavior.core.annotations.ts +29 -0
  53. package/src/behaviors/behavior.core.block-objects.ts +13 -13
  54. package/src/behaviors/behavior.core.decorators.ts +19 -0
  55. package/src/behaviors/behavior.core.insert-break.ts +122 -0
  56. package/src/behaviors/behavior.core.lists.ts +57 -23
  57. package/src/behaviors/behavior.core.style.ts +19 -0
  58. package/src/behaviors/behavior.core.ts +18 -2
  59. package/src/behaviors/behavior.types.ts +103 -88
  60. package/src/converters/converter.json.ts +4 -4
  61. package/src/converters/converter.portable-text.deserialize.test.ts +1 -1
  62. package/src/converters/converter.portable-text.ts +4 -4
  63. package/src/converters/converter.text-html.deserialize.test.ts +1 -1
  64. package/src/converters/converter.text-html.serialize.test.ts +1 -1
  65. package/src/converters/converter.text-html.ts +4 -4
  66. package/src/converters/converter.text-plain.test.ts +1 -1
  67. package/src/converters/converter.text-plain.ts +3 -3
  68. package/src/converters/{converter.ts → converter.types.ts} +6 -0
  69. package/src/editor/create-editor.ts +50 -7
  70. package/src/editor/editor-machine.ts +46 -3
  71. package/src/editor/editor-snapshot.ts +1 -1
  72. package/src/editor/plugins/__tests__/withPortableTextMarkModel.test.tsx +2 -2
  73. package/src/editor/plugins/create-with-event-listeners.ts +41 -106
  74. package/src/selectors/index.ts +2 -0
  75. package/src/selectors/selector.is-at-the-end-of-block.ts +22 -0
  76. package/src/selectors/selector.is-at-the-start-of-block.ts +25 -0
  77. package/src/selectors/selector.is-selection-collapsed.ts +6 -2
  78. package/src/utils/index.ts +2 -0
  79. package/src/utils/util.get-block-end-point.ts +34 -0
  80. package/src/utils/util.is-equal-selection-points.ts +13 -0
  81. package/lib/_chunks-cjs/selector.is-selection-collapsed.cjs +0 -231
  82. package/lib/_chunks-cjs/selector.is-selection-collapsed.cjs.map +0 -1
  83. package/lib/_chunks-cjs/util.is-keyed-segment.cjs +0 -6
  84. package/lib/_chunks-cjs/util.is-keyed-segment.cjs.map +0 -1
  85. package/lib/_chunks-es/selector.is-selection-collapsed.js +0 -232
  86. package/lib/_chunks-es/selector.is-selection-collapsed.js.map +0 -1
  87. package/lib/_chunks-es/util.is-keyed-segment.js +0 -7
  88. package/lib/_chunks-es/util.is-keyed-segment.js.map +0 -1
  89. /package/src/converters/{converters.ts → converters.core.ts} +0 -0
@@ -38,6 +38,17 @@ export declare type EditorSelectionPoint = {
38
38
  offset: number
39
39
  }
40
40
 
41
+ /**
42
+ * @public
43
+ */
44
+ export declare function getBlockEndPoint({
45
+ node,
46
+ path,
47
+ }: {
48
+ node: PortableTextBlock
49
+ path: [KeyedSegment]
50
+ }): EditorSelectionPoint
51
+
41
52
  /**
42
53
  * @public
43
54
  */
@@ -59,6 +70,14 @@ export declare function getTextBlockText(block: PortableTextTextBlock): string
59
70
  */
60
71
  export declare function isEmptyTextBlock(block: PortableTextBlock): boolean
61
72
 
73
+ /**
74
+ * @public
75
+ */
76
+ export declare function isEqualSelectionPoints(
77
+ a: EditorSelectionPoint,
78
+ b: EditorSelectionPoint,
79
+ ): boolean
80
+
62
81
  /**
63
82
  * @public
64
83
  */
@@ -1,12 +1,14 @@
1
1
  import { blockOffsetToSpanSelectionPoint, getTextBlockText, isEmptyTextBlock, spanSelectionPointToBlockOffset } from "../_chunks-es/util.is-empty-text-block.js";
2
- import { getBlockStartPoint, reverseSelection } from "../_chunks-es/util.reverse-selection.js";
3
- import { isKeyedSegment } from "../_chunks-es/util.is-keyed-segment.js";
2
+ import { getBlockEndPoint, getBlockStartPoint, isEqualSelectionPoints, isKeyedSegment } from "../_chunks-es/util.is-equal-selection-points.js";
3
+ import { reverseSelection } from "../_chunks-es/util.reverse-selection.js";
4
4
  import { sliceBlocks } from "../_chunks-es/util.slice-blocks.js";
5
5
  export {
6
6
  blockOffsetToSpanSelectionPoint,
7
+ getBlockEndPoint,
7
8
  getBlockStartPoint,
8
9
  getTextBlockText,
9
10
  isEmptyTextBlock,
11
+ isEqualSelectionPoints,
10
12
  isKeyedSegment,
11
13
  reverseSelection,
12
14
  sliceBlocks,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@portabletext/editor",
3
- "version": "1.24.0",
3
+ "version": "1.26.0",
4
4
  "description": "Portable Text Editor made in React",
5
5
  "keywords": [
6
6
  "sanity",
@@ -73,15 +73,15 @@
73
73
  "slate-react": "0.112.1",
74
74
  "use-effect-event": "^1.0.2",
75
75
  "xstate": "^5.19.2",
76
- "@portabletext/block-tools": "1.1.1",
76
+ "@portabletext/block-tools": "1.1.2",
77
77
  "@portabletext/patches": "1.1.2"
78
78
  },
79
79
  "devDependencies": {
80
80
  "@portabletext/toolkit": "^2.0.16",
81
81
  "@sanity/diff-match-patch": "^3.2.0",
82
82
  "@sanity/pkg-utils": "^7.0.2",
83
- "@sanity/schema": "^3.71.0",
84
- "@sanity/types": "^3.71.0",
83
+ "@sanity/schema": "^3.71.1",
84
+ "@sanity/types": "^3.71.1",
85
85
  "@testing-library/jest-dom": "^6.6.3",
86
86
  "@testing-library/react": "^16.2.0",
87
87
  "@types/debug": "^4.1.12",
@@ -109,8 +109,8 @@
109
109
  "racejar": "1.1.1"
110
110
  },
111
111
  "peerDependencies": {
112
- "@sanity/schema": "^3.71.0",
113
- "@sanity/types": "^3.71.0",
112
+ "@sanity/schema": "^3.71.1",
113
+ "@sanity/types": "^3.71.1",
114
114
  "react": "^16.9 || ^17 || ^18 || ^19",
115
115
  "rxjs": "^7.8.1"
116
116
  },
@@ -52,10 +52,10 @@ export function insertBlock({
52
52
  Transforms.insertNodes(editor, block, {at: focusBlockPath})
53
53
  } else {
54
54
  Editor.insertNode(editor, block)
55
- }
56
55
 
57
- if (focusBlock && isEqualToEmptyEditor([focusBlock], schema)) {
58
- Transforms.removeNodes(editor, {at: focusBlockPath})
56
+ if (focusBlock && isEqualToEmptyEditor([focusBlock], schema)) {
57
+ Transforms.removeNodes(editor, {at: focusBlockPath})
58
+ }
59
59
  }
60
60
  }
61
61
  }
@@ -0,0 +1,23 @@
1
+ import {Transforms} from 'slate'
2
+ import {toSlateRange} from '../internal-utils/ranges'
3
+ import type {BehaviorActionImplementation} from './behavior.actions'
4
+
5
+ export const blockSetBehaviorActionImplementation: BehaviorActionImplementation<
6
+ 'block.set'
7
+ > = ({action}) => {
8
+ const location = toSlateRange(
9
+ {
10
+ anchor: {path: action.at, offset: 0},
11
+ focus: {path: action.at, offset: 0},
12
+ },
13
+ action.editor,
14
+ )
15
+
16
+ if (!location) {
17
+ return
18
+ }
19
+
20
+ const {at, editor, type, ...payload} = action
21
+
22
+ Transforms.setNodes(action.editor, payload, {at: location})
23
+ }
@@ -0,0 +1,21 @@
1
+ import {Transforms} from 'slate'
2
+ import {toSlateRange} from '../internal-utils/ranges'
3
+ import type {BehaviorActionImplementation} from './behavior.actions'
4
+
5
+ export const blockUnsetBehaviorActionImplementation: BehaviorActionImplementation<
6
+ 'block.unset'
7
+ > = ({action}) => {
8
+ const location = toSlateRange(
9
+ {
10
+ anchor: {path: action.at, offset: 0},
11
+ focus: {path: action.at, offset: 0},
12
+ },
13
+ action.editor,
14
+ )
15
+
16
+ if (!location) {
17
+ return
18
+ }
19
+
20
+ Transforms.unsetNodes(action.editor, action.props, {at: location})
21
+ }
@@ -1,5 +1,5 @@
1
1
  import {isEqual} from 'lodash'
2
- import {Editor, Node, Path, Range, Transforms} from 'slate'
2
+ import {Editor, Node, Path, Transforms} from 'slate'
3
3
  import type {SlateTextBlock, VoidElement} from '../types/slate'
4
4
  import type {BehaviorActionImplementation} from './behavior.actions'
5
5
 
@@ -14,24 +14,6 @@ export const insertBreakActionImplementation: BehaviorActionImplementation<
14
14
  return
15
15
  }
16
16
 
17
- const [focusSpan] = Array.from(
18
- Editor.nodes(editor, {
19
- mode: 'lowest',
20
- at: editor.selection.focus,
21
- match: (n) => editor.isTextSpan(n),
22
- voids: false,
23
- }),
24
- )[0] ?? [undefined]
25
- const focusDecorators =
26
- focusSpan?.marks?.filter((mark) =>
27
- schema.decorators.some((decorator) => decorator.value === mark),
28
- ) ?? []
29
- const focusAnnotations =
30
- focusSpan?.marks?.filter(
31
- (mark) =>
32
- !schema.decorators.some((decorator) => decorator.value === mark),
33
- ) ?? []
34
-
35
17
  const anchorBlockPath = editor.selection.anchor.path.slice(0, 1)
36
18
  const focusBlockPath = editor.selection.focus.path.slice(0, 1)
37
19
  const focusBlock = Node.descendant(editor, focusBlockPath) as
@@ -39,58 +21,9 @@ export const insertBreakActionImplementation: BehaviorActionImplementation<
39
21
  | VoidElement
40
22
 
41
23
  if (editor.isTextBlock(focusBlock)) {
42
- const [start, end] = Range.edges(editor.selection)
43
- const lastFocusBlockChild =
44
- focusBlock.children[focusBlock.children.length - 1]
45
- const atTheEndOfBlock = isEqual(start, {
46
- path: [...focusBlockPath, focusBlock.children.length - 1],
47
- offset: editor.isTextSpan(lastFocusBlockChild)
48
- ? lastFocusBlockChild.text.length
49
- : 0,
50
- })
51
- const atTheStartOfBlock = isEqual(end, {
52
- path: [...focusBlockPath, 0],
53
- offset: 0,
54
- })
55
-
56
- if (atTheEndOfBlock && Range.isCollapsed(editor.selection)) {
57
- Editor.insertNode(
58
- editor,
59
- editor.pteCreateTextBlock({
60
- decorators: [],
61
- listItem: focusBlock.listItem,
62
- level: focusBlock.level,
63
- }),
64
- )
65
-
66
- return
67
- }
68
-
69
- if (atTheStartOfBlock && Range.isCollapsed(editor.selection)) {
70
- Editor.insertNode(
71
- editor,
72
- editor.pteCreateTextBlock({
73
- decorators: focusAnnotations.length === 0 ? focusDecorators : [],
74
- listItem: focusBlock.listItem,
75
- level: focusBlock.level,
76
- }),
77
- )
78
-
79
- const [nextBlockPath] = Path.next(focusBlockPath)
80
-
81
- Transforms.select(editor, {
82
- anchor: {path: [nextBlockPath, 0], offset: 0},
83
- focus: {path: [nextBlockPath, 0], offset: 0},
84
- })
85
-
86
- return
87
- }
88
-
89
24
  const selectionAcrossBlocks = anchorBlockPath[0] !== focusBlockPath[0]
90
25
 
91
- const isInTheMiddleOfNode = !atTheStartOfBlock && !atTheEndOfBlock
92
-
93
- if (isInTheMiddleOfNode && !selectionAcrossBlocks) {
26
+ if (!selectionAcrossBlocks) {
94
27
  Editor.withoutNormalizing(editor, () => {
95
28
  if (!editor.selection) {
96
29
  return
@@ -0,0 +1,29 @@
1
+ import {parseBlock} from '../internal-utils/parse-blocks'
2
+ import {toSlateValue} from '../internal-utils/values'
3
+ import {insertBlock} from './behavior.action-utils.insert-block'
4
+ import type {BehaviorActionImplementation} from './behavior.actions'
5
+
6
+ export const insertBlockActionImplementation: BehaviorActionImplementation<
7
+ 'insert.block'
8
+ > = ({context, action}) => {
9
+ const parsedBlock = parseBlock({block: action.block, context})
10
+
11
+ if (!parsedBlock) {
12
+ throw new Error(`Failed to parse block ${JSON.stringify(action.block)}`)
13
+ }
14
+
15
+ const fragment = toSlateValue([parsedBlock], {schemaTypes: context.schema})[0]
16
+
17
+ if (!fragment) {
18
+ throw new Error(
19
+ `Failed to convert block to Slate fragment ${JSON.stringify(parsedBlock)}`,
20
+ )
21
+ }
22
+
23
+ insertBlock({
24
+ block: fragment,
25
+ placement: action.placement,
26
+ editor: action.editor,
27
+ schema: context.schema,
28
+ })
29
+ }