@wordpress/editor 14.45.2-next.v.202605131032.0 → 14.47.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 (326) hide show
  1. package/CHANGELOG.md +12 -1
  2. package/build/components/collab-sidebar/add-note.cjs +6 -0
  3. package/build/components/collab-sidebar/add-note.cjs.map +2 -2
  4. package/build/components/collab-sidebar/hooks.cjs +36 -24
  5. package/build/components/collab-sidebar/hooks.cjs.map +2 -2
  6. package/build/components/collab-sidebar/index.cjs +19 -10
  7. package/build/components/collab-sidebar/index.cjs.map +2 -2
  8. package/build/components/collab-sidebar/note-byline.cjs +16 -9
  9. package/build/components/collab-sidebar/note-byline.cjs.map +2 -2
  10. package/build/components/collab-sidebar/notes.cjs +20 -11
  11. package/build/components/collab-sidebar/notes.cjs.map +2 -2
  12. package/build/components/collab-sidebar/utils.cjs +42 -2
  13. package/build/components/collab-sidebar/utils.cjs.map +2 -2
  14. package/build/components/collaborators-overlay/compute-selection.cjs +39 -15
  15. package/build/components/collaborators-overlay/compute-selection.cjs.map +3 -3
  16. package/build/components/collaborators-overlay/use-block-highlighting.cjs +10 -2
  17. package/build/components/collaborators-overlay/use-block-highlighting.cjs.map +3 -3
  18. package/build/components/collaborators-overlay/use-render-cursors.cjs +15 -7
  19. package/build/components/collaborators-overlay/use-render-cursors.cjs.map +3 -3
  20. package/build/components/collaborators-presence/avatar/component.cjs +5 -1
  21. package/build/components/collaborators-presence/avatar/component.cjs.map +3 -3
  22. package/build/components/collaborators-presence/use-collaborator-notifications.cjs +6 -3
  23. package/build/components/collaborators-presence/use-collaborator-notifications.cjs.map +2 -2
  24. package/build/components/editor-interface/index.cjs +17 -16
  25. package/build/components/editor-interface/index.cjs.map +3 -3
  26. package/build/components/editor-notices/index.cjs +6 -1
  27. package/build/components/editor-notices/index.cjs.map +3 -3
  28. package/build/components/global-styles/hooks.cjs +12 -1
  29. package/build/components/global-styles/hooks.cjs.map +2 -2
  30. package/build/components/header/index.cjs +11 -6
  31. package/build/components/header/index.cjs.map +2 -2
  32. package/build/components/media/media-editor-modal.cjs +14 -1
  33. package/build/components/media/media-editor-modal.cjs.map +2 -2
  34. package/build/components/post-card-panel/index.cjs +7 -1
  35. package/build/components/post-card-panel/index.cjs.map +3 -3
  36. package/build/components/post-last-revision/index.cjs +28 -8
  37. package/build/components/post-last-revision/index.cjs.map +2 -2
  38. package/build/components/post-locked-modal/index.cjs +9 -6
  39. package/build/components/post-locked-modal/index.cjs.map +2 -2
  40. package/build/components/post-publish-button/label.cjs +0 -7
  41. package/build/components/post-publish-button/label.cjs.map +2 -2
  42. package/build/components/post-publish-panel/prepublish.cjs +8 -1
  43. package/build/components/post-publish-panel/prepublish.cjs.map +3 -3
  44. package/build/components/post-revisions-panel/index.cjs +7 -0
  45. package/build/components/post-revisions-panel/index.cjs.map +2 -2
  46. package/build/components/post-revisions-preview/diff-markers.cjs +21 -13
  47. package/build/components/post-revisions-preview/diff-markers.cjs.map +2 -2
  48. package/build/components/post-revisions-preview/revisions-canvas.cjs +7 -1
  49. package/build/components/post-revisions-preview/revisions-canvas.cjs.map +2 -2
  50. package/build/components/post-taxonomies/check.cjs +1 -2
  51. package/build/components/post-taxonomies/check.cjs.map +2 -2
  52. package/build/components/post-taxonomies/index.cjs +1 -2
  53. package/build/components/post-taxonomies/index.cjs.map +2 -2
  54. package/build/components/preferences-modal/index.cjs +1 -1
  55. package/build/components/preferences-modal/index.cjs.map +2 -2
  56. package/build/components/preview-dropdown/index.cjs.map +3 -3
  57. package/build/components/provider/index.cjs +5 -21
  58. package/build/components/provider/index.cjs.map +3 -3
  59. package/build/components/provider/use-block-editor-settings.cjs +16 -9
  60. package/build/components/provider/use-block-editor-settings.cjs.map +3 -3
  61. package/build/components/resizable-editor/resize-handle.cjs +24 -16
  62. package/build/components/resizable-editor/resize-handle.cjs.map +2 -2
  63. package/build/components/sidebar/header.cjs +16 -21
  64. package/build/components/sidebar/header.cjs.map +3 -3
  65. package/build/components/sidebar/index.cjs +2 -6
  66. package/build/components/sidebar/index.cjs.map +2 -2
  67. package/build/components/start-page-options/index.cjs +14 -4
  68. package/build/components/start-page-options/index.cjs.map +2 -2
  69. package/build/components/start-template-options/index.cjs +8 -6
  70. package/build/components/start-template-options/index.cjs.map +2 -2
  71. package/build/components/styles-canvas/style-book.cjs +59 -2
  72. package/build/components/styles-canvas/style-book.cjs.map +2 -2
  73. package/build/components/sync-connection-error-modal/index.cjs +10 -7
  74. package/build/components/sync-connection-error-modal/index.cjs.map +3 -3
  75. package/build/components/template-actions-panel/block-theme-content.cjs +21 -12
  76. package/build/components/template-actions-panel/block-theme-content.cjs.map +2 -2
  77. package/build/dataviews/store/private-actions.cjs.map +2 -2
  78. package/build/store/private-actions.cjs +17 -1
  79. package/build/store/private-actions.cjs.map +3 -3
  80. package/build/store/private-selectors.cjs +18 -0
  81. package/build/store/private-selectors.cjs.map +2 -2
  82. package/build/store/selectors.cjs +0 -17
  83. package/build/store/selectors.cjs.map +2 -2
  84. package/build/{components/media → utils/media-delete}/index.cjs +12 -13
  85. package/build/utils/media-delete/index.cjs.map +7 -0
  86. package/build/utils/media-finalize/index.cjs +3 -1
  87. package/build/utils/media-finalize/index.cjs.map +2 -2
  88. package/build/utils/sync-error-messages.cjs +9 -0
  89. package/build/utils/sync-error-messages.cjs.map +2 -2
  90. package/build-module/components/collab-sidebar/add-note.mjs +6 -0
  91. package/build-module/components/collab-sidebar/add-note.mjs.map +2 -2
  92. package/build-module/components/collab-sidebar/hooks.mjs +42 -25
  93. package/build-module/components/collab-sidebar/hooks.mjs.map +2 -2
  94. package/build-module/components/collab-sidebar/index.mjs +19 -10
  95. package/build-module/components/collab-sidebar/index.mjs.map +2 -2
  96. package/build-module/components/collab-sidebar/note-byline.mjs +17 -10
  97. package/build-module/components/collab-sidebar/note-byline.mjs.map +2 -2
  98. package/build-module/components/collab-sidebar/notes.mjs +26 -13
  99. package/build-module/components/collab-sidebar/notes.mjs.map +2 -2
  100. package/build-module/components/collab-sidebar/utils.mjs +38 -2
  101. package/build-module/components/collab-sidebar/utils.mjs.map +2 -2
  102. package/build-module/components/collaborators-overlay/compute-selection.mjs +36 -12
  103. package/build-module/components/collaborators-overlay/compute-selection.mjs.map +2 -2
  104. package/build-module/components/collaborators-overlay/use-block-highlighting.mjs +10 -3
  105. package/build-module/components/collaborators-overlay/use-block-highlighting.mjs.map +2 -2
  106. package/build-module/components/collaborators-overlay/use-render-cursors.mjs +11 -6
  107. package/build-module/components/collaborators-overlay/use-render-cursors.mjs.map +2 -2
  108. package/build-module/components/collaborators-presence/avatar/component.mjs +7 -3
  109. package/build-module/components/collaborators-presence/avatar/component.mjs.map +2 -2
  110. package/build-module/components/collaborators-presence/use-collaborator-notifications.mjs +6 -3
  111. package/build-module/components/collaborators-presence/use-collaborator-notifications.mjs.map +2 -2
  112. package/build-module/components/editor-interface/index.mjs +22 -17
  113. package/build-module/components/editor-interface/index.mjs.map +2 -2
  114. package/build-module/components/editor-notices/index.mjs +6 -1
  115. package/build-module/components/editor-notices/index.mjs.map +2 -2
  116. package/build-module/components/global-styles/hooks.mjs +12 -1
  117. package/build-module/components/global-styles/hooks.mjs.map +2 -2
  118. package/build-module/components/header/index.mjs +11 -6
  119. package/build-module/components/header/index.mjs.map +2 -2
  120. package/build-module/components/media/media-editor-modal.mjs +14 -1
  121. package/build-module/components/media/media-editor-modal.mjs.map +2 -2
  122. package/build-module/components/post-card-panel/index.mjs +8 -2
  123. package/build-module/components/post-card-panel/index.mjs.map +2 -2
  124. package/build-module/components/post-last-revision/index.mjs +29 -9
  125. package/build-module/components/post-last-revision/index.mjs.map +2 -2
  126. package/build-module/components/post-locked-modal/index.mjs +9 -6
  127. package/build-module/components/post-locked-modal/index.mjs.map +2 -2
  128. package/build-module/components/post-publish-button/label.mjs +0 -7
  129. package/build-module/components/post-publish-button/label.mjs.map +2 -2
  130. package/build-module/components/post-publish-panel/prepublish.mjs +9 -2
  131. package/build-module/components/post-publish-panel/prepublish.mjs.map +2 -2
  132. package/build-module/components/post-revisions-panel/index.mjs +7 -0
  133. package/build-module/components/post-revisions-panel/index.mjs.map +2 -2
  134. package/build-module/components/post-revisions-preview/diff-markers.mjs +22 -14
  135. package/build-module/components/post-revisions-preview/diff-markers.mjs.map +2 -2
  136. package/build-module/components/post-revisions-preview/revisions-canvas.mjs +7 -1
  137. package/build-module/components/post-revisions-preview/revisions-canvas.mjs.map +2 -2
  138. package/build-module/components/post-taxonomies/check.mjs +1 -2
  139. package/build-module/components/post-taxonomies/check.mjs.map +2 -2
  140. package/build-module/components/post-taxonomies/index.mjs +1 -2
  141. package/build-module/components/post-taxonomies/index.mjs.map +2 -2
  142. package/build-module/components/preferences-modal/index.mjs +1 -1
  143. package/build-module/components/preferences-modal/index.mjs.map +2 -2
  144. package/build-module/components/preview-dropdown/index.mjs +2 -2
  145. package/build-module/components/preview-dropdown/index.mjs.map +2 -2
  146. package/build-module/components/provider/index.mjs +6 -22
  147. package/build-module/components/provider/index.mjs.map +2 -2
  148. package/build-module/components/provider/use-block-editor-settings.mjs +16 -9
  149. package/build-module/components/provider/use-block-editor-settings.mjs.map +2 -2
  150. package/build-module/components/resizable-editor/resize-handle.mjs +26 -18
  151. package/build-module/components/resizable-editor/resize-handle.mjs.map +2 -2
  152. package/build-module/components/sidebar/header.mjs +11 -16
  153. package/build-module/components/sidebar/header.mjs.map +2 -2
  154. package/build-module/components/sidebar/index.mjs +2 -7
  155. package/build-module/components/sidebar/index.mjs.map +2 -2
  156. package/build-module/components/start-page-options/index.mjs +14 -4
  157. package/build-module/components/start-page-options/index.mjs.map +2 -2
  158. package/build-module/components/start-template-options/index.mjs +8 -6
  159. package/build-module/components/start-template-options/index.mjs.map +2 -2
  160. package/build-module/components/styles-canvas/style-book.mjs +60 -3
  161. package/build-module/components/styles-canvas/style-book.mjs.map +2 -2
  162. package/build-module/components/sync-connection-error-modal/index.mjs +14 -8
  163. package/build-module/components/sync-connection-error-modal/index.mjs.map +2 -2
  164. package/build-module/components/template-actions-panel/block-theme-content.mjs +21 -13
  165. package/build-module/components/template-actions-panel/block-theme-content.mjs.map +2 -2
  166. package/build-module/dataviews/store/private-actions.mjs.map +2 -2
  167. package/build-module/store/private-actions.mjs +16 -1
  168. package/build-module/store/private-actions.mjs.map +2 -2
  169. package/build-module/store/private-selectors.mjs +18 -0
  170. package/build-module/store/private-selectors.mjs.map +2 -2
  171. package/build-module/store/selectors.mjs +0 -16
  172. package/build-module/store/selectors.mjs.map +2 -2
  173. package/build-module/utils/media-delete/index.mjs +12 -0
  174. package/build-module/utils/media-delete/index.mjs.map +7 -0
  175. package/build-module/utils/media-finalize/index.mjs +3 -1
  176. package/build-module/utils/media-finalize/index.mjs.map +2 -2
  177. package/build-module/utils/sync-error-messages.mjs +8 -0
  178. package/build-module/utils/sync-error-messages.mjs.map +2 -2
  179. package/build-style/style-rtl.css +462 -408
  180. package/build-style/style.css +462 -408
  181. package/build-types/components/collab-sidebar/add-note.d.ts.map +1 -1
  182. package/build-types/components/collab-sidebar/hooks.d.ts.map +1 -1
  183. package/build-types/components/collab-sidebar/index.d.ts.map +1 -1
  184. package/build-types/components/collab-sidebar/note-byline.d.ts +3 -0
  185. package/build-types/components/collab-sidebar/note-byline.d.ts.map +1 -1
  186. package/build-types/components/collab-sidebar/notes.d.ts.map +1 -1
  187. package/build-types/components/collab-sidebar/utils.d.ts +33 -0
  188. package/build-types/components/collab-sidebar/utils.d.ts.map +1 -1
  189. package/build-types/components/collaborators-overlay/compute-selection.d.ts.map +1 -1
  190. package/build-types/components/collaborators-overlay/use-block-highlighting.d.ts +3 -0
  191. package/build-types/components/collaborators-overlay/use-block-highlighting.d.ts.map +1 -1
  192. package/build-types/components/collaborators-overlay/use-render-cursors.d.ts.map +1 -1
  193. package/build-types/components/collaborators-presence/avatar/component.d.ts.map +1 -1
  194. package/build-types/components/collaborators-presence/use-collaborator-notifications.d.ts.map +1 -1
  195. package/build-types/components/editor-interface/index.d.ts.map +1 -1
  196. package/build-types/components/editor-notices/index.d.ts.map +1 -1
  197. package/build-types/components/global-styles/hooks.d.ts.map +1 -1
  198. package/build-types/components/media/media-editor-modal.d.ts +6 -2
  199. package/build-types/components/media/media-editor-modal.d.ts.map +1 -1
  200. package/build-types/components/post-card-panel/index.d.ts.map +1 -1
  201. package/build-types/components/post-last-revision/index.d.ts.map +1 -1
  202. package/build-types/components/post-locked-modal/index.d.ts +1 -6
  203. package/build-types/components/post-locked-modal/index.d.ts.map +1 -1
  204. package/build-types/components/post-publish-button/label.d.ts.map +1 -1
  205. package/build-types/components/post-publish-panel/prepublish.d.ts.map +1 -1
  206. package/build-types/components/post-revisions-panel/index.d.ts +1 -1
  207. package/build-types/components/post-revisions-panel/index.d.ts.map +1 -1
  208. package/build-types/components/post-revisions-preview/diff-markers.d.ts.map +1 -1
  209. package/build-types/components/post-revisions-preview/revisions-canvas.d.ts.map +1 -1
  210. package/build-types/components/post-taxonomies/check.d.ts.map +1 -1
  211. package/build-types/components/post-taxonomies/flat-term-selector.d.ts +1 -6
  212. package/build-types/components/post-taxonomies/hierarchical-term-selector.d.ts +1 -6
  213. package/build-types/components/post-taxonomies/index.d.ts.map +1 -1
  214. package/build-types/components/provider/index.d.ts.map +1 -1
  215. package/build-types/components/provider/use-block-editor-settings.d.ts.map +1 -1
  216. package/build-types/components/resizable-editor/resize-handle.d.ts.map +1 -1
  217. package/build-types/components/sidebar/index.d.ts.map +1 -1
  218. package/build-types/components/start-page-options/index.d.ts.map +1 -1
  219. package/build-types/components/start-template-options/index.d.ts.map +1 -1
  220. package/build-types/components/styles-canvas/style-book.d.ts.map +1 -1
  221. package/build-types/components/sync-connection-error-modal/index.d.ts.map +1 -1
  222. package/build-types/components/template-actions-panel/block-theme-content.d.ts.map +1 -1
  223. package/build-types/dataviews/store/private-actions.d.ts +0 -1
  224. package/build-types/dataviews/store/private-actions.d.ts.map +1 -1
  225. package/build-types/store/private-actions.d.ts +15 -0
  226. package/build-types/store/private-actions.d.ts.map +1 -1
  227. package/build-types/store/private-selectors.d.ts +10 -0
  228. package/build-types/store/private-selectors.d.ts.map +1 -1
  229. package/build-types/store/selectors.d.ts +0 -10
  230. package/build-types/store/selectors.d.ts.map +1 -1
  231. package/build-types/utils/get-template-part-icon.d.ts.map +1 -1
  232. package/build-types/utils/media-delete/index.d.ts +2 -0
  233. package/build-types/utils/media-delete/index.d.ts.map +1 -0
  234. package/build-types/utils/media-finalize/index.d.ts +1 -1
  235. package/build-types/utils/media-finalize/index.d.ts.map +1 -1
  236. package/build-types/utils/sync-error-messages.d.ts +1 -0
  237. package/build-types/utils/sync-error-messages.d.ts.map +1 -1
  238. package/package.json +48 -48
  239. package/src/components/collab-sidebar/add-note.js +9 -0
  240. package/src/components/collab-sidebar/hooks.js +53 -29
  241. package/src/components/collab-sidebar/index.js +28 -14
  242. package/src/components/collab-sidebar/note-byline.js +15 -10
  243. package/src/components/collab-sidebar/notes.js +36 -14
  244. package/src/components/collab-sidebar/test/utils.js +375 -1
  245. package/src/components/collab-sidebar/utils.js +70 -1
  246. package/src/components/collaborators-overlay/compute-selection.ts +67 -19
  247. package/src/components/collaborators-overlay/use-block-highlighting.ts +14 -1
  248. package/src/components/collaborators-overlay/use-render-cursors.ts +15 -4
  249. package/src/components/collaborators-presence/avatar/component.tsx +10 -3
  250. package/src/components/collaborators-presence/avatar/test/index.tsx +50 -18
  251. package/src/components/collaborators-presence/styles/collaborators-presence.scss +4 -1
  252. package/src/components/collaborators-presence/test/use-collaborator-notifications.ts +2 -1
  253. package/src/components/collaborators-presence/use-collaborator-notifications.ts +6 -4
  254. package/src/components/editor-help/help-topic-row.native.js +2 -2
  255. package/src/components/editor-interface/index.js +22 -23
  256. package/src/components/editor-interface/style.scss +4 -0
  257. package/src/components/editor-notices/index.js +7 -1
  258. package/src/components/error-boundary/index.native.js +2 -2
  259. package/src/components/global-styles/hooks.js +26 -0
  260. package/src/components/global-styles-sidebar/style.scss +0 -9
  261. package/src/components/header/index.js +12 -12
  262. package/src/components/media/media-editor-modal.js +20 -2
  263. package/src/components/offline-status/index.native.js +2 -2
  264. package/src/components/post-card-panel/index.js +5 -2
  265. package/src/components/post-last-revision/index.js +37 -9
  266. package/src/components/post-last-revision/style.scss +0 -3
  267. package/src/components/post-locked-modal/index.js +8 -5
  268. package/src/components/post-panel-row/style.scss +1 -0
  269. package/src/components/post-publish-button/label.js +0 -11
  270. package/src/components/post-publish-panel/prepublish.js +6 -2
  271. package/src/components/post-revisions-panel/index.js +8 -0
  272. package/src/components/post-revisions-preview/diff-markers.js +17 -11
  273. package/src/components/post-revisions-preview/revisions-canvas.js +7 -1
  274. package/src/components/post-revisions-preview/style.scss +4 -4
  275. package/src/components/post-taxonomies/check.js +1 -2
  276. package/src/components/post-taxonomies/index.js +1 -2
  277. package/src/components/preferences-modal/index.js +1 -1
  278. package/src/components/preview-dropdown/index.js +2 -2
  279. package/src/components/provider/index.js +10 -31
  280. package/src/components/provider/use-block-editor-settings.js +19 -12
  281. package/src/components/resizable-editor/resize-handle.js +22 -16
  282. package/src/components/sidebar/header.js +18 -28
  283. package/src/components/sidebar/index.js +5 -14
  284. package/src/components/start-page-options/index.js +19 -4
  285. package/src/components/start-template-options/index.js +13 -6
  286. package/src/components/styles-canvas/style-book.js +75 -13
  287. package/src/components/sync-connection-error-modal/index.tsx +25 -11
  288. package/src/components/template-actions-panel/block-theme-content.js +19 -13
  289. package/src/components/text-editor/style.scss +2 -2
  290. package/src/dataviews/store/private-actions.ts +0 -1
  291. package/src/store/private-actions.js +27 -0
  292. package/src/store/private-selectors.js +26 -0
  293. package/src/store/selectors.js +0 -24
  294. package/src/store/test/actions.js +34 -0
  295. package/src/utils/media-delete/index.js +11 -0
  296. package/src/utils/media-finalize/index.js +6 -1
  297. package/src/utils/media-finalize/test/index.js +32 -2
  298. package/src/utils/sync-error-messages.ts +8 -0
  299. package/src/utils/test/sync-error-messages.js +1 -0
  300. package/build/components/global-styles-provider/index.cjs +0 -181
  301. package/build/components/global-styles-provider/index.cjs.map +0 -7
  302. package/build/components/media/index.cjs.map +0 -7
  303. package/build/components/media/metadata-panel.cjs +0 -96
  304. package/build/components/media/metadata-panel.cjs.map +0 -7
  305. package/build/components/media/preview.cjs +0 -39
  306. package/build/components/media/preview.cjs.map +0 -7
  307. package/build-module/components/global-styles-provider/index.mjs +0 -156
  308. package/build-module/components/global-styles-provider/index.mjs.map +0 -7
  309. package/build-module/components/media/index.mjs +0 -8
  310. package/build-module/components/media/index.mjs.map +0 -7
  311. package/build-module/components/media/metadata-panel.mjs +0 -65
  312. package/build-module/components/media/metadata-panel.mjs.map +0 -7
  313. package/build-module/components/media/preview.mjs +0 -21
  314. package/build-module/components/media/preview.mjs.map +0 -7
  315. package/build-types/components/global-styles-provider/index.d.ts +0 -16
  316. package/build-types/components/global-styles-provider/index.d.ts.map +0 -1
  317. package/build-types/components/media/index.d.ts +0 -3
  318. package/build-types/components/media/index.d.ts.map +0 -1
  319. package/build-types/components/media/metadata-panel.d.ts +0 -12
  320. package/build-types/components/media/metadata-panel.d.ts.map +0 -1
  321. package/build-types/components/media/preview.d.ts +0 -9
  322. package/build-types/components/media/preview.d.ts.map +0 -1
  323. package/src/components/global-styles-provider/index.js +0 -207
  324. package/src/components/media/index.js +0 -2
  325. package/src/components/media/metadata-panel.js +0 -77
  326. package/src/components/media/preview.js +0 -35
@@ -1,12 +1,321 @@
1
1
  /**
2
2
  * Internal dependencies
3
3
  */
4
- import { calculateNotePositions } from '../utils';
4
+ import {
5
+ getNoteIdsFromMetadata,
6
+ addNoteIdToMetadata,
7
+ removeNoteIdFromMetadata,
8
+ calculateNotePositions,
9
+ pickPrimaryNote,
10
+ } from '../utils';
5
11
 
6
12
  function makeRect( top ) {
7
13
  return { top };
8
14
  }
9
15
 
16
+ describe( 'getNoteIdsFromMetadata', () => {
17
+ it( 'returns empty array for null metadata', () => {
18
+ expect( getNoteIdsFromMetadata( null ) ).toEqual( [] );
19
+ } );
20
+
21
+ it( 'returns empty array for undefined metadata', () => {
22
+ expect( getNoteIdsFromMetadata( undefined ) ).toEqual( [] );
23
+ } );
24
+
25
+ it( 'returns empty array for metadata without noteId', () => {
26
+ expect( getNoteIdsFromMetadata( {} ) ).toEqual( [] );
27
+ expect( getNoteIdsFromMetadata( { name: 'test' } ) ).toEqual( [] );
28
+ } );
29
+
30
+ it( 'returns empty array for noteId of 0', () => {
31
+ expect( getNoteIdsFromMetadata( { noteId: 0 } ) ).toEqual( [] );
32
+ } );
33
+
34
+ it( 'returns empty array for noteId of empty string', () => {
35
+ expect( getNoteIdsFromMetadata( { noteId: '' } ) ).toEqual( [] );
36
+ } );
37
+
38
+ it( 'returns empty array for noteId of false', () => {
39
+ expect( getNoteIdsFromMetadata( { noteId: false } ) ).toEqual( [] );
40
+ } );
41
+
42
+ it( 'returns array from scalar noteId (legacy format)', () => {
43
+ expect( getNoteIdsFromMetadata( { noteId: 42 } ) ).toEqual( [ 42 ] );
44
+ } );
45
+
46
+ it( 'coerces a string-typed legacy noteId to a number', () => {
47
+ expect( getNoteIdsFromMetadata( { noteId: '42' } ) ).toEqual( [ 42 ] );
48
+ } );
49
+
50
+ it( 'drops non-numeric and non-positive ids', () => {
51
+ expect(
52
+ getNoteIdsFromMetadata( { noteId: [ 1, 'abc', -3, 2 ] } )
53
+ ).toEqual( [ 1, 2 ] );
54
+ } );
55
+
56
+ it( 'returns array from array noteId', () => {
57
+ expect( getNoteIdsFromMetadata( { noteId: [ 1, 2, 3 ] } ) ).toEqual( [
58
+ 1, 2, 3,
59
+ ] );
60
+ } );
61
+
62
+ it( 'filters out falsy values from array', () => {
63
+ expect(
64
+ getNoteIdsFromMetadata( { noteId: [ 1, null, 2, undefined, 3 ] } )
65
+ ).toEqual( [ 1, 2, 3 ] );
66
+ } );
67
+
68
+ it( 'filters out zero and empty string from array', () => {
69
+ expect(
70
+ getNoteIdsFromMetadata( { noteId: [ 0, '', 1, false, 2 ] } )
71
+ ).toEqual( [ 1, 2 ] );
72
+ } );
73
+
74
+ it( 'returns empty array when all array values are falsy', () => {
75
+ expect(
76
+ getNoteIdsFromMetadata( { noteId: [ null, undefined, 0, '' ] } )
77
+ ).toEqual( [] );
78
+ } );
79
+
80
+ it( 'deduplicates repeated ids while preserving first occurrence order', () => {
81
+ expect( getNoteIdsFromMetadata( { noteId: [ 1, 1, 1 ] } ) ).toEqual( [
82
+ 1,
83
+ ] );
84
+ expect(
85
+ getNoteIdsFromMetadata( { noteId: [ 1, 2, 1, 3, 2 ] } )
86
+ ).toEqual( [ 1, 2, 3 ] );
87
+ } );
88
+
89
+ it( 'deduplicates across numeric and string-typed duplicates', () => {
90
+ expect(
91
+ getNoteIdsFromMetadata( { noteId: [ 1, '1', 2, '2' ] } )
92
+ ).toEqual( [ 1, 2 ] );
93
+ } );
94
+ } );
95
+
96
+ describe( 'addNoteIdToMetadata', () => {
97
+ it( 'creates array for first note on empty metadata', () => {
98
+ const result = addNoteIdToMetadata( {}, 42 );
99
+ expect( result.noteId ).toEqual( [ 42 ] );
100
+ } );
101
+
102
+ it( 'creates array for first note on null metadata', () => {
103
+ const result = addNoteIdToMetadata( null, 42 );
104
+ expect( result.noteId ).toEqual( [ 42 ] );
105
+ } );
106
+
107
+ it( 'creates array for first note on undefined metadata', () => {
108
+ const result = addNoteIdToMetadata( undefined, 42 );
109
+ expect( result.noteId ).toEqual( [ 42 ] );
110
+ } );
111
+
112
+ it( 'converts scalar noteId to array and appends new id', () => {
113
+ const result = addNoteIdToMetadata( { noteId: 1 }, 2 );
114
+ expect( result.noteId ).toEqual( [ 1, 2 ] );
115
+ } );
116
+
117
+ it( 'appends to existing array', () => {
118
+ const result = addNoteIdToMetadata( { noteId: [ 1, 2 ] }, 3 );
119
+ expect( result.noteId ).toEqual( [ 1, 2, 3 ] );
120
+ } );
121
+
122
+ it( 'prevents duplicates', () => {
123
+ const result = addNoteIdToMetadata( { noteId: [ 1, 2 ] }, 1 );
124
+ expect( result ).toEqual( { noteId: [ 1, 2 ] } );
125
+ } );
126
+
127
+ it( 'preserves other metadata properties', () => {
128
+ const result = addNoteIdToMetadata( { noteId: 1, name: 'test' }, 2 );
129
+ expect( result ).toEqual( { noteId: [ 1, 2 ], name: 'test' } );
130
+ } );
131
+
132
+ it( 'returns original metadata object when duplicate is added', () => {
133
+ const metadata = { noteId: [ 1, 2 ] };
134
+ const result = addNoteIdToMetadata( metadata, 1 );
135
+ expect( result ).toBe( metadata );
136
+ } );
137
+
138
+ it( 'handles adding to metadata with other properties but no noteId', () => {
139
+ const result = addNoteIdToMetadata( { name: 'test' }, 5 );
140
+ expect( result ).toEqual( { name: 'test', noteId: [ 5 ] } );
141
+ } );
142
+
143
+ it( 'dedupes a numeric id against a string-typed legacy id', () => {
144
+ const metadata = { noteId: '5' };
145
+ const result = addNoteIdToMetadata( metadata, 5 );
146
+ expect( result ).toBe( metadata );
147
+ } );
148
+
149
+ it( 'dedupes a string id against a numeric id already in the array', () => {
150
+ const result = addNoteIdToMetadata( { noteId: [ 1, 2 ] }, '2' );
151
+ expect( result ).toEqual( { noteId: [ 1, 2 ] } );
152
+ } );
153
+ } );
154
+
155
+ describe( 'removeNoteIdFromMetadata', () => {
156
+ it( 'removes noteId from array', () => {
157
+ const result = removeNoteIdFromMetadata( { noteId: [ 1, 2, 3 ] }, 2 );
158
+ expect( result.noteId ).toEqual( [ 1, 3 ] );
159
+ } );
160
+
161
+ it( 'returns undefined noteId when array becomes empty', () => {
162
+ const result = removeNoteIdFromMetadata( { noteId: [ 1 ] }, 1 );
163
+ expect( result.noteId ).toBeUndefined();
164
+ } );
165
+
166
+ it( 'handles removing from scalar noteId (legacy format)', () => {
167
+ const result = removeNoteIdFromMetadata( { noteId: 42 }, 42 );
168
+ expect( result.noteId ).toBeUndefined();
169
+ } );
170
+
171
+ it( 'handles removing non-existent id', () => {
172
+ const result = removeNoteIdFromMetadata( { noteId: [ 1, 2 ] }, 99 );
173
+ expect( result.noteId ).toEqual( [ 1, 2 ] );
174
+ } );
175
+
176
+ it( 'handles empty metadata', () => {
177
+ const result = removeNoteIdFromMetadata( {}, 1 );
178
+ expect( result.noteId ).toBeUndefined();
179
+ } );
180
+
181
+ it( 'preserves other metadata properties', () => {
182
+ const result = removeNoteIdFromMetadata(
183
+ { noteId: [ 1, 2 ], name: 'test' },
184
+ 1
185
+ );
186
+ expect( result ).toEqual( { noteId: [ 2 ], name: 'test' } );
187
+ } );
188
+
189
+ it( 'handles null metadata', () => {
190
+ const result = removeNoteIdFromMetadata( null, 1 );
191
+ expect( result.noteId ).toBeUndefined();
192
+ } );
193
+
194
+ it( 'handles undefined metadata', () => {
195
+ const result = removeNoteIdFromMetadata( undefined, 1 );
196
+ expect( result.noteId ).toBeUndefined();
197
+ } );
198
+
199
+ it( 'removes last note and cleans up noteId to undefined', () => {
200
+ const result = removeNoteIdFromMetadata(
201
+ { noteId: [ 42 ], name: 'test' },
202
+ 42
203
+ );
204
+ expect( result ).toEqual( { name: 'test', noteId: undefined } );
205
+ } );
206
+
207
+ it( 'removes a numeric id when stored as a string-typed legacy scalar', () => {
208
+ const result = removeNoteIdFromMetadata( { noteId: '42' }, 42 );
209
+ expect( result.noteId ).toBeUndefined();
210
+ } );
211
+
212
+ it( 'removes a string id when stored as a number in the array', () => {
213
+ const result = removeNoteIdFromMetadata( { noteId: [ 1, 2, 3 ] }, '2' );
214
+ expect( result.noteId ).toEqual( [ 1, 3 ] );
215
+ } );
216
+ } );
217
+
218
+ describe( 'note id order preservation', () => {
219
+ // The collab sidebar relies on insertion order: the first id in the
220
+ // metadata array is treated as the first (block-aligned) note, with
221
+ // subsequent notes stacking below. These tests pin that contract.
222
+ // See https://github.com/WordPress/gutenberg/issues/75145#issuecomment-4361104794
223
+
224
+ it( 'preserves insertion order across multiple sequential adds', () => {
225
+ let metadata = {};
226
+ metadata = addNoteIdToMetadata( metadata, 5 );
227
+ metadata = addNoteIdToMetadata( metadata, 3 );
228
+ metadata = addNoteIdToMetadata( metadata, 7 );
229
+ metadata = addNoteIdToMetadata( metadata, 1 );
230
+ expect( metadata.noteId ).toEqual( [ 5, 3, 7, 1 ] );
231
+ } );
232
+
233
+ it( 'does not sort or reorder ids when adding', () => {
234
+ // A naive implementation might sort numerically; this confirms it
235
+ // preserves the order the user added notes in.
236
+ const result = addNoteIdToMetadata( { noteId: [ 10, 2, 30 ] }, 4 );
237
+ expect( result.noteId ).toEqual( [ 10, 2, 30, 4 ] );
238
+ } );
239
+
240
+ it( 'keeps the first id first after appending more notes', () => {
241
+ let metadata = addNoteIdToMetadata( {}, 42 );
242
+ metadata = addNoteIdToMetadata( metadata, 99 );
243
+ metadata = addNoteIdToMetadata( metadata, 7 );
244
+ const ids = getNoteIdsFromMetadata( metadata );
245
+ expect( ids[ 0 ] ).toBe( 42 );
246
+ } );
247
+
248
+ it( 'preserves order of remaining ids after removing one from the middle', () => {
249
+ const result = removeNoteIdFromMetadata(
250
+ { noteId: [ 1, 2, 3, 4, 5 ] },
251
+ 3
252
+ );
253
+ expect( result.noteId ).toEqual( [ 1, 2, 4, 5 ] );
254
+ } );
255
+
256
+ it( 'preserves remaining ids in order after removing the first id', () => {
257
+ const result = removeNoteIdFromMetadata(
258
+ { noteId: [ 1, 2, 3, 4 ] },
259
+ 1
260
+ );
261
+ expect( result.noteId ).toEqual( [ 2, 3, 4 ] );
262
+ } );
263
+
264
+ it( 'preserves remaining ids in order after removing the last id', () => {
265
+ const result = removeNoteIdFromMetadata(
266
+ { noteId: [ 1, 2, 3, 4 ] },
267
+ 4
268
+ );
269
+ expect( result.noteId ).toEqual( [ 1, 2, 3 ] );
270
+ } );
271
+
272
+ it( 'preserves order across an interleaved sequence of adds and removes', () => {
273
+ let metadata = {};
274
+ metadata = addNoteIdToMetadata( metadata, 10 );
275
+ metadata = addNoteIdToMetadata( metadata, 20 );
276
+ metadata = addNoteIdToMetadata( metadata, 30 );
277
+ metadata = removeNoteIdFromMetadata( metadata, 20 );
278
+ metadata = addNoteIdToMetadata( metadata, 40 );
279
+ expect( metadata.noteId ).toEqual( [ 10, 30, 40 ] );
280
+ } );
281
+
282
+ it( 'preserves array order through a getNoteIdsFromMetadata round-trip', () => {
283
+ const ids = [ 9, 4, 7, 2, 11 ];
284
+ expect( getNoteIdsFromMetadata( { noteId: ids } ) ).toEqual( ids );
285
+ } );
286
+
287
+ it( 'keeps the legacy scalar id as the first id when migrating to an array', () => {
288
+ // When a legacy single-note post gains a second note, the original
289
+ // note must remain the block-aligned (first) note.
290
+ const result = addNoteIdToMetadata( { noteId: 42 }, 99 );
291
+ expect( result.noteId ).toEqual( [ 42, 99 ] );
292
+ expect( result.noteId[ 0 ] ).toBe( 42 );
293
+ } );
294
+ } );
295
+
296
+ describe( 'pickPrimaryNote', () => {
297
+ it( 'returns null for an empty list', () => {
298
+ expect( pickPrimaryNote( [] ) ).toBeNull();
299
+ } );
300
+
301
+ it( 'returns the first unresolved thread when one exists', () => {
302
+ const threads = [
303
+ { id: 1, status: 'approved' },
304
+ { id: 2, status: 'hold' },
305
+ { id: 3, status: 'hold' },
306
+ ];
307
+ expect( pickPrimaryNote( threads ) ).toBe( threads[ 1 ] );
308
+ } );
309
+
310
+ it( 'falls back to the first thread when none are unresolved', () => {
311
+ const threads = [
312
+ { id: 1, status: 'approved' },
313
+ { id: 2, status: 'approved' },
314
+ ];
315
+ expect( pickPrimaryNote( threads ) ).toBe( threads[ 0 ] );
316
+ } );
317
+ } );
318
+
10
319
  describe( 'calculateNotePositions', () => {
11
320
  it( 'returns empty positions when the anchor thread has no blockRect', () => {
12
321
  const { positions } = calculateNotePositions( {
@@ -152,4 +461,69 @@ describe( 'calculateNotePositions', () => {
152
461
  // 2: 300 + 500 - 16 = 784
153
462
  expect( positions ).toEqual( { 1: 584, 2: 784 } );
154
463
  } );
464
+
465
+ it( 'stacks two threads that share a block with the first as anchor', () => {
466
+ const threads = [ { id: 1 }, { id: 2 } ];
467
+ const blockRects = {
468
+ 1: makeRect( 200 ),
469
+ 2: makeRect( 200 ),
470
+ };
471
+ const heights = { 1: 50, 2: 50 };
472
+
473
+ const { positions } = calculateNotePositions( {
474
+ threads,
475
+ selectedNoteId: 1,
476
+ blockRects,
477
+ heights,
478
+ scrollTop: 0,
479
+ } );
480
+
481
+ // 1 (anchor): 200 - 16 = 184
482
+ // 2 (downward): (184 + 50) - 200 + 20 = 54 → 200 + 54 = 254
483
+ expect( positions ).toEqual( { 1: 184, 2: 254 } );
484
+ } );
485
+
486
+ it( 'stacks two threads that share a block with the second as anchor', () => {
487
+ const threads = [ { id: 1 }, { id: 2 } ];
488
+ const blockRects = {
489
+ 1: makeRect( 200 ),
490
+ 2: makeRect( 200 ),
491
+ };
492
+ const heights = { 1: 50, 2: 50 };
493
+
494
+ const { positions } = calculateNotePositions( {
495
+ threads,
496
+ selectedNoteId: 2,
497
+ blockRects,
498
+ heights,
499
+ scrollTop: 0,
500
+ } );
501
+
502
+ // 2 (anchor): 200 - 16 = 184
503
+ // 1 (upward): 184 - 200 - 50 - 20 = -86 → 200 + (-86) = 114
504
+ expect( positions ).toEqual( { 1: 114, 2: 184 } );
505
+ } );
506
+
507
+ it( 'stacks three threads sharing a block with middle as anchor', () => {
508
+ const threads = [ { id: 1 }, { id: 2 }, { id: 3 } ];
509
+ const blockRects = {
510
+ 1: makeRect( 200 ),
511
+ 2: makeRect( 200 ),
512
+ 3: makeRect( 200 ),
513
+ };
514
+ const heights = { 1: 50, 2: 50, 3: 50 };
515
+
516
+ const { positions } = calculateNotePositions( {
517
+ threads,
518
+ selectedNoteId: 2,
519
+ blockRects,
520
+ heights,
521
+ scrollTop: 0,
522
+ } );
523
+
524
+ // 2 (anchor): 200 - 16 = 184
525
+ // 3 (downward): (184 + 50) - 200 + 20 = 54 → 254
526
+ // 1 (upward): 184 - 200 - 50 - 20 = -86 → 114
527
+ expect( positions ).toEqual( { 1: 114, 2: 184, 3: 254 } );
528
+ } );
155
529
  } );
@@ -23,7 +23,7 @@ const OVERLAP_MARGIN = 20;
23
23
  */
24
24
  const AVATAR_BORDER_COLORS = [
25
25
  '#C36EFF', // Purple
26
- '#FF51A8', // Pink
26
+ '#D94145', // Red
27
27
  '#E4780A', // Orange
28
28
  '#FF35EE', // Magenta
29
29
  '#879F11', // Olive
@@ -90,6 +90,75 @@ export function getNoteExcerpt( text, excerptLength = 10 ) {
90
90
  return isTrimmed ? trimmedExcerpt + '…' : trimmedExcerpt;
91
91
  }
92
92
 
93
+ /**
94
+ * Normalizes noteId metadata to always return an array of unique numeric ids,
95
+ * preserving insertion order. Handles both scalar (legacy, possibly
96
+ * string-typed) and array (new) values.
97
+ *
98
+ * @param {Object} metadata Block metadata object
99
+ * @return {number[]} Array of note IDs (may be empty)
100
+ */
101
+ export function getNoteIdsFromMetadata( metadata ) {
102
+ const noteId = metadata?.noteId;
103
+ const raw = Array.isArray( noteId ) ? noteId : [ noteId ];
104
+ const ids = new Set();
105
+ for ( const value of raw ) {
106
+ const id = Number( value );
107
+ if ( Number.isFinite( id ) && id > 0 ) {
108
+ ids.add( id );
109
+ }
110
+ }
111
+ return [ ...ids ];
112
+ }
113
+
114
+ /**
115
+ * Adds a note ID to the metadata.
116
+ * Converts scalar to array if needed, otherwise appends.
117
+ *
118
+ * @param {Object} metadata Existing block metadata
119
+ * @param {number} noteId Note ID to add
120
+ * @return {Object} Updated metadata object
121
+ */
122
+ export function addNoteIdToMetadata( metadata, noteId ) {
123
+ const ids = new Set( getNoteIdsFromMetadata( metadata ) );
124
+ const id = Number( noteId );
125
+ if ( ids.has( id ) ) {
126
+ return metadata;
127
+ }
128
+ ids.add( id );
129
+ return { ...metadata, noteId: [ ...ids ] };
130
+ }
131
+
132
+ /**
133
+ * Picks the most relevant thread from a list: first unresolved, else first.
134
+ *
135
+ * @param {Array} threads Ordered list of thread objects.
136
+ * @return {Object|null} Selected thread or null when the list is empty.
137
+ */
138
+ export function pickPrimaryNote( threads ) {
139
+ return (
140
+ threads.find( ( thread ) => thread.status === 'hold' ) ??
141
+ threads[ 0 ] ??
142
+ null
143
+ );
144
+ }
145
+
146
+ /**
147
+ * Removes a note ID from the metadata.
148
+ *
149
+ * @param {Object} metadata Existing block metadata
150
+ * @param {number} noteId Note ID to remove
151
+ * @return {Object} Updated metadata object
152
+ */
153
+ export function removeNoteIdFromMetadata( metadata, noteId ) {
154
+ const ids = new Set( getNoteIdsFromMetadata( metadata ) );
155
+ ids.delete( Number( noteId ) );
156
+ return {
157
+ ...metadata,
158
+ noteId: ids.size > 0 ? [ ...ids ] : undefined,
159
+ };
160
+ }
161
+
93
162
  /**
94
163
  * Calculate final top positions for all floating note threads in the
95
164
  * editor's content coordinate space. Adjusts positions to prevent overlapping
@@ -1,6 +1,10 @@
1
- import { SelectionDirection, SelectionType } from '@wordpress/core-data';
2
- import type { ResolvedSelection } from '@wordpress/core-data';
1
+ import { privateApis as coreDataPrivateApis } from '@wordpress/core-data';
2
+ import type {
3
+ CoreDataPrivateApis,
4
+ ResolvedSelection,
5
+ } from '@wordpress/core-data';
3
6
 
7
+ import { unlock } from '../../lock-unlock';
4
8
  import {
5
9
  getCursorPosition,
6
10
  getSelectionRects,
@@ -10,6 +14,10 @@ import {
10
14
  } from './cursor-dom-utils';
11
15
  import type { CursorCoords, SelectionRect } from './cursor-dom-utils';
12
16
 
17
+ const { SelectionDirection, SelectionType } = unlock(
18
+ coreDataPrivateApis
19
+ ) as Pick< CoreDataPrivateApis, 'SelectionDirection' | 'SelectionType' >;
20
+
13
21
  /** Common parameters passed to cursor/selection computation helpers. */
14
22
  interface OverlayContext {
15
23
  editorDocument: Document;
@@ -36,6 +44,44 @@ export interface SelectionVisual {
36
44
  selectionRects?: SelectionRect[];
37
45
  }
38
46
 
47
+ /**
48
+ * Resolve the most specific editor element the selection refers to.
49
+ *
50
+ * When the sender carries an `attributeKey`, narrow to the RichText element
51
+ * matching `data-wp-block-attribute-key` inside the block. This is what makes
52
+ * cursor placement work for blocks with multiple RichText fields (e.g.
53
+ * `core/table` cells: `body.0.cells.0.content`, etc.). Falls back to the
54
+ * block element when `attributeKey` is missing (WholeBlock selections,
55
+ * older senders, or DOM lookup miss).
56
+ *
57
+ * @param editorDocument - The editor document.
58
+ * @param resolvedSelection - The resolved selection.
59
+ * @return The target element (RichText editable or block), or null.
60
+ */
61
+ function resolveTargetElement(
62
+ editorDocument: Document,
63
+ resolvedSelection: ResolvedSelection
64
+ ): HTMLElement | null {
65
+ if ( ! resolvedSelection.localClientId ) {
66
+ return null;
67
+ }
68
+
69
+ const blockElement = editorDocument.querySelector< HTMLElement >(
70
+ `[data-block="${ resolvedSelection.localClientId }"]`
71
+ );
72
+
73
+ if ( ! blockElement || ! resolvedSelection.attributeKey ) {
74
+ return blockElement;
75
+ }
76
+
77
+ const attrKey = CSS.escape( resolvedSelection.attributeKey );
78
+ return (
79
+ blockElement.querySelector< HTMLElement >(
80
+ `[data-wp-block-attribute-key="${ attrKey }"]`
81
+ ) ?? blockElement
82
+ );
83
+ }
84
+
39
85
  /**
40
86
  * Compute cursor coords and optional selection rects for a single user's selection.
41
87
  *
@@ -83,14 +129,14 @@ function computeCursorOnly(
83
129
  if ( ! start.localClientId ) {
84
130
  return {};
85
131
  }
86
- const blockElement =
87
- overlayContext.editorDocument.querySelector< HTMLElement >(
88
- `[data-block="${ start.localClientId }"]`
89
- );
132
+ const targetElement = resolveTargetElement(
133
+ overlayContext.editorDocument,
134
+ start
135
+ );
90
136
  return {
91
137
  coords: getCursorPosition(
92
138
  start.richTextOffset,
93
- blockElement,
139
+ targetElement,
94
140
  overlayContext.editorDocument,
95
141
  overlayContext.overlayRect
96
142
  ),
@@ -157,10 +203,10 @@ function computeTextSelection(
157
203
  }
158
204
 
159
205
  // Fallback: cursor at start position only.
160
- const startBlock =
161
- overlayContext.editorDocument.querySelector< HTMLElement >(
162
- `[data-block="${ start.localClientId }"]`
163
- );
206
+ const startBlock = resolveTargetElement(
207
+ overlayContext.editorDocument,
208
+ start
209
+ );
164
210
 
165
211
  return {
166
212
  coords: getCursorPosition(
@@ -185,10 +231,10 @@ function computeSingleBlockRects(
185
231
  end: ResolvedSelection,
186
232
  overlayContext: OverlayContext
187
233
  ): SingleBlockResult {
188
- const blockElement =
189
- overlayContext.editorDocument.querySelector< HTMLElement >(
190
- `[data-block="${ start.localClientId }"]`
191
- );
234
+ const blockElement = resolveTargetElement(
235
+ overlayContext.editorDocument,
236
+ start
237
+ );
192
238
  if (
193
239
  ! blockElement ||
194
240
  start.richTextOffset === null ||
@@ -227,11 +273,13 @@ function computeMultiBlockRects(
227
273
  ): MultiBlockResult {
228
274
  let docFirst = start;
229
275
  let docLast = end;
230
- let firstBlock = overlayContext.editorDocument.querySelector< HTMLElement >(
231
- `[data-block="${ docFirst.localClientId }"]`
276
+ let firstBlock = resolveTargetElement(
277
+ overlayContext.editorDocument,
278
+ docFirst
232
279
  );
233
- let lastBlock = overlayContext.editorDocument.querySelector< HTMLElement >(
234
- `[data-block="${ docLast.localClientId }"]`
280
+ let lastBlock = resolveTargetElement(
281
+ overlayContext.editorDocument,
282
+ docLast
235
283
  );
236
284
 
237
285
  // Swap to document order if needed.
@@ -1,11 +1,14 @@
1
1
  /**
2
2
  * WordPress dependencies
3
3
  */
4
+ // @ts-expect-error No exported types
5
+ import { store as blockEditorStore } from '@wordpress/block-editor';
4
6
  import {
5
7
  privateApis as coreDataPrivateApis,
6
- SelectionType,
8
+ type CoreDataPrivateApis,
7
9
  type PostEditorAwarenessState as ActiveCollaborator,
8
10
  } from '@wordpress/core-data';
11
+ import { useSelect } from '@wordpress/data';
9
12
  import { useEffect, useRef, useState } from '@wordpress/element';
10
13
 
11
14
  /**
@@ -18,6 +21,10 @@ import { useDebouncedRecompute } from './use-debounced-recompute';
18
21
 
19
22
  const { useActiveCollaborators, useResolvedSelection } =
20
23
  unlock( coreDataPrivateApis );
24
+ const { SelectionType } = unlock( coreDataPrivateApis ) as Pick<
25
+ CoreDataPrivateApis,
26
+ 'SelectionType'
27
+ >;
21
28
 
22
29
  export interface BlockHighlightData {
23
30
  blockId: string;
@@ -67,6 +74,11 @@ export function useBlockHighlighting(
67
74
  const [ recomputeToken, rerenderHighlightsAfterDelay ] =
68
75
  useDebouncedRecompute( delayMs );
69
76
 
77
+ const blockClientIds = useSelect(
78
+ ( select ) => select( blockEditorStore ).getClientIdsWithDescendants(),
79
+ []
80
+ );
81
+
70
82
  // All DOM mutations and position computations live inside useEffect.
71
83
  useEffect( () => {
72
84
  if ( ! blockEditorDocument ) {
@@ -203,6 +215,7 @@ export function useBlockHighlighting(
203
215
  overlayElement,
204
216
  recomputeToken,
205
217
  resolveSelection,
218
+ blockClientIds,
206
219
  ] );
207
220
 
208
221
  return { highlights, rerenderHighlightsAfterDelay };
@@ -1,8 +1,8 @@
1
- import {
2
- privateApis as coreDataPrivateApis,
3
- SelectionType,
4
- } from '@wordpress/core-data';
1
+ // @ts-expect-error No exported types
2
+ import { store as blockEditorStore } from '@wordpress/block-editor';
3
+ import { privateApis as coreDataPrivateApis } from '@wordpress/core-data';
5
4
  import type {
5
+ CoreDataPrivateApis,
6
6
  ResolvedSelection,
7
7
  PostEditorAwarenessState as ActiveCollaborator,
8
8
  } from '@wordpress/core-data';
@@ -19,6 +19,10 @@ import type { SelectionRect } from './cursor-dom-utils';
19
19
 
20
20
  const { useActiveCollaborators, useResolvedSelection } =
21
21
  unlock( coreDataPrivateApis );
22
+ const { SelectionType } = unlock( coreDataPrivateApis ) as Pick<
23
+ CoreDataPrivateApis,
24
+ 'SelectionType'
25
+ >;
22
26
 
23
27
  export type { SelectionRect };
24
28
 
@@ -66,6 +70,11 @@ export function useRenderCursors(
66
70
  []
67
71
  );
68
72
 
73
+ const blockClientIds = useSelect(
74
+ ( select ) => select( blockEditorStore ).getClientIdsWithDescendants(),
75
+ []
76
+ );
77
+
69
78
  const [ cursorPositions, setCursorPositions ] = useState< CursorData[] >(
70
79
  []
71
80
  );
@@ -106,6 +115,7 @@ export function useRenderCursors(
106
115
  let start: ResolvedSelection = {
107
116
  richTextOffset: null,
108
117
  localClientId: null,
118
+ attributeKey: null,
109
119
  };
110
120
  let end: ResolvedSelection | undefined;
111
121
 
@@ -176,6 +186,7 @@ export function useRenderCursors(
176
186
  sortedUsers,
177
187
  showOwnCursor,
178
188
  recomputeToken,
189
+ blockClientIds,
179
190
  ] );
180
191
 
181
192
  return { cursors: cursorPositions, rerenderCursorsAfterDelay };