@wordpress/core-data 7.40.1 → 7.40.2-next.v.202602241322.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 (118) hide show
  1. package/build/actions.cjs +23 -29
  2. package/build/actions.cjs.map +2 -2
  3. package/build/awareness/block-lookup.cjs +103 -0
  4. package/build/awareness/block-lookup.cjs.map +7 -0
  5. package/build/awareness/post-editor-awareness.cjs +45 -7
  6. package/build/awareness/post-editor-awareness.cjs.map +3 -3
  7. package/build/entities.cjs +60 -63
  8. package/build/entities.cjs.map +2 -2
  9. package/build/entity-types/icon.cjs +19 -0
  10. package/build/entity-types/icon.cjs.map +7 -0
  11. package/build/entity-types/index.cjs.map +1 -1
  12. package/build/hooks/use-post-editor-awareness-state.cjs +12 -8
  13. package/build/hooks/use-post-editor-awareness-state.cjs.map +2 -2
  14. package/build/private-actions.cjs +0 -8
  15. package/build/private-actions.cjs.map +2 -2
  16. package/build/private-apis.cjs +1 -1
  17. package/build/private-apis.cjs.map +1 -1
  18. package/build/private-selectors.cjs +1 -9
  19. package/build/private-selectors.cjs.map +2 -2
  20. package/build/reducer.cjs +0 -10
  21. package/build/reducer.cjs.map +2 -2
  22. package/build/resolvers.cjs +101 -113
  23. package/build/resolvers.cjs.map +2 -2
  24. package/build/selectors.cjs.map +2 -2
  25. package/build/sync.cjs +0 -3
  26. package/build/sync.cjs.map +2 -2
  27. package/build/types.cjs.map +1 -1
  28. package/build/utils/crdt-selection.cjs +1 -1
  29. package/build/utils/crdt-selection.cjs.map +2 -2
  30. package/build/utils/crdt-user-selections.cjs +78 -22
  31. package/build/utils/crdt-user-selections.cjs.map +3 -3
  32. package/build-module/actions.mjs +23 -29
  33. package/build-module/actions.mjs.map +2 -2
  34. package/build-module/awareness/block-lookup.mjs +77 -0
  35. package/build-module/awareness/block-lookup.mjs.map +7 -0
  36. package/build-module/awareness/post-editor-awareness.mjs +47 -8
  37. package/build-module/awareness/post-editor-awareness.mjs.map +3 -3
  38. package/build-module/entities.mjs +60 -63
  39. package/build-module/entities.mjs.map +2 -2
  40. package/build-module/entity-types/icon.mjs +1 -0
  41. package/build-module/entity-types/icon.mjs.map +7 -0
  42. package/build-module/hooks/use-post-editor-awareness-state.mjs +10 -6
  43. package/build-module/hooks/use-post-editor-awareness-state.mjs.map +2 -2
  44. package/build-module/private-actions.mjs +0 -7
  45. package/build-module/private-actions.mjs.map +2 -2
  46. package/build-module/private-apis.mjs +2 -2
  47. package/build-module/private-apis.mjs.map +1 -1
  48. package/build-module/private-selectors.mjs +2 -12
  49. package/build-module/private-selectors.mjs.map +2 -2
  50. package/build-module/reducer.mjs +0 -9
  51. package/build-module/reducer.mjs.map +2 -2
  52. package/build-module/resolvers.mjs +101 -112
  53. package/build-module/resolvers.mjs.map +2 -2
  54. package/build-module/selectors.mjs.map +2 -2
  55. package/build-module/sync.mjs +0 -2
  56. package/build-module/sync.mjs.map +2 -2
  57. package/build-module/utils/crdt-selection.mjs +1 -1
  58. package/build-module/utils/crdt-selection.mjs.map +2 -2
  59. package/build-module/utils/crdt-user-selections.mjs +77 -22
  60. package/build-module/utils/crdt-user-selections.mjs.map +2 -2
  61. package/build-types/actions.d.ts.map +1 -1
  62. package/build-types/awareness/block-lookup.d.ts +29 -0
  63. package/build-types/awareness/block-lookup.d.ts.map +1 -0
  64. package/build-types/awareness/post-editor-awareness.d.ts +18 -5
  65. package/build-types/awareness/post-editor-awareness.d.ts.map +1 -1
  66. package/build-types/awareness/test/block-lookup.d.ts +2 -0
  67. package/build-types/awareness/test/block-lookup.d.ts.map +1 -0
  68. package/build-types/entities.d.ts +16 -0
  69. package/build-types/entities.d.ts.map +1 -1
  70. package/build-types/entity-types/icon.d.ts +25 -0
  71. package/build-types/entity-types/icon.d.ts.map +1 -0
  72. package/build-types/entity-types/index.d.ts +3 -2
  73. package/build-types/entity-types/index.d.ts.map +1 -1
  74. package/build-types/hooks/use-post-editor-awareness-state.d.ts +11 -6
  75. package/build-types/hooks/use-post-editor-awareness-state.d.ts.map +1 -1
  76. package/build-types/index.d.ts.map +1 -1
  77. package/build-types/private-actions.d.ts +0 -8
  78. package/build-types/private-actions.d.ts.map +1 -1
  79. package/build-types/private-selectors.d.ts +1 -8
  80. package/build-types/private-selectors.d.ts.map +1 -1
  81. package/build-types/reducer.d.ts +0 -11
  82. package/build-types/reducer.d.ts.map +1 -1
  83. package/build-types/resolvers.d.ts +0 -3
  84. package/build-types/resolvers.d.ts.map +1 -1
  85. package/build-types/selectors.d.ts +0 -6
  86. package/build-types/selectors.d.ts.map +1 -1
  87. package/build-types/sync.d.ts +2 -2
  88. package/build-types/sync.d.ts.map +1 -1
  89. package/build-types/types.d.ts +13 -5
  90. package/build-types/types.d.ts.map +1 -1
  91. package/build-types/utils/crdt-selection.d.ts.map +1 -1
  92. package/build-types/utils/crdt-user-selections.d.ts +21 -4
  93. package/build-types/utils/crdt-user-selections.d.ts.map +1 -1
  94. package/build-types/utils/test/crdt-user-selections.d.ts +2 -0
  95. package/build-types/utils/test/crdt-user-selections.d.ts.map +1 -0
  96. package/package.json +18 -18
  97. package/src/actions.js +39 -45
  98. package/src/awareness/block-lookup.ts +169 -0
  99. package/src/awareness/post-editor-awareness.ts +68 -11
  100. package/src/awareness/test/block-lookup.ts +504 -0
  101. package/src/awareness/test/post-editor-awareness.ts +662 -38
  102. package/src/entities.js +63 -66
  103. package/src/entity-types/icon.ts +30 -0
  104. package/src/entity-types/index.ts +3 -0
  105. package/src/hooks/test/use-post-editor-awareness-state.ts +21 -14
  106. package/src/hooks/use-post-editor-awareness-state.ts +22 -13
  107. package/src/private-actions.js +0 -14
  108. package/src/private-apis.js +2 -2
  109. package/src/private-selectors.ts +3 -22
  110. package/src/reducer.js +0 -17
  111. package/src/resolvers.js +137 -156
  112. package/src/selectors.ts +0 -7
  113. package/src/sync.ts +0 -2
  114. package/src/test/resolvers.js +109 -1
  115. package/src/types.ts +22 -5
  116. package/src/utils/crdt-selection.ts +3 -1
  117. package/src/utils/crdt-user-selections.ts +129 -47
  118. package/src/utils/test/crdt-user-selections.ts +894 -0
@@ -1,7 +1,10 @@
1
1
  /**
2
2
  * WordPress dependencies
3
3
  */
4
+ import { select } from '@wordpress/data';
4
5
  import { Y } from '@wordpress/sync';
6
+ // @ts-ignore No exported types for block editor store selectors.
7
+ import { store as blockEditorStore } from '@wordpress/block-editor';
5
8
 
6
9
  /**
7
10
  * Internal dependencies
@@ -11,6 +14,7 @@ import type { YPostRecord } from './crdt';
11
14
  import type { YBlock, YBlocks } from './crdt-blocks';
12
15
  import { getRootMap } from './crdt-utils';
13
16
  import type {
17
+ AbsoluteBlockIndexPath,
14
18
  WPBlockSelection,
15
19
  SelectionState,
16
20
  SelectionNone,
@@ -35,6 +39,11 @@ export enum SelectionType {
35
39
  /**
36
40
  * Converts WordPress block editor selection to a SelectionState.
37
41
  *
42
+ * Uses getBlockPathForLocalClientId to locate blocks in the Yjs document by
43
+ * their tree position (index path) rather than clientId, since clientIds may
44
+ * differ between the block-editor store and the Yjs document (e.g. in "Show
45
+ * Template" mode).
46
+ *
38
47
  * @param selectionStart - The start position of the selection
39
48
  * @param selectionEnd - The end position of the selection
40
49
  * @param yDoc - The Yjs document
@@ -46,15 +55,15 @@ export function getSelectionState(
46
55
  yDoc: Y.Doc
47
56
  ): SelectionState {
48
57
  const ymap = getRootMap< YPostRecord >( yDoc, CRDT_RECORD_MAP_KEY );
49
- const yBlocks = ymap.get( 'blocks' ) ?? new Y.Array< YBlock >();
58
+ const yBlocks = ymap.get( 'blocks' );
50
59
 
51
60
  const isSelectionEmpty = Object.keys( selectionStart ).length === 0;
52
61
  const noSelection: SelectionNone = {
53
62
  type: SelectionType.None,
54
63
  };
55
64
 
56
- if ( isSelectionEmpty ) {
57
- // Case 1: No selection
65
+ if ( isSelectionEmpty || ! yBlocks ) {
66
+ // Case 1: No selection, or no blocks in the document.
58
67
  return noSelection;
59
68
  }
60
69
 
@@ -70,9 +79,18 @@ export function getSelectionState(
70
79
 
71
80
  if ( isSelectionAWholeBlock ) {
72
81
  // Case 2: A whole block is selected.
82
+ const path = getBlockPathForLocalClientId( selectionStart.clientId );
83
+ const blockPosition = path
84
+ ? createRelativePositionForBlockPath( path, yBlocks )
85
+ : null;
86
+
87
+ if ( ! blockPosition ) {
88
+ return noSelection;
89
+ }
90
+
73
91
  return {
74
92
  type: SelectionType.WholeBlock,
75
- blockId: selectionStart.clientId,
93
+ blockPosition,
76
94
  };
77
95
  } else if ( isCursorOnly ) {
78
96
  // Case 3: Cursor only, no text selected
@@ -85,7 +103,6 @@ export function getSelectionState(
85
103
 
86
104
  return {
87
105
  type: SelectionType.Cursor,
88
- blockId: selectionStart.clientId,
89
106
  cursorPosition,
90
107
  };
91
108
  } else if ( isSelectionInOneBlock ) {
@@ -103,13 +120,12 @@ export function getSelectionState(
103
120
 
104
121
  return {
105
122
  type: SelectionType.SelectionInOneBlock,
106
- blockId: selectionStart.clientId,
107
123
  cursorStartPosition,
108
124
  cursorEndPosition,
109
125
  };
110
126
  }
111
127
 
112
- // Caes 5: Selection in multiple blocks
128
+ // Case 5: Selection in multiple blocks
113
129
  const cursorStartPosition = getCursorPosition( selectionStart, yBlocks );
114
130
  const cursorEndPosition = getCursorPosition( selectionEnd, yBlocks );
115
131
  if ( ! cursorStartPosition || ! cursorEndPosition ) {
@@ -119,8 +135,6 @@ export function getSelectionState(
119
135
 
120
136
  return {
121
137
  type: SelectionType.SelectionInMultipleBlocks,
122
- blockStartId: selectionStart.clientId,
123
- blockEndId: selectionEnd.clientId,
124
138
  cursorStartPosition,
125
139
  cursorEndPosition,
126
140
  };
@@ -137,7 +151,8 @@ function getCursorPosition(
137
151
  selection: WPBlockSelection,
138
152
  blocks: YBlocks
139
153
  ): CursorPosition | null {
140
- const block = findBlockByClientId( selection.clientId, blocks );
154
+ const path = getBlockPathForLocalClientId( selection.clientId );
155
+ const block = path ? findBlockByPath( path, blocks ) : null;
141
156
  if (
142
157
  ! block ||
143
158
  ! selection.attributeKey ||
@@ -147,7 +162,12 @@ function getCursorPosition(
147
162
  }
148
163
 
149
164
  const attributes = block.get( 'attributes' );
150
- const currentYText = attributes?.get( selection.attributeKey ) as Y.Text;
165
+ const currentYText = attributes?.get( selection.attributeKey );
166
+
167
+ // If the attribute is not a Y.Text, return null.
168
+ if ( ! ( currentYText instanceof Y.Text ) ) {
169
+ return null;
170
+ }
151
171
 
152
172
  const relativePosition = Y.createRelativePositionFromTypeIndex(
153
173
  currentYText,
@@ -161,32 +181,103 @@ function getCursorPosition(
161
181
  }
162
182
 
163
183
  /**
164
- * Find a block by its client ID.
184
+ * Resolves a local block-editor clientId to its index path relative to the
185
+ * post content blocks. This allows finding the corresponding block in the Yjs
186
+ * document even when clientIds differ (e.g. in "Show Template" mode where
187
+ * blocks are cloned).
188
+ *
189
+ * In template mode, the block tree includes template parts and wrapper blocks
190
+ * around a core/post-content block. The Yjs document only contains the post
191
+ * content blocks, so we stop the upward walk when the parent is
192
+ * core/post-content (its inner blocks correspond to the Yjs root blocks).
193
+ *
194
+ * @param clientId - The local block-editor clientId to resolve.
195
+ * @return The index path from root, or null if not resolvable.
196
+ */
197
+ export function getBlockPathForLocalClientId(
198
+ clientId: string
199
+ ): AbsoluteBlockIndexPath | null {
200
+ const { getBlockIndex, getBlockRootClientId, getBlockName } =
201
+ select( blockEditorStore );
202
+
203
+ const path: AbsoluteBlockIndexPath = [];
204
+ let current: string | null = clientId;
205
+ while ( current ) {
206
+ const index = getBlockIndex( current );
207
+ if ( index === -1 ) {
208
+ return null;
209
+ }
210
+ path.unshift( index );
211
+ const parent = getBlockRootClientId( current );
212
+ if ( ! parent ) {
213
+ break;
214
+ }
215
+ // If the parent is core/post-content, stop here — the Yjs doc
216
+ // root blocks correspond to post-content's inner blocks.
217
+ const parentName = getBlockName( parent );
218
+ if ( parentName === 'core/post-content' ) {
219
+ break;
220
+ }
221
+ current = parent;
222
+ }
223
+ return path.length > 0 ? path : null;
224
+ }
225
+
226
+ /**
227
+ * Find a block by navigating a tree index path in the Yjs block hierarchy.
165
228
  *
166
- * @param blockId - The client ID of the block.
167
- * @param blocks - The blocks to search through.
168
- * @return The block if found, null otherwise.
229
+ * @param path - The index path, e.g. [0, 1] for blocks[0].innerBlocks[1].
230
+ * @param blocks - The root-level Yjs blocks array.
231
+ * @return The block Y.Map if found, null otherwise.
169
232
  */
170
- function findBlockByClientId(
171
- blockId: string,
233
+ function findBlockByPath(
234
+ path: AbsoluteBlockIndexPath,
172
235
  blocks: YBlocks
173
236
  ): YBlock | null {
174
- for ( const block of blocks ) {
175
- if ( block.get( 'clientId' ) === blockId ) {
237
+ let currentBlocks = blocks;
238
+ for ( let i = 0; i < path.length; i++ ) {
239
+ if ( path[ i ] >= currentBlocks.length ) {
240
+ return null;
241
+ }
242
+ const block = currentBlocks.get( path[ i ] );
243
+ if ( ! block ) {
244
+ return null;
245
+ }
246
+ if ( i === path.length - 1 ) {
176
247
  return block;
177
248
  }
249
+ currentBlocks =
250
+ block.get( 'innerBlocks' ) ?? ( new Y.Array() as YBlocks );
251
+ }
252
+ return null;
253
+ }
178
254
 
179
- const innerBlocks = block.get( 'innerBlocks' );
180
-
181
- if ( innerBlocks && innerBlocks.length > 0 ) {
182
- const innerBlock = findBlockByClientId( blockId, innerBlocks );
183
-
184
- if ( innerBlock ) {
185
- return innerBlock;
186
- }
255
+ /**
256
+ * Create a Y.RelativePosition for a block by navigating a tree index path.
257
+ *
258
+ * @param path - The index path, e.g. [0, 1] for blocks[0].innerBlocks[1].
259
+ * @param blocks - The root-level Yjs blocks array.
260
+ * @return A Y.RelativePosition for the block, or null if the path is invalid.
261
+ */
262
+ function createRelativePositionForBlockPath(
263
+ path: AbsoluteBlockIndexPath,
264
+ blocks: YBlocks
265
+ ): Y.RelativePosition | null {
266
+ let currentBlocks = blocks;
267
+ for ( let i = 0; i < path.length; i++ ) {
268
+ if ( path[ i ] >= currentBlocks.length ) {
269
+ return null;
270
+ }
271
+ if ( i === path.length - 1 ) {
272
+ return Y.createRelativePositionFromTypeIndex(
273
+ currentBlocks,
274
+ path[ i ]
275
+ );
187
276
  }
277
+ const block = currentBlocks.get( path[ i ] );
278
+ currentBlocks =
279
+ block?.get( 'innerBlocks' ) ?? ( new Y.Array() as YBlocks );
188
280
  }
189
-
190
281
  return null;
191
282
  }
192
283
 
@@ -210,19 +301,13 @@ export function areSelectionsStatesEqual(
210
301
  return true;
211
302
 
212
303
  case SelectionType.Cursor:
213
- return (
214
- selection1.blockId ===
215
- ( selection2 as SelectionCursor ).blockId &&
216
- areCursorPositionsEqual(
217
- selection1.cursorPosition,
218
- ( selection2 as SelectionCursor ).cursorPosition
219
- )
304
+ return areCursorPositionsEqual(
305
+ selection1.cursorPosition,
306
+ ( selection2 as SelectionCursor ).cursorPosition
220
307
  );
221
308
 
222
309
  case SelectionType.SelectionInOneBlock:
223
310
  return (
224
- selection1.blockId ===
225
- ( selection2 as SelectionInOneBlock ).blockId &&
226
311
  areCursorPositionsEqual(
227
312
  selection1.cursorStartPosition,
228
313
  ( selection2 as SelectionInOneBlock ).cursorStartPosition
@@ -235,10 +320,6 @@ export function areSelectionsStatesEqual(
235
320
 
236
321
  case SelectionType.SelectionInMultipleBlocks:
237
322
  return (
238
- selection1.blockStartId ===
239
- ( selection2 as SelectionInMultipleBlocks ).blockStartId &&
240
- selection1.blockEndId ===
241
- ( selection2 as SelectionInMultipleBlocks ).blockEndId &&
242
323
  areCursorPositionsEqual(
243
324
  selection1.cursorStartPosition,
244
325
  ( selection2 as SelectionInMultipleBlocks )
@@ -251,9 +332,9 @@ export function areSelectionsStatesEqual(
251
332
  )
252
333
  );
253
334
  case SelectionType.WholeBlock:
254
- return (
255
- selection1.blockId ===
256
- ( selection2 as SelectionWholeBlock ).blockId
335
+ return Y.compareRelativePositions(
336
+ selection1.blockPosition,
337
+ ( selection2 as SelectionWholeBlock ).blockPosition
257
338
  );
258
339
 
259
340
  default:
@@ -272,9 +353,10 @@ function areCursorPositionsEqual(
272
353
  cursorPosition1: CursorPosition,
273
354
  cursorPosition2: CursorPosition
274
355
  ): boolean {
275
- const isRelativePositionEqual =
276
- JSON.stringify( cursorPosition1.relativePosition ) ===
277
- JSON.stringify( cursorPosition2.relativePosition );
356
+ const isRelativePositionEqual = Y.compareRelativePositions(
357
+ cursorPosition1.relativePosition,
358
+ cursorPosition2.relativePosition
359
+ );
278
360
 
279
361
  // Ensure a change in calculated absolute offset results in a treating the cursor as modified.
280
362
  // This is necessary because Y.Text relative positions can remain the same after text changes.