@portabletext/editor 2.19.3 → 2.21.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.
@@ -1,12 +1,12 @@
1
+ import { getBlockKeyFromSelectionPoint } from "../_chunks-es/util.get-text-block-text.js";
1
2
  import { blockOffsetToSpanSelectionPoint, getBlockStartPoint, getSelectionEndPoint, getSelectionStartPoint, getTextBlockText, isKeyedSegment, sliceBlocks, spanSelectionPointToBlockOffset } from "../_chunks-es/util.get-text-block-text.js";
2
- import { blockOffsetToBlockSelectionPoint, blockOffsetToSelectionPoint, blockOffsetsToSelection, childSelectionPointToBlockOffset } from "../_chunks-es/util.child-selection-point-to-block-offset.js";
3
+ import { childSelectionPointToBlockOffset } from "../_chunks-es/util.merge-text-blocks.js";
4
+ import { blockOffsetToBlockSelectionPoint, blockOffsetToSelectionPoint, blockOffsetsToSelection, mergeTextBlocks } from "../_chunks-es/util.merge-text-blocks.js";
3
5
  import { isEqualSelectionPoints } from "../_chunks-es/util.is-empty-text-block.js";
4
6
  import { getBlockEndPoint, isEmptyTextBlock, isSelectionCollapsed } from "../_chunks-es/util.is-empty-text-block.js";
5
7
  import { isSpan } from "@portabletext/schema";
6
8
  import { isSpan as isSpan2, isTextBlock } from "@portabletext/schema";
7
- import { mergeTextBlocks } from "../_chunks-es/util.merge-text-blocks.js";
8
9
  import { sliceTextBlock } from "../_chunks-es/util.slice-text-block.js";
9
- import { selectionPointToBlockOffset } from "../_chunks-es/util.slice-text-block.js";
10
10
  function isEqualSelections(a, b) {
11
11
  return !a && !b ? !0 : !a || !b ? !1 : isEqualSelectionPoints(a.anchor, b.anchor) && isEqualSelectionPoints(a.focus, b.focus);
12
12
  }
@@ -21,6 +21,21 @@ function reverseSelection(selection) {
21
21
  backward: !0
22
22
  });
23
23
  }
24
+ function selectionPointToBlockOffset({
25
+ context,
26
+ selectionPoint
27
+ }) {
28
+ const blockKey = getBlockKeyFromSelectionPoint(selectionPoint);
29
+ return selectionPoint.path.length === 1 && blockKey !== void 0 ? {
30
+ path: [{
31
+ _key: blockKey
32
+ }],
33
+ offset: selectionPoint.offset
34
+ } : childSelectionPointToBlockOffset({
35
+ context,
36
+ selectionPoint
37
+ });
38
+ }
24
39
  function splitTextBlock({
25
40
  context,
26
41
  block,
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":["../../src/utils/util.is-equal-selections.ts","../../src/utils/util.reverse-selection.ts","../../src/utils/util.split-text-block.ts"],"sourcesContent":["import type {EditorSelection} from '../types/editor'\nimport {isEqualSelectionPoints} from './util.is-equal-selection-points'\n\n/**\n * @public\n */\nexport function isEqualSelections(a: EditorSelection, b: EditorSelection) {\n if (!a && !b) {\n return true\n }\n\n if (!a || !b) {\n return false\n }\n\n return (\n isEqualSelectionPoints(a.anchor, b.anchor) &&\n isEqualSelectionPoints(a.focus, b.focus)\n )\n}\n","import type {EditorSelection} from '../types/editor'\n\n/**\n * @public\n */\nexport function reverseSelection<\n TEditorSelection extends NonNullable<EditorSelection> | null,\n>(selection: TEditorSelection): TEditorSelection {\n if (!selection) {\n return selection\n }\n\n if (selection.backward) {\n return {\n anchor: selection.focus,\n focus: selection.anchor,\n backward: false,\n } as TEditorSelection\n }\n\n return {\n anchor: selection.focus,\n focus: selection.anchor,\n backward: true,\n } as TEditorSelection\n}\n","import {isSpan} from '@portabletext/schema'\nimport type {PortableTextTextBlock} from '@sanity/types'\nimport type {EditorContext} from '../editor/editor-snapshot'\nimport type {EditorSelectionPoint} from '../types/editor'\nimport {sliceTextBlock} from './util.slice-text-block'\n\n/**\n * @beta\n */\nexport function splitTextBlock({\n context,\n block,\n point,\n}: {\n context: Pick<EditorContext, 'schema'>\n block: PortableTextTextBlock\n point: EditorSelectionPoint\n}): {before: PortableTextTextBlock; after: PortableTextTextBlock} | undefined {\n const firstChild = block.children.at(0)\n const lastChild = block.children.at(block.children.length - 1)\n\n if (!firstChild || !lastChild) {\n return undefined\n }\n\n const before = sliceTextBlock({\n context: {\n schema: context.schema,\n selection: {\n anchor: {\n path: [{_key: block._key}, 'children', {_key: firstChild._key}],\n offset: 0,\n },\n focus: point,\n },\n },\n block,\n })\n const after = sliceTextBlock({\n context: {\n schema: context.schema,\n selection: {\n anchor: point,\n focus: {\n path: [{_key: block._key}, 'children', {_key: lastChild._key}],\n offset: isSpan(context, lastChild) ? lastChild.text.length : 0,\n },\n },\n },\n block,\n })\n\n return {before, after}\n}\n"],"names":["isEqualSelections","a","b","isEqualSelectionPoints","anchor","focus","reverseSelection","selection","backward","splitTextBlock","context","block","point","firstChild","children","at","lastChild","length","before","sliceTextBlock","schema","path","_key","offset","after","isSpan","text"],"mappings":";;;;;;;;;AAMO,SAASA,kBAAkBC,GAAoBC,GAAoB;AACxE,SAAI,CAACD,KAAK,CAACC,IACF,KAGL,CAACD,KAAK,CAACC,IACF,KAIPC,uBAAuBF,EAAEG,QAAQF,EAAEE,MAAM,KACzCD,uBAAuBF,EAAEI,OAAOH,EAAEG,KAAK;AAE3C;ACdO,SAASC,iBAEdC,WAA+C;AAC/C,SAAKA,cAIDA,UAAUC,WACL;AAAA,IACLJ,QAAQG,UAAUF;AAAAA,IAClBA,OAAOE,UAAUH;AAAAA,IACjBI,UAAU;AAAA,EAAA,IAIP;AAAA,IACLJ,QAAQG,UAAUF;AAAAA,IAClBA,OAAOE,UAAUH;AAAAA,IACjBI,UAAU;AAAA,EAAA;AAEd;AChBO,SAASC,eAAe;AAAA,EAC7BC;AAAAA,EACAC;AAAAA,EACAC;AAKF,GAA8E;AAC5E,QAAMC,aAAaF,MAAMG,SAASC,GAAG,CAAC,GAChCC,YAAYL,MAAMG,SAASC,GAAGJ,MAAMG,SAASG,SAAS,CAAC;AAE7D,MAAI,CAACJ,cAAc,CAACG;AAClB;AAGF,QAAME,SAASC,eAAe;AAAA,IAC5BT,SAAS;AAAA,MACPU,QAAQV,QAAQU;AAAAA,MAChBb,WAAW;AAAA,QACTH,QAAQ;AAAA,UACNiB,MAAM,CAAC;AAAA,YAACC,MAAMX,MAAMW;AAAAA,UAAAA,GAAO,YAAY;AAAA,YAACA,MAAMT,WAAWS;AAAAA,UAAAA,CAAK;AAAA,UAC9DC,QAAQ;AAAA,QAAA;AAAA,QAEVlB,OAAOO;AAAAA,MAAAA;AAAAA,IACT;AAAA,IAEFD;AAAAA,EAAAA,CACD,GACKa,QAAQL,eAAe;AAAA,IAC3BT,SAAS;AAAA,MACPU,QAAQV,QAAQU;AAAAA,MAChBb,WAAW;AAAA,QACTH,QAAQQ;AAAAA,QACRP,OAAO;AAAA,UACLgB,MAAM,CAAC;AAAA,YAACC,MAAMX,MAAMW;AAAAA,UAAAA,GAAO,YAAY;AAAA,YAACA,MAAMN,UAAUM;AAAAA,UAAAA,CAAK;AAAA,UAC7DC,QAAQE,OAAOf,SAASM,SAAS,IAAIA,UAAUU,KAAKT,SAAS;AAAA,QAAA;AAAA,MAC/D;AAAA,IACF;AAAA,IAEFN;AAAAA,EAAAA,CACD;AAED,SAAO;AAAA,IAACO;AAAAA,IAAQM;AAAAA,EAAAA;AAClB;"}
1
+ {"version":3,"file":"index.js","sources":["../../src/utils/util.is-equal-selections.ts","../../src/utils/util.reverse-selection.ts","../../src/utils/util.selection-point-to-block-offset.ts","../../src/utils/util.split-text-block.ts"],"sourcesContent":["import type {EditorSelection} from '../types/editor'\nimport {isEqualSelectionPoints} from './util.is-equal-selection-points'\n\n/**\n * @public\n */\nexport function isEqualSelections(a: EditorSelection, b: EditorSelection) {\n if (!a && !b) {\n return true\n }\n\n if (!a || !b) {\n return false\n }\n\n return (\n isEqualSelectionPoints(a.anchor, b.anchor) &&\n isEqualSelectionPoints(a.focus, b.focus)\n )\n}\n","import type {EditorSelection} from '../types/editor'\n\n/**\n * @public\n */\nexport function reverseSelection<\n TEditorSelection extends NonNullable<EditorSelection> | null,\n>(selection: TEditorSelection): TEditorSelection {\n if (!selection) {\n return selection\n }\n\n if (selection.backward) {\n return {\n anchor: selection.focus,\n focus: selection.anchor,\n backward: false,\n } as TEditorSelection\n }\n\n return {\n anchor: selection.focus,\n focus: selection.anchor,\n backward: true,\n } as TEditorSelection\n}\n","import type {EditorContext} from '../editor/editor-snapshot'\nimport type {BlockOffset} from '../types/block-offset'\nimport type {EditorSelectionPoint} from '../types/editor'\nimport {childSelectionPointToBlockOffset} from './util.child-selection-point-to-block-offset'\nimport {getBlockKeyFromSelectionPoint} from './util.selection-point'\n\n/**\n * @public\n */\nexport function selectionPointToBlockOffset({\n context,\n selectionPoint,\n}: {\n context: Pick<EditorContext, 'schema' | 'value'>\n selectionPoint: EditorSelectionPoint\n}): BlockOffset | undefined {\n const blockKey = getBlockKeyFromSelectionPoint(selectionPoint)\n\n if (selectionPoint.path.length === 1 && blockKey !== undefined) {\n return {\n path: [{_key: blockKey}],\n offset: selectionPoint.offset,\n }\n }\n\n return childSelectionPointToBlockOffset({\n context,\n selectionPoint,\n })\n}\n","import {isSpan} from '@portabletext/schema'\nimport type {PortableTextTextBlock} from '@sanity/types'\nimport type {EditorContext} from '../editor/editor-snapshot'\nimport type {EditorSelectionPoint} from '../types/editor'\nimport {sliceTextBlock} from './util.slice-text-block'\n\n/**\n * @beta\n */\nexport function splitTextBlock({\n context,\n block,\n point,\n}: {\n context: Pick<EditorContext, 'schema'>\n block: PortableTextTextBlock\n point: EditorSelectionPoint\n}): {before: PortableTextTextBlock; after: PortableTextTextBlock} | undefined {\n const firstChild = block.children.at(0)\n const lastChild = block.children.at(block.children.length - 1)\n\n if (!firstChild || !lastChild) {\n return undefined\n }\n\n const before = sliceTextBlock({\n context: {\n schema: context.schema,\n selection: {\n anchor: {\n path: [{_key: block._key}, 'children', {_key: firstChild._key}],\n offset: 0,\n },\n focus: point,\n },\n },\n block,\n })\n const after = sliceTextBlock({\n context: {\n schema: context.schema,\n selection: {\n anchor: point,\n focus: {\n path: [{_key: block._key}, 'children', {_key: lastChild._key}],\n offset: isSpan(context, lastChild) ? lastChild.text.length : 0,\n },\n },\n },\n block,\n })\n\n return {before, after}\n}\n"],"names":["isEqualSelections","a","b","isEqualSelectionPoints","anchor","focus","reverseSelection","selection","backward","selectionPointToBlockOffset","context","selectionPoint","blockKey","getBlockKeyFromSelectionPoint","path","length","undefined","_key","offset","childSelectionPointToBlockOffset","splitTextBlock","block","point","firstChild","children","at","lastChild","before","sliceTextBlock","schema","after","isSpan","text"],"mappings":";;;;;;;;;AAMO,SAASA,kBAAkBC,GAAoBC,GAAoB;AACxE,SAAI,CAACD,KAAK,CAACC,IACF,KAGL,CAACD,KAAK,CAACC,IACF,KAIPC,uBAAuBF,EAAEG,QAAQF,EAAEE,MAAM,KACzCD,uBAAuBF,EAAEI,OAAOH,EAAEG,KAAK;AAE3C;ACdO,SAASC,iBAEdC,WAA+C;AAC/C,SAAKA,cAIDA,UAAUC,WACL;AAAA,IACLJ,QAAQG,UAAUF;AAAAA,IAClBA,OAAOE,UAAUH;AAAAA,IACjBI,UAAU;AAAA,EAAA,IAIP;AAAA,IACLJ,QAAQG,UAAUF;AAAAA,IAClBA,OAAOE,UAAUH;AAAAA,IACjBI,UAAU;AAAA,EAAA;AAEd;AChBO,SAASC,4BAA4B;AAAA,EAC1CC;AAAAA,EACAC;AAIF,GAA4B;AAC1B,QAAMC,WAAWC,8BAA8BF,cAAc;AAE7D,SAAIA,eAAeG,KAAKC,WAAW,KAAKH,aAAaI,SAC5C;AAAA,IACLF,MAAM,CAAC;AAAA,MAACG,MAAML;AAAAA,IAAAA,CAAS;AAAA,IACvBM,QAAQP,eAAeO;AAAAA,EAAAA,IAIpBC,iCAAiC;AAAA,IACtCT;AAAAA,IACAC;AAAAA,EAAAA,CACD;AACH;ACpBO,SAASS,eAAe;AAAA,EAC7BV;AAAAA,EACAW;AAAAA,EACAC;AAKF,GAA8E;AAC5E,QAAMC,aAAaF,MAAMG,SAASC,GAAG,CAAC,GAChCC,YAAYL,MAAMG,SAASC,GAAGJ,MAAMG,SAAST,SAAS,CAAC;AAE7D,MAAI,CAACQ,cAAc,CAACG;AAClB;AAGF,QAAMC,SAASC,eAAe;AAAA,IAC5BlB,SAAS;AAAA,MACPmB,QAAQnB,QAAQmB;AAAAA,MAChBtB,WAAW;AAAA,QACTH,QAAQ;AAAA,UACNU,MAAM,CAAC;AAAA,YAACG,MAAMI,MAAMJ;AAAAA,UAAAA,GAAO,YAAY;AAAA,YAACA,MAAMM,WAAWN;AAAAA,UAAAA,CAAK;AAAA,UAC9DC,QAAQ;AAAA,QAAA;AAAA,QAEVb,OAAOiB;AAAAA,MAAAA;AAAAA,IACT;AAAA,IAEFD;AAAAA,EAAAA,CACD,GACKS,QAAQF,eAAe;AAAA,IAC3BlB,SAAS;AAAA,MACPmB,QAAQnB,QAAQmB;AAAAA,MAChBtB,WAAW;AAAA,QACTH,QAAQkB;AAAAA,QACRjB,OAAO;AAAA,UACLS,MAAM,CAAC;AAAA,YAACG,MAAMI,MAAMJ;AAAAA,UAAAA,GAAO,YAAY;AAAA,YAACA,MAAMS,UAAUT;AAAAA,UAAAA,CAAK;AAAA,UAC7DC,QAAQa,OAAOrB,SAASgB,SAAS,IAAIA,UAAUM,KAAKjB,SAAS;AAAA,QAAA;AAAA,MAC/D;AAAA,IACF;AAAA,IAEFM;AAAAA,EAAAA,CACD;AAED,SAAO;AAAA,IAACM;AAAAA,IAAQG;AAAAA,EAAAA;AAClB;"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@portabletext/editor",
3
- "version": "2.19.3",
3
+ "version": "2.21.0",
4
4
  "description": "Portable Text Editor made in React",
5
5
  "keywords": [
6
6
  "sanity",
@@ -73,7 +73,7 @@
73
73
  "slate-dom": "^0.118.1",
74
74
  "slate-react": "0.118.2",
75
75
  "xstate": "^5.24.0",
76
- "@portabletext/block-tools": "^4.0.1",
76
+ "@portabletext/block-tools": "^4.0.2",
77
77
  "@portabletext/keyboard-shortcuts": "^2.1.0",
78
78
  "@portabletext/patches": "^2.0.0",
79
79
  "@portabletext/schema": "^2.0.0"
@@ -81,8 +81,8 @@
81
81
  "devDependencies": {
82
82
  "@sanity/diff-match-patch": "^3.2.0",
83
83
  "@sanity/pkg-utils": "^9.0.3",
84
- "@sanity/schema": "^4.14.1",
85
- "@sanity/types": "^4.14.1",
84
+ "@sanity/schema": "^4.14.2",
85
+ "@sanity/types": "^4.14.2",
86
86
  "@types/debug": "^4.1.12",
87
87
  "@types/lodash": "^4.17.20",
88
88
  "@types/lodash.startcase": "^4.4.9",
@@ -106,14 +106,14 @@
106
106
  "vite": "^7.1.12",
107
107
  "vitest": "^4.0.6",
108
108
  "vitest-browser-react": "^2.0.2",
109
- "@portabletext/sanity-bridge": "1.2.1",
109
+ "@portabletext/sanity-bridge": "1.2.2",
110
110
  "@portabletext/test": "^1.0.0",
111
111
  "racejar": "2.0.0"
112
112
  },
113
113
  "peerDependencies": {
114
- "@portabletext/sanity-bridge": "^1.2.1",
115
- "@sanity/schema": "^4.14.1",
116
- "@sanity/types": "^4.14.1",
114
+ "@portabletext/sanity-bridge": "^1.2.2",
115
+ "@sanity/schema": "^4.14.2",
116
+ "@sanity/types": "^4.14.2",
117
117
  "react": "^18.3 || ^19",
118
118
  "rxjs": "^7.8.2"
119
119
  },
@@ -1,5 +1,4 @@
1
1
  import {isActiveDecorator} from '../selectors/selector.is-active-decorator'
2
- import {blockOffsetsToSelection} from '../utils/util.block-offsets-to-selection'
3
2
  import {raise} from './behavior.types.action'
4
3
  import {defineBehavior} from './behavior.types.behavior'
5
4
 
@@ -16,19 +15,12 @@ export const abstractDecoratorBehaviors = [
16
15
  defineBehavior({
17
16
  on: 'decorator.toggle',
18
17
  guard: ({snapshot, event}) => {
19
- const manualSelection = event.at
20
- ? blockOffsetsToSelection({
21
- context: snapshot.context,
22
- offsets: event.at,
23
- })
24
- : null
25
-
26
- if (manualSelection) {
18
+ if (event.at) {
27
19
  return !isActiveDecorator(event.decorator)({
28
20
  ...snapshot,
29
21
  context: {
30
22
  ...snapshot.context,
31
- selection: manualSelection,
23
+ selection: event.at,
32
24
  },
33
25
  })
34
26
  }
@@ -3,10 +3,8 @@ import {getFocusChild} from '../selectors/selector.get-focus-child'
3
3
  import {getFocusTextBlock} from '../selectors/selector.get-focus-text-block'
4
4
  import {getNextBlock} from '../selectors/selector.get-next-block'
5
5
  import {getPreviousBlock} from '../selectors/selector.get-previous-block'
6
- import {getTrimmedSelection} from '../selectors/selector.get-trimmed-selection'
7
6
  import {isAtTheEndOfBlock} from '../selectors/selector.is-at-the-end-of-block'
8
7
  import {isAtTheStartOfBlock} from '../selectors/selector.is-at-the-start-of-block'
9
- import {blockOffsetsToSelection} from '../utils/util.block-offsets-to-selection'
10
8
  import {getBlockEndPoint} from '../utils/util.get-block-end-point'
11
9
  import {getBlockStartPoint} from '../utils/util.get-block-start-point'
12
10
  import {isEmptyTextBlock} from '../utils/util.is-empty-text-block'
@@ -270,33 +268,6 @@ export const abstractDeleteBehaviors = [
270
268
  }),
271
269
  defineBehavior({
272
270
  on: 'delete.text',
273
- guard: ({snapshot, event}) => {
274
- const selection = blockOffsetsToSelection({
275
- context: snapshot.context,
276
- offsets: event.at,
277
- })
278
-
279
- if (!selection) {
280
- return false
281
- }
282
-
283
- const trimmedSelection = getTrimmedSelection({
284
- ...snapshot,
285
- context: {
286
- ...snapshot.context,
287
- value: snapshot.context.value,
288
- selection,
289
- },
290
- })
291
-
292
- if (!trimmedSelection) {
293
- return false
294
- }
295
-
296
- return {
297
- selection: trimmedSelection,
298
- }
299
- },
300
- actions: [(_, {selection}) => [raise({type: 'delete', at: selection})]],
271
+ actions: [({event}) => [raise({...event, type: 'delete'})]],
301
272
  }),
302
273
  ]
@@ -2,7 +2,6 @@ import type {PortableTextBlock} from '@sanity/types'
2
2
  import type {EventPosition} from '../internal-utils/event-position'
3
3
  import type {MIMEType} from '../internal-utils/mime-type'
4
4
  import type {OmitFromUnion, PickFromUnion, StrictExtract} from '../type-utils'
5
- import type {BlockOffset} from '../types/block-offset'
6
5
  import type {
7
6
  BlockWithOptionalKey,
8
7
  ChildWithOptionalKey,
@@ -130,10 +129,7 @@ export type SyntheticBehaviorEvent =
130
129
  | {
131
130
  type: StrictExtract<SyntheticBehaviorEventType, 'decorator.add'>
132
131
  decorator: string
133
- at?: {
134
- anchor: BlockOffset
135
- focus: BlockOffset
136
- }
132
+ at?: NonNullable<EditorSelection>
137
133
  }
138
134
  | {
139
135
  type: StrictExtract<SyntheticBehaviorEventType, 'decorator.remove'>
@@ -162,6 +158,7 @@ export type SyntheticBehaviorEvent =
162
158
  block: BlockWithOptionalKey
163
159
  placement: InsertPlacement
164
160
  select?: 'start' | 'end' | 'none'
161
+ at?: NonNullable<EditorSelection>
165
162
  }
166
163
  | {
167
164
  type: StrictExtract<SyntheticBehaviorEventType, 'insert.child'>
@@ -268,7 +265,7 @@ type AbstractBehaviorEvent =
268
265
  | {
269
266
  type: StrictExtract<SyntheticBehaviorEventType, 'decorator.toggle'>
270
267
  decorator: string
271
- at?: {anchor: BlockOffset; focus: BlockOffset}
268
+ at?: NonNullable<EditorSelection>
272
269
  }
273
270
  | {
274
271
  type: StrictExtract<SyntheticBehaviorEventType, 'delete.backward'>
@@ -288,10 +285,7 @@ type AbstractBehaviorEvent =
288
285
  }
289
286
  | {
290
287
  type: StrictExtract<SyntheticBehaviorEventType, 'delete.text'>
291
- at: {
292
- anchor: BlockOffset
293
- focus: BlockOffset
294
- }
288
+ at: NonNullable<EditorSelection>
295
289
  }
296
290
  | {
297
291
  type: StrictExtract<SyntheticBehaviorEventType, 'deserialize'>
@@ -1,12 +1,5 @@
1
1
  import {Editor, Range, Text, Transforms} from 'slate'
2
- import {KEY_TO_VALUE_ELEMENT} from '../editor/weakMaps'
3
- import {slateRangeToSelection} from '../internal-utils/slate-utils'
4
2
  import {toSlateRange} from '../internal-utils/to-slate-range'
5
- import {fromSlateValue} from '../internal-utils/values'
6
- import {getTrimmedSelection} from '../selectors/selector.get-trimmed-selection'
7
- import {blockOffsetToSpanSelectionPoint} from '../utils/util.block-offset'
8
- import {blockOffsetsToSelection} from '../utils/util.block-offsets-to-selection'
9
- import {selectionPointToBlockOffset} from '../utils/util.selection-point-to-block-offset'
10
3
  import type {BehaviorOperationImplementation} from './behavior.operations'
11
4
 
12
5
  export const decoratorAddOperationImplementation: BehaviorOperationImplementation<
@@ -14,147 +7,61 @@ export const decoratorAddOperationImplementation: BehaviorOperationImplementatio
14
7
  > = ({context, operation}) => {
15
8
  const editor = operation.editor
16
9
  const mark = operation.decorator
17
- const value = fromSlateValue(
18
- editor.children,
19
- context.schema.block.name,
20
- KEY_TO_VALUE_ELEMENT.get(editor),
21
- )
22
-
23
- const manualAnchor = operation.at?.anchor
24
- ? blockOffsetToSpanSelectionPoint({
25
- context: {
26
- ...context,
27
- value,
28
- },
29
- blockOffset: operation.at.anchor,
30
- direction: 'backward',
31
- })
32
- : undefined
33
- const manualFocus = operation.at?.focus
34
- ? blockOffsetToSpanSelectionPoint({
35
- context: {
36
- ...context,
37
- value,
38
- },
39
- blockOffset: operation.at.focus,
40
- direction: 'forward',
41
- })
42
- : undefined
43
- const manualSelection =
44
- manualAnchor && manualFocus
45
- ? {
46
- anchor: manualAnchor,
47
- focus: manualFocus,
48
- }
49
- : undefined
50
-
51
- const selection = manualSelection
52
- ? (toSlateRange({
10
+
11
+ let at = operation.at
12
+ ? toSlateRange({
53
13
  context: {
54
14
  schema: context.schema,
55
15
  value: operation.editor.value,
56
- selection: manualSelection,
16
+ selection: operation.at,
57
17
  },
58
18
  blockIndexMap: operation.editor.blockIndexMap,
59
- }) ?? editor.selection)
60
- : editor.selection
19
+ })
20
+ : operation.editor.selection
61
21
 
62
- if (!selection) {
63
- return
22
+ if (!at) {
23
+ throw new Error('Unable to add decorator without a selection')
64
24
  }
65
25
 
66
- const editorSelection = slateRangeToSelection({
67
- schema: context.schema,
68
- editor,
69
- range: selection,
70
- })
71
- const anchorOffset = editorSelection
72
- ? selectionPointToBlockOffset({
73
- context: {
74
- ...context,
75
- value,
76
- },
77
- selectionPoint: editorSelection.anchor,
78
- })
79
- : undefined
80
- const focusOffset = editorSelection
81
- ? selectionPointToBlockOffset({
82
- context: {
83
- ...context,
84
- value,
85
- },
86
- selectionPoint: editorSelection.focus,
87
- })
88
- : undefined
26
+ if (Range.isExpanded(at)) {
27
+ const rangeRef = Editor.rangeRef(editor, at, {affinity: 'inward'})
28
+ const [start, end] = Range.edges(at)
89
29
 
90
- if (!anchorOffset || !focusOffset) {
91
- throw new Error('Unable to find anchor or focus offset')
92
- }
30
+ const endAtEndOfNode = Editor.isEnd(editor, end, end.path)
93
31
 
94
- if (Range.isExpanded(selection)) {
95
- // Split if needed
96
- Transforms.setNodes(
97
- editor,
98
- {},
99
- {at: selection, match: Text.isText, split: true, hanging: true},
100
- )
101
-
102
- // The value might have changed after splitting
103
- const newValue = fromSlateValue(
104
- editor.children,
105
- context.schema.block.name,
106
- KEY_TO_VALUE_ELEMENT.get(editor),
107
- )
108
- // We need to find the new selection from the original offsets because the
109
- // split operation might have changed the value.
110
- const newSelection = blockOffsetsToSelection({
111
- context: {
112
- ...context,
113
- value: newValue,
114
- },
115
- offsets: {anchor: anchorOffset, focus: focusOffset},
116
- backward: editorSelection?.backward,
32
+ Transforms.splitNodes(editor, {
33
+ at: end,
34
+ match: Text.isText,
35
+ mode: 'lowest',
36
+ voids: false,
37
+ always: !endAtEndOfNode,
117
38
  })
118
39
 
119
- const trimmedSelection = getTrimmedSelection({
120
- blockIndexMap: editor.blockIndexMap,
121
- context: {
122
- converters: [],
123
- keyGenerator: context.keyGenerator,
124
- readOnly: false,
125
- schema: context.schema,
126
- selection: newSelection,
127
- value: newValue,
128
- },
129
- decoratorState: editor.decoratorState,
40
+ const startAtStartOfNode = Editor.isStart(editor, start, start.path)
41
+
42
+ Transforms.splitNodes(editor, {
43
+ at: start,
44
+ match: Text.isText,
45
+ mode: 'lowest',
46
+ voids: false,
47
+ always: !startAtStartOfNode,
130
48
  })
131
49
 
132
- if (!trimmedSelection) {
133
- throw new Error('Unable to find trimmed selection')
134
- }
50
+ at = rangeRef.unref()
135
51
 
136
- const newRange = toSlateRange({
137
- context: {
138
- schema: context.schema,
139
- value: operation.editor.value,
140
- selection: trimmedSelection,
141
- },
142
- blockIndexMap: operation.editor.blockIndexMap,
143
- })
52
+ if (!at) {
53
+ throw new Error('Unable to add decorator without a selection')
54
+ }
144
55
 
145
- if (!newRange) {
146
- throw new Error('Unable to find new selection')
56
+ if (!operation.at) {
57
+ Transforms.select(editor, at)
147
58
  }
148
59
 
149
60
  // Use new selection to find nodes to decorate
150
- const splitTextNodes = Range.isRange(newRange)
151
- ? [
152
- ...Editor.nodes(editor, {
153
- at: newRange,
154
- match: (node) => Text.isText(node),
155
- }),
156
- ]
157
- : []
61
+ const splitTextNodes = Editor.nodes(editor, {
62
+ at,
63
+ match: Text.isText,
64
+ })
158
65
 
159
66
  for (const [node, path] of splitTextNodes) {
160
67
  const marks = [
@@ -172,7 +79,7 @@ export const decoratorAddOperationImplementation: BehaviorOperationImplementatio
172
79
  } else {
173
80
  const selectedSpan = Array.from(
174
81
  Editor.nodes(editor, {
175
- at: selection,
82
+ at,
176
83
  match: (node) => editor.isTextSpan(node),
177
84
  }),
178
85
  )?.at(0)
@@ -181,7 +88,7 @@ export const decoratorAddOperationImplementation: BehaviorOperationImplementatio
181
88
  return
182
89
  }
183
90
 
184
- const [block, blockPath] = Editor.node(editor, selection, {
91
+ const [block, blockPath] = Editor.node(editor, at, {
185
92
  depth: 1,
186
93
  })
187
94
  const lonelyEmptySpan =