@portabletext/editor 1.30.6 → 1.31.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 (65) hide show
  1. package/lib/_chunks-cjs/behavior.core.cjs +35 -2
  2. package/lib/_chunks-cjs/behavior.core.cjs.map +1 -1
  3. package/lib/_chunks-cjs/behavior.markdown.cjs +4 -4
  4. package/lib/_chunks-cjs/plugin.event-listener.cjs +143 -170
  5. package/lib/_chunks-cjs/plugin.event-listener.cjs.map +1 -1
  6. package/lib/_chunks-cjs/selector.get-text-before.cjs +5 -41
  7. package/lib/_chunks-cjs/selector.get-text-before.cjs.map +1 -1
  8. package/lib/_chunks-cjs/selector.is-at-the-start-of-block.cjs +174 -6
  9. package/lib/_chunks-cjs/selector.is-at-the-start-of-block.cjs.map +1 -1
  10. package/lib/_chunks-cjs/util.is-empty-text-block.cjs +1 -58
  11. package/lib/_chunks-cjs/util.is-empty-text-block.cjs.map +1 -1
  12. package/lib/_chunks-cjs/util.reverse-selection.cjs +105 -0
  13. package/lib/_chunks-cjs/util.reverse-selection.cjs.map +1 -1
  14. package/lib/_chunks-es/behavior.core.js +36 -3
  15. package/lib/_chunks-es/behavior.core.js.map +1 -1
  16. package/lib/_chunks-es/behavior.markdown.js +2 -1
  17. package/lib/_chunks-es/behavior.markdown.js.map +1 -1
  18. package/lib/_chunks-es/plugin.event-listener.js +143 -170
  19. package/lib/_chunks-es/plugin.event-listener.js.map +1 -1
  20. package/lib/_chunks-es/selector.get-text-before.js +4 -41
  21. package/lib/_chunks-es/selector.get-text-before.js.map +1 -1
  22. package/lib/_chunks-es/selector.is-at-the-start-of-block.js +170 -2
  23. package/lib/_chunks-es/selector.is-at-the-start-of-block.js.map +1 -1
  24. package/lib/_chunks-es/util.is-empty-text-block.js +1 -59
  25. package/lib/_chunks-es/util.is-empty-text-block.js.map +1 -1
  26. package/lib/_chunks-es/util.reverse-selection.js +106 -1
  27. package/lib/_chunks-es/util.reverse-selection.js.map +1 -1
  28. package/lib/index.d.cts +0 -4
  29. package/lib/index.d.ts +0 -4
  30. package/lib/plugins/index.cjs +8 -8
  31. package/lib/plugins/index.cjs.map +1 -1
  32. package/lib/plugins/index.d.cts +0 -4
  33. package/lib/plugins/index.d.ts +0 -4
  34. package/lib/plugins/index.js +2 -3
  35. package/lib/plugins/index.js.map +1 -1
  36. package/lib/selectors/index.cjs +33 -6
  37. package/lib/selectors/index.cjs.map +1 -1
  38. package/lib/selectors/index.d.cts +209 -0
  39. package/lib/selectors/index.d.ts +209 -0
  40. package/lib/selectors/index.js +31 -5
  41. package/lib/selectors/index.js.map +1 -1
  42. package/lib/utils/index.cjs +10 -13
  43. package/lib/utils/index.cjs.map +1 -1
  44. package/lib/utils/index.js +3 -6
  45. package/lib/utils/index.js.map +1 -1
  46. package/package.json +1 -1
  47. package/src/behaviors/behavior.core.annotations.ts +32 -0
  48. package/src/behaviors/behavior.core.ts +1 -0
  49. package/src/editor/plugins/createWithEditableAPI.ts +1 -2
  50. package/src/editor/plugins/createWithUtils.ts +1 -52
  51. package/src/selectors/index.ts +4 -0
  52. package/src/selectors/selector.get-block-offsets.ts +33 -0
  53. package/src/selectors/selector.get-caret-word-selection.test.ts +288 -0
  54. package/src/selectors/selector.get-caret-word-selection.ts +116 -0
  55. package/src/selectors/selector.get-next-inline-object.ts +56 -0
  56. package/src/selectors/selector.get-previous-inline-object.ts +53 -0
  57. package/src/types/editor.ts +0 -5
  58. package/lib/_chunks-cjs/selector.get-selection-start-point.cjs +0 -15
  59. package/lib/_chunks-cjs/selector.get-selection-start-point.cjs.map +0 -1
  60. package/lib/_chunks-cjs/util.is-equal-selection-points.cjs +0 -46
  61. package/lib/_chunks-cjs/util.is-equal-selection-points.cjs.map +0 -1
  62. package/lib/_chunks-es/selector.get-selection-start-point.js +0 -16
  63. package/lib/_chunks-es/selector.get-selection-start-point.js.map +0 -1
  64. package/lib/_chunks-es/util.is-equal-selection-points.js +0 -47
  65. package/lib/_chunks-es/util.is-equal-selection-points.js.map +0 -1
@@ -1,9 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: !0 });
3
- var util_isEmptyTextBlock = require("../_chunks-cjs/util.is-empty-text-block.cjs"), util_isEqualSelectionPoints = require("../_chunks-cjs/util.is-equal-selection-points.cjs"), parseBlocks = require("../_chunks-cjs/parse-blocks.cjs"), util_reverseSelection = require("../_chunks-cjs/util.reverse-selection.cjs"), util_sliceBlocks = require("../_chunks-cjs/util.slice-blocks.cjs");
4
- function isSpan(context, child) {
5
- return child._type === context.schema.span.name;
6
- }
3
+ var util_reverseSelection = require("../_chunks-cjs/util.reverse-selection.cjs"), util_isEmptyTextBlock = require("../_chunks-cjs/util.is-empty-text-block.cjs"), parseBlocks = require("../_chunks-cjs/parse-blocks.cjs"), util_sliceBlocks = require("../_chunks-cjs/util.slice-blocks.cjs");
7
4
  function isTextBlock(context, block) {
8
5
  return block._type === context.schema.block.name;
9
6
  }
@@ -56,7 +53,7 @@ function splitTextBlock({
56
53
  }, "children", {
57
54
  _key: lastChild._key
58
55
  }],
59
- offset: isSpan(context, lastChild) ? lastChild.text.length : 0
56
+ offset: util_reverseSelection.isSpan(context, lastChild) ? lastChild.text.length : 0
60
57
  }
61
58
  }
62
59
  }).at(0);
@@ -66,17 +63,17 @@ function splitTextBlock({
66
63
  after
67
64
  };
68
65
  }
69
- exports.blockOffsetToSpanSelectionPoint = util_isEmptyTextBlock.blockOffsetToSpanSelectionPoint;
66
+ exports.blockOffsetToSpanSelectionPoint = util_reverseSelection.blockOffsetToSpanSelectionPoint;
67
+ exports.getBlockEndPoint = util_reverseSelection.getBlockEndPoint;
68
+ exports.getBlockStartPoint = util_reverseSelection.getBlockStartPoint;
69
+ exports.isEqualSelectionPoints = util_reverseSelection.isEqualSelectionPoints;
70
+ exports.isKeyedSegment = util_reverseSelection.isKeyedSegment;
71
+ exports.isSpan = util_reverseSelection.isSpan;
72
+ exports.reverseSelection = util_reverseSelection.reverseSelection;
73
+ exports.spanSelectionPointToBlockOffset = util_reverseSelection.spanSelectionPointToBlockOffset;
70
74
  exports.getTextBlockText = util_isEmptyTextBlock.getTextBlockText;
71
75
  exports.isEmptyTextBlock = util_isEmptyTextBlock.isEmptyTextBlock;
72
- exports.spanSelectionPointToBlockOffset = util_isEmptyTextBlock.spanSelectionPointToBlockOffset;
73
- exports.getBlockEndPoint = util_isEqualSelectionPoints.getBlockEndPoint;
74
- exports.getBlockStartPoint = util_isEqualSelectionPoints.getBlockStartPoint;
75
- exports.isEqualSelectionPoints = util_isEqualSelectionPoints.isEqualSelectionPoints;
76
- exports.isKeyedSegment = util_isEqualSelectionPoints.isKeyedSegment;
77
- exports.reverseSelection = util_reverseSelection.reverseSelection;
78
76
  exports.sliceBlocks = util_sliceBlocks.sliceBlocks;
79
- exports.isSpan = isSpan;
80
77
  exports.isTextBlock = isTextBlock;
81
78
  exports.mergeTextBlocks = mergeTextBlocks;
82
79
  exports.splitTextBlock = splitTextBlock;
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","sources":["../../src/utils/util.is-span.ts","../../src/utils/util.is-text-block.ts","../../src/utils/util.merge-text-blocks.ts","../../src/utils/util.split-text-block.ts"],"sourcesContent":["import type {PortableTextChild, PortableTextSpan} from '@sanity/types'\nimport type {EditorContext} from '../selectors'\n\n/**\n * @public\n */\nexport function isSpan(\n context: Pick<EditorContext, 'schema'>,\n child: PortableTextChild,\n): child is PortableTextSpan {\n return child._type === context.schema.span.name\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":["isSpan","context","child","_type","schema","span","name","isTextBlock","block","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","text"],"mappings":";;;AAMgBA,SAAAA,OACdC,SACAC,OAC2B;AAC3B,SAAOA,MAAMC,UAAUF,QAAQG,OAAOC,KAAKC;AAC7C;ACLgBC,SAAAA,YACdN,SACAO,OACgC;AAChC,SAAOA,MAAML,UAAUF,QAAQG,OAAOI,MAAMF;AAC9C;ACHO,SAASG,gBAAgB;AAAA,EAC9BR;AAAAA,EACAS;AAAAA,EACAC;AAKF,GAAG;AACD,QAAMC,sBAAsBC,YAAAA,WAAW;AAAA,IACrCZ;AAAAA,IACAO,OAAOG;AAAAA,IACPG,SAAS;AAAA,MAACC,aAAa;AAAA,IAAA;AAAA,EAAI,CAC5B;AAED,SAAI,CAACH,uBAAuB,CAACL,YAAYN,SAASW,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,EAC7BjB;AAAAA,EACAO;AAAAA,EACAW;AAKF,GAA8E;AAC5E,QAAMC,aAAaZ,MAAMQ,SAASK,GAAG,CAAC,GAChCC,YAAYd,MAAMQ,SAASK,GAAGb,MAAMQ,SAASO,SAAS,CAAC;AAEzD,MAAA,CAACH,cAAc,CAACE;AAClB;AAGF,QAAME,SAASC,iBAAAA,YAAY;AAAA,IACzBC,QAAQ,CAAClB,KAAK;AAAA,IACdmB,WAAW;AAAA,MACTC,QAAQ;AAAA,QACNC,MAAM,CAAC;AAAA,UAACC,MAAMtB,MAAMsB;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,iBAAAA,YAAY;AAAA,IACxBC,QAAQ,CAAClB,KAAK;AAAA,IACdmB,WAAW;AAAA,MACTC,QAAQT;AAAAA,MACRa,OAAO;AAAA,QACLH,MAAM,CAAC;AAAA,UAACC,MAAMtB,MAAMsB;AAAAA,WAAO,YAAY;AAAA,UAACA,MAAMR,UAAUQ;AAAAA,QAAAA,CAAK;AAAA,QAC7DC,QAAQ/B,OAAOC,SAASqB,SAAS,IAAIA,UAAUY,KAAKX,SAAS;AAAA,MAAA;AAAA,IAC/D;AAAA,EACF,CACD,EAAEF,GAAG,CAAC;AAEP,MAAI,EAACG,CAAAA,UAAU,CAACS,UAIZ,EAAC1B,CAAAA,YAAYN,SAASuB,MAAM,KAAK,CAACjB,YAAYN,SAASgC,KAAK;AAIzD,WAAA;AAAA,MAACT;AAAAA,MAAQS;AAAAA,IAAK;AACvB;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"index.cjs","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,YAAAA,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,iBAAAA,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,iBAAAA,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,sBAAO9B,OAAAA,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,11 +1,8 @@
1
- import { blockOffsetToSpanSelectionPoint, getTextBlockText, isEmptyTextBlock, spanSelectionPointToBlockOffset } from "../_chunks-es/util.is-empty-text-block.js";
2
- import { getBlockEndPoint, getBlockStartPoint, isEqualSelectionPoints, isKeyedSegment } from "../_chunks-es/util.is-equal-selection-points.js";
1
+ import { isSpan } from "../_chunks-es/util.reverse-selection.js";
2
+ import { blockOffsetToSpanSelectionPoint, getBlockEndPoint, getBlockStartPoint, isEqualSelectionPoints, isKeyedSegment, reverseSelection, spanSelectionPointToBlockOffset } from "../_chunks-es/util.reverse-selection.js";
3
+ import { getTextBlockText, isEmptyTextBlock } from "../_chunks-es/util.is-empty-text-block.js";
3
4
  import { parseBlock } from "../_chunks-es/parse-blocks.js";
4
- import { reverseSelection } from "../_chunks-es/util.reverse-selection.js";
5
5
  import { sliceBlocks } from "../_chunks-es/util.slice-blocks.js";
6
- function isSpan(context, child) {
7
- return child._type === context.schema.span.name;
8
- }
9
6
  function isTextBlock(context, block) {
10
7
  return block._type === context.schema.block.name;
11
8
  }
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":["../../src/utils/util.is-span.ts","../../src/utils/util.is-text-block.ts","../../src/utils/util.merge-text-blocks.ts","../../src/utils/util.split-text-block.ts"],"sourcesContent":["import type {PortableTextChild, PortableTextSpan} from '@sanity/types'\nimport type {EditorContext} from '../selectors'\n\n/**\n * @public\n */\nexport function isSpan(\n context: Pick<EditorContext, 'schema'>,\n child: PortableTextChild,\n): child is PortableTextSpan {\n return child._type === context.schema.span.name\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":["isSpan","context","child","_type","schema","span","name","isTextBlock","block","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","text"],"mappings":";;;;;AAMgBA,SAAAA,OACdC,SACAC,OAC2B;AAC3B,SAAOA,MAAMC,UAAUF,QAAQG,OAAOC,KAAKC;AAC7C;ACLgBC,SAAAA,YACdN,SACAO,OACgC;AAChC,SAAOA,MAAML,UAAUF,QAAQG,OAAOI,MAAMF;AAC9C;ACHO,SAASG,gBAAgB;AAAA,EAC9BR;AAAAA,EACAS;AAAAA,EACAC;AAKF,GAAG;AACD,QAAMC,sBAAsBC,WAAW;AAAA,IACrCZ;AAAAA,IACAO,OAAOG;AAAAA,IACPG,SAAS;AAAA,MAACC,aAAa;AAAA,IAAA;AAAA,EAAI,CAC5B;AAED,SAAI,CAACH,uBAAuB,CAACL,YAAYN,SAASW,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,EAC7BjB;AAAAA,EACAO;AAAAA,EACAW;AAKF,GAA8E;AAC5E,QAAMC,aAAaZ,MAAMQ,SAASK,GAAG,CAAC,GAChCC,YAAYd,MAAMQ,SAASK,GAAGb,MAAMQ,SAASO,SAAS,CAAC;AAEzD,MAAA,CAACH,cAAc,CAACE;AAClB;AAGF,QAAME,SAASC,YAAY;AAAA,IACzBC,QAAQ,CAAClB,KAAK;AAAA,IACdmB,WAAW;AAAA,MACTC,QAAQ;AAAA,QACNC,MAAM,CAAC;AAAA,UAACC,MAAMtB,MAAMsB;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,CAAClB,KAAK;AAAA,IACdmB,WAAW;AAAA,MACTC,QAAQT;AAAAA,MACRa,OAAO;AAAA,QACLH,MAAM,CAAC;AAAA,UAACC,MAAMtB,MAAMsB;AAAAA,WAAO,YAAY;AAAA,UAACA,MAAMR,UAAUQ;AAAAA,QAAAA,CAAK;AAAA,QAC7DC,QAAQ/B,OAAOC,SAASqB,SAAS,IAAIA,UAAUY,KAAKX,SAAS;AAAA,MAAA;AAAA,IAC/D;AAAA,EACF,CACD,EAAEF,GAAG,CAAC;AAEP,MAAI,EAACG,CAAAA,UAAU,CAACS,UAIZ,EAAC1B,CAAAA,YAAYN,SAASuB,MAAM,KAAK,CAACjB,YAAYN,SAASgC,KAAK;AAIzD,WAAA;AAAA,MAACT;AAAAA,MAAQS;AAAAA,IAAK;AACvB;"}
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;"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@portabletext/editor",
3
- "version": "1.30.6",
3
+ "version": "1.31.0",
4
4
  "description": "Portable Text Editor made in React",
5
5
  "keywords": [
6
6
  "sanity",
@@ -23,7 +23,39 @@ const toggleAnnotationOn = defineBehavior({
23
23
  ],
24
24
  })
25
25
 
26
+ const addAnnotationOnCollapsedSelection = defineBehavior({
27
+ on: 'annotation.add',
28
+ guard: ({context}) => {
29
+ if (!selectors.isSelectionCollapsed({context})) {
30
+ return false
31
+ }
32
+
33
+ const caretWordSelection = selectors.getCaretWordSelection({context})
34
+
35
+ if (
36
+ !caretWordSelection ||
37
+ !selectors.isSelectionExpanded({
38
+ context: {
39
+ ...context,
40
+ selection: caretWordSelection,
41
+ },
42
+ })
43
+ ) {
44
+ return false
45
+ }
46
+
47
+ return {caretWordSelection}
48
+ },
49
+ actions: [
50
+ ({event}, {caretWordSelection}) => [
51
+ raise({type: 'select', selection: caretWordSelection}),
52
+ raise({type: 'annotation.add', annotation: event.annotation}),
53
+ ],
54
+ ],
55
+ })
56
+
26
57
  export const coreAnnotationBehaviors = {
27
58
  toggleAnnotationOff,
28
59
  toggleAnnotationOn,
60
+ addAnnotationOnCollapsedSelection,
29
61
  }
@@ -13,6 +13,7 @@ import {coreStyleBehaviors} from './behavior.core.style'
13
13
  export const coreBehaviors = [
14
14
  coreAnnotationBehaviors.toggleAnnotationOff,
15
15
  coreAnnotationBehaviors.toggleAnnotationOn,
16
+ coreAnnotationBehaviors.addAnnotationOnCollapsedSelection,
16
17
  coreDecoratorBehaviors.toggleDecoratorOff,
17
18
  coreDecoratorBehaviors.toggleDecoratorOn,
18
19
  coreDecoratorBehaviors.strongShortcut,
@@ -634,8 +634,7 @@ export const addAnnotationActionImplementation: BehaviorActionImplementation<
634
634
 
635
635
  if (originalSelection) {
636
636
  if (Range.isCollapsed(originalSelection)) {
637
- editor.pteExpandToWord()
638
- editor.onChange()
637
+ return
639
638
  }
640
639
 
641
640
  // If we still have a selection, add the annotation to the selected text
@@ -1,5 +1,3 @@
1
- import {Editor, Range, Text, Transforms} from 'slate'
2
- import {debugWithName} from '../../internal-utils/debug'
3
1
  import {toSlateValue} from '../../internal-utils/values'
4
2
  import type {
5
3
  PortableTextMemberSchemaTypes,
@@ -7,12 +5,11 @@ import type {
7
5
  } from '../../types/editor'
8
6
  import type {EditorActor} from '../editor-machine'
9
7
 
10
- const debug = debugWithName('plugin:withUtils')
11
-
12
8
  interface Options {
13
9
  editorActor: EditorActor
14
10
  schemaTypes: PortableTextMemberSchemaTypes
15
11
  }
12
+
16
13
  /**
17
14
  * This plugin makes various util commands available in the editor
18
15
  *
@@ -21,54 +18,6 @@ export function createWithUtils({editorActor, schemaTypes}: Options) {
21
18
  return function withUtils(
22
19
  editor: PortableTextSlateEditor,
23
20
  ): PortableTextSlateEditor {
24
- // Expands the the selection to wrap around the word the focus is at
25
- editor.pteExpandToWord = () => {
26
- const {selection} = editor
27
- if (selection && !Range.isExpanded(selection)) {
28
- const [textNode] = Editor.node(editor, selection.focus, {depth: 2})
29
- if (!textNode || !Text.isText(textNode) || textNode.text.length === 0) {
30
- debug(`pteExpandToWord: Can't expand to word here`)
31
- return
32
- }
33
- const {focus} = selection
34
- const focusOffset = focus.offset
35
- const charsBefore = textNode.text.slice(0, focusOffset)
36
- const charsAfter = textNode.text.slice(focusOffset, -1)
37
- const isEmpty = (str: string) => str.match(/\s/g)
38
- const whiteSpaceBeforeIndex = charsBefore
39
- .split('')
40
- .reverse()
41
- .findIndex((str) => isEmpty(str))
42
- const newStartOffset =
43
- whiteSpaceBeforeIndex > -1
44
- ? charsBefore.length - whiteSpaceBeforeIndex
45
- : 0
46
- const whiteSpaceAfterIndex = charsAfter
47
- .split('')
48
- .findIndex((obj) => isEmpty(obj))
49
- const newEndOffset =
50
- charsBefore.length +
51
- (whiteSpaceAfterIndex > -1
52
- ? whiteSpaceAfterIndex
53
- : charsAfter.length + 1)
54
- if (
55
- !(
56
- newStartOffset === newEndOffset ||
57
- Number.isNaN(newStartOffset) ||
58
- Number.isNaN(newEndOffset)
59
- )
60
- ) {
61
- debug('pteExpandToWord: Expanding to focused word')
62
- Transforms.setSelection(editor, {
63
- anchor: {...selection.anchor, offset: newStartOffset},
64
- focus: {...selection.focus, offset: newEndOffset},
65
- })
66
- return
67
- }
68
- debug(`pteExpandToWord: Can't expand to word here`)
69
- }
70
- }
71
-
72
21
  editor.pteCreateTextBlock = (options: {
73
22
  decorators: Array<string>
74
23
  listItem?: string
@@ -9,6 +9,10 @@ export type {
9
9
  export {getActiveAnnotations} from './selector.get-active-annotations'
10
10
  export {getActiveListItem} from './selector.get-active-list-item'
11
11
  export {getActiveStyle} from './selector.get-active-style'
12
+ export {getBlockOffsets} from './selector.get-block-offsets'
13
+ export {getCaretWordSelection} from './selector.get-caret-word-selection'
14
+ export {getNextInlineObject} from './selector.get-next-inline-object'
15
+ export {getPreviousInlineObject} from './selector.get-previous-inline-object'
12
16
  export {getSelectedSlice} from './selector.get-selected-slice'
13
17
  export {getSelectedSpans} from './selector.get-selected-spans'
14
18
  export {getSelection} from './selector.get-selection'
@@ -0,0 +1,33 @@
1
+ import type {EditorSelector} from '../editor/editor-selector'
2
+ import * as utils from '../utils'
3
+ import {getSelectionEndPoint} from './selector.get-selection-end-point'
4
+ import {getSelectionStartPoint} from './selector.get-selection-start-point'
5
+
6
+ /**
7
+ * @public
8
+ */
9
+ export const getBlockOffsets: EditorSelector<
10
+ {start: utils.BlockOffset; end: utils.BlockOffset} | undefined
11
+ > = ({context}) => {
12
+ if (!context.selection) {
13
+ return undefined
14
+ }
15
+
16
+ const selectionStartPoint = getSelectionStartPoint({context})
17
+ const selectionEndPoint = getSelectionEndPoint({context})
18
+
19
+ if (!selectionStartPoint || !selectionEndPoint) {
20
+ return undefined
21
+ }
22
+
23
+ const start = utils.spanSelectionPointToBlockOffset({
24
+ value: context.value,
25
+ selectionPoint: selectionStartPoint,
26
+ })
27
+ const end = utils.spanSelectionPointToBlockOffset({
28
+ value: context.value,
29
+ selectionPoint: selectionEndPoint,
30
+ })
31
+
32
+ return start && end ? {start, end} : undefined
33
+ }
@@ -0,0 +1,288 @@
1
+ import type {PortableTextBlock} from '@sanity/types'
2
+ import {describe, expect, test} from 'vitest'
3
+ import {compileSchemaDefinition, defineSchema} from '../editor/define-schema'
4
+ import type {EditorSnapshot} from '../editor/editor-snapshot'
5
+ import {createTestKeyGenerator} from '../internal-utils/test-key-generator'
6
+ import type {EditorSelection} from '../utils'
7
+ import {getCaretWordSelection} from './selector.get-caret-word-selection'
8
+
9
+ const keyGenerator = createTestKeyGenerator()
10
+
11
+ function snapshot(value: Array<PortableTextBlock>, selection: EditorSelection) {
12
+ return {
13
+ context: {
14
+ value,
15
+ selection,
16
+ keyGenerator,
17
+ activeDecorators: [],
18
+ converters: [],
19
+ schema: compileSchemaDefinition(defineSchema({})),
20
+ },
21
+ } satisfies EditorSnapshot
22
+ }
23
+
24
+ describe(getCaretWordSelection.name, () => {
25
+ test('empty block', () => {
26
+ expect(
27
+ getCaretWordSelection(
28
+ snapshot(
29
+ [
30
+ {
31
+ _key: 'k0',
32
+ _type: 'block',
33
+ children: [
34
+ {
35
+ _key: 'k1',
36
+ _type: 'span',
37
+ text: '',
38
+ },
39
+ ],
40
+ },
41
+ ],
42
+ {
43
+ anchor: {
44
+ path: [{_key: 'k0'}, 'children', {_key: 'k1'}],
45
+ offset: 0,
46
+ },
47
+ focus: {
48
+ path: [{_key: 'k0'}, 'children', {_key: 'k1'}],
49
+ offset: 0,
50
+ },
51
+ },
52
+ ),
53
+ ),
54
+ ).toEqual(null)
55
+ })
56
+
57
+ test('inline object', () => {
58
+ expect(
59
+ getCaretWordSelection(
60
+ snapshot(
61
+ [
62
+ {
63
+ _key: 'k0',
64
+ _type: 'block',
65
+ children: [
66
+ {
67
+ _key: 'k1',
68
+ _type: 'stock-ticker',
69
+ symbol: 'FOO',
70
+ },
71
+ ],
72
+ },
73
+ ],
74
+ {
75
+ anchor: {
76
+ path: [{_key: 'k0'}, 'children', {_key: 'k1'}],
77
+ offset: 0,
78
+ },
79
+ focus: {
80
+ path: [{_key: 'k0'}, 'children', {_key: 'k1'}],
81
+ offset: 0,
82
+ },
83
+ },
84
+ ),
85
+ ),
86
+ ).toEqual(null)
87
+ })
88
+
89
+ test('between inline objects', () => {
90
+ expect(
91
+ getCaretWordSelection(
92
+ snapshot(
93
+ [
94
+ {
95
+ _key: 'k0',
96
+ _type: 'block',
97
+ children: [
98
+ {
99
+ _key: 'k1',
100
+ _type: 'stock-ticker',
101
+ symbol: 'FOO',
102
+ },
103
+ {
104
+ _key: 'k2',
105
+ _type: 'span',
106
+ text: '',
107
+ },
108
+ {
109
+ _key: 'k3',
110
+ _type: 'stock-ticker',
111
+ symbol: 'BAR',
112
+ },
113
+ ],
114
+ },
115
+ ],
116
+ {
117
+ anchor: {
118
+ path: [{_key: 'k0'}, 'children', {_key: 'k2'}],
119
+ offset: 0,
120
+ },
121
+ focus: {
122
+ path: [{_key: 'k0'}, 'children', {_key: 'k2'}],
123
+ offset: 0,
124
+ },
125
+ },
126
+ ),
127
+ ),
128
+ ).toEqual(null)
129
+ })
130
+
131
+ test('word between inline objects', () => {
132
+ expect(
133
+ getCaretWordSelection(
134
+ snapshot(
135
+ [
136
+ {
137
+ _key: 'k0',
138
+ _type: 'block',
139
+ children: [
140
+ {
141
+ _key: 'k1',
142
+ _type: 'stock-ticker',
143
+ symbol: 'FOO',
144
+ },
145
+ {
146
+ _key: 'k2',
147
+ _type: 'span',
148
+ text: 'foo',
149
+ },
150
+ {
151
+ _key: 'k3',
152
+ _type: 'stock-ticker',
153
+ symbol: 'BAR',
154
+ },
155
+ ],
156
+ },
157
+ ],
158
+ {
159
+ anchor: {
160
+ path: [{_key: 'k0'}, 'children', {_key: 'k2'}],
161
+ offset: 0,
162
+ },
163
+ focus: {
164
+ path: [{_key: 'k0'}, 'children', {_key: 'k2'}],
165
+ offset: 0,
166
+ },
167
+ },
168
+ ),
169
+ ),
170
+ ).toEqual({
171
+ anchor: {
172
+ path: [{_key: 'k0'}, 'children', {_key: 'k2'}],
173
+ offset: 0,
174
+ },
175
+ focus: {
176
+ path: [{_key: 'k0'}, 'children', {_key: 'k2'}],
177
+ offset: 3,
178
+ },
179
+ })
180
+ })
181
+
182
+ test('no formatting', () => {
183
+ expect(
184
+ getCaretWordSelection(
185
+ snapshot(
186
+ [
187
+ {
188
+ _key: 'k0',
189
+ _type: 'block',
190
+ children: [
191
+ {
192
+ _key: 'k1',
193
+ _type: 'span',
194
+ text: 'foo bar baz',
195
+ },
196
+ ],
197
+ },
198
+ ],
199
+ {
200
+ anchor: {
201
+ path: [{_key: 'k0'}, 'children', {_key: 'k1'}],
202
+ offset: 5,
203
+ },
204
+ focus: {
205
+ path: [{_key: 'k0'}, 'children', {_key: 'k1'}],
206
+ offset: 5,
207
+ },
208
+ },
209
+ ),
210
+ ),
211
+ ).toEqual({
212
+ anchor: {
213
+ path: [{_key: 'k0'}, 'children', {_key: 'k1'}],
214
+ offset: 4,
215
+ },
216
+ focus: {
217
+ path: [{_key: 'k0'}, 'children', {_key: 'k1'}],
218
+ offset: 7,
219
+ },
220
+ })
221
+ })
222
+
223
+ test('mixed formatting', () => {
224
+ expect(
225
+ getCaretWordSelection(
226
+ snapshot(
227
+ [
228
+ {
229
+ _key: 'k0',
230
+ _type: 'block',
231
+ children: [
232
+ {
233
+ _type: 'span',
234
+ _key: 'k1',
235
+ text: 'f',
236
+ marks: ['strong'],
237
+ },
238
+ {
239
+ _type: 'span',
240
+ _key: 'k2',
241
+ marks: ['strong', 'em'],
242
+ text: 'oo b',
243
+ },
244
+ {
245
+ _type: 'span',
246
+ _key: 'k3',
247
+ marks: ['strong', 'em', 'underline'],
248
+ text: 'a',
249
+ },
250
+ {
251
+ _type: 'span',
252
+ _key: 'k4',
253
+ marks: ['strong', 'underline'],
254
+ text: 'r ba',
255
+ },
256
+ {
257
+ _type: 'span',
258
+ _key: 'k5',
259
+ marks: ['strong'],
260
+ text: 'z',
261
+ },
262
+ ],
263
+ },
264
+ ],
265
+ {
266
+ anchor: {
267
+ path: [{_key: 'k0'}, 'children', {_key: 'k3'}],
268
+ offset: 0,
269
+ },
270
+ focus: {
271
+ path: [{_key: 'k0'}, 'children', {_key: 'k3'}],
272
+ offset: 0,
273
+ },
274
+ },
275
+ ),
276
+ ),
277
+ ).toEqual({
278
+ anchor: {
279
+ path: [{_key: 'k0'}, 'children', {_key: 'k2'}],
280
+ offset: 3,
281
+ },
282
+ focus: {
283
+ path: [{_key: 'k0'}, 'children', {_key: 'k4'}],
284
+ offset: 1,
285
+ },
286
+ })
287
+ })
288
+ })