@portabletext/editor 1.33.6 → 1.34.1

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,8 +1,30 @@
1
- import { sliceBlocks, isSpan } from "../_chunks-es/util.slice-blocks.js";
2
- import { blockOffsetToSpanSelectionPoint, getBlockEndPoint, getBlockStartPoint, getTextBlockText, isEmptyTextBlock, isEqualSelectionPoints, isKeyedSegment, spanSelectionPointToBlockOffset } from "../_chunks-es/util.slice-blocks.js";
1
+ import { isKeyedSegment, sliceBlocks, isSpan } from "../_chunks-es/util.slice-blocks.js";
2
+ import { blockOffsetToSpanSelectionPoint, getBlockEndPoint, getBlockStartPoint, getTextBlockText, isEmptyTextBlock, isEqualSelectionPoints, spanSelectionPointToBlockOffset } from "../_chunks-es/util.slice-blocks.js";
3
3
  import { parseBlock } from "../_chunks-es/util.block-offsets-to-selection.js";
4
4
  import { blockOffsetsToSelection } from "../_chunks-es/util.block-offsets-to-selection.js";
5
+ import { isPortableTextTextBlock, isPortableTextSpan } from "@sanity/types";
5
6
  import { reverseSelection } from "../_chunks-es/util.reverse-selection.js";
7
+ function childSelectionPointToBlockOffset({
8
+ value,
9
+ selectionPoint
10
+ }) {
11
+ let offset = 0;
12
+ const blockKey = isKeyedSegment(selectionPoint.path[0]) ? selectionPoint.path[0]._key : void 0, childKey = isKeyedSegment(selectionPoint.path[2]) ? selectionPoint.path[2]._key : void 0;
13
+ if (!(!blockKey || !childKey)) {
14
+ for (const block of value)
15
+ if (block._key === blockKey && isPortableTextTextBlock(block))
16
+ for (const child of block.children) {
17
+ if (child._key === childKey)
18
+ return {
19
+ path: [{
20
+ _key: block._key
21
+ }],
22
+ offset: offset + selectionPoint.offset
23
+ };
24
+ isPortableTextSpan(child) && (offset += child.text.length);
25
+ }
26
+ }
27
+ }
6
28
  function isTextBlock(context, block) {
7
29
  return block._type === context.schema.block.name;
8
30
  }
@@ -68,6 +90,7 @@ function splitTextBlock({
68
90
  export {
69
91
  blockOffsetToSpanSelectionPoint,
70
92
  blockOffsetsToSelection,
93
+ childSelectionPointToBlockOffset,
71
94
  getBlockEndPoint,
72
95
  getBlockStartPoint,
73
96
  getTextBlockText,
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":["../../src/utils/util.is-text-block.ts","../../src/utils/util.merge-text-blocks.ts","../../src/utils/util.split-text-block.ts"],"sourcesContent":["import type {PortableTextBlock, PortableTextTextBlock} from '@sanity/types'\nimport type {EditorContext} from '../selectors'\n\n/**\n * @public\n */\nexport function isTextBlock(\n context: Pick<EditorContext, 'schema'>,\n block: PortableTextBlock,\n): block is PortableTextTextBlock {\n return block._type === context.schema.block.name\n}\n","import type {PortableTextTextBlock} from '@sanity/types'\nimport {parseBlock} from '../internal-utils/parse-blocks'\nimport type {EditorContext} from '../selectors'\nimport {isTextBlock} from './util.is-text-block'\n\n/**\n * @beta\n */\nexport function mergeTextBlocks({\n context,\n targetBlock,\n incomingBlock,\n}: {\n context: Pick<EditorContext, 'keyGenerator' | 'schema'>\n targetBlock: PortableTextTextBlock\n incomingBlock: PortableTextTextBlock\n}) {\n const parsedIncomingBlock = parseBlock({\n context,\n block: incomingBlock,\n options: {refreshKeys: true},\n })\n\n if (!parsedIncomingBlock || !isTextBlock(context, parsedIncomingBlock)) {\n return targetBlock\n }\n\n return {\n ...targetBlock,\n children: [...targetBlock.children, ...parsedIncomingBlock.children],\n markDefs: [\n ...(targetBlock.markDefs ?? []),\n ...(parsedIncomingBlock.markDefs ?? []),\n ],\n }\n}\n","import type {PortableTextTextBlock} from '@sanity/types'\nimport {isTextBlock, sliceBlocks, type EditorSelectionPoint} from '.'\nimport type {EditorContext} from '../selectors'\nimport {isSpan} from './util.is-span'\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 = sliceBlocks({\n blocks: [block],\n selection: {\n anchor: {\n path: [{_key: block._key}, 'children', {_key: firstChild._key}],\n offset: 0,\n },\n focus: point,\n },\n }).at(0)\n const after = sliceBlocks({\n blocks: [block],\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 }).at(0)\n\n if (!before || !after) {\n return undefined\n }\n\n if (!isTextBlock(context, before) || !isTextBlock(context, after)) {\n return undefined\n }\n\n return {before, after}\n}\n"],"names":["isTextBlock","context","block","_type","schema","name","mergeTextBlocks","targetBlock","incomingBlock","parsedIncomingBlock","parseBlock","options","refreshKeys","children","markDefs","splitTextBlock","point","firstChild","at","lastChild","length","before","sliceBlocks","blocks","selection","anchor","path","_key","offset","focus","after","isSpan","text"],"mappings":";;;;;AAMgBA,SAAAA,YACdC,SACAC,OACgC;AAChC,SAAOA,MAAMC,UAAUF,QAAQG,OAAOF,MAAMG;AAC9C;ACHO,SAASC,gBAAgB;AAAA,EAC9BL;AAAAA,EACAM;AAAAA,EACAC;AAKF,GAAG;AACD,QAAMC,sBAAsBC,WAAW;AAAA,IACrCT;AAAAA,IACAC,OAAOM;AAAAA,IACPG,SAAS;AAAA,MAACC,aAAa;AAAA,IAAA;AAAA,EAAI,CAC5B;AAED,SAAI,CAACH,uBAAuB,CAACT,YAAYC,SAASQ,mBAAmB,IAC5DF,cAGF;AAAA,IACL,GAAGA;AAAAA,IACHM,UAAU,CAAC,GAAGN,YAAYM,UAAU,GAAGJ,oBAAoBI,QAAQ;AAAA,IACnEC,UAAU,CACR,GAAIP,YAAYO,YAAY,CAAA,GAC5B,GAAIL,oBAAoBK,YAAY,CAAG,CAAA;AAAA,EAE3C;AACF;AC3BO,SAASC,eAAe;AAAA,EAC7Bd;AAAAA,EACAC;AAAAA,EACAc;AAKF,GAA8E;AAC5E,QAAMC,aAAaf,MAAMW,SAASK,GAAG,CAAC,GAChCC,YAAYjB,MAAMW,SAASK,GAAGhB,MAAMW,SAASO,SAAS,CAAC;AAEzD,MAAA,CAACH,cAAc,CAACE;AAClB;AAGF,QAAME,SAASC,YAAY;AAAA,IACzBC,QAAQ,CAACrB,KAAK;AAAA,IACdsB,WAAW;AAAA,MACTC,QAAQ;AAAA,QACNC,MAAM,CAAC;AAAA,UAACC,MAAMzB,MAAMyB;AAAAA,WAAO,YAAY;AAAA,UAACA,MAAMV,WAAWU;AAAAA,QAAAA,CAAK;AAAA,QAC9DC,QAAQ;AAAA,MACV;AAAA,MACAC,OAAOb;AAAAA,IAAAA;AAAAA,EAEV,CAAA,EAAEE,GAAG,CAAC,GACDY,QAAQR,YAAY;AAAA,IACxBC,QAAQ,CAACrB,KAAK;AAAA,IACdsB,WAAW;AAAA,MACTC,QAAQT;AAAAA,MACRa,OAAO;AAAA,QACLH,MAAM,CAAC;AAAA,UAACC,MAAMzB,MAAMyB;AAAAA,WAAO,YAAY;AAAA,UAACA,MAAMR,UAAUQ;AAAAA,QAAAA,CAAK;AAAA,QAC7DC,QAAQG,OAAO9B,SAASkB,SAAS,IAAIA,UAAUa,KAAKZ,SAAS;AAAA,MAAA;AAAA,IAC/D;AAAA,EACF,CACD,EAAEF,GAAG,CAAC;AAEP,MAAI,EAACG,CAAAA,UAAU,CAACS,UAIZ,EAAC9B,CAAAA,YAAYC,SAASoB,MAAM,KAAK,CAACrB,YAAYC,SAAS6B,KAAK;AAIzD,WAAA;AAAA,MAACT;AAAAA,MAAQS;AAAAA,IAAK;AACvB;"}
1
+ {"version":3,"file":"index.js","sources":["../../src/utils/util.child-selection-point-to-block-offset.ts","../../src/utils/util.is-text-block.ts","../../src/utils/util.merge-text-blocks.ts","../../src/utils/util.split-text-block.ts"],"sourcesContent":["import {\n isPortableTextSpan,\n isPortableTextTextBlock,\n type PortableTextBlock,\n} from '@sanity/types'\nimport type {BlockOffset} from '../behaviors/behavior.types'\nimport type {EditorSelectionPoint} from '../types/editor'\nimport {isKeyedSegment} from './util.is-keyed-segment'\n\n/**\n * @public\n */\nexport function childSelectionPointToBlockOffset({\n value,\n selectionPoint,\n}: {\n value: Array<PortableTextBlock>\n selectionPoint: EditorSelectionPoint\n}): BlockOffset | undefined {\n let offset = 0\n\n const blockKey = isKeyedSegment(selectionPoint.path[0])\n ? selectionPoint.path[0]._key\n : undefined\n const childKey = isKeyedSegment(selectionPoint.path[2])\n ? selectionPoint.path[2]._key\n : undefined\n\n if (!blockKey || !childKey) {\n return undefined\n }\n\n for (const block of value) {\n if (block._key !== blockKey) {\n continue\n }\n\n if (!isPortableTextTextBlock(block)) {\n continue\n }\n\n for (const child of block.children) {\n if (child._key === childKey) {\n return {\n path: [{_key: block._key}],\n offset: offset + selectionPoint.offset,\n }\n }\n\n if (isPortableTextSpan(child)) {\n offset += child.text.length\n }\n }\n }\n}\n","import type {PortableTextBlock, PortableTextTextBlock} from '@sanity/types'\nimport type {EditorContext} from '../selectors'\n\n/**\n * @public\n */\nexport function isTextBlock(\n context: Pick<EditorContext, 'schema'>,\n block: PortableTextBlock,\n): block is PortableTextTextBlock {\n return block._type === context.schema.block.name\n}\n","import type {PortableTextTextBlock} from '@sanity/types'\nimport {parseBlock} from '../internal-utils/parse-blocks'\nimport type {EditorContext} from '../selectors'\nimport {isTextBlock} from './util.is-text-block'\n\n/**\n * @beta\n */\nexport function mergeTextBlocks({\n context,\n targetBlock,\n incomingBlock,\n}: {\n context: Pick<EditorContext, 'keyGenerator' | 'schema'>\n targetBlock: PortableTextTextBlock\n incomingBlock: PortableTextTextBlock\n}) {\n const parsedIncomingBlock = parseBlock({\n context,\n block: incomingBlock,\n options: {refreshKeys: true},\n })\n\n if (!parsedIncomingBlock || !isTextBlock(context, parsedIncomingBlock)) {\n return targetBlock\n }\n\n return {\n ...targetBlock,\n children: [...targetBlock.children, ...parsedIncomingBlock.children],\n markDefs: [\n ...(targetBlock.markDefs ?? []),\n ...(parsedIncomingBlock.markDefs ?? []),\n ],\n }\n}\n","import type {PortableTextTextBlock} from '@sanity/types'\nimport {isTextBlock, sliceBlocks, type EditorSelectionPoint} from '.'\nimport type {EditorContext} from '../selectors'\nimport {isSpan} from './util.is-span'\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 = sliceBlocks({\n blocks: [block],\n selection: {\n anchor: {\n path: [{_key: block._key}, 'children', {_key: firstChild._key}],\n offset: 0,\n },\n focus: point,\n },\n }).at(0)\n const after = sliceBlocks({\n blocks: [block],\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 }).at(0)\n\n if (!before || !after) {\n return undefined\n }\n\n if (!isTextBlock(context, before) || !isTextBlock(context, after)) {\n return undefined\n }\n\n return {before, after}\n}\n"],"names":["childSelectionPointToBlockOffset","value","selectionPoint","offset","blockKey","isKeyedSegment","path","_key","undefined","childKey","block","isPortableTextTextBlock","child","children","isPortableTextSpan","text","length","isTextBlock","context","_type","schema","name","mergeTextBlocks","targetBlock","incomingBlock","parsedIncomingBlock","parseBlock","options","refreshKeys","markDefs","splitTextBlock","point","firstChild","at","lastChild","before","sliceBlocks","blocks","selection","anchor","focus","after","isSpan"],"mappings":";;;;;;AAYO,SAASA,iCAAiC;AAAA,EAC/CC;AAAAA,EACAC;AAIF,GAA4B;AAC1B,MAAIC,SAAS;AAEPC,QAAAA,WAAWC,eAAeH,eAAeI,KAAK,CAAC,CAAC,IAClDJ,eAAeI,KAAK,CAAC,EAAEC,OACvBC,QACEC,WAAWJ,eAAeH,eAAeI,KAAK,CAAC,CAAC,IAClDJ,eAAeI,KAAK,CAAC,EAAEC,OACvBC;AAEA,MAAA,EAAA,CAACJ,YAAY,CAACK;AAIlB,eAAWC,SAAST;AAClB,UAAIS,MAAMH,SAASH,YAIdO,wBAAwBD,KAAK;AAIvBE,mBAAAA,SAASF,MAAMG,UAAU;AAClC,cAAID,MAAML,SAASE;AACV,mBAAA;AAAA,cACLH,MAAM,CAAC;AAAA,gBAACC,MAAMG,MAAMH;AAAAA,cAAAA,CAAK;AAAA,cACzBJ,QAAQA,SAASD,eAAeC;AAAAA,YAClC;AAGEW,6BAAmBF,KAAK,MAC1BT,UAAUS,MAAMG,KAAKC;AAAAA,QAAAA;AAAAA;AAI7B;AChDgBC,SAAAA,YACdC,SACAR,OACgC;AAChC,SAAOA,MAAMS,UAAUD,QAAQE,OAAOV,MAAMW;AAC9C;ACHO,SAASC,gBAAgB;AAAA,EAC9BJ;AAAAA,EACAK;AAAAA,EACAC;AAKF,GAAG;AACD,QAAMC,sBAAsBC,WAAW;AAAA,IACrCR;AAAAA,IACAR,OAAOc;AAAAA,IACPG,SAAS;AAAA,MAACC,aAAa;AAAA,IAAA;AAAA,EAAI,CAC5B;AAED,SAAI,CAACH,uBAAuB,CAACR,YAAYC,SAASO,mBAAmB,IAC5DF,cAGF;AAAA,IACL,GAAGA;AAAAA,IACHV,UAAU,CAAC,GAAGU,YAAYV,UAAU,GAAGY,oBAAoBZ,QAAQ;AAAA,IACnEgB,UAAU,CACR,GAAIN,YAAYM,YAAY,CAAA,GAC5B,GAAIJ,oBAAoBI,YAAY,CAAG,CAAA;AAAA,EAE3C;AACF;AC3BO,SAASC,eAAe;AAAA,EAC7BZ;AAAAA,EACAR;AAAAA,EACAqB;AAKF,GAA8E;AAC5E,QAAMC,aAAatB,MAAMG,SAASoB,GAAG,CAAC,GAChCC,YAAYxB,MAAMG,SAASoB,GAAGvB,MAAMG,SAASG,SAAS,CAAC;AAEzD,MAAA,CAACgB,cAAc,CAACE;AAClB;AAGF,QAAMC,SAASC,YAAY;AAAA,IACzBC,QAAQ,CAAC3B,KAAK;AAAA,IACd4B,WAAW;AAAA,MACTC,QAAQ;AAAA,QACNjC,MAAM,CAAC;AAAA,UAACC,MAAMG,MAAMH;AAAAA,WAAO,YAAY;AAAA,UAACA,MAAMyB,WAAWzB;AAAAA,QAAAA,CAAK;AAAA,QAC9DJ,QAAQ;AAAA,MACV;AAAA,MACAqC,OAAOT;AAAAA,IAAAA;AAAAA,EAEV,CAAA,EAAEE,GAAG,CAAC,GACDQ,QAAQL,YAAY;AAAA,IACxBC,QAAQ,CAAC3B,KAAK;AAAA,IACd4B,WAAW;AAAA,MACTC,QAAQR;AAAAA,MACRS,OAAO;AAAA,QACLlC,MAAM,CAAC;AAAA,UAACC,MAAMG,MAAMH;AAAAA,WAAO,YAAY;AAAA,UAACA,MAAM2B,UAAU3B;AAAAA,QAAAA,CAAK;AAAA,QAC7DJ,QAAQuC,OAAOxB,SAASgB,SAAS,IAAIA,UAAUnB,KAAKC,SAAS;AAAA,MAAA;AAAA,IAC/D;AAAA,EACF,CACD,EAAEiB,GAAG,CAAC;AAEP,MAAI,EAACE,CAAAA,UAAU,CAACM,UAIZ,EAACxB,CAAAA,YAAYC,SAASiB,MAAM,KAAK,CAAClB,YAAYC,SAASuB,KAAK;AAIzD,WAAA;AAAA,MAACN;AAAAA,MAAQM;AAAAA,IAAK;AACvB;"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@portabletext/editor",
3
- "version": "1.33.6",
3
+ "version": "1.34.1",
4
4
  "description": "Portable Text Editor made in React",
5
5
  "keywords": [
6
6
  "sanity",
@@ -79,8 +79,8 @@
79
79
  "slate-react": "0.112.1",
80
80
  "use-effect-event": "^1.0.2",
81
81
  "xstate": "^5.19.2",
82
- "@portabletext/block-tools": "1.1.8",
83
- "@portabletext/patches": "1.1.3"
82
+ "@portabletext/patches": "1.1.3",
83
+ "@portabletext/block-tools": "1.1.8"
84
84
  },
85
85
  "devDependencies": {
86
86
  "@portabletext/toolkit": "^2.0.17",
@@ -11,20 +11,42 @@ export const decoratorAddActionImplementation: BehaviorActionImplementation<
11
11
  > = ({context, action}) => {
12
12
  const editor = action.editor
13
13
  const mark = action.decorator
14
- const selection = action.selection
15
- ? (toSlateRange(action.selection, action.editor) ?? editor.selection)
16
- : editor.selection
17
-
18
- if (!selection) {
19
- return
20
- }
21
-
22
14
  const value = fromSlateValue(
23
15
  editor.children,
24
16
  context.schema.block.name,
25
17
  KEY_TO_VALUE_ELEMENT.get(editor),
26
18
  )
27
19
 
20
+ const manualAnchor = action.offsets?.anchor
21
+ ? utils.blockOffsetToSpanSelectionPoint({
22
+ value,
23
+ blockOffset: action.offsets.anchor,
24
+ direction: 'backward',
25
+ })
26
+ : undefined
27
+ const manualFocus = action.offsets?.focus
28
+ ? utils.blockOffsetToSpanSelectionPoint({
29
+ value,
30
+ blockOffset: action.offsets.focus,
31
+ direction: 'forward',
32
+ })
33
+ : undefined
34
+ const manualSelection =
35
+ manualAnchor && manualFocus
36
+ ? {
37
+ anchor: manualAnchor,
38
+ focus: manualFocus,
39
+ }
40
+ : undefined
41
+
42
+ const selection = manualSelection
43
+ ? (toSlateRange(manualSelection, action.editor) ?? editor.selection)
44
+ : editor.selection
45
+
46
+ if (!selection) {
47
+ return
48
+ }
49
+
28
50
  const editorSelection = toPortableTextRange(value, selection, context.schema)
29
51
  const anchorOffset = editorSelection
30
52
  ? utils.spanSelectionPointToBlockOffset({
@@ -79,10 +79,6 @@ const emphasisListener: CallbackLogicFunction<
79
79
  return false
80
80
  }
81
81
 
82
- if (event.text !== '*' && event.text !== '_') {
83
- return false
84
- }
85
-
86
82
  const focusTextBlock = selectors.getFocusTextBlock({context})
87
83
  const selectionStartPoint = selectors.getSelectionStartPoint({context})
88
84
  const selectionStartOffset = selectionStartPoint
@@ -104,44 +100,33 @@ const emphasisListener: CallbackLogicFunction<
104
100
  const prefixOffsets = {
105
101
  anchor: {
106
102
  path: focusTextBlock.path,
107
- offset: textBefore.length - textToItalic.length + 1,
103
+ // Example: "foo *bar*".length - "*bar*".length = 4
104
+ offset: `${textBefore}${event.text}`.length - textToItalic.length,
108
105
  },
109
106
  focus: {
110
107
  path: focusTextBlock.path,
111
- offset: textBefore.length - textToItalic.length + 1 + 1,
108
+ // Example: "foo *bar*".length - "*bar*".length + 1 = 5
109
+ offset:
110
+ `${textBefore}${event.text}`.length - textToItalic.length + 1,
112
111
  },
113
112
  }
114
113
  const suffixOffsets = {
115
114
  anchor: {
116
115
  path: focusTextBlock.path,
117
- offset: selectionStartOffset.offset,
116
+ // Example: "foo *bar|" (8) + "*".length - 1 = 8
117
+ offset: selectionStartOffset.offset + event.text.length - 1,
118
118
  },
119
119
  focus: {
120
120
  path: focusTextBlock.path,
121
- offset: selectionStartOffset.offset + 1,
121
+ // Example: "foo *bar|" (8) + "*".length = 9
122
+ offset: selectionStartOffset.offset + event.text.length,
122
123
  },
123
124
  }
124
125
 
125
- const anchor = utils.blockOffsetToSpanSelectionPoint({
126
- value: context.value,
127
- blockOffset: prefixOffsets.focus,
128
- direction: 'backward',
129
- })
130
- const focus = utils.blockOffsetToSpanSelectionPoint({
131
- value: context.value,
132
- blockOffset: suffixOffsets.anchor,
133
- direction: 'forward',
134
- })
135
-
136
- if (!anchor || !focus) {
137
- return false
138
- }
139
-
140
126
  return {
141
127
  prefixOffsets,
142
128
  suffixOffsets,
143
129
  decorator: italicDecorator,
144
- selection: {anchor, focus},
145
130
  }
146
131
  }
147
132
 
@@ -151,35 +136,85 @@ const emphasisListener: CallbackLogicFunction<
151
136
  const prefixOffsets = {
152
137
  anchor: {
153
138
  path: focusTextBlock.path,
154
- offset: textBefore.length - textToBold.length + 1,
139
+ // Example: "foo **bar**".length - "**bar**".length = 4
140
+ offset: `${textBefore}${event.text}`.length - textToBold.length,
155
141
  },
156
142
  focus: {
157
143
  path: focusTextBlock.path,
158
- offset: textBefore.length - textToBold.length + 1 + 2,
144
+ // Example: "foo **bar**".length - "**bar**".length + 2 = 6
145
+ offset:
146
+ `${textBefore}${event.text}`.length - textToBold.length + 2,
159
147
  },
160
148
  }
149
+
150
+ const prefixSelection = utils.blockOffsetsToSelection({
151
+ value: context.value,
152
+ offsets: prefixOffsets,
153
+ })
154
+ const inlineObjectBeforePrefixFocus =
155
+ selectors.getPreviousInlineObject({
156
+ context: {
157
+ ...context,
158
+ selection: prefixSelection
159
+ ? {
160
+ anchor: prefixSelection.focus,
161
+ focus: prefixSelection.focus,
162
+ }
163
+ : null,
164
+ },
165
+ })
166
+ const inlineObjectBeforePrefixFocusOffset =
167
+ inlineObjectBeforePrefixFocus
168
+ ? utils.childSelectionPointToBlockOffset({
169
+ value: context.value,
170
+ selectionPoint: {
171
+ path: inlineObjectBeforePrefixFocus.path,
172
+ offset: 0,
173
+ },
174
+ })
175
+ : undefined
176
+
177
+ if (
178
+ inlineObjectBeforePrefixFocusOffset &&
179
+ inlineObjectBeforePrefixFocusOffset.offset >
180
+ prefixOffsets.anchor.offset &&
181
+ inlineObjectBeforePrefixFocusOffset.offset <
182
+ prefixOffsets.focus.offset
183
+ ) {
184
+ return false
185
+ }
186
+
161
187
  const suffixOffsets = {
162
188
  anchor: {
163
189
  path: focusTextBlock.path,
164
- offset: selectionStartOffset.offset - 1,
190
+ // Example: "foo **bar*|" (10) + "*".length - 2 = 9
191
+ offset: selectionStartOffset.offset + event.text.length - 2,
165
192
  },
166
193
  focus: {
167
194
  path: focusTextBlock.path,
168
- offset: selectionStartOffset.offset + 1,
195
+ // Example: "foo **bar*|" (10) + "*".length = 11
196
+ offset: selectionStartOffset.offset + event.text.length,
169
197
  },
170
198
  }
171
- const anchor = utils.blockOffsetToSpanSelectionPoint({
172
- value: context.value,
173
- blockOffset: prefixOffsets.focus,
174
- direction: 'backward',
175
- })
176
- const focus = utils.blockOffsetToSpanSelectionPoint({
177
- value: context.value,
178
- blockOffset: suffixOffsets.anchor,
179
- direction: 'forward',
199
+
200
+ const previousInlineObject = selectors.getPreviousInlineObject({
201
+ context,
180
202
  })
203
+ const previousInlineObjectOffset = previousInlineObject
204
+ ? utils.childSelectionPointToBlockOffset({
205
+ value: context.value,
206
+ selectionPoint: {
207
+ path: previousInlineObject.path,
208
+ offset: 0,
209
+ },
210
+ })
211
+ : undefined
181
212
 
182
- if (!anchor || !focus) {
213
+ if (
214
+ previousInlineObjectOffset &&
215
+ previousInlineObjectOffset.offset > suffixOffsets.anchor.offset &&
216
+ previousInlineObjectOffset.offset < suffixOffsets.focus.offset
217
+ ) {
183
218
  return false
184
219
  }
185
220
 
@@ -187,7 +222,6 @@ const emphasisListener: CallbackLogicFunction<
187
222
  prefixOffsets,
188
223
  suffixOffsets,
189
224
  decorator: boldDecorator,
190
- selection: {anchor, focus},
191
225
  }
192
226
  }
193
227
 
@@ -195,11 +229,14 @@ const emphasisListener: CallbackLogicFunction<
195
229
  },
196
230
  actions: [
197
231
  ({event}) => [event],
198
- (_, {prefixOffsets, suffixOffsets, decorator, selection}) => [
232
+ (_, {prefixOffsets, suffixOffsets, decorator}) => [
199
233
  {
200
234
  type: 'decorator.add',
201
235
  decorator,
202
- selection,
236
+ offsets: {
237
+ anchor: prefixOffsets.focus,
238
+ focus: suffixOffsets.anchor,
239
+ },
203
240
  },
204
241
  {
205
242
  type: 'delete.text',
@@ -209,6 +246,10 @@ const emphasisListener: CallbackLogicFunction<
209
246
  type: 'delete.text',
210
247
  ...prefixOffsets,
211
248
  },
249
+ {
250
+ type: 'decorator.remove',
251
+ decorator,
252
+ },
212
253
  {
213
254
  type: 'effect',
214
255
  effect: () => {
@@ -57,7 +57,7 @@ export type SyntheticBehaviorEvent =
57
57
  | {
58
58
  type: 'decorator.add'
59
59
  decorator: string
60
- selection?: NonNullable<EditorSelection>
60
+ offsets?: {anchor: BlockOffset; focus: BlockOffset}
61
61
  }
62
62
  | {
63
63
  type: 'decorator.remove'
@@ -236,7 +236,7 @@ export type NativeBehaviorEvent =
236
236
  }
237
237
  | {
238
238
  type: 'serialize'
239
- originEvent: 'copy' | 'cut' | 'unknown'
239
+ originEvent: 'copy' | 'cut' | 'drag' | 'unknown'
240
240
  dataTransfer: DataTransfer
241
241
  }
242
242
 
@@ -18,18 +18,19 @@ export function defineConverter<TMIMEType extends MIMEType>(
18
18
  export type ConverterEvent<TMIMEType extends MIMEType = MIMEType> =
19
19
  | {
20
20
  type: 'serialize'
21
- originEvent: 'copy' | 'cut' | 'unknown'
21
+ originEvent: 'copy' | 'cut' | 'drag' | 'unknown'
22
22
  }
23
23
  | {
24
24
  type: 'serialization.failure'
25
25
  mimeType: TMIMEType
26
+ originEvent: 'copy' | 'cut' | 'drag' | 'unknown'
26
27
  reason: string
27
28
  }
28
29
  | {
29
30
  type: 'serialization.success'
30
31
  data: string
31
32
  mimeType: TMIMEType
32
- originEvent: 'copy' | 'cut' | 'unknown'
33
+ originEvent: 'copy' | 'cut' | 'drag' | 'unknown'
33
34
  }
34
35
  | {
35
36
  type: 'deserialize'
@@ -236,11 +236,6 @@ export function createWithEventListeners(
236
236
  }
237
237
 
238
238
  editor.setFragmentData = (dataTransfer, originEvent) => {
239
- if (originEvent === 'drag') {
240
- setFragmentData(dataTransfer)
241
- return
242
- }
243
-
244
239
  if (isApplyingBehaviorActions(editor)) {
245
240
  setFragmentData(dataTransfer)
246
241
  return
@@ -5,6 +5,7 @@ export {
5
5
  spanSelectionPointToBlockOffset,
6
6
  } from './util.block-offset'
7
7
  export {blockOffsetsToSelection} from './util.block-offsets-to-selection'
8
+ export {childSelectionPointToBlockOffset} from './util.child-selection-point-to-block-offset'
8
9
  export {getBlockEndPoint} from './util.get-block-end-point'
9
10
  export {getBlockStartPoint} from './util.get-block-start-point'
10
11
  export {getTextBlockText} from './util.get-text-block-text'
@@ -0,0 +1,55 @@
1
+ import {
2
+ isPortableTextSpan,
3
+ isPortableTextTextBlock,
4
+ type PortableTextBlock,
5
+ } from '@sanity/types'
6
+ import type {BlockOffset} from '../behaviors/behavior.types'
7
+ import type {EditorSelectionPoint} from '../types/editor'
8
+ import {isKeyedSegment} from './util.is-keyed-segment'
9
+
10
+ /**
11
+ * @public
12
+ */
13
+ export function childSelectionPointToBlockOffset({
14
+ value,
15
+ selectionPoint,
16
+ }: {
17
+ value: Array<PortableTextBlock>
18
+ selectionPoint: EditorSelectionPoint
19
+ }): BlockOffset | undefined {
20
+ let offset = 0
21
+
22
+ const blockKey = isKeyedSegment(selectionPoint.path[0])
23
+ ? selectionPoint.path[0]._key
24
+ : undefined
25
+ const childKey = isKeyedSegment(selectionPoint.path[2])
26
+ ? selectionPoint.path[2]._key
27
+ : undefined
28
+
29
+ if (!blockKey || !childKey) {
30
+ return undefined
31
+ }
32
+
33
+ for (const block of value) {
34
+ if (block._key !== blockKey) {
35
+ continue
36
+ }
37
+
38
+ if (!isPortableTextTextBlock(block)) {
39
+ continue
40
+ }
41
+
42
+ for (const child of block.children) {
43
+ if (child._key === childKey) {
44
+ return {
45
+ path: [{_key: block._key}],
46
+ offset: offset + selectionPoint.offset,
47
+ }
48
+ }
49
+
50
+ if (isPortableTextSpan(child)) {
51
+ offset += child.text.length
52
+ }
53
+ }
54
+ }
55
+ }