@portabletext/editor 1.54.4 → 1.55.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 (66) hide show
  1. package/lib/_chunks-cjs/selector.get-text-before.cjs +2 -0
  2. package/lib/_chunks-cjs/selector.get-text-before.cjs.map +1 -1
  3. package/lib/_chunks-cjs/selector.is-selecting-entire-blocks.cjs +93 -104
  4. package/lib/_chunks-cjs/selector.is-selecting-entire-blocks.cjs.map +1 -1
  5. package/lib/_chunks-cjs/selector.is-selection-expanded.cjs +18 -6
  6. package/lib/_chunks-cjs/selector.is-selection-expanded.cjs.map +1 -1
  7. package/lib/_chunks-es/selector.get-text-before.js +2 -0
  8. package/lib/_chunks-es/selector.get-text-before.js.map +1 -1
  9. package/lib/_chunks-es/selector.is-selecting-entire-blocks.js +93 -104
  10. package/lib/_chunks-es/selector.is-selecting-entire-blocks.js.map +1 -1
  11. package/lib/_chunks-es/selector.is-selection-expanded.js +19 -7
  12. package/lib/_chunks-es/selector.is-selection-expanded.js.map +1 -1
  13. package/lib/behaviors/index.d.cts +1 -0
  14. package/lib/behaviors/index.d.ts +1 -0
  15. package/lib/index.cjs +129 -57
  16. package/lib/index.cjs.map +1 -1
  17. package/lib/index.d.cts +3 -0
  18. package/lib/index.d.ts +3 -0
  19. package/lib/index.js +131 -59
  20. package/lib/index.js.map +1 -1
  21. package/lib/plugins/index.cjs +1 -0
  22. package/lib/plugins/index.cjs.map +1 -1
  23. package/lib/plugins/index.d.cts +1 -0
  24. package/lib/plugins/index.d.ts +1 -0
  25. package/lib/plugins/index.js +1 -0
  26. package/lib/plugins/index.js.map +1 -1
  27. package/lib/selectors/index.cjs +15 -57
  28. package/lib/selectors/index.cjs.map +1 -1
  29. package/lib/selectors/index.d.cts +8 -0
  30. package/lib/selectors/index.d.ts +8 -0
  31. package/lib/selectors/index.js +18 -60
  32. package/lib/selectors/index.js.map +1 -1
  33. package/lib/utils/index.d.cts +1 -0
  34. package/lib/utils/index.d.ts +1 -0
  35. package/package.json +5 -5
  36. package/src/behaviors/behavior.abstract.delete.ts +2 -8
  37. package/src/behaviors/behavior.abstract.split.ts +4 -3
  38. package/src/converters/converter.portable-text.ts +2 -8
  39. package/src/converters/converter.text-html.ts +2 -8
  40. package/src/converters/converter.text-plain.ts +2 -8
  41. package/src/editor/components/render-text-block.tsx +9 -0
  42. package/src/editor/create-slate-editor.tsx +14 -3
  43. package/src/editor/editor-selector.ts +1 -0
  44. package/src/editor/editor-snapshot.ts +2 -0
  45. package/src/editor/plugins/slate-plugin.update-value.ts +5 -0
  46. package/src/internal-utils/build-index-maps.test.ts +232 -0
  47. package/src/internal-utils/build-index-maps.ts +97 -0
  48. package/src/internal-utils/create-test-snapshot.ts +17 -9
  49. package/src/internal-utils/mark-state.ts +1 -0
  50. package/src/operations/behavior.operation.decorator.add.ts +1 -0
  51. package/src/selectors/index.ts +1 -0
  52. package/src/selectors/selector.get-anchor-block.ts +3 -4
  53. package/src/selectors/selector.get-focus-block.ts +3 -3
  54. package/src/selectors/selector.get-list-state.ts +34 -96
  55. package/src/selectors/selector.get-next-block.ts +7 -16
  56. package/src/selectors/selector.get-previous-block.ts +7 -13
  57. package/src/selectors/selector.get-selected-blocks.ts +13 -1
  58. package/src/selectors/selector.get-selected-slice.ts +3 -5
  59. package/src/selectors/selector.get-selected-spans.ts +14 -3
  60. package/src/selectors/selector.get-selected-text-blocks.ts +13 -1
  61. package/src/selectors/selector.get-selected-value.ts +47 -0
  62. package/src/selectors/selector.get-selection-text.ts +3 -3
  63. package/src/selectors/selector.get-trimmed-selection.ts +13 -1
  64. package/src/selectors/selector.is-point-after-selection.ts +58 -38
  65. package/src/selectors/selector.is-point-before-selection.ts +58 -38
  66. package/src/types/editor.ts +2 -0
@@ -1,14 +1,14 @@
1
- import { getSelectionEndPoint, getPreviousBlock } from "../_chunks-es/selector.is-selecting-entire-blocks.js";
2
- import { getActiveAnnotations, getActiveListItem, getActiveStyle, getCaretWordSelection, getFirstBlock, getFocusBlockObject, getFocusInlineObject, getFocusListBlock, getLastBlock, getNextBlock, getNextInlineObject, getSelectedBlocks, getSelectedSpans, getSelectedTextBlocks, getSelectionEndBlock, getSelectionStartBlock, getTrimmedSelection, isActiveAnnotation, isActiveDecorator, isActiveListItem, isActiveStyle, isAtTheEndOfBlock, isAtTheStartOfBlock, isOverlappingSelection, isPointAfterSelection, isPointBeforeSelection, isSelectingEntireBlocks } from "../_chunks-es/selector.is-selecting-entire-blocks.js";
1
+ import { getSelectionEndPoint } from "../_chunks-es/selector.is-selecting-entire-blocks.js";
2
+ import { getActiveAnnotations, getActiveListItem, getActiveStyle, getCaretWordSelection, getFirstBlock, getFocusBlockObject, getFocusInlineObject, getFocusListBlock, getLastBlock, getNextBlock, getNextInlineObject, getPreviousBlock, getSelectedBlocks, getSelectedSpans, getSelectedTextBlocks, getSelectionEndBlock, getSelectionStartBlock, getTrimmedSelection, isActiveAnnotation, isActiveDecorator, isActiveListItem, isActiveStyle, isAtTheEndOfBlock, isAtTheStartOfBlock, isOverlappingSelection, isPointAfterSelection, isPointBeforeSelection, isSelectingEntireBlocks } from "../_chunks-es/selector.is-selecting-entire-blocks.js";
3
3
  import { getBlockKeyFromSelectionPoint, isTextBlock, getChildKeyFromSelectionPoint, spanSelectionPointToBlockOffset } from "../_chunks-es/selection-point.js";
4
4
  import { isPortableTextSpan } from "@sanity/types";
5
- import { getSelectionStartPoint, getFocusTextBlock } from "../_chunks-es/selector.is-selection-expanded.js";
6
- import { getFocusBlock, getFocusChild, getFocusSpan, getPreviousInlineObject, getSelectedSlice, getSelectionText, isSelectionCollapsed, isSelectionExpanded } from "../_chunks-es/selector.is-selection-expanded.js";
5
+ import { getSelectionStartPoint, getFocusTextBlock, getSelectedValue } from "../_chunks-es/selector.is-selection-expanded.js";
6
+ import { getFocusBlock, getFocusChild, getFocusSpan, getPreviousInlineObject, getSelectionText, isSelectionCollapsed, isSelectionExpanded } from "../_chunks-es/selector.is-selection-expanded.js";
7
7
  import { getBlockTextBefore } from "../_chunks-es/selector.get-text-before.js";
8
8
  const getAnchorBlock = (snapshot) => {
9
9
  if (!snapshot.context.selection)
10
10
  return;
11
- const key = getBlockKeyFromSelectionPoint(snapshot.context.selection.anchor), node = key ? snapshot.context.value.find((block) => block._key === key) : void 0;
11
+ const key = getBlockKeyFromSelectionPoint(snapshot.context.selection.anchor), index = key ? snapshot.blockIndexMap.get(key) : void 0, node = index !== void 0 ? snapshot.context.value.at(index) : void 0;
12
12
  return node && key ? {
13
13
  node,
14
14
  path: [{
@@ -72,6 +72,7 @@ function getListIndex({
72
72
  offset: 0
73
73
  }
74
74
  }, focusTextBlock = getFocusTextBlock({
75
+ ...snapshot,
75
76
  context: {
76
77
  ...snapshot.context,
77
78
  selection
@@ -79,64 +80,20 @@ function getListIndex({
79
80
  });
80
81
  if (!focusTextBlock || focusTextBlock.node.listItem === void 0 || focusTextBlock.node.level === void 0)
81
82
  return;
82
- const previousListItem = getPreviousListItem({
83
- listItem: focusTextBlock.node.listItem,
84
- level: focusTextBlock.node.level
85
- })({
86
- ...snapshot,
87
- context: {
88
- ...snapshot.context,
89
- selection
90
- }
91
- });
92
- if (!previousListItem || previousListItem.node.listItem !== focusTextBlock.node.listItem || previousListItem.node.level !== void 0 && previousListItem.node.level < focusTextBlock.node.level)
93
- return 1;
94
- const previousListItemListState = getListIndex({
95
- path: previousListItem.path
96
- })(snapshot);
97
- return previousListItemListState === void 0 ? 1 : previousListItemListState + 1;
98
- };
99
- }
100
- function getPreviousListItem({
101
- listItem,
102
- level
103
- }) {
104
- return (snapshot) => {
105
- const previousBlock = getPreviousBlock({
106
- context: {
107
- ...snapshot.context
108
- }
109
- });
110
- if (previousBlock && isTextBlock(snapshot.context, previousBlock.node) && !(previousBlock.node.listItem === void 0 || previousBlock.node.level === void 0) && previousBlock.node.listItem === listItem) {
111
- if (previousBlock.node.level === level)
112
- return {
113
- node: previousBlock.node,
114
- path: previousBlock.path
115
- };
116
- if (!(previousBlock.node.level < level))
117
- return getPreviousListItem({
118
- listItem,
119
- level
120
- })({
121
- ...snapshot,
122
- context: {
123
- ...snapshot.context,
124
- selection: {
125
- anchor: {
126
- path: previousBlock.path,
127
- offset: 0
128
- },
129
- focus: {
130
- path: previousBlock.path,
131
- offset: 0
132
- }
133
- }
134
- }
135
- });
83
+ const targetListItem = focusTextBlock.node.listItem, targetLevel = focusTextBlock.node.level, targetKey = focusTextBlock.node._key, targetIndex = snapshot.blockIndexMap.get(targetKey);
84
+ if (targetIndex === void 0)
85
+ return;
86
+ let listIndex = 1;
87
+ for (let i = targetIndex - 1; i >= 0; i--) {
88
+ const block = snapshot.context.value[i];
89
+ if (!isTextBlock(snapshot.context, block) || block.listItem === void 0 || block.level === void 0 || block.listItem !== targetListItem || block.level < targetLevel)
90
+ break;
91
+ block.level === targetLevel && listIndex++;
136
92
  }
93
+ return listIndex;
137
94
  };
138
95
  }
139
- const getSelection = (snapshot) => snapshot.context.selection, getValue = (snapshot) => snapshot.context.value;
96
+ const getSelectedSlice = (snapshot) => getSelectedValue(snapshot), getSelection = (snapshot) => snapshot.context.selection, getValue = (snapshot) => snapshot.context.value;
140
97
  export {
141
98
  getActiveAnnotations,
142
99
  getActiveListItem,
@@ -166,6 +123,7 @@ export {
166
123
  getSelectedSlice,
167
124
  getSelectedSpans,
168
125
  getSelectedTextBlocks,
126
+ getSelectedValue,
169
127
  getSelection,
170
128
  getSelectionEndBlock,
171
129
  getSelectionEndPoint,
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":["../../src/selectors/selector.get-anchor-block.ts","../../src/selectors/selector.get-anchor-text-block.ts","../../src/selectors/selector.get-anchor-child.ts","../../src/selectors/selector.get-anchor-span.ts","../../src/selectors/selector.get-block-offsets.ts","../../src/selectors/selector.get-list-state.ts","../../src/selectors/selector.get-selection.ts","../../src/selectors/selector.get-value.ts"],"sourcesContent":["import type {PortableTextBlock} from '@sanity/types'\nimport type {EditorSelector} from '../editor/editor-selector'\nimport {getBlockKeyFromSelectionPoint} from '../selection/selection-point'\nimport type {BlockPath} from '../types/paths'\n\n/**\n * @public\n */\nexport const getAnchorBlock: EditorSelector<\n {node: PortableTextBlock; path: BlockPath} | undefined\n> = (snapshot) => {\n if (!snapshot.context.selection) {\n return undefined\n }\n\n const key = getBlockKeyFromSelectionPoint(snapshot.context.selection.anchor)\n\n const node = key\n ? snapshot.context.value.find((block) => block._key === key)\n : undefined\n\n return node && key ? {node, path: [{_key: key}]} : undefined\n}\n","import type {PortableTextTextBlock} from '@sanity/types'\nimport type {EditorSelector} from '../editor/editor-selector'\nimport {isTextBlock} from '../internal-utils/parse-blocks'\nimport type {BlockPath} from '../types/paths'\nimport {getAnchorBlock} from './selector.get-anchor-block'\n\n/**\n * @public\n */\nexport const getAnchorTextBlock: EditorSelector<\n {node: PortableTextTextBlock; path: BlockPath} | undefined\n> = (snapshot) => {\n const anchorBlock = getAnchorBlock(snapshot)\n\n return anchorBlock && isTextBlock(snapshot.context, anchorBlock.node)\n ? {node: anchorBlock.node, path: anchorBlock.path}\n : undefined\n}\n","import type {PortableTextObject, PortableTextSpan} from '@sanity/types'\nimport type {EditorSelector} from '../editor/editor-selector'\nimport {getChildKeyFromSelectionPoint} from '../selection/selection-point'\nimport type {ChildPath} from '../types/paths'\nimport {getAnchorTextBlock} from './selector.get-anchor-text-block'\n\n/**\n * @public\n */\nexport const getAnchorChild: EditorSelector<\n | {\n node: PortableTextObject | PortableTextSpan\n path: ChildPath\n }\n | undefined\n> = (snapshot) => {\n if (!snapshot.context.selection) {\n return undefined\n }\n\n const anchorBlock = getAnchorTextBlock(snapshot)\n\n if (!anchorBlock) {\n return undefined\n }\n\n const key = getChildKeyFromSelectionPoint(snapshot.context.selection.anchor)\n\n const node = key\n ? anchorBlock.node.children.find((span) => span._key === key)\n : undefined\n\n return node && key\n ? {node, path: [...anchorBlock.path, 'children', {_key: key}]}\n : undefined\n}\n","import {isPortableTextSpan, type PortableTextSpan} from '@sanity/types'\nimport type {EditorSelector} from '../editor/editor-selector'\nimport type {ChildPath} from '../types/paths'\nimport {getAnchorChild} from './selector.get-anchor-child'\n\n/**\n * @public\n */\nexport const getAnchorSpan: EditorSelector<\n {node: PortableTextSpan; path: ChildPath} | undefined\n> = (snapshot) => {\n const anchorChild = getAnchorChild(snapshot)\n\n return anchorChild && isPortableTextSpan(anchorChild.node)\n ? {node: anchorChild.node, path: anchorChild.path}\n : undefined\n}\n","import type {EditorSelector} from '../editor/editor-selector'\nimport type {BlockOffset} from '../types/block-offset'\nimport * as utils from '../utils'\nimport {getSelectionEndPoint} from './selector.get-selection-end-point'\nimport {getSelectionStartPoint} from './selector.get-selection-start-point'\n\n/**\n * @public\n */\nexport const getBlockOffsets: EditorSelector<\n {start: BlockOffset; end: BlockOffset} | undefined\n> = (snapshot) => {\n if (!snapshot.context.selection) {\n return undefined\n }\n\n const selectionStartPoint = getSelectionStartPoint(snapshot)\n const selectionEndPoint = getSelectionEndPoint(snapshot)\n\n if (!selectionStartPoint || !selectionEndPoint) {\n return undefined\n }\n\n const start = utils.spanSelectionPointToBlockOffset({\n context: snapshot.context,\n selectionPoint: selectionStartPoint,\n })\n const end = utils.spanSelectionPointToBlockOffset({\n context: snapshot.context,\n selectionPoint: selectionEndPoint,\n })\n\n return start && end ? {start, end} : undefined\n}\n","import type {PortableTextTextBlock} from '@sanity/types'\nimport type {EditorSelector} from '../editor/editor-selector'\nimport {isTextBlock} from '../internal-utils/parse-blocks'\nimport type {BlockPath} from '../types/paths'\nimport {getFocusTextBlock} from './selector.get-focus-text-block'\nimport {getPreviousBlock} from './selector.get-previous-block'\n\n/**\n * @beta\n * Given the `path` of a block, this selector will return the \"list index\" of\n * the block.\n */\nexport function getListIndex({\n path,\n}: {\n path: BlockPath\n}): EditorSelector<number | undefined> {\n return (snapshot) => {\n const selection = {\n anchor: {\n path,\n offset: 0,\n },\n focus: {\n path,\n offset: 0,\n },\n }\n\n const focusTextBlock = getFocusTextBlock({\n ...snapshot,\n context: {\n ...snapshot.context,\n selection,\n },\n })\n\n if (!focusTextBlock) {\n return undefined\n }\n\n if (\n focusTextBlock.node.listItem === undefined ||\n focusTextBlock.node.level === undefined\n ) {\n return undefined\n }\n\n const previousListItem = getPreviousListItem({\n listItem: focusTextBlock.node.listItem,\n level: focusTextBlock.node.level,\n })({\n ...snapshot,\n context: {\n ...snapshot.context,\n selection,\n },\n })\n\n if (!previousListItem) {\n return 1\n }\n\n if (previousListItem.node.listItem !== focusTextBlock.node.listItem) {\n return 1\n }\n\n if (\n previousListItem.node.level !== undefined &&\n previousListItem.node.level < focusTextBlock.node.level\n ) {\n return 1\n }\n\n const previousListItemListState = getListIndex({\n path: previousListItem.path,\n })(snapshot)\n\n if (previousListItemListState === undefined) {\n return 1\n }\n\n return previousListItemListState + 1\n }\n}\n\nfunction getPreviousListItem({\n listItem,\n level,\n}: {\n listItem: string\n level: number\n}): EditorSelector<\n | {\n node: PortableTextTextBlock\n path: [{_key: string}]\n }\n | undefined\n> {\n return (snapshot) => {\n const previousBlock = getPreviousBlock({\n ...snapshot,\n context: {\n ...snapshot.context,\n },\n })\n\n if (!previousBlock) {\n return undefined\n }\n\n if (!isTextBlock(snapshot.context, previousBlock.node)) {\n return undefined\n }\n\n if (\n previousBlock.node.listItem === undefined ||\n previousBlock.node.level === undefined\n ) {\n return undefined\n }\n\n if (previousBlock.node.listItem !== listItem) {\n return undefined\n }\n\n if (previousBlock.node.level === level) {\n return {\n node: previousBlock.node,\n path: previousBlock.path,\n }\n }\n\n if (previousBlock.node.level < level) {\n return undefined\n }\n\n return getPreviousListItem({\n listItem,\n level,\n })({\n ...snapshot,\n context: {\n ...snapshot.context,\n selection: {\n anchor: {\n path: previousBlock.path,\n offset: 0,\n },\n focus: {\n path: previousBlock.path,\n offset: 0,\n },\n },\n },\n })\n }\n}\n","import type {EditorSelection} from '..'\nimport type {EditorSelector} from '../editor/editor-selector'\n\n/**\n * @public\n */\nexport const getSelection: EditorSelector<EditorSelection> = (snapshot) => {\n return snapshot.context.selection\n}\n","import type {PortableTextBlock} from '@sanity/types'\nimport type {EditorSelector} from '../editor/editor-selector'\n\n/**\n * @public\n */\nexport const getValue: EditorSelector<Array<PortableTextBlock>> = (\n snapshot,\n) => {\n return snapshot.context.value\n}\n"],"names":["getAnchorBlock","snapshot","context","selection","key","getBlockKeyFromSelectionPoint","anchor","node","value","find","block","_key","undefined","path","getAnchorTextBlock","anchorBlock","isTextBlock","getAnchorChild","getChildKeyFromSelectionPoint","children","span","getAnchorSpan","anchorChild","isPortableTextSpan","getBlockOffsets","selectionStartPoint","getSelectionStartPoint","selectionEndPoint","getSelectionEndPoint","start","utils","selectionPoint","end","getListIndex","offset","focus","focusTextBlock","getFocusTextBlock","listItem","level","previousListItem","getPreviousListItem","previousListItemListState","previousBlock","getPreviousBlock","getSelection","getValue"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sources":["../../src/selectors/selector.get-anchor-block.ts","../../src/selectors/selector.get-anchor-text-block.ts","../../src/selectors/selector.get-anchor-child.ts","../../src/selectors/selector.get-anchor-span.ts","../../src/selectors/selector.get-block-offsets.ts","../../src/selectors/selector.get-list-state.ts","../../src/selectors/selector.get-selected-slice.ts","../../src/selectors/selector.get-selection.ts","../../src/selectors/selector.get-value.ts"],"sourcesContent":["import type {PortableTextBlock} from '@sanity/types'\nimport type {EditorSelector} from '../editor/editor-selector'\nimport {getBlockKeyFromSelectionPoint} from '../selection/selection-point'\nimport type {BlockPath} from '../types/paths'\n\n/**\n * @public\n */\nexport const getAnchorBlock: EditorSelector<\n {node: PortableTextBlock; path: BlockPath} | undefined\n> = (snapshot) => {\n if (!snapshot.context.selection) {\n return undefined\n }\n\n const key = getBlockKeyFromSelectionPoint(snapshot.context.selection.anchor)\n const index = key ? snapshot.blockIndexMap.get(key) : undefined\n const node =\n index !== undefined ? snapshot.context.value.at(index) : undefined\n\n return node && key ? {node, path: [{_key: key}]} : undefined\n}\n","import type {PortableTextTextBlock} from '@sanity/types'\nimport type {EditorSelector} from '../editor/editor-selector'\nimport {isTextBlock} from '../internal-utils/parse-blocks'\nimport type {BlockPath} from '../types/paths'\nimport {getAnchorBlock} from './selector.get-anchor-block'\n\n/**\n * @public\n */\nexport const getAnchorTextBlock: EditorSelector<\n {node: PortableTextTextBlock; path: BlockPath} | undefined\n> = (snapshot) => {\n const anchorBlock = getAnchorBlock(snapshot)\n\n return anchorBlock && isTextBlock(snapshot.context, anchorBlock.node)\n ? {node: anchorBlock.node, path: anchorBlock.path}\n : undefined\n}\n","import type {PortableTextObject, PortableTextSpan} from '@sanity/types'\nimport type {EditorSelector} from '../editor/editor-selector'\nimport {getChildKeyFromSelectionPoint} from '../selection/selection-point'\nimport type {ChildPath} from '../types/paths'\nimport {getAnchorTextBlock} from './selector.get-anchor-text-block'\n\n/**\n * @public\n */\nexport const getAnchorChild: EditorSelector<\n | {\n node: PortableTextObject | PortableTextSpan\n path: ChildPath\n }\n | undefined\n> = (snapshot) => {\n if (!snapshot.context.selection) {\n return undefined\n }\n\n const anchorBlock = getAnchorTextBlock(snapshot)\n\n if (!anchorBlock) {\n return undefined\n }\n\n const key = getChildKeyFromSelectionPoint(snapshot.context.selection.anchor)\n\n const node = key\n ? anchorBlock.node.children.find((span) => span._key === key)\n : undefined\n\n return node && key\n ? {node, path: [...anchorBlock.path, 'children', {_key: key}]}\n : undefined\n}\n","import {isPortableTextSpan, type PortableTextSpan} from '@sanity/types'\nimport type {EditorSelector} from '../editor/editor-selector'\nimport type {ChildPath} from '../types/paths'\nimport {getAnchorChild} from './selector.get-anchor-child'\n\n/**\n * @public\n */\nexport const getAnchorSpan: EditorSelector<\n {node: PortableTextSpan; path: ChildPath} | undefined\n> = (snapshot) => {\n const anchorChild = getAnchorChild(snapshot)\n\n return anchorChild && isPortableTextSpan(anchorChild.node)\n ? {node: anchorChild.node, path: anchorChild.path}\n : undefined\n}\n","import type {EditorSelector} from '../editor/editor-selector'\nimport type {BlockOffset} from '../types/block-offset'\nimport * as utils from '../utils'\nimport {getSelectionEndPoint} from './selector.get-selection-end-point'\nimport {getSelectionStartPoint} from './selector.get-selection-start-point'\n\n/**\n * @public\n */\nexport const getBlockOffsets: EditorSelector<\n {start: BlockOffset; end: BlockOffset} | undefined\n> = (snapshot) => {\n if (!snapshot.context.selection) {\n return undefined\n }\n\n const selectionStartPoint = getSelectionStartPoint(snapshot)\n const selectionEndPoint = getSelectionEndPoint(snapshot)\n\n if (!selectionStartPoint || !selectionEndPoint) {\n return undefined\n }\n\n const start = utils.spanSelectionPointToBlockOffset({\n context: snapshot.context,\n selectionPoint: selectionStartPoint,\n })\n const end = utils.spanSelectionPointToBlockOffset({\n context: snapshot.context,\n selectionPoint: selectionEndPoint,\n })\n\n return start && end ? {start, end} : undefined\n}\n","import type {EditorSelector} from '../editor/editor-selector'\nimport {isTextBlock} from '../internal-utils/parse-blocks'\nimport type {BlockPath} from '../types/paths'\nimport {getFocusTextBlock} from './selector.get-focus-text-block'\n\n/**\n * @beta\n * @deprecated Use the precomputed `data-list-index` on text blocks instead.\n * Given the `path` of a block, this selector will return the \"list index\" of\n * the block.\n */\nexport function getListIndex({\n path,\n}: {\n path: BlockPath\n}): EditorSelector<number | undefined> {\n return (snapshot) => {\n const selection = {\n anchor: {\n path,\n offset: 0,\n },\n focus: {\n path,\n offset: 0,\n },\n }\n\n const focusTextBlock = getFocusTextBlock({\n ...snapshot,\n context: {\n ...snapshot.context,\n selection,\n },\n })\n\n if (!focusTextBlock) {\n return undefined\n }\n\n if (\n focusTextBlock.node.listItem === undefined ||\n focusTextBlock.node.level === undefined\n ) {\n return undefined\n }\n\n const targetListItem = focusTextBlock.node.listItem\n const targetLevel = focusTextBlock.node.level\n const targetKey = focusTextBlock.node._key\n\n // Find the target block's index\n const targetIndex = snapshot.blockIndexMap.get(targetKey)\n\n if (targetIndex === undefined) {\n return undefined\n }\n\n // Walk backwards from the target block and count consecutive list items\n // of the same type and level\n let listIndex = 1 // Start at 1 for the target block itself\n\n for (let i = targetIndex - 1; i >= 0; i--) {\n const block = snapshot.context.value[i]\n\n if (!isTextBlock(snapshot.context, block)) {\n // Non-text block breaks the sequence\n break\n }\n\n if (block.listItem === undefined || block.level === undefined) {\n // Non-list item breaks the sequence\n break\n }\n\n if (block.listItem !== targetListItem) {\n // Different list type breaks the sequence\n break\n }\n\n if (block.level < targetLevel) {\n // Lower level breaks the sequence\n break\n }\n\n if (block.level === targetLevel) {\n // Same level - continue counting\n listIndex++\n }\n\n // Higher level items don't affect the count for the target level\n }\n\n return listIndex\n }\n}\n","import type {PortableTextBlock} from '@sanity/types'\nimport type {EditorSelector} from '../editor/editor-selector'\nimport {getSelectedValue} from './selector.get-selected-value'\n\n/**\n * @public\n * @deprecated Renamed to `getSelectedValue`.\n */\nexport const getSelectedSlice: EditorSelector<Array<PortableTextBlock>> = (\n snapshot,\n) => {\n return getSelectedValue(snapshot)\n}\n","import type {EditorSelection} from '..'\nimport type {EditorSelector} from '../editor/editor-selector'\n\n/**\n * @public\n */\nexport const getSelection: EditorSelector<EditorSelection> = (snapshot) => {\n return snapshot.context.selection\n}\n","import type {PortableTextBlock} from '@sanity/types'\nimport type {EditorSelector} from '../editor/editor-selector'\n\n/**\n * @public\n */\nexport const getValue: EditorSelector<Array<PortableTextBlock>> = (\n snapshot,\n) => {\n return snapshot.context.value\n}\n"],"names":["getAnchorBlock","snapshot","context","selection","key","getBlockKeyFromSelectionPoint","anchor","index","blockIndexMap","get","undefined","node","value","at","path","_key","getAnchorTextBlock","anchorBlock","isTextBlock","getAnchorChild","getChildKeyFromSelectionPoint","children","find","span","getAnchorSpan","anchorChild","isPortableTextSpan","getBlockOffsets","selectionStartPoint","getSelectionStartPoint","selectionEndPoint","getSelectionEndPoint","start","utils","selectionPoint","end","getListIndex","offset","focus","focusTextBlock","getFocusTextBlock","listItem","level","targetListItem","targetLevel","targetKey","targetIndex","listIndex","i","block","getSelectedSlice","getSelectedValue","getSelection","getValue"],"mappings":"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"}
@@ -265,6 +265,7 @@ declare type EditorSnapshot = {
265
265
  activeAnnotations: Array<string>
266
266
  activeDecorators: Array<string>
267
267
  }
268
+ blockIndexMap: Map<string, number>
268
269
  }
269
270
 
270
271
  /**
@@ -265,6 +265,7 @@ declare type EditorSnapshot = {
265
265
  activeAnnotations: Array<string>
266
266
  activeDecorators: Array<string>
267
267
  }
268
+ blockIndexMap: Map<string, number>
268
269
  }
269
270
 
270
271
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@portabletext/editor",
3
- "version": "1.54.4",
3
+ "version": "1.55.0",
4
4
  "description": "Portable Text Editor made in React",
5
5
  "keywords": [
6
6
  "sanity",
@@ -68,7 +68,7 @@
68
68
  ],
69
69
  "dependencies": {
70
70
  "@portabletext/to-html": "^2.0.14",
71
- "@xstate/react": "^5.0.5",
71
+ "@xstate/react": "^6.0.0",
72
72
  "debug": "^4.4.1",
73
73
  "get-random-values-esm": "^1.0.2",
74
74
  "immer": "^10.1.1",
@@ -79,9 +79,9 @@
79
79
  "slate-dom": "^0.116.0",
80
80
  "slate-react": "0.117.1",
81
81
  "use-effect-event": "^1.0.2",
82
- "xstate": "^5.19.4",
83
- "@portabletext/patches": "1.1.5",
84
- "@portabletext/block-tools": "1.1.31"
82
+ "xstate": "^5.20.0",
83
+ "@portabletext/block-tools": "1.1.31",
84
+ "@portabletext/patches": "1.1.5"
85
85
  },
86
86
  "devDependencies": {
87
87
  "@portabletext/toolkit": "^2.0.17",
@@ -132,15 +132,9 @@ export const abstractDeleteBehaviors = [
132
132
  }
133
133
 
134
134
  const trimmedSelection = selectors.getTrimmedSelection({
135
- beta: {
136
- activeAnnotations: [],
137
- activeDecorators: [],
138
- },
135
+ ...snapshot,
139
136
  context: {
140
- converters: [],
141
- schema: snapshot.context.schema,
142
- keyGenerator: snapshot.context.keyGenerator,
143
- readOnly: false,
137
+ ...snapshot.context,
144
138
  value: snapshot.context.value,
145
139
  selection,
146
140
  },
@@ -102,13 +102,14 @@ export const abstractSplitBehaviors = [
102
102
  }
103
103
 
104
104
  const newTextBlock = parseBlock({
105
- block: utils
106
- .sliceBlocks({
105
+ block: selectors
106
+ .getSelectedValue({
107
+ ...snapshot,
107
108
  context: {
108
109
  ...snapshot.context,
109
110
  selection: newTextBlockSelection,
111
+ value: [focusTextBlock.node],
110
112
  },
111
- blocks: [focusTextBlock.node],
112
113
  })
113
114
  .at(0),
114
115
  context: snapshot.context,
@@ -1,5 +1,5 @@
1
1
  import {parseBlock} from '../internal-utils/parse-blocks'
2
- import {sliceBlocks} from '../utils'
2
+ import * as selectors from '../selectors'
3
3
  import {defineConverter} from './converter.types'
4
4
 
5
5
  export const converterPortableText = defineConverter({
@@ -16,13 +16,7 @@ export const converterPortableText = defineConverter({
16
16
  }
17
17
  }
18
18
 
19
- const blocks = sliceBlocks({
20
- context: {
21
- selection,
22
- schema: snapshot.context.schema,
23
- },
24
- blocks: snapshot.context.value,
25
- })
19
+ const blocks = selectors.getSelectedValue(snapshot)
26
20
 
27
21
  if (blocks.length === 0) {
28
22
  return {
@@ -2,8 +2,8 @@ import {htmlToBlocks} from '@portabletext/block-tools'
2
2
  import {toHTML} from '@portabletext/to-html'
3
3
  import type {PortableTextBlock} from '@sanity/types'
4
4
  import {parseBlock} from '../internal-utils/parse-blocks'
5
+ import * as selectors from '../selectors'
5
6
  import type {PortableTextMemberSchemaTypes} from '../types/editor'
6
- import {sliceBlocks} from '../utils'
7
7
  import {defineConverter} from './converter.types'
8
8
 
9
9
  export function createConverterTextHtml(
@@ -23,13 +23,7 @@ export function createConverterTextHtml(
23
23
  }
24
24
  }
25
25
 
26
- const blocks = sliceBlocks({
27
- context: {
28
- selection,
29
- schema: snapshot.context.schema,
30
- },
31
- blocks: snapshot.context.value,
32
- })
26
+ const blocks = selectors.getSelectedValue(snapshot)
33
27
 
34
28
  const html = toHTML(blocks, {
35
29
  onMissingComponent: false,
@@ -1,8 +1,8 @@
1
1
  import {htmlToBlocks} from '@portabletext/block-tools'
2
2
  import type {PortableTextBlock} from '@sanity/types'
3
3
  import {isTextBlock, parseBlock} from '../internal-utils/parse-blocks'
4
+ import * as selectors from '../selectors'
4
5
  import type {PortableTextMemberSchemaTypes} from '../types/editor'
5
- import {sliceBlocks} from '../utils'
6
6
  import {defineConverter} from './converter.types'
7
7
 
8
8
  export function createConverterTextPlain(
@@ -22,13 +22,7 @@ export function createConverterTextPlain(
22
22
  }
23
23
  }
24
24
 
25
- const blocks = sliceBlocks({
26
- context: {
27
- selection,
28
- schema: snapshot.context.schema,
29
- },
30
- blocks: snapshot.context.value,
31
- })
25
+ const blocks = selectors.getSelectedValue(snapshot)
32
26
 
33
27
  const data = blocks
34
28
  .map((block) => {
@@ -51,6 +51,10 @@ export function RenderTextBlock(props: {
51
51
  s.context.getLegacySchema(),
52
52
  )
53
53
 
54
+ const listIndex = useSlateSelector((editor) =>
55
+ editor.listIndexMap.get(props.textBlock._key),
56
+ )
57
+
54
58
  let children = props.children
55
59
 
56
60
  const legacyBlockSchemaType = legacySchema.block
@@ -141,6 +145,11 @@ export function RenderTextBlock(props: {
141
145
  'data-style': props.textBlock.style,
142
146
  }
143
147
  : {})}
148
+ {...(listIndex !== undefined
149
+ ? {
150
+ 'data-list-index': listIndex,
151
+ }
152
+ : {})}
144
153
  >
145
154
  {dragPositionBlock === 'start' ? <DropIndicator /> : null}
146
155
  <div ref={blockRef}>
@@ -1,5 +1,6 @@
1
1
  import {createEditor, type Descendant} from 'slate'
2
2
  import {withReact} from 'slate-react'
3
+ import {buildIndexMaps} from '../internal-utils/build-index-maps'
3
4
  import {createPlaceholderBlock} from '../internal-utils/create-placeholder-block'
4
5
  import {debugWithName} from '../internal-utils/debug'
5
6
  import {toSlateValue} from '../internal-utils/values'
@@ -40,9 +41,19 @@ export function createSlateEditor(config: SlateEditorConfig): SlateEditor {
40
41
  instance.decoratedRanges = []
41
42
  instance.decoratorState = {}
42
43
  instance.markState = undefined
43
- instance.value = [
44
- createPlaceholderBlock(config.editorActor.getSnapshot().context),
45
- ]
44
+
45
+ const placeholderBlock = createPlaceholderBlock(
46
+ config.editorActor.getSnapshot().context,
47
+ )
48
+ instance.value = [placeholderBlock]
49
+
50
+ const {blockIndexMap, listIndexMap} = buildIndexMaps(
51
+ config.editorActor.getSnapshot().context,
52
+ instance.value,
53
+ )
54
+
55
+ instance.blockIndexMap = blockIndexMap
56
+ instance.listIndexMap = listIndexMap
46
57
 
47
58
  const initialValue = toSlateValue(instance.value, {
48
59
  schemaTypes: config.editorActor.getSnapshot().context.schema,
@@ -68,6 +68,7 @@ export function getEditorSnapshot({
68
68
  slateEditorInstance: PortableTextSlateEditor
69
69
  }): EditorSnapshot {
70
70
  return {
71
+ blockIndexMap: slateEditorInstance.blockIndexMap,
71
72
  context: {
72
73
  converters: [...editorActorSnapshot.context.converters],
73
74
  keyGenerator: editorActorSnapshot.context.keyGenerator,
@@ -31,6 +31,7 @@ export type EditorSnapshot = {
31
31
  activeAnnotations: Array<string>
32
32
  activeDecorators: Array<string>
33
33
  }
34
+ blockIndexMap: Map<string, number>
34
35
  }
35
36
 
36
37
  export function createEditorSnapshot({
@@ -64,6 +65,7 @@ export function createEditorSnapshot({
64
65
  } satisfies EditorContext
65
66
 
66
67
  return {
68
+ blockIndexMap: editor.blockIndexMap,
67
69
  context,
68
70
  beta: {
69
71
  activeAnnotations: getActiveAnnotations({
@@ -1,4 +1,5 @@
1
1
  import {applyOperationToPortableText} from '../../internal-utils/apply-operation-to-portable-text'
2
+ import {buildIndexMaps} from '../../internal-utils/build-index-maps'
2
3
  import type {PortableTextSlateEditor} from '../../types/editor'
3
4
  import type {EditorContext} from '../editor-snapshot'
4
5
 
@@ -20,6 +21,10 @@ export function pluginUpdateValue(
20
21
  operation,
21
22
  )
22
23
 
24
+ const {blockIndexMap, listIndexMap} = buildIndexMaps(context, editor.value)
25
+ editor.blockIndexMap = blockIndexMap
26
+ editor.listIndexMap = listIndexMap
27
+
23
28
  apply(operation)
24
29
  }
25
30
 
@@ -0,0 +1,232 @@
1
+ import type {PortableTextBlock} from '@sanity/types'
2
+ import {describe, expect, test} from 'vitest'
3
+ import {compileSchemaDefinition, defineSchema} from '../editor/editor-schema'
4
+ import {defaultKeyGenerator} from '../editor/key-generator'
5
+ import {buildIndexMaps} from './build-index-maps'
6
+
7
+ function blockObject(_key: string, name: string) {
8
+ return {
9
+ _key,
10
+ _type: name,
11
+ }
12
+ }
13
+
14
+ function textBlock(
15
+ _key: string,
16
+ {
17
+ listItem,
18
+ level,
19
+ }: {
20
+ listItem?: 'bullet' | 'number'
21
+ level?: number
22
+ },
23
+ ): PortableTextBlock {
24
+ return {
25
+ _key,
26
+ _type: 'block',
27
+ children: [
28
+ {
29
+ _key: defaultKeyGenerator(),
30
+ _type: 'span',
31
+ text: `${listItem}-${level}`,
32
+ },
33
+ ],
34
+ style: 'normal',
35
+ level,
36
+ listItem,
37
+ }
38
+ }
39
+
40
+ const schema = compileSchemaDefinition(
41
+ defineSchema({
42
+ blockObjects: [{name: 'image'}],
43
+ }),
44
+ )
45
+
46
+ describe(buildIndexMaps.name, () => {
47
+ test('empty', () => {
48
+ expect(buildIndexMaps({schema}, [])).toEqual({
49
+ blockIndexMap: new Map(),
50
+ listIndexMap: new Map(),
51
+ })
52
+ })
53
+
54
+ test('single list item', () => {
55
+ expect(
56
+ buildIndexMaps({schema}, [
57
+ textBlock('k0', {listItem: 'number', level: 1}),
58
+ ]),
59
+ ).toEqual({
60
+ blockIndexMap: new Map([['k0', 0]]),
61
+ listIndexMap: new Map([['k0', 1]]),
62
+ })
63
+ })
64
+
65
+ test('single indented list item', () => {
66
+ expect(
67
+ buildIndexMaps({schema}, [
68
+ textBlock('k0', {listItem: 'number', level: 2}),
69
+ ]),
70
+ ).toEqual({
71
+ blockIndexMap: new Map([['k0', 0]]),
72
+ listIndexMap: new Map([['k0', 1]]),
73
+ })
74
+ })
75
+
76
+ test('two lists broken up by a paragraph', () => {
77
+ expect(
78
+ buildIndexMaps({schema}, [
79
+ textBlock('k0', {listItem: 'number', level: 1}),
80
+ textBlock('k1', {listItem: 'number', level: 1}),
81
+ textBlock('k2', {}),
82
+ textBlock('k3', {listItem: 'number', level: 1}),
83
+ textBlock('k4', {listItem: 'number', level: 1}),
84
+ ]),
85
+ ).toEqual({
86
+ blockIndexMap: new Map([
87
+ ['k0', 0],
88
+ ['k1', 1],
89
+ ['k2', 2],
90
+ ['k3', 3],
91
+ ['k4', 4],
92
+ ]),
93
+ listIndexMap: new Map([
94
+ ['k0', 1],
95
+ ['k1', 2],
96
+ ['k3', 1],
97
+ ['k4', 2],
98
+ ]),
99
+ })
100
+ })
101
+
102
+ test('two lists broken up by an image', () => {
103
+ expect(
104
+ buildIndexMaps({schema}, [
105
+ textBlock('k0', {listItem: 'number', level: 1}),
106
+ textBlock('k1', {listItem: 'number', level: 1}),
107
+ blockObject('k2', 'image'),
108
+ textBlock('k3', {listItem: 'number', level: 1}),
109
+ textBlock('k4', {listItem: 'number', level: 1}),
110
+ ]),
111
+ ).toEqual({
112
+ blockIndexMap: new Map([
113
+ ['k0', 0],
114
+ ['k1', 1],
115
+ ['k2', 2],
116
+ ['k3', 3],
117
+ ['k4', 4],
118
+ ]),
119
+ listIndexMap: new Map([
120
+ ['k0', 1],
121
+ ['k1', 2],
122
+ ['k3', 1],
123
+ ['k4', 2],
124
+ ]),
125
+ })
126
+ })
127
+
128
+ test('numbered lists broken up by a bulleted list', () => {
129
+ expect(
130
+ buildIndexMaps({schema}, [
131
+ textBlock('k0', {listItem: 'number', level: 1}),
132
+ textBlock('k1', {listItem: 'bullet', level: 1}),
133
+ textBlock('k2', {listItem: 'number', level: 1}),
134
+ ]),
135
+ ).toEqual({
136
+ blockIndexMap: new Map([
137
+ ['k0', 0],
138
+ ['k1', 1],
139
+ ['k2', 2],
140
+ ]),
141
+ listIndexMap: new Map([
142
+ ['k0', 1],
143
+ ['k1', 1],
144
+ ['k2', 1],
145
+ ]),
146
+ })
147
+ })
148
+
149
+ test('simple indented list', () => {
150
+ expect(
151
+ buildIndexMaps({schema}, [
152
+ textBlock('k0', {listItem: 'number', level: 1}),
153
+ textBlock('k1', {listItem: 'number', level: 2}),
154
+ textBlock('k2', {listItem: 'number', level: 2}),
155
+ textBlock('k3', {listItem: 'number', level: 1}),
156
+ ]),
157
+ ).toEqual({
158
+ blockIndexMap: new Map([
159
+ ['k0', 0],
160
+ ['k1', 1],
161
+ ['k2', 2],
162
+ ['k3', 3],
163
+ ]),
164
+ listIndexMap: new Map([
165
+ ['k0', 1],
166
+ ['k1', 1],
167
+ ['k2', 2],
168
+ ['k3', 2],
169
+ ]),
170
+ })
171
+ })
172
+
173
+ test('reverse indented list', () => {
174
+ expect(
175
+ buildIndexMaps({schema}, [
176
+ textBlock('k0', {listItem: 'number', level: 2}),
177
+ textBlock('k1', {listItem: 'number', level: 1}),
178
+ textBlock('k2', {listItem: 'number', level: 2}),
179
+ ]),
180
+ ).toEqual({
181
+ blockIndexMap: new Map([
182
+ ['k0', 0],
183
+ ['k1', 1],
184
+ ['k2', 2],
185
+ ]),
186
+ listIndexMap: new Map([
187
+ ['k0', 1],
188
+ ['k1', 1],
189
+ ['k2', 1],
190
+ ]),
191
+ })
192
+ })
193
+
194
+ test('complex list', () => {
195
+ expect(
196
+ buildIndexMaps({schema}, [
197
+ textBlock('k0', {listItem: 'number', level: 1}),
198
+ textBlock('k1', {listItem: 'number', level: 3}),
199
+ textBlock('k2', {listItem: 'number', level: 2}),
200
+ textBlock('k3', {listItem: 'number', level: 3}),
201
+ textBlock('k4', {listItem: 'number', level: 1}),
202
+ textBlock('k5', {listItem: 'number', level: 3}),
203
+ textBlock('k6', {listItem: 'number', level: 4}),
204
+ textBlock('k7', {listItem: 'number', level: 3}),
205
+ textBlock('k8', {listItem: 'number', level: 1}),
206
+ ]),
207
+ ).toEqual({
208
+ blockIndexMap: new Map([
209
+ ['k0', 0],
210
+ ['k1', 1],
211
+ ['k2', 2],
212
+ ['k3', 3],
213
+ ['k4', 4],
214
+ ['k5', 5],
215
+ ['k6', 6],
216
+ ['k7', 7],
217
+ ['k8', 8],
218
+ ]),
219
+ listIndexMap: new Map([
220
+ ['k0', 1],
221
+ ['k1', 1],
222
+ ['k2', 1],
223
+ ['k3', 1],
224
+ ['k4', 2],
225
+ ['k5', 1],
226
+ ['k6', 1],
227
+ ['k7', 2],
228
+ ['k8', 3],
229
+ ]),
230
+ })
231
+ })
232
+ })