@portabletext/editor 1.39.0 → 1.40.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 (109) hide show
  1. package/lib/_chunks-cjs/behavior.core.cjs +16 -5
  2. package/lib/_chunks-cjs/behavior.core.cjs.map +1 -1
  3. package/lib/_chunks-cjs/editor-provider.cjs +158 -153
  4. package/lib/_chunks-cjs/editor-provider.cjs.map +1 -1
  5. package/lib/_chunks-cjs/selector.get-text-before.cjs +0 -1
  6. package/lib/_chunks-cjs/selector.get-text-before.cjs.map +1 -1
  7. package/lib/_chunks-cjs/selector.is-at-the-start-of-block.cjs +0 -3
  8. package/lib/_chunks-cjs/selector.is-at-the-start-of-block.cjs.map +1 -1
  9. package/lib/_chunks-cjs/selector.is-overlapping-selection.cjs +0 -4
  10. package/lib/_chunks-cjs/selector.is-overlapping-selection.cjs.map +1 -1
  11. package/lib/_chunks-cjs/{parse-blocks.cjs → util.selection-point-to-block-offset.cjs} +74 -4
  12. package/lib/_chunks-cjs/util.selection-point-to-block-offset.cjs.map +1 -0
  13. package/lib/_chunks-cjs/util.slice-blocks.cjs +2 -2
  14. package/lib/_chunks-cjs/util.slice-blocks.cjs.map +1 -1
  15. package/lib/_chunks-cjs/util.split-text-block.cjs +68 -0
  16. package/lib/_chunks-cjs/util.split-text-block.cjs.map +1 -0
  17. package/lib/_chunks-es/behavior.core.js +17 -6
  18. package/lib/_chunks-es/behavior.core.js.map +1 -1
  19. package/lib/_chunks-es/editor-provider.js +155 -150
  20. package/lib/_chunks-es/editor-provider.js.map +1 -1
  21. package/lib/_chunks-es/selector.get-text-before.js +1 -2
  22. package/lib/_chunks-es/selector.get-text-before.js.map +1 -1
  23. package/lib/_chunks-es/selector.is-at-the-start-of-block.js +1 -4
  24. package/lib/_chunks-es/selector.is-at-the-start-of-block.js.map +1 -1
  25. package/lib/_chunks-es/selector.is-overlapping-selection.js +1 -5
  26. package/lib/_chunks-es/selector.is-overlapping-selection.js.map +1 -1
  27. package/lib/_chunks-es/{parse-blocks.js → util.selection-point-to-block-offset.js} +76 -5
  28. package/lib/_chunks-es/util.selection-point-to-block-offset.js.map +1 -0
  29. package/lib/_chunks-es/util.slice-blocks.js +2 -2
  30. package/lib/_chunks-es/util.slice-blocks.js.map +1 -1
  31. package/lib/_chunks-es/util.split-text-block.js +70 -0
  32. package/lib/_chunks-es/util.split-text-block.js.map +1 -0
  33. package/lib/behaviors/index.d.cts +1516 -911
  34. package/lib/behaviors/index.d.ts +1516 -911
  35. package/lib/index.cjs +197 -200
  36. package/lib/index.cjs.map +1 -1
  37. package/lib/index.d.cts +1247 -717
  38. package/lib/index.d.ts +1247 -717
  39. package/lib/index.js +204 -207
  40. package/lib/index.js.map +1 -1
  41. package/lib/plugins/index.cjs +11 -12
  42. package/lib/plugins/index.cjs.map +1 -1
  43. package/lib/plugins/index.d.cts +1238 -721
  44. package/lib/plugins/index.d.ts +1238 -721
  45. package/lib/plugins/index.js +3 -4
  46. package/lib/plugins/index.js.map +1 -1
  47. package/lib/selectors/index.d.cts +1237 -710
  48. package/lib/selectors/index.d.ts +1237 -710
  49. package/lib/utils/index.cjs +15 -87
  50. package/lib/utils/index.cjs.map +1 -1
  51. package/lib/utils/index.d.cts +1290 -713
  52. package/lib/utils/index.d.ts +1290 -713
  53. package/lib/utils/index.js +13 -86
  54. package/lib/utils/index.js.map +1 -1
  55. package/package.json +10 -10
  56. package/src/behavior-actions/behavior.action.decorator.add.ts +13 -2
  57. package/src/behaviors/behavior.core.block-objects.ts +32 -2
  58. package/src/behaviors/behavior.default.ts +59 -16
  59. package/src/behaviors/behavior.types.ts +67 -30
  60. package/src/editor/Editable.tsx +122 -68
  61. package/src/editor/PortableTextEditor.tsx +8 -8
  62. package/src/editor/__tests__/self-solving.test.tsx +1 -1
  63. package/src/editor/components/Element.tsx +1 -3
  64. package/src/editor/create-editor.ts +13 -5
  65. package/src/editor/editor-machine.ts +13 -3
  66. package/src/editor/editor-provider.tsx +11 -7
  67. package/src/editor/editor-selector.ts +4 -3
  68. package/src/editor/editor-snapshot.ts +2 -2
  69. package/src/editor/plugins/create-with-event-listeners.ts +18 -3
  70. package/src/editor/plugins/createWithPortableTextMarkModel.ts +1 -2
  71. package/src/internal-utils/block-keys.ts +9 -0
  72. package/src/internal-utils/collapse-selection.ts +36 -0
  73. package/src/internal-utils/compound-client-rect.ts +28 -0
  74. package/src/internal-utils/drag-selection.test.ts +507 -0
  75. package/src/internal-utils/drag-selection.ts +66 -0
  76. package/src/internal-utils/editor-selection.test.ts +40 -0
  77. package/src/internal-utils/editor-selection.ts +60 -0
  78. package/src/internal-utils/event-position.ts +55 -80
  79. package/src/internal-utils/inline-object-selection.ts +115 -0
  80. package/src/internal-utils/selection-block-keys.ts +20 -0
  81. package/src/internal-utils/selection-elements.ts +61 -0
  82. package/src/internal-utils/selection-focus-text.ts +38 -0
  83. package/src/internal-utils/selection-text.test.ts +23 -0
  84. package/src/internal-utils/selection-text.ts +90 -0
  85. package/src/internal-utils/split-string.ts +12 -0
  86. package/src/internal-utils/string-overlap.test.ts +14 -0
  87. package/src/internal-utils/string-overlap.ts +28 -0
  88. package/src/internal-utils/string-utils.ts +7 -0
  89. package/src/internal-utils/terse-pt.test.ts +60 -0
  90. package/src/internal-utils/terse-pt.ts +36 -0
  91. package/src/internal-utils/text-block-key.test.ts +30 -0
  92. package/src/internal-utils/text-block-key.ts +30 -0
  93. package/src/internal-utils/text-marks.test.ts +33 -0
  94. package/src/internal-utils/text-marks.ts +26 -0
  95. package/src/internal-utils/text-selection.test.ts +175 -0
  96. package/src/internal-utils/text-selection.ts +122 -0
  97. package/src/internal-utils/value-annotations.ts +31 -0
  98. package/src/internal-utils/values.ts +16 -5
  99. package/src/utils/index.ts +5 -0
  100. package/src/utils/util.block-offset-to-block-selection-point.ts +28 -0
  101. package/src/utils/util.block-offset-to-selection-point.ts +33 -0
  102. package/src/utils/util.block-offsets-to-selection.ts +3 -3
  103. package/src/utils/util.is-equal-selections.ts +20 -0
  104. package/src/utils/util.is-selection-collapsed.ts +15 -0
  105. package/src/utils/util.reverse-selection.ts +9 -5
  106. package/src/utils/util.selection-point-to-block-offset.ts +31 -0
  107. package/lib/_chunks-cjs/parse-blocks.cjs.map +0 -1
  108. package/lib/_chunks-es/parse-blocks.js.map +0 -1
  109. package/src/editor/components/use-draggable.ts +0 -123
@@ -1,92 +1,16 @@
1
- import { isKeyedSegment, sliceBlocks, isSpan } from "../_chunks-es/util.slice-blocks.js";
2
- import { blockOffsetToSpanSelectionPoint, getBlockEndPoint, getBlockStartPoint, getTextBlockText, isEmptyTextBlock, isEqualSelectionPoints, reverseSelection, spanSelectionPointToBlockOffset } from "../_chunks-es/util.slice-blocks.js";
3
- import { parseBlock } from "../_chunks-es/parse-blocks.js";
4
- import { blockOffsetsToSelection } from "../_chunks-es/parse-blocks.js";
5
- import { isPortableTextTextBlock, isPortableTextSpan } from "@sanity/types";
6
- function childSelectionPointToBlockOffset({
7
- value,
8
- selectionPoint
9
- }) {
10
- let offset = 0;
11
- const blockKey = isKeyedSegment(selectionPoint.path[0]) ? selectionPoint.path[0]._key : void 0, childKey = isKeyedSegment(selectionPoint.path[2]) ? selectionPoint.path[2]._key : void 0;
12
- if (!(!blockKey || !childKey)) {
13
- for (const block of value)
14
- if (block._key === blockKey && isPortableTextTextBlock(block))
15
- for (const child of block.children) {
16
- if (child._key === childKey)
17
- return {
18
- path: [{
19
- _key: block._key
20
- }],
21
- offset: offset + selectionPoint.offset
22
- };
23
- isPortableTextSpan(child) && (offset += child.text.length);
24
- }
25
- }
1
+ import { isEqualSelectionPoints } from "../_chunks-es/util.slice-blocks.js";
2
+ import { blockOffsetToSpanSelectionPoint, getBlockEndPoint, getBlockStartPoint, getTextBlockText, isEmptyTextBlock, isKeyedSegment, isSpan, reverseSelection, sliceBlocks, spanSelectionPointToBlockOffset } from "../_chunks-es/util.slice-blocks.js";
3
+ import { blockOffsetToBlockSelectionPoint, blockOffsetToSelectionPoint, blockOffsetsToSelection, childSelectionPointToBlockOffset, selectionPointToBlockOffset } from "../_chunks-es/util.selection-point-to-block-offset.js";
4
+ import { isTextBlock, mergeTextBlocks, splitTextBlock } from "../_chunks-es/util.split-text-block.js";
5
+ function isEqualSelections(a, b) {
6
+ return !a && !b ? !0 : !a || !b ? !1 : isEqualSelectionPoints(a.anchor, b.anchor) && isEqualSelectionPoints(a.focus, b.focus);
26
7
  }
27
- function isTextBlock(context, block) {
28
- return block._type === context.schema.block.name;
29
- }
30
- function mergeTextBlocks({
31
- context,
32
- targetBlock,
33
- incomingBlock
34
- }) {
35
- const parsedIncomingBlock = parseBlock({
36
- context,
37
- block: incomingBlock,
38
- options: {
39
- refreshKeys: !0
40
- }
41
- });
42
- return !parsedIncomingBlock || !isTextBlock(context, parsedIncomingBlock) ? targetBlock : {
43
- ...targetBlock,
44
- children: [...targetBlock.children, ...parsedIncomingBlock.children],
45
- markDefs: [...targetBlock.markDefs ?? [], ...parsedIncomingBlock.markDefs ?? []]
46
- };
47
- }
48
- function splitTextBlock({
49
- context,
50
- block,
51
- point
52
- }) {
53
- const firstChild = block.children.at(0), lastChild = block.children.at(block.children.length - 1);
54
- if (!firstChild || !lastChild)
55
- return;
56
- const before = sliceBlocks({
57
- blocks: [block],
58
- selection: {
59
- anchor: {
60
- path: [{
61
- _key: block._key
62
- }, "children", {
63
- _key: firstChild._key
64
- }],
65
- offset: 0
66
- },
67
- focus: point
68
- }
69
- }).at(0), after = sliceBlocks({
70
- blocks: [block],
71
- selection: {
72
- anchor: point,
73
- focus: {
74
- path: [{
75
- _key: block._key
76
- }, "children", {
77
- _key: lastChild._key
78
- }],
79
- offset: isSpan(context, lastChild) ? lastChild.text.length : 0
80
- }
81
- }
82
- }).at(0);
83
- if (!(!before || !after) && !(!isTextBlock(context, before) || !isTextBlock(context, after)))
84
- return {
85
- before,
86
- after
87
- };
8
+ function isSelectionCollapsed(selection) {
9
+ return selection ? selection.anchor.path.join() === selection.focus.path.join() && selection.anchor.offset === selection.focus.offset : !1;
88
10
  }
89
11
  export {
12
+ blockOffsetToBlockSelectionPoint,
13
+ blockOffsetToSelectionPoint,
90
14
  blockOffsetToSpanSelectionPoint,
91
15
  blockOffsetsToSelection,
92
16
  childSelectionPointToBlockOffset,
@@ -95,11 +19,14 @@ export {
95
19
  getTextBlockText,
96
20
  isEmptyTextBlock,
97
21
  isEqualSelectionPoints,
22
+ isEqualSelections,
98
23
  isKeyedSegment,
24
+ isSelectionCollapsed,
99
25
  isSpan,
100
26
  isTextBlock,
101
27
  mergeTextBlocks,
102
28
  reverseSelection,
29
+ selectionPointToBlockOffset,
103
30
  sliceBlocks,
104
31
  spanSelectionPointToBlockOffset,
105
32
  splitTextBlock
@@ -1 +1 @@
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 '../types/block-offset'\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 '..'\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 type {EditorContext} from '..'\nimport {parseBlock} from '../internal-utils/parse-blocks'\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 type {EditorSelectionPoint} from '..'\nimport type {EditorContext} from '../editor/editor-snapshot'\nimport {isSpan} from './util.is-span'\nimport {isTextBlock} from './util.is-text-block'\nimport {sliceBlocks} from './util.slice-blocks'\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;ACzBO,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;"}
1
+ {"version":3,"file":"index.js","sources":["../../src/utils/util.is-equal-selections.ts","../../src/utils/util.is-selection-collapsed.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 isSelectionCollapsed(selection: EditorSelection) {\n if (!selection) {\n return false\n }\n\n return (\n selection.anchor.path.join() === selection.focus.path.join() &&\n selection.anchor.offset === selection.focus.offset\n )\n}\n"],"names":["isEqualSelections","a","b","isEqualSelectionPoints","anchor","focus","isSelectionCollapsed","selection","path","join","offset"],"mappings":";;;;AAMgBA,SAAAA,kBAAkBC,GAAoBC,GAAoB;AACpE,SAAA,CAACD,KAAK,CAACC,IACF,KAGL,CAACD,KAAK,CAACC,IACF,KAIPC,uBAAuBF,EAAEG,QAAQF,EAAEE,MAAM,KACzCD,uBAAuBF,EAAEI,OAAOH,EAAEG,KAAK;AAE3C;ACdO,SAASC,qBAAqBC,WAA4B;AAC/D,SAAKA,YAKHA,UAAUH,OAAOI,KAAKC,KAAAA,MAAWF,UAAUF,MAAMG,KAAKC,KAAAA,KACtDF,UAAUH,OAAOM,WAAWH,UAAUF,MAAMK,SALrC;AAOX;"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@portabletext/editor",
3
- "version": "1.39.0",
3
+ "version": "1.40.0",
4
4
  "description": "Portable Text Editor made in React",
5
5
  "keywords": [
6
6
  "sanity",
@@ -73,19 +73,19 @@
73
73
  "get-random-values-esm": "^1.0.2",
74
74
  "lodash": "^4.17.21",
75
75
  "lodash.startcase": "^4.4.0",
76
- "react-compiler-runtime": "19.0.0-beta-e1e972c-20250221",
76
+ "react-compiler-runtime": "19.0.0-beta-3229e95-20250315",
77
77
  "slate": "0.112.0",
78
78
  "slate-dom": "^0.112.2",
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.13",
83
- "@portabletext/patches": "1.1.3"
82
+ "@portabletext/patches": "1.1.3",
83
+ "@portabletext/block-tools": "1.1.13"
84
84
  },
85
85
  "devDependencies": {
86
86
  "@portabletext/toolkit": "^2.0.17",
87
87
  "@sanity/diff-match-patch": "^3.2.0",
88
- "@sanity/pkg-utils": "^7.0.4",
88
+ "@sanity/pkg-utils": "^7.1.0",
89
89
  "@sanity/schema": "^3.79.0",
90
90
  "@sanity/types": "^3.79.0",
91
91
  "@testing-library/jest-dom": "^6.6.3",
@@ -95,20 +95,20 @@
95
95
  "@types/lodash.startcase": "^4.4.9",
96
96
  "@types/react": "^19.0.10",
97
97
  "@types/react-dom": "^19.0.4",
98
- "@typescript-eslint/eslint-plugin": "^8.18.1",
99
- "@typescript-eslint/parser": "^8.18.1",
98
+ "@typescript-eslint/eslint-plugin": "^8.26.1",
99
+ "@typescript-eslint/parser": "^8.26.1",
100
100
  "@vitejs/plugin-react": "^4.3.4",
101
101
  "@vitest/browser": "^3.0.8",
102
102
  "@vitest/coverage-istanbul": "^3.0.8",
103
- "babel-plugin-react-compiler": "19.0.0-beta-bafa41b-20250307",
103
+ "babel-plugin-react-compiler": "19.0.0-beta-3229e95-20250315",
104
104
  "eslint": "8.57.1",
105
- "eslint-plugin-react-compiler": "19.0.0-beta-bafa41b-20250307",
105
+ "eslint-plugin-react-compiler": "19.0.0-beta-3229e95-20250315",
106
106
  "eslint-plugin-react-hooks": "experimental",
107
107
  "jsdom": "^26.0.0",
108
108
  "react": "^19.0.0",
109
109
  "react-dom": "^19.0.0",
110
110
  "rxjs": "^7.8.2",
111
- "typescript": "5.7.3",
111
+ "typescript": "5.8.2",
112
112
  "vite": "^6.2.0",
113
113
  "vitest": "^3.0.8",
114
114
  "vitest-browser-react": "^0.1.1",
@@ -49,13 +49,13 @@ export const decoratorAddActionImplementation: BehaviorActionImplementation<
49
49
 
50
50
  const editorSelection = toPortableTextRange(value, selection, context.schema)
51
51
  const anchorOffset = editorSelection
52
- ? utils.spanSelectionPointToBlockOffset({
52
+ ? utils.selectionPointToBlockOffset({
53
53
  value,
54
54
  selectionPoint: editorSelection.anchor,
55
55
  })
56
56
  : undefined
57
57
  const focusOffset = editorSelection
58
- ? utils.spanSelectionPointToBlockOffset({
58
+ ? utils.selectionPointToBlockOffset({
59
59
  value,
60
60
  selectionPoint: editorSelection.focus,
61
61
  })
@@ -134,6 +134,17 @@ export const decoratorAddActionImplementation: BehaviorActionImplementation<
134
134
  )
135
135
  }
136
136
  } else {
137
+ const selectedSpan = Array.from(
138
+ Editor.nodes(editor, {
139
+ at: selection,
140
+ match: (node) => editor.isTextSpan(node),
141
+ }),
142
+ )?.at(0)
143
+
144
+ if (!selectedSpan) {
145
+ return
146
+ }
147
+
137
148
  const [block, blockPath] = Editor.node(editor, selection, {
138
149
  depth: 1,
139
150
  })
@@ -8,10 +8,21 @@ const arrowDownOnLonelyBlockObject = defineBehavior({
8
8
  on: 'keyboard.keydown',
9
9
  guard: ({snapshot, event}) => {
10
10
  const isArrowDown = isHotkey('ArrowDown', event.originEvent)
11
+
12
+ if (!isArrowDown) {
13
+ return false
14
+ }
15
+
16
+ const collapsedSelection = selectors.isSelectionCollapsed(snapshot)
17
+
18
+ if (!collapsedSelection) {
19
+ return false
20
+ }
21
+
11
22
  const focusBlockObject = selectors.getFocusBlockObject(snapshot)
12
23
  const nextBlock = selectors.getNextBlock(snapshot)
13
24
 
14
- return isArrowDown && focusBlockObject && !nextBlock
25
+ return focusBlockObject && !nextBlock
15
26
  },
16
27
  actions: [() => [raise({type: 'insert.text block', placement: 'after'})]],
17
28
  })
@@ -20,10 +31,21 @@ const arrowUpOnLonelyBlockObject = defineBehavior({
20
31
  on: 'keyboard.keydown',
21
32
  guard: ({snapshot, event}) => {
22
33
  const isArrowUp = isHotkey('ArrowUp', event.originEvent)
34
+
35
+ if (!isArrowUp) {
36
+ return false
37
+ }
38
+
39
+ const collapsedSelection = selectors.isSelectionCollapsed(snapshot)
40
+
41
+ if (!collapsedSelection) {
42
+ return false
43
+ }
44
+
23
45
  const focusBlockObject = selectors.getFocusBlockObject(snapshot)
24
46
  const previousBlock = selectors.getPreviousBlock(snapshot)
25
47
 
26
- return isArrowUp && focusBlockObject && !previousBlock
48
+ return focusBlockObject && !previousBlock
27
49
  },
28
50
  actions: [() => [raise({type: 'insert.text block', placement: 'before'})]],
29
51
  })
@@ -42,6 +64,10 @@ const breakingBlockObject = defineBehavior({
42
64
  const clickingAboveLonelyBlockObject = defineBehavior({
43
65
  on: 'mouse.click',
44
66
  guard: ({snapshot, event}) => {
67
+ if (!selectors.isSelectionCollapsed(snapshot)) {
68
+ return false
69
+ }
70
+
45
71
  const focusBlockObject = selectors.getFocusBlockObject(snapshot)
46
72
  const previousBlock = selectors.getPreviousBlock(snapshot)
47
73
 
@@ -58,6 +84,10 @@ const clickingAboveLonelyBlockObject = defineBehavior({
58
84
  const clickingBelowLonelyBlockObject = defineBehavior({
59
85
  on: 'mouse.click',
60
86
  guard: ({snapshot, event}) => {
87
+ if (!selectors.isSelectionCollapsed(snapshot)) {
88
+ return false
89
+ }
90
+
61
91
  const focusBlockObject = selectors.getFocusBlockObject(snapshot)
62
92
  const nextBlock = selectors.getNextBlock(snapshot)
63
93
 
@@ -1,4 +1,5 @@
1
1
  import * as selectors from '../selectors'
2
+ import {blockOffsetsToSelection} from '../utils'
2
3
  import {raiseInsertSoftBreak} from './behavior.default.raise-soft-break'
3
4
  import {defineBehavior, raise} from './behavior.types'
4
5
 
@@ -37,10 +38,33 @@ const toggleDecoratorOff = defineBehavior({
37
38
 
38
39
  const toggleDecoratorOn = defineBehavior({
39
40
  on: 'decorator.toggle',
40
- guard: ({snapshot, event}) =>
41
- !selectors.isActiveDecorator(event.decorator)(snapshot),
41
+ guard: ({snapshot, event}) => {
42
+ const manualSelection = event.offsets
43
+ ? blockOffsetsToSelection({
44
+ value: snapshot.context.value,
45
+ offsets: event.offsets,
46
+ })
47
+ : null
48
+
49
+ if (manualSelection) {
50
+ return !selectors.isActiveDecorator(event.decorator)({
51
+ ...snapshot,
52
+ context: {
53
+ ...snapshot.context,
54
+ selection: manualSelection,
55
+ },
56
+ })
57
+ }
58
+
59
+ return !selectors.isActiveDecorator(event.decorator)(snapshot)
60
+ },
42
61
  actions: [
43
- ({event}) => [raise({type: 'decorator.add', decorator: event.decorator})],
62
+ ({event}) => [
63
+ raise({
64
+ ...event,
65
+ type: 'decorator.add',
66
+ }),
67
+ ],
44
68
  ],
45
69
  })
46
70
 
@@ -316,24 +340,21 @@ export const defaultBehaviors = [
316
340
  })
317
341
 
318
342
  if (!droppingOnDragOrigin) {
319
- return {draggingEntireBlocks, draggedBlocks, dragOrigin}
343
+ return {
344
+ draggingEntireBlocks,
345
+ draggedBlocks,
346
+ dragOrigin,
347
+ originEvent: event.originEvent,
348
+ }
320
349
  }
321
350
 
322
351
  return false
323
352
  },
324
353
  actions: [
325
- ({event}, {draggingEntireBlocks, draggedBlocks, dragOrigin}) => [
326
- raise({
327
- type: 'insert.blocks',
328
- blocks: event.data,
329
- placement: draggingEntireBlocks
330
- ? event.originEvent.position.block === 'start'
331
- ? 'before'
332
- : event.originEvent.position.block === 'end'
333
- ? 'after'
334
- : 'auto'
335
- : 'auto',
336
- }),
354
+ (
355
+ {event},
356
+ {draggingEntireBlocks, draggedBlocks, dragOrigin, originEvent},
357
+ ) => [
337
358
  ...(draggingEntireBlocks
338
359
  ? draggedBlocks.map((block) =>
339
360
  raise({
@@ -347,6 +368,17 @@ export const defaultBehaviors = [
347
368
  selection: dragOrigin.selection,
348
369
  }),
349
370
  ]),
371
+ raise({
372
+ type: 'insert.blocks',
373
+ blocks: event.data,
374
+ placement: draggingEntireBlocks
375
+ ? originEvent.position.block === 'start'
376
+ ? 'before'
377
+ : originEvent.position.block === 'end'
378
+ ? 'after'
379
+ : 'auto'
380
+ : 'auto',
381
+ }),
350
382
  ],
351
383
  ],
352
384
  }),
@@ -394,6 +426,17 @@ export const defaultBehaviors = [
394
426
  ],
395
427
  ],
396
428
  }),
429
+ defineBehavior({
430
+ on: 'input.*',
431
+ actions: [
432
+ ({event}) => [
433
+ raise({
434
+ type: 'deserialize',
435
+ originEvent: event,
436
+ }),
437
+ ],
438
+ ],
439
+ }),
397
440
  toggleAnnotationOff,
398
441
  toggleAnnotationOn,
399
442
  toggleDecoratorOff,
@@ -68,6 +68,7 @@ export type SyntheticBehaviorEvent =
68
68
  | {
69
69
  type: 'decorator.toggle'
70
70
  decorator: string
71
+ offsets?: {anchor: BlockOffset; focus: BlockOffset}
71
72
  }
72
73
  | {
73
74
  type: 'delete'
@@ -206,11 +207,13 @@ export type SyntheticBehaviorEvent =
206
207
  'type',
207
208
  'deserialization.failure' | 'deserialization.success'
208
209
  > & {
209
- originEvent: PickFromUnion<
210
- NativeBehaviorEvent,
211
- 'type',
212
- 'drag.drop' | 'clipboard.paste'
213
- >
210
+ originEvent:
211
+ | PickFromUnion<
212
+ NativeBehaviorEvent,
213
+ 'type',
214
+ 'drag.drop' | 'clipboard.paste'
215
+ >
216
+ | InputBehaviorEvent
214
217
  })
215
218
  | {
216
219
  type: 'serialization.success'
@@ -241,21 +244,21 @@ type ClipboardBehaviorEvent =
241
244
  originEvent: {
242
245
  dataTransfer: DataTransfer
243
246
  }
244
- position: EventPosition
247
+ position: Pick<EventPosition, 'selection'>
245
248
  }
246
249
  | {
247
250
  type: 'clipboard.cut'
248
251
  originEvent: {
249
252
  dataTransfer: DataTransfer
250
253
  }
251
- position: EventPosition
254
+ position: Pick<EventPosition, 'selection'>
252
255
  }
253
256
  | {
254
257
  type: 'clipboard.paste'
255
258
  originEvent: {
256
259
  dataTransfer: DataTransfer
257
260
  }
258
- position: EventPosition
261
+ position: Pick<EventPosition, 'selection'>
259
262
  }
260
263
 
261
264
  export function isClipboardBehaviorEvent(
@@ -270,7 +273,7 @@ type DragBehaviorEvent =
270
273
  originEvent: {
271
274
  dataTransfer: DataTransfer
272
275
  }
273
- position: EventPosition
276
+ position: Pick<EventPosition, 'selection'>
274
277
  }
275
278
  | {
276
279
  type: 'drag.drag'
@@ -318,6 +321,30 @@ export function isDragBehaviorEvent(
318
321
  return event.type.startsWith('drag.')
319
322
  }
320
323
 
324
+ /**
325
+ * Used to represent native InputEvents that hold a DataTransfer object.
326
+ *
327
+ * These can either be one of:
328
+ *
329
+ * - insertFromPaste
330
+ * - insertFromPasteAsQuotation
331
+ * - insertFromDrop
332
+ * - insertReplacementText
333
+ * - insertFromYank
334
+ */
335
+ export type InputBehaviorEvent = {
336
+ type: 'input.*'
337
+ originEvent: {
338
+ dataTransfer: DataTransfer
339
+ }
340
+ }
341
+
342
+ export function isInputBehaviorEvent(
343
+ event: BehaviorEvent,
344
+ ): event is InputBehaviorEvent {
345
+ return event.type.startsWith('input.')
346
+ }
347
+
321
348
  export type KeyboardBehaviorEvent =
322
349
  | {
323
350
  type: 'keyboard.keydown'
@@ -343,11 +370,13 @@ export function isKeyboardBehaviorEvent(
343
370
  export type DataBehaviorEvent =
344
371
  | {
345
372
  type: 'deserialize'
346
- originEvent: PickFromUnion<
347
- NativeBehaviorEvent,
348
- 'type',
349
- 'drag.drop' | 'clipboard.paste'
350
- >
373
+ originEvent:
374
+ | PickFromUnion<
375
+ NativeBehaviorEvent,
376
+ 'type',
377
+ 'drag.drop' | 'clipboard.paste'
378
+ >
379
+ | InputBehaviorEvent
351
380
  }
352
381
  | {
353
382
  type: 'serialize'
@@ -374,9 +403,10 @@ export function isMouseBehaviorEvent(
374
403
  */
375
404
  export type NativeBehaviorEvent =
376
405
  | ClipboardBehaviorEvent
406
+ | DragBehaviorEvent
407
+ | InputBehaviorEvent
377
408
  | KeyboardBehaviorEvent
378
409
  | MouseBehaviorEvent
379
- | DragBehaviorEvent
380
410
 
381
411
  /**
382
412
  * @beta
@@ -440,6 +470,7 @@ export type BehaviorEvent =
440
470
  | {type: '*'}
441
471
  | {type: 'clipboard.*'}
442
472
  | {type: 'drag.*'}
473
+ | {type: 'input.*'}
443
474
  | {type: 'keyboard.*'}
444
475
  | {type: 'mouse.*'}
445
476
 
@@ -455,11 +486,13 @@ export type Behavior<
455
486
  ? ClipboardBehaviorEvent
456
487
  : TBehaviorEventType extends 'drag.*'
457
488
  ? DragBehaviorEvent
458
- : TBehaviorEventType extends 'keyboard.*'
459
- ? KeyboardBehaviorEvent
460
- : TBehaviorEventType extends 'mouse.*'
461
- ? MouseBehaviorEvent
462
- : PickFromUnion<BehaviorEvent, 'type', TBehaviorEventType>,
489
+ : TBehaviorEventType extends 'input.*'
490
+ ? InputBehaviorEvent
491
+ : TBehaviorEventType extends 'keyboard.*'
492
+ ? KeyboardBehaviorEvent
493
+ : TBehaviorEventType extends 'mouse.*'
494
+ ? MouseBehaviorEvent
495
+ : PickFromUnion<BehaviorEvent, 'type', TBehaviorEventType>,
463
496
  > = {
464
497
  /**
465
498
  * The internal editor event that triggers this behavior.
@@ -541,11 +574,13 @@ export function defineBehavior<
541
574
  ? ClipboardBehaviorEvent
542
575
  : TBehaviorEventType extends `drag.*`
543
576
  ? DragBehaviorEvent
544
- : TBehaviorEventType extends 'keyboard.*'
545
- ? KeyboardBehaviorEvent
546
- : TBehaviorEventType extends 'mouse.*'
547
- ? MouseBehaviorEvent
548
- : PickFromUnion<BehaviorEvent, 'type', TBehaviorEventType>
577
+ : TBehaviorEventType extends 'input.*'
578
+ ? InputBehaviorEvent
579
+ : TBehaviorEventType extends 'keyboard.*'
580
+ ? KeyboardBehaviorEvent
581
+ : TBehaviorEventType extends 'mouse.*'
582
+ ? MouseBehaviorEvent
583
+ : PickFromUnion<BehaviorEvent, 'type', TBehaviorEventType>
549
584
  >,
550
585
  ): Behavior
551
586
  export function defineBehavior<
@@ -561,11 +596,13 @@ export function defineBehavior<
561
596
  ? ClipboardBehaviorEvent
562
597
  : TBehaviorEventType extends `drag.*`
563
598
  ? DragBehaviorEvent
564
- : TBehaviorEventType extends 'keyboard.*'
565
- ? KeyboardBehaviorEvent
566
- : TBehaviorEventType extends 'mouse.*'
567
- ? MouseBehaviorEvent
568
- : PickFromUnion<BehaviorEvent, 'type', TBehaviorEventType>,
599
+ : TBehaviorEventType extends 'input.*'
600
+ ? InputBehaviorEvent
601
+ : TBehaviorEventType extends 'keyboard.*'
602
+ ? KeyboardBehaviorEvent
603
+ : TBehaviorEventType extends 'mouse.*'
604
+ ? MouseBehaviorEvent
605
+ : PickFromUnion<BehaviorEvent, 'type', TBehaviorEventType>,
569
606
  >(
570
607
  behavior: Behavior<TBehaviorEventType, TGuardResponse, TBehaviorEvent>,
571
608
  ): Behavior {