@wordpress/editor 14.41.0 → 14.41.2-next.v.202603161435.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.
- package/CHANGELOG.md +6 -0
- package/build/components/collab-sidebar/index.cjs +7 -4
- package/build/components/collab-sidebar/index.cjs.map +2 -2
- package/build/components/collab-sidebar/utils.cjs +13 -15
- package/build/components/collab-sidebar/utils.cjs.map +2 -2
- package/build/components/collaborators-overlay/avatar-iframe-styles.cjs +141 -0
- package/build/components/collaborators-overlay/avatar-iframe-styles.cjs.map +7 -0
- package/build/components/collaborators-overlay/collaborator-styles.cjs +38 -2
- package/build/components/collaborators-overlay/collaborator-styles.cjs.map +2 -2
- package/build/components/collaborators-overlay/compute-selection.cjs +181 -0
- package/build/components/collaborators-overlay/compute-selection.cjs.map +7 -0
- package/build/components/collaborators-overlay/cursor-dom-utils.cjs +243 -0
- package/build/components/collaborators-overlay/cursor-dom-utils.cjs.map +7 -0
- package/build/components/collaborators-overlay/overlay-iframe-styles.cjs +148 -0
- package/build/components/collaborators-overlay/overlay-iframe-styles.cjs.map +7 -0
- package/build/components/collaborators-overlay/overlay.cjs +100 -229
- package/build/components/collaborators-overlay/overlay.cjs.map +3 -3
- package/build/components/collaborators-overlay/use-block-highlighting.cjs +90 -42
- package/build/components/collaborators-overlay/use-block-highlighting.cjs.map +2 -2
- package/build/components/collaborators-overlay/use-debounced-recompute.cjs +49 -0
- package/build/components/collaborators-overlay/use-debounced-recompute.cjs.map +7 -0
- package/build/components/collaborators-overlay/use-render-cursors.cjs +80 -171
- package/build/components/collaborators-overlay/use-render-cursors.cjs.map +3 -3
- package/build/components/collaborators-presence/avatar/component.cjs +121 -0
- package/build/components/collaborators-presence/avatar/component.cjs.map +7 -0
- package/build/components/collaborators-presence/avatar/index.cjs +37 -0
- package/build/components/collaborators-presence/avatar/index.cjs.map +7 -0
- package/build/components/collaborators-presence/avatar/types.cjs +19 -0
- package/build/components/collaborators-presence/avatar/types.cjs.map +7 -0
- package/build/components/collaborators-presence/avatar/use-image-loading-status.cjs +44 -0
- package/build/components/collaborators-presence/avatar/use-image-loading-status.cjs.map +7 -0
- package/build/components/collaborators-presence/avatar-group/component.cjs +78 -0
- package/build/components/collaborators-presence/avatar-group/component.cjs.map +7 -0
- package/build/components/collaborators-presence/avatar-group/index.cjs +37 -0
- package/build/components/collaborators-presence/avatar-group/index.cjs.map +7 -0
- package/build/components/collaborators-presence/avatar-group/types.cjs +19 -0
- package/build/components/collaborators-presence/avatar-group/types.cjs.map +7 -0
- package/build/components/collaborators-presence/index.cjs +54 -17
- package/build/components/collaborators-presence/index.cjs.map +3 -3
- package/build/components/collaborators-presence/list.cjs +43 -37
- package/build/components/collaborators-presence/list.cjs.map +3 -3
- package/build/components/collaborators-presence/use-collaborator-notifications.cjs +79 -107
- package/build/components/collaborators-presence/use-collaborator-notifications.cjs.map +3 -3
- package/build/components/editor-interface/index.cjs +9 -6
- package/build/components/editor-interface/index.cjs.map +2 -2
- package/build/components/entities-saved-states/hooks/use-is-dirty.cjs +14 -5
- package/build/components/entities-saved-states/hooks/use-is-dirty.cjs.map +2 -2
- package/build/components/global-styles/index.cjs +15 -24
- package/build/components/global-styles/index.cjs.map +3 -3
- package/build/components/global-styles-sidebar/index.cjs +6 -3
- package/build/components/global-styles-sidebar/index.cjs.map +2 -2
- package/build/components/inserter-sidebar/index.cjs +2 -1
- package/build/components/inserter-sidebar/index.cjs.map +2 -2
- package/build/components/page-attributes/parent.cjs +2 -1
- package/build/components/page-attributes/parent.cjs.map +2 -2
- package/build/components/post-revisions-preview/revisions-canvas.cjs +8 -58
- package/build/components/post-revisions-preview/revisions-canvas.cjs.map +3 -3
- package/build/components/post-revisions-preview/revisions-slider.cjs +5 -1
- package/build/components/post-revisions-preview/revisions-slider.cjs.map +2 -2
- package/build/components/post-url/panel.cjs +1 -0
- package/build/components/post-url/panel.cjs.map +2 -2
- package/build/components/preferences-modal/index.cjs +24 -0
- package/build/components/preferences-modal/index.cjs.map +2 -2
- package/build/components/provider/disable-non-page-content-blocks.cjs +31 -28
- package/build/components/provider/disable-non-page-content-blocks.cjs.map +3 -3
- package/build/components/provider/index.cjs +17 -5
- package/build/components/provider/index.cjs.map +2 -2
- package/build/components/provider/use-block-editor-settings.cjs +21 -6
- package/build/components/provider/use-block-editor-settings.cjs.map +3 -3
- package/build/components/provider/{use-post-content-blocks.cjs → use-post-content-block-types.cjs} +8 -19
- package/build/components/provider/use-post-content-block-types.cjs.map +7 -0
- package/build/components/provider/use-revision-blocks.cjs +106 -0
- package/build/components/provider/use-revision-blocks.cjs.map +7 -0
- package/build/components/sidebar/dataform-post-summary.cjs +182 -0
- package/build/components/sidebar/dataform-post-summary.cjs.map +7 -0
- package/build/components/sidebar/header.cjs +1 -1
- package/build/components/sidebar/header.cjs.map +2 -2
- package/build/components/sidebar/post-summary.cjs +11 -0
- package/build/components/sidebar/post-summary.cjs.map +3 -3
- package/build/components/sync-connection-modal/index.cjs +8 -5
- package/build/components/sync-connection-modal/index.cjs.map +2 -2
- package/build/components/template-content-panel/index.cjs +35 -31
- package/build/components/template-content-panel/index.cjs.map +3 -3
- package/build/components/visual-editor/index.cjs +1 -1
- package/build/components/visual-editor/index.cjs.map +2 -2
- package/build/dataviews/store/private-actions.cjs +4 -0
- package/build/dataviews/store/private-actions.cjs.map +2 -2
- package/build/store/private-actions.cjs +11 -2
- package/build/store/private-actions.cjs.map +2 -2
- package/build/store/private-selectors.cjs +52 -13
- package/build/store/private-selectors.cjs.map +2 -2
- package/build/store/reducer.cjs +12 -0
- package/build/store/reducer.cjs.map +2 -2
- package/build/utils/media-upload/on-success.cjs +46 -0
- package/build/utils/media-upload/on-success.cjs.map +7 -0
- package/build-module/components/collab-sidebar/index.mjs +7 -4
- package/build-module/components/collab-sidebar/index.mjs.map +2 -2
- package/build-module/components/collab-sidebar/utils.mjs +13 -15
- package/build-module/components/collab-sidebar/utils.mjs.map +2 -2
- package/build-module/components/collaborators-overlay/avatar-iframe-styles.mjs +128 -0
- package/build-module/components/collaborators-overlay/avatar-iframe-styles.mjs.map +7 -0
- package/build-module/components/collaborators-overlay/collaborator-styles.mjs +25 -1
- package/build-module/components/collaborators-overlay/collaborator-styles.mjs.map +2 -2
- package/build-module/components/collaborators-overlay/compute-selection.mjs +162 -0
- package/build-module/components/collaborators-overlay/compute-selection.mjs.map +7 -0
- package/build-module/components/collaborators-overlay/cursor-dom-utils.mjs +213 -0
- package/build-module/components/collaborators-overlay/cursor-dom-utils.mjs.map +7 -0
- package/build-module/components/collaborators-overlay/overlay-iframe-styles.mjs +130 -0
- package/build-module/components/collaborators-overlay/overlay-iframe-styles.mjs.map +7 -0
- package/build-module/components/collaborators-overlay/overlay.mjs +91 -230
- package/build-module/components/collaborators-overlay/overlay.mjs.map +2 -2
- package/build-module/components/collaborators-overlay/use-block-highlighting.mjs +91 -43
- package/build-module/components/collaborators-overlay/use-block-highlighting.mjs.map +2 -2
- package/build-module/components/collaborators-overlay/use-debounced-recompute.mjs +24 -0
- package/build-module/components/collaborators-overlay/use-debounced-recompute.mjs.map +7 -0
- package/build-module/components/collaborators-overlay/use-render-cursors.mjs +81 -172
- package/build-module/components/collaborators-overlay/use-render-cursors.mjs.map +2 -2
- package/build-module/components/collaborators-presence/avatar/component.mjs +90 -0
- package/build-module/components/collaborators-presence/avatar/component.mjs.map +7 -0
- package/build-module/components/collaborators-presence/avatar/index.mjs +6 -0
- package/build-module/components/collaborators-presence/avatar/index.mjs.map +7 -0
- package/build-module/components/collaborators-presence/avatar/types.mjs +1 -0
- package/build-module/components/collaborators-presence/avatar/types.mjs.map +7 -0
- package/build-module/components/collaborators-presence/avatar/use-image-loading-status.mjs +19 -0
- package/build-module/components/collaborators-presence/avatar/use-image-loading-status.mjs.map +7 -0
- package/build-module/components/collaborators-presence/avatar-group/component.mjs +47 -0
- package/build-module/components/collaborators-presence/avatar-group/component.mjs.map +7 -0
- package/build-module/components/collaborators-presence/avatar-group/index.mjs +6 -0
- package/build-module/components/collaborators-presence/avatar-group/index.mjs.map +7 -0
- package/build-module/components/collaborators-presence/avatar-group/types.mjs +1 -0
- package/build-module/components/collaborators-presence/avatar-group/types.mjs.map +7 -0
- package/build-module/components/collaborators-presence/index.mjs +45 -21
- package/build-module/components/collaborators-presence/index.mjs.map +2 -2
- package/build-module/components/collaborators-presence/list.mjs +35 -43
- package/build-module/components/collaborators-presence/list.mjs.map +2 -2
- package/build-module/components/collaborators-presence/use-collaborator-notifications.mjs +80 -108
- package/build-module/components/collaborators-presence/use-collaborator-notifications.mjs.map +2 -2
- package/build-module/components/editor-interface/index.mjs +10 -7
- package/build-module/components/editor-interface/index.mjs.map +2 -2
- package/build-module/components/entities-saved-states/hooks/use-is-dirty.mjs +14 -5
- package/build-module/components/entities-saved-states/hooks/use-is-dirty.mjs.map +2 -2
- package/build-module/components/global-styles/index.mjs +15 -24
- package/build-module/components/global-styles/index.mjs.map +2 -2
- package/build-module/components/global-styles-sidebar/index.mjs +6 -3
- package/build-module/components/global-styles-sidebar/index.mjs.map +2 -2
- package/build-module/components/inserter-sidebar/index.mjs +2 -1
- package/build-module/components/inserter-sidebar/index.mjs.map +2 -2
- package/build-module/components/page-attributes/parent.mjs +2 -1
- package/build-module/components/page-attributes/parent.mjs.map +2 -2
- package/build-module/components/post-revisions-preview/revisions-canvas.mjs +10 -63
- package/build-module/components/post-revisions-preview/revisions-canvas.mjs.map +2 -2
- package/build-module/components/post-revisions-preview/revisions-slider.mjs +5 -1
- package/build-module/components/post-revisions-preview/revisions-slider.mjs.map +2 -2
- package/build-module/components/post-url/panel.mjs +1 -0
- package/build-module/components/post-url/panel.mjs.map +2 -2
- package/build-module/components/preferences-modal/index.mjs +24 -0
- package/build-module/components/preferences-modal/index.mjs.map +2 -2
- package/build-module/components/provider/disable-non-page-content-blocks.mjs +31 -28
- package/build-module/components/provider/disable-non-page-content-blocks.mjs.map +2 -2
- package/build-module/components/provider/index.mjs +17 -5
- package/build-module/components/provider/index.mjs.map +2 -2
- package/build-module/components/provider/use-block-editor-settings.mjs +21 -6
- package/build-module/components/provider/use-block-editor-settings.mjs.map +2 -2
- package/build-module/components/provider/use-post-content-block-types.mjs +23 -0
- package/build-module/components/provider/use-post-content-block-types.mjs.map +7 -0
- package/build-module/components/provider/use-revision-blocks.mjs +81 -0
- package/build-module/components/provider/use-revision-blocks.mjs.map +7 -0
- package/build-module/components/sidebar/dataform-post-summary.mjs +151 -0
- package/build-module/components/sidebar/dataform-post-summary.mjs.map +7 -0
- package/build-module/components/sidebar/header.mjs +1 -1
- package/build-module/components/sidebar/header.mjs.map +2 -2
- package/build-module/components/sidebar/post-summary.mjs +11 -0
- package/build-module/components/sidebar/post-summary.mjs.map +2 -2
- package/build-module/components/sync-connection-modal/index.mjs +8 -5
- package/build-module/components/sync-connection-modal/index.mjs.map +2 -2
- package/build-module/components/template-content-panel/index.mjs +25 -31
- package/build-module/components/template-content-panel/index.mjs.map +2 -2
- package/build-module/components/visual-editor/index.mjs +1 -1
- package/build-module/components/visual-editor/index.mjs.map +2 -2
- package/build-module/dataviews/store/private-actions.mjs +8 -1
- package/build-module/dataviews/store/private-actions.mjs.map +2 -2
- package/build-module/store/private-actions.mjs +10 -2
- package/build-module/store/private-actions.mjs.map +2 -2
- package/build-module/store/private-selectors.mjs +50 -12
- package/build-module/store/private-selectors.mjs.map +2 -2
- package/build-module/store/reducer.mjs +11 -0
- package/build-module/store/reducer.mjs.map +2 -2
- package/build-module/utils/media-upload/on-success.mjs +25 -0
- package/build-module/utils/media-upload/on-success.mjs.map +7 -0
- package/build-style/style-rtl.css +961 -159
- package/build-style/style.css +961 -159
- package/build-types/components/collab-sidebar/index.d.ts.map +1 -1
- package/build-types/components/collab-sidebar/utils.d.ts.map +1 -1
- package/build-types/components/collaborators-overlay/avatar-iframe-styles.d.ts +11 -0
- package/build-types/components/collaborators-overlay/avatar-iframe-styles.d.ts.map +1 -0
- package/build-types/components/collaborators-overlay/collaborator-styles.d.ts +17 -2
- package/build-types/components/collaborators-overlay/collaborator-styles.d.ts.map +1 -1
- package/build-types/components/collaborators-overlay/compute-selection.d.ts +24 -0
- package/build-types/components/collaborators-overlay/compute-selection.d.ts.map +1 -0
- package/build-types/components/collaborators-overlay/cursor-dom-utils.d.ts +72 -0
- package/build-types/components/collaborators-overlay/cursor-dom-utils.d.ts.map +1 -0
- package/build-types/components/collaborators-overlay/overlay-iframe-styles.d.ts +6 -0
- package/build-types/components/collaborators-overlay/overlay-iframe-styles.d.ts.map +1 -0
- package/build-types/components/collaborators-overlay/overlay.d.ts.map +1 -1
- package/build-types/components/collaborators-overlay/use-block-highlighting.d.ts +21 -5
- package/build-types/components/collaborators-overlay/use-block-highlighting.d.ts.map +1 -1
- package/build-types/components/collaborators-overlay/use-debounced-recompute.d.ts +10 -0
- package/build-types/components/collaborators-overlay/use-debounced-recompute.d.ts.map +1 -0
- package/build-types/components/collaborators-overlay/use-render-cursors.d.ts +6 -1
- package/build-types/components/collaborators-overlay/use-render-cursors.d.ts.map +1 -1
- package/build-types/components/collaborators-presence/avatar/component.d.ts +7 -0
- package/build-types/components/collaborators-presence/avatar/component.d.ts.map +1 -0
- package/build-types/components/collaborators-presence/avatar/index.d.ts +3 -0
- package/build-types/components/collaborators-presence/avatar/index.d.ts.map +1 -0
- package/build-types/components/collaborators-presence/avatar/types.d.ts +66 -0
- package/build-types/components/collaborators-presence/avatar/types.d.ts.map +1 -0
- package/build-types/components/collaborators-presence/avatar/use-image-loading-status.d.ts +17 -0
- package/build-types/components/collaborators-presence/avatar/use-image-loading-status.d.ts.map +1 -0
- package/build-types/components/collaborators-presence/avatar-group/component.d.ts +7 -0
- package/build-types/components/collaborators-presence/avatar-group/component.d.ts.map +1 -0
- package/build-types/components/collaborators-presence/avatar-group/index.d.ts +3 -0
- package/build-types/components/collaborators-presence/avatar-group/index.d.ts.map +1 -0
- package/build-types/components/collaborators-presence/avatar-group/types.d.ts +14 -0
- package/build-types/components/collaborators-presence/avatar-group/types.d.ts.map +1 -0
- package/build-types/components/collaborators-presence/index.d.ts.map +1 -1
- package/build-types/components/collaborators-presence/list.d.ts +2 -1
- package/build-types/components/collaborators-presence/list.d.ts.map +1 -1
- package/build-types/components/collaborators-presence/use-collaborator-notifications.d.ts.map +1 -1
- package/build-types/components/editor-interface/index.d.ts.map +1 -1
- package/build-types/components/entities-saved-states/hooks/use-is-dirty.d.ts.map +1 -1
- package/build-types/components/global-styles/index.d.ts +2 -1
- package/build-types/components/global-styles/index.d.ts.map +1 -1
- package/build-types/components/global-styles-sidebar/index.d.ts.map +1 -1
- package/build-types/components/inserter-sidebar/index.d.ts.map +1 -1
- package/build-types/components/page-attributes/parent.d.ts.map +1 -1
- package/build-types/components/post-author/hook.d.ts +1 -1
- package/build-types/components/post-revisions-preview/revisions-canvas.d.ts +2 -5
- package/build-types/components/post-revisions-preview/revisions-canvas.d.ts.map +1 -1
- package/build-types/components/post-revisions-preview/revisions-slider.d.ts.map +1 -1
- package/build-types/components/provider/disable-non-page-content-blocks.d.ts.map +1 -1
- package/build-types/components/provider/index.d.ts.map +1 -1
- package/build-types/components/provider/use-block-editor-settings.d.ts.map +1 -1
- package/build-types/components/provider/use-post-content-block-types.d.ts +9 -0
- package/build-types/components/provider/use-post-content-block-types.d.ts.map +1 -0
- package/build-types/components/provider/use-revision-blocks.d.ts +10 -0
- package/build-types/components/provider/use-revision-blocks.d.ts.map +1 -0
- package/build-types/components/sidebar/dataform-post-summary.d.ts +4 -0
- package/build-types/components/sidebar/dataform-post-summary.d.ts.map +1 -0
- package/build-types/components/sidebar/post-summary.d.ts.map +1 -1
- package/build-types/components/sync-connection-modal/index.d.ts.map +1 -1
- package/build-types/components/template-content-panel/index.d.ts.map +1 -1
- package/build-types/dataviews/store/private-actions.d.ts.map +1 -1
- package/build-types/store/private-actions.d.ts +7 -0
- package/build-types/store/private-actions.d.ts.map +1 -1
- package/build-types/store/private-selectors.d.ts +7 -0
- package/build-types/store/private-selectors.d.ts.map +1 -1
- package/build-types/store/reducer.d.ts +14 -3
- package/build-types/store/reducer.d.ts.map +1 -1
- package/build-types/utils/media-upload/on-success.d.ts +9 -0
- package/build-types/utils/media-upload/on-success.d.ts.map +1 -0
- package/package.json +45 -44
- package/src/components/collab-sidebar/index.js +7 -4
- package/src/components/collab-sidebar/utils.js +9 -10
- package/src/components/collaborators-overlay/avatar-iframe-styles.ts +134 -0
- package/src/components/collaborators-overlay/collaborator-styles.ts +43 -2
- package/src/components/collaborators-overlay/compute-selection.ts +307 -0
- package/src/components/collaborators-overlay/cursor-dom-utils.ts +382 -0
- package/src/components/collaborators-overlay/overlay-iframe-styles.ts +131 -0
- package/src/components/collaborators-overlay/overlay.tsx +86 -226
- package/src/components/collaborators-overlay/use-block-highlighting.ts +147 -63
- package/src/components/collaborators-overlay/use-debounced-recompute.ts +32 -0
- package/src/components/collaborators-overlay/use-render-cursors.ts +113 -279
- package/src/components/collaborators-presence/avatar/component.tsx +123 -0
- package/src/components/collaborators-presence/avatar/index.ts +2 -0
- package/src/components/collaborators-presence/avatar/styles.scss +184 -0
- package/src/components/collaborators-presence/avatar/test/index.tsx +389 -0
- package/src/components/collaborators-presence/avatar/types.ts +66 -0
- package/src/components/collaborators-presence/avatar/use-image-loading-status.ts +36 -0
- package/src/components/collaborators-presence/avatar-group/component.tsx +55 -0
- package/src/components/collaborators-presence/avatar-group/index.ts +2 -0
- package/src/components/collaborators-presence/avatar-group/styles.scss +33 -0
- package/src/components/collaborators-presence/avatar-group/test/index.tsx +139 -0
- package/src/components/collaborators-presence/avatar-group/types.ts +13 -0
- package/src/components/collaborators-presence/index.tsx +34 -11
- package/src/components/collaborators-presence/list.tsx +44 -40
- package/src/components/collaborators-presence/styles/collaborators-list.scss +26 -19
- package/src/components/collaborators-presence/styles/collaborators-presence.scss +6 -2
- package/src/components/collaborators-presence/test/use-collaborator-notifications.ts +188 -246
- package/src/components/collaborators-presence/use-collaborator-notifications.ts +109 -166
- package/src/components/document-bar/style.scss +1 -1
- package/src/components/editor-interface/index.js +8 -6
- package/src/components/entities-saved-states/hooks/use-is-dirty.js +14 -5
- package/src/components/global-styles/index.js +20 -27
- package/src/components/global-styles-sidebar/index.js +3 -0
- package/src/components/inserter-sidebar/index.js +4 -1
- package/src/components/page-attributes/parent.js +2 -1
- package/src/components/post-publish-panel/test/__snapshots__/index.js.snap +2 -2
- package/src/components/post-revisions-preview/revisions-canvas.js +15 -84
- package/src/components/post-revisions-preview/revisions-slider.js +6 -1
- package/src/components/post-url/panel.js +1 -0
- package/src/components/post-url/style.scss +5 -0
- package/src/components/preferences-modal/index.js +18 -0
- package/src/components/provider/disable-non-page-content-blocks.js +42 -40
- package/src/components/provider/index.js +20 -2
- package/src/components/provider/use-block-editor-settings.js +24 -8
- package/src/components/provider/use-post-content-block-types.js +30 -0
- package/src/components/provider/use-revision-blocks.js +105 -0
- package/src/components/sidebar/dataform-post-summary.js +170 -0
- package/src/components/sidebar/header.js +1 -1
- package/src/components/sidebar/post-summary.js +15 -0
- package/src/components/sync-connection-modal/index.js +12 -6
- package/src/components/sync-connection-modal/style.scss +5 -0
- package/src/components/template-content-panel/index.js +30 -38
- package/src/components/visual-editor/index.js +1 -1
- package/src/dataviews/store/private-actions.ts +14 -0
- package/src/store/private-actions.js +21 -2
- package/src/store/private-selectors.js +75 -10
- package/src/store/reducer.js +19 -0
- package/src/style.scss +3 -0
- package/src/utils/media-upload/on-success.js +34 -0
- package/build/components/provider/use-post-content-blocks.cjs.map +0 -7
- package/build-module/components/provider/use-post-content-blocks.mjs +0 -34
- package/build-module/components/provider/use-post-content-blocks.mjs.map +0 -7
- package/build-types/components/provider/use-post-content-blocks.d.ts +0 -2
- package/build-types/components/provider/use-post-content-blocks.d.ts.map +0 -1
- package/src/components/provider/use-post-content-blocks.js +0 -42
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
import { SelectionDirection, SelectionType } from '@wordpress/core-data';
|
|
2
|
+
import type { ResolvedSelection } from '@wordpress/core-data';
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
getCursorPosition,
|
|
6
|
+
getSelectionRects,
|
|
7
|
+
getFullBlockSelectionRects,
|
|
8
|
+
getBlocksBetween,
|
|
9
|
+
isNodeBefore,
|
|
10
|
+
} from './cursor-dom-utils';
|
|
11
|
+
import type { CursorCoords, SelectionRect } from './cursor-dom-utils';
|
|
12
|
+
|
|
13
|
+
/** Common parameters passed to cursor/selection computation helpers. */
|
|
14
|
+
interface OverlayContext {
|
|
15
|
+
editorDocument: Document;
|
|
16
|
+
overlayRect: DOMRect;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** Selection rects and the resolved block element for a single-block selection. */
|
|
20
|
+
interface SingleBlockResult {
|
|
21
|
+
rects: SelectionRect[];
|
|
22
|
+
blockElement: HTMLElement | null;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Selection rects and the resolved block elements for a multi-block selection. */
|
|
26
|
+
interface MultiBlockResult {
|
|
27
|
+
rects: SelectionRect[];
|
|
28
|
+
firstBlock: HTMLElement | null;
|
|
29
|
+
lastBlock: HTMLElement | null;
|
|
30
|
+
firstBlockClientId: string | null;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Result of computing visual cursor/selection state for a single user. */
|
|
34
|
+
export interface SelectionVisual {
|
|
35
|
+
coords?: CursorCoords | null;
|
|
36
|
+
selectionRects?: SelectionRect[];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Compute cursor coords and optional selection rects for a single user's selection.
|
|
41
|
+
*
|
|
42
|
+
* @param selection - The selection state from the awareness layer.
|
|
43
|
+
* @param start - Start position (block clientId + text index).
|
|
44
|
+
* @param end - End position (only for range selections).
|
|
45
|
+
* @param overlayContext - Shared editor document / overlay references.
|
|
46
|
+
* @return Cursor coordinates and optional selection rectangles.
|
|
47
|
+
*/
|
|
48
|
+
export function computeSelectionVisual(
|
|
49
|
+
selection: any,
|
|
50
|
+
start: ResolvedSelection,
|
|
51
|
+
end: ResolvedSelection | undefined,
|
|
52
|
+
overlayContext: OverlayContext
|
|
53
|
+
): SelectionVisual {
|
|
54
|
+
if (
|
|
55
|
+
selection.type === SelectionType.None ||
|
|
56
|
+
selection.type === SelectionType.WholeBlock
|
|
57
|
+
) {
|
|
58
|
+
return {};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if ( selection.type === SelectionType.Cursor ) {
|
|
62
|
+
return computeCursorOnly( start, overlayContext );
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// SelectionInOneBlock or SelectionInMultipleBlocks.
|
|
66
|
+
if ( ! end ) {
|
|
67
|
+
return {};
|
|
68
|
+
}
|
|
69
|
+
return computeTextSelection( selection, start, end, overlayContext );
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Compute cursor coordinates for a simple cursor (no highlighted text).
|
|
74
|
+
*
|
|
75
|
+
* @param start - Cursor position (block clientId + text index).
|
|
76
|
+
* @param overlayContext - Shared editor document / overlay references.
|
|
77
|
+
* @return Cursor coordinates.
|
|
78
|
+
*/
|
|
79
|
+
function computeCursorOnly(
|
|
80
|
+
start: ResolvedSelection,
|
|
81
|
+
overlayContext: OverlayContext
|
|
82
|
+
): SelectionVisual {
|
|
83
|
+
if ( ! start.localClientId ) {
|
|
84
|
+
return {};
|
|
85
|
+
}
|
|
86
|
+
const blockElement =
|
|
87
|
+
overlayContext.editorDocument.querySelector< HTMLElement >(
|
|
88
|
+
`[data-block="${ start.localClientId }"]`
|
|
89
|
+
);
|
|
90
|
+
return {
|
|
91
|
+
coords: getCursorPosition(
|
|
92
|
+
start.textIndex,
|
|
93
|
+
blockElement,
|
|
94
|
+
overlayContext.editorDocument,
|
|
95
|
+
overlayContext.overlayRect
|
|
96
|
+
),
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Compute cursor coordinates and selection highlight rects for a text selection
|
|
102
|
+
* (single-block or multi-block).
|
|
103
|
+
*
|
|
104
|
+
* @param selection - The selection state.
|
|
105
|
+
* @param start - Start position (block clientId + text index).
|
|
106
|
+
* @param end - End position (block clientId + text index).
|
|
107
|
+
* @param overlayContext - Shared editor document / overlay references.
|
|
108
|
+
* @return Cursor coordinates and optional selection rectangles.
|
|
109
|
+
*/
|
|
110
|
+
function computeTextSelection(
|
|
111
|
+
selection: any,
|
|
112
|
+
start: ResolvedSelection,
|
|
113
|
+
end: ResolvedSelection,
|
|
114
|
+
overlayContext: OverlayContext
|
|
115
|
+
): SelectionVisual {
|
|
116
|
+
if (
|
|
117
|
+
! start.localClientId ||
|
|
118
|
+
! end.localClientId ||
|
|
119
|
+
start.textIndex === null ||
|
|
120
|
+
end.textIndex === null
|
|
121
|
+
) {
|
|
122
|
+
return {};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const isReverse =
|
|
126
|
+
selection.selectionDirection === SelectionDirection.Backward;
|
|
127
|
+
const activeEnd = isReverse ? start : end;
|
|
128
|
+
|
|
129
|
+
let allRects: SelectionRect[];
|
|
130
|
+
let activeEndBlock: HTMLElement | null = null;
|
|
131
|
+
|
|
132
|
+
if ( selection.type === SelectionType.SelectionInOneBlock ) {
|
|
133
|
+
const result = computeSingleBlockRects( start, end, overlayContext );
|
|
134
|
+
allRects = result.rects;
|
|
135
|
+
// Single block: start and end share the same block element.
|
|
136
|
+
activeEndBlock = result.blockElement;
|
|
137
|
+
} else {
|
|
138
|
+
const result = computeMultiBlockRects( start, end, overlayContext );
|
|
139
|
+
allRects = result.rects;
|
|
140
|
+
// Pick the block element that matches the active end.
|
|
141
|
+
activeEndBlock =
|
|
142
|
+
activeEnd.localClientId === result.firstBlockClientId
|
|
143
|
+
? result.firstBlock
|
|
144
|
+
: result.lastBlock;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if ( allRects.length > 0 ) {
|
|
148
|
+
return {
|
|
149
|
+
coords: getCursorPosition(
|
|
150
|
+
activeEnd.textIndex,
|
|
151
|
+
activeEndBlock,
|
|
152
|
+
overlayContext.editorDocument,
|
|
153
|
+
overlayContext.overlayRect
|
|
154
|
+
),
|
|
155
|
+
selectionRects: allRects,
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Fallback: cursor at start position only.
|
|
160
|
+
const startBlock =
|
|
161
|
+
overlayContext.editorDocument.querySelector< HTMLElement >(
|
|
162
|
+
`[data-block="${ start.localClientId }"]`
|
|
163
|
+
);
|
|
164
|
+
|
|
165
|
+
return {
|
|
166
|
+
coords: getCursorPosition(
|
|
167
|
+
start.textIndex,
|
|
168
|
+
startBlock,
|
|
169
|
+
overlayContext.editorDocument,
|
|
170
|
+
overlayContext.overlayRect
|
|
171
|
+
),
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Compute selection rects for a selection within a single block.
|
|
177
|
+
*
|
|
178
|
+
* @param start - Start position (block clientId + text index).
|
|
179
|
+
* @param end - End position (block clientId + text index).
|
|
180
|
+
* @param overlayContext - Shared editor document / overlay references.
|
|
181
|
+
* @return Array of selection rectangles.
|
|
182
|
+
*/
|
|
183
|
+
function computeSingleBlockRects(
|
|
184
|
+
start: ResolvedSelection,
|
|
185
|
+
end: ResolvedSelection,
|
|
186
|
+
overlayContext: OverlayContext
|
|
187
|
+
): SingleBlockResult {
|
|
188
|
+
const blockElement =
|
|
189
|
+
overlayContext.editorDocument.querySelector< HTMLElement >(
|
|
190
|
+
`[data-block="${ start.localClientId }"]`
|
|
191
|
+
);
|
|
192
|
+
if (
|
|
193
|
+
! blockElement ||
|
|
194
|
+
start.textIndex === null ||
|
|
195
|
+
end.textIndex === null
|
|
196
|
+
) {
|
|
197
|
+
return { rects: [], blockElement: null };
|
|
198
|
+
}
|
|
199
|
+
return {
|
|
200
|
+
rects:
|
|
201
|
+
getSelectionRects(
|
|
202
|
+
blockElement,
|
|
203
|
+
start.textIndex,
|
|
204
|
+
end.textIndex,
|
|
205
|
+
overlayContext.editorDocument,
|
|
206
|
+
overlayContext.overlayRect
|
|
207
|
+
) ?? [],
|
|
208
|
+
blockElement,
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Compute selection rects for a selection spanning multiple blocks.
|
|
214
|
+
*
|
|
215
|
+
* Normalizes to document order — for backward selections the block editor
|
|
216
|
+
* reports start after end.
|
|
217
|
+
*
|
|
218
|
+
* @param start - Start position (block clientId + text index).
|
|
219
|
+
* @param end - End position (block clientId + text index).
|
|
220
|
+
* @param overlayContext - Shared editor document / overlay references.
|
|
221
|
+
* @return Array of selection rectangles.
|
|
222
|
+
*/
|
|
223
|
+
function computeMultiBlockRects(
|
|
224
|
+
start: ResolvedSelection,
|
|
225
|
+
end: ResolvedSelection,
|
|
226
|
+
overlayContext: OverlayContext
|
|
227
|
+
): MultiBlockResult {
|
|
228
|
+
let docFirst = start;
|
|
229
|
+
let docLast = end;
|
|
230
|
+
let firstBlock = overlayContext.editorDocument.querySelector< HTMLElement >(
|
|
231
|
+
`[data-block="${ docFirst.localClientId }"]`
|
|
232
|
+
);
|
|
233
|
+
let lastBlock = overlayContext.editorDocument.querySelector< HTMLElement >(
|
|
234
|
+
`[data-block="${ docLast.localClientId }"]`
|
|
235
|
+
);
|
|
236
|
+
|
|
237
|
+
// Swap to document order if needed.
|
|
238
|
+
if ( firstBlock && lastBlock && isNodeBefore( lastBlock, firstBlock ) ) {
|
|
239
|
+
docFirst = end;
|
|
240
|
+
docLast = start;
|
|
241
|
+
[ firstBlock, lastBlock ] = [ lastBlock, firstBlock ];
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
if (
|
|
245
|
+
! firstBlock ||
|
|
246
|
+
! lastBlock ||
|
|
247
|
+
docFirst.textIndex === null ||
|
|
248
|
+
docLast.textIndex === null ||
|
|
249
|
+
! docFirst.localClientId ||
|
|
250
|
+
! docLast.localClientId
|
|
251
|
+
) {
|
|
252
|
+
return {
|
|
253
|
+
rects: [],
|
|
254
|
+
firstBlock: null,
|
|
255
|
+
lastBlock: null,
|
|
256
|
+
firstBlockClientId: null,
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
const allRects: SelectionRect[] = [];
|
|
261
|
+
|
|
262
|
+
// First block: from start offset to end of block.
|
|
263
|
+
const startRects = getSelectionRects(
|
|
264
|
+
firstBlock,
|
|
265
|
+
docFirst.textIndex,
|
|
266
|
+
Number.MAX_SAFE_INTEGER,
|
|
267
|
+
overlayContext.editorDocument,
|
|
268
|
+
overlayContext.overlayRect
|
|
269
|
+
);
|
|
270
|
+
if ( startRects ) {
|
|
271
|
+
allRects.push( ...startRects );
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// Intermediate blocks: full content.
|
|
275
|
+
const intermediateBlocks = getBlocksBetween(
|
|
276
|
+
docFirst.localClientId,
|
|
277
|
+
docLast.localClientId,
|
|
278
|
+
overlayContext.editorDocument
|
|
279
|
+
);
|
|
280
|
+
for ( const intermediateBlock of intermediateBlocks ) {
|
|
281
|
+
const rects = getFullBlockSelectionRects(
|
|
282
|
+
intermediateBlock,
|
|
283
|
+
overlayContext.editorDocument,
|
|
284
|
+
overlayContext.overlayRect
|
|
285
|
+
);
|
|
286
|
+
allRects.push( ...rects );
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// Last block: from 0 to end offset.
|
|
290
|
+
const endRects = getSelectionRects(
|
|
291
|
+
lastBlock,
|
|
292
|
+
0,
|
|
293
|
+
docLast.textIndex,
|
|
294
|
+
overlayContext.editorDocument,
|
|
295
|
+
overlayContext.overlayRect
|
|
296
|
+
);
|
|
297
|
+
if ( endRects ) {
|
|
298
|
+
allRects.push( ...endRects );
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
return {
|
|
302
|
+
rects: allRects,
|
|
303
|
+
firstBlock,
|
|
304
|
+
lastBlock,
|
|
305
|
+
firstBlockClientId: docFirst.localClientId,
|
|
306
|
+
};
|
|
307
|
+
}
|
|
@@ -0,0 +1,382 @@
|
|
|
1
|
+
export interface SelectionRect {
|
|
2
|
+
x: number;
|
|
3
|
+
y: number;
|
|
4
|
+
width: number;
|
|
5
|
+
height: number;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface CursorCoords {
|
|
9
|
+
x: number;
|
|
10
|
+
y: number;
|
|
11
|
+
height: number;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const MAX_NODE_OFFSET_COUNT = 500;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Given a selection, returns the coordinates of the cursor in the block.
|
|
18
|
+
*
|
|
19
|
+
* @param absolutePositionIndex - The absolute position index
|
|
20
|
+
* @param blockElement - The block element (or null if deleted)
|
|
21
|
+
* @param editorDocument - The editor document
|
|
22
|
+
* @param overlayRect - Pre-computed bounding rect of the overlay element
|
|
23
|
+
* @return The position of the cursor
|
|
24
|
+
*/
|
|
25
|
+
export const getCursorPosition = (
|
|
26
|
+
absolutePositionIndex: number | null,
|
|
27
|
+
blockElement: HTMLElement | null,
|
|
28
|
+
editorDocument: Document,
|
|
29
|
+
overlayRect: DOMRect
|
|
30
|
+
): CursorCoords | null => {
|
|
31
|
+
if ( absolutePositionIndex === null || ! blockElement ) {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
getOffsetPositionInBlock(
|
|
37
|
+
blockElement,
|
|
38
|
+
absolutePositionIndex,
|
|
39
|
+
editorDocument,
|
|
40
|
+
overlayRect
|
|
41
|
+
) ?? null
|
|
42
|
+
);
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Given a block element and a character offset, returns the coordinates for drawing a visual cursor in the block.
|
|
47
|
+
*
|
|
48
|
+
* @param blockElement - The block element
|
|
49
|
+
* @param charOffset - The character offset
|
|
50
|
+
* @param editorDocument - The editor document
|
|
51
|
+
* @param overlayRect - Pre-computed bounding rect of the overlay element
|
|
52
|
+
* @return The position of the cursor
|
|
53
|
+
*/
|
|
54
|
+
const getOffsetPositionInBlock = (
|
|
55
|
+
blockElement: HTMLElement,
|
|
56
|
+
charOffset: number,
|
|
57
|
+
editorDocument: Document,
|
|
58
|
+
overlayRect: DOMRect
|
|
59
|
+
) => {
|
|
60
|
+
const { node, offset } = findInnerBlockOffset(
|
|
61
|
+
blockElement,
|
|
62
|
+
charOffset,
|
|
63
|
+
editorDocument
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
const cursorRange = editorDocument.createRange();
|
|
67
|
+
|
|
68
|
+
try {
|
|
69
|
+
cursorRange.setStart( node, offset );
|
|
70
|
+
} catch ( error ) {
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Ensure the range only represents single point in the DOM.
|
|
75
|
+
cursorRange.collapse( true );
|
|
76
|
+
|
|
77
|
+
const cursorRect = cursorRange.getBoundingClientRect();
|
|
78
|
+
const blockRect = blockElement.getBoundingClientRect();
|
|
79
|
+
|
|
80
|
+
let cursorX = 0;
|
|
81
|
+
let cursorY = 0;
|
|
82
|
+
|
|
83
|
+
if (
|
|
84
|
+
cursorRect.x === 0 &&
|
|
85
|
+
cursorRect.y === 0 &&
|
|
86
|
+
cursorRect.width === 0 &&
|
|
87
|
+
cursorRect.height === 0
|
|
88
|
+
) {
|
|
89
|
+
// This can happen for empty blocks.
|
|
90
|
+
cursorX = blockRect.left - overlayRect.left;
|
|
91
|
+
cursorY = blockRect.top - overlayRect.top;
|
|
92
|
+
} else {
|
|
93
|
+
cursorX = cursorRect.left - overlayRect.left;
|
|
94
|
+
cursorY = cursorRect.top - overlayRect.top;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
let cursorHeight = cursorRect.height;
|
|
98
|
+
if ( cursorHeight === 0 ) {
|
|
99
|
+
const view = editorDocument.defaultView ?? window;
|
|
100
|
+
cursorHeight =
|
|
101
|
+
parseInt( view.getComputedStyle( blockElement ).lineHeight, 10 ) ||
|
|
102
|
+
blockRect.height;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return {
|
|
106
|
+
x: cursorX,
|
|
107
|
+
y: cursorY,
|
|
108
|
+
height: cursorHeight,
|
|
109
|
+
};
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Computes selection highlight rectangles for a text range within a single block.
|
|
114
|
+
*
|
|
115
|
+
* @param blockElement - The block element
|
|
116
|
+
* @param startOffset - Start character offset within the block
|
|
117
|
+
* @param endOffset - End character offset within the block
|
|
118
|
+
* @param editorDocument - The editor document
|
|
119
|
+
* @param overlayRect - Pre-computed bounding rect of the overlay element
|
|
120
|
+
* @return Array of selection rectangles relative to the overlay, or null on failure
|
|
121
|
+
*/
|
|
122
|
+
export const getSelectionRects = (
|
|
123
|
+
blockElement: HTMLElement,
|
|
124
|
+
startOffset: number,
|
|
125
|
+
endOffset: number,
|
|
126
|
+
editorDocument: Document,
|
|
127
|
+
overlayRect: DOMRect
|
|
128
|
+
): SelectionRect[] | null => {
|
|
129
|
+
// Normalize direction.
|
|
130
|
+
let normalizedStart = startOffset;
|
|
131
|
+
let normalizedEnd = endOffset;
|
|
132
|
+
if ( normalizedStart > normalizedEnd ) {
|
|
133
|
+
[ normalizedStart, normalizedEnd ] = [ normalizedEnd, normalizedStart ];
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const startPos = findInnerBlockOffset(
|
|
137
|
+
blockElement,
|
|
138
|
+
normalizedStart,
|
|
139
|
+
editorDocument
|
|
140
|
+
);
|
|
141
|
+
const endPos = findInnerBlockOffset(
|
|
142
|
+
blockElement,
|
|
143
|
+
normalizedEnd,
|
|
144
|
+
editorDocument
|
|
145
|
+
);
|
|
146
|
+
|
|
147
|
+
const range = editorDocument.createRange();
|
|
148
|
+
try {
|
|
149
|
+
range.setStart( startPos.node, startPos.offset );
|
|
150
|
+
range.setEnd( endPos.node, endPos.offset );
|
|
151
|
+
} catch {
|
|
152
|
+
return null;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const clientRects = range.getClientRects();
|
|
156
|
+
const rects: SelectionRect[] = [];
|
|
157
|
+
|
|
158
|
+
for ( const rect of clientRects ) {
|
|
159
|
+
if ( rect.width === 0 && rect.height === 0 ) {
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
const x = rect.left - overlayRect.left;
|
|
163
|
+
const y = rect.top - overlayRect.top;
|
|
164
|
+
|
|
165
|
+
// Range.getClientRects() can return duplicate rects at inline
|
|
166
|
+
// formatting boundaries (e.g. <em>, <strong>). Skip exact matches.
|
|
167
|
+
const isDuplicate = rects.some(
|
|
168
|
+
( r ) =>
|
|
169
|
+
r.x === x &&
|
|
170
|
+
r.y === y &&
|
|
171
|
+
r.width === rect.width &&
|
|
172
|
+
r.height === rect.height
|
|
173
|
+
);
|
|
174
|
+
if ( isDuplicate ) {
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
rects.push( {
|
|
179
|
+
x,
|
|
180
|
+
y,
|
|
181
|
+
width: rect.width,
|
|
182
|
+
height: rect.height,
|
|
183
|
+
} );
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return rects.length > 0 ? rects : null;
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Computes selection highlight rectangles for the full content of a block.
|
|
191
|
+
* Used for intermediate blocks in a multi-block selection.
|
|
192
|
+
*
|
|
193
|
+
* @param blockElement - The block element
|
|
194
|
+
* @param editorDocument - The editor document
|
|
195
|
+
* @param overlayRect - Pre-computed bounding rect of the overlay element
|
|
196
|
+
* @return Array of selection rectangles relative to the overlay
|
|
197
|
+
*/
|
|
198
|
+
export const getFullBlockSelectionRects = (
|
|
199
|
+
blockElement: HTMLElement,
|
|
200
|
+
editorDocument: Document,
|
|
201
|
+
overlayRect: DOMRect
|
|
202
|
+
): SelectionRect[] => {
|
|
203
|
+
const range = editorDocument.createRange();
|
|
204
|
+
range.selectNodeContents( blockElement );
|
|
205
|
+
const clientRects = range.getClientRects();
|
|
206
|
+
const rects: SelectionRect[] = [];
|
|
207
|
+
|
|
208
|
+
for ( const rect of clientRects ) {
|
|
209
|
+
if ( rect.width === 0 && rect.height === 0 ) {
|
|
210
|
+
continue;
|
|
211
|
+
}
|
|
212
|
+
rects.push( {
|
|
213
|
+
x: rect.left - overlayRect.left,
|
|
214
|
+
y: rect.top - overlayRect.top,
|
|
215
|
+
width: rect.width,
|
|
216
|
+
height: rect.height,
|
|
217
|
+
} );
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Fallback: if getClientRects returned nothing, use the block's bounding rect.
|
|
221
|
+
if ( rects.length === 0 ) {
|
|
222
|
+
const blockRect = blockElement.getBoundingClientRect();
|
|
223
|
+
if ( blockRect.width > 0 && blockRect.height > 0 ) {
|
|
224
|
+
rects.push( {
|
|
225
|
+
x: blockRect.left - overlayRect.left,
|
|
226
|
+
y: blockRect.top - overlayRect.top,
|
|
227
|
+
width: blockRect.width,
|
|
228
|
+
height: blockRect.height,
|
|
229
|
+
} );
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
return rects;
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Finds all block elements between two blocks in DOM order (exclusive of start and end).
|
|
238
|
+
*
|
|
239
|
+
* @param startBlockId - The clientId of the start block
|
|
240
|
+
* @param endBlockId - The clientId of the end block
|
|
241
|
+
* @param editorDocument - The editor document
|
|
242
|
+
* @return Array of intermediate block HTMLElements in document order
|
|
243
|
+
*/
|
|
244
|
+
export const getBlocksBetween = (
|
|
245
|
+
startBlockId: string,
|
|
246
|
+
endBlockId: string,
|
|
247
|
+
editorDocument: Document
|
|
248
|
+
): HTMLElement[] => {
|
|
249
|
+
const allBlocks =
|
|
250
|
+
editorDocument.querySelectorAll< HTMLElement >( '[data-block]' );
|
|
251
|
+
|
|
252
|
+
let startIndex = -1;
|
|
253
|
+
let endIndex = -1;
|
|
254
|
+
|
|
255
|
+
for ( let i = 0; i < allBlocks.length; i++ ) {
|
|
256
|
+
const blockId = allBlocks[ i ].getAttribute( 'data-block' );
|
|
257
|
+
if ( blockId === startBlockId ) {
|
|
258
|
+
startIndex = i;
|
|
259
|
+
}
|
|
260
|
+
if ( blockId === endBlockId ) {
|
|
261
|
+
endIndex = i;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
if ( startIndex === -1 || endIndex === -1 ) {
|
|
266
|
+
return [];
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// Normalize order.
|
|
270
|
+
if ( startIndex > endIndex ) {
|
|
271
|
+
[ startIndex, endIndex ] = [ endIndex, startIndex ];
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
const result: HTMLElement[] = [];
|
|
275
|
+
for ( let i = startIndex + 1; i < endIndex; i++ ) {
|
|
276
|
+
result.push( allBlocks[ i ] );
|
|
277
|
+
}
|
|
278
|
+
return result;
|
|
279
|
+
};
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Given a block element and a character offset, returns an exact inner node and offset for use in a range.
|
|
283
|
+
*
|
|
284
|
+
* @param blockElement - The block element
|
|
285
|
+
* @param offset - The character offset
|
|
286
|
+
* @param editorDocument - The editor document
|
|
287
|
+
* @return The node and offset of the character at the offset
|
|
288
|
+
*/
|
|
289
|
+
export const findInnerBlockOffset = (
|
|
290
|
+
blockElement: HTMLElement,
|
|
291
|
+
offset: number,
|
|
292
|
+
editorDocument: Document
|
|
293
|
+
) => {
|
|
294
|
+
const treeWalker = editorDocument.createTreeWalker(
|
|
295
|
+
blockElement,
|
|
296
|
+
NodeFilter.SHOW_TEXT | NodeFilter.SHOW_ELEMENT // eslint-disable-line no-bitwise
|
|
297
|
+
);
|
|
298
|
+
|
|
299
|
+
let currentOffset = 0;
|
|
300
|
+
let lastTextNode: Node | null = null;
|
|
301
|
+
|
|
302
|
+
let node: Node | null = null;
|
|
303
|
+
let nodeCount = 1;
|
|
304
|
+
|
|
305
|
+
while ( ( node = treeWalker.nextNode() ) ) {
|
|
306
|
+
nodeCount++;
|
|
307
|
+
|
|
308
|
+
if ( nodeCount > MAX_NODE_OFFSET_COUNT ) {
|
|
309
|
+
// If we've walked too many nodes, return the last text node or the beginning of the block.
|
|
310
|
+
if ( lastTextNode ) {
|
|
311
|
+
return { node: lastTextNode, offset: 0 };
|
|
312
|
+
}
|
|
313
|
+
return { node: blockElement, offset: 0 };
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
const nodeLength = node.nodeValue?.length ?? 0;
|
|
317
|
+
|
|
318
|
+
if ( node.nodeType === Node.ELEMENT_NODE ) {
|
|
319
|
+
if ( node.nodeName === 'BR' ) {
|
|
320
|
+
// Treat <br> as a single "\n" character.
|
|
321
|
+
|
|
322
|
+
if ( currentOffset + 1 >= offset ) {
|
|
323
|
+
// If the <br> occurs right on the target offset, return the next text node.
|
|
324
|
+
const nodeAfterBr = treeWalker.nextNode();
|
|
325
|
+
|
|
326
|
+
if ( nodeAfterBr?.nodeType === Node.TEXT_NODE ) {
|
|
327
|
+
return { node: nodeAfterBr, offset: 0 };
|
|
328
|
+
} else if ( lastTextNode ) {
|
|
329
|
+
// If there's no text node after the <br>, return the end offset of the last text node.
|
|
330
|
+
return {
|
|
331
|
+
node: lastTextNode,
|
|
332
|
+
offset: lastTextNode.nodeValue?.length ?? 0,
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
// Just in case, if there's no last text node, return the beginning of the block.
|
|
336
|
+
return { node: blockElement, offset: 0 };
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// The <br> is before the target offset. Count it as a single character.
|
|
340
|
+
currentOffset += 1;
|
|
341
|
+
continue;
|
|
342
|
+
} else {
|
|
343
|
+
// Skip other element types.
|
|
344
|
+
continue;
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
if ( nodeLength === 0 ) {
|
|
349
|
+
// Skip empty nodes.
|
|
350
|
+
continue;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
if ( currentOffset + nodeLength >= offset ) {
|
|
354
|
+
// This node exceeds the target offset. Return the node and the position of the offset within it.
|
|
355
|
+
return { node, offset: offset - currentOffset };
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
currentOffset += nodeLength;
|
|
359
|
+
|
|
360
|
+
if ( node.nodeType === Node.TEXT_NODE ) {
|
|
361
|
+
lastTextNode = node;
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
if ( lastTextNode && lastTextNode.nodeValue?.length ) {
|
|
366
|
+
// We didn't reach the target offset. Return the last text node's last character.
|
|
367
|
+
return { node: lastTextNode, offset: lastTextNode.nodeValue.length };
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// We didn't find any text nodes. Return the beginning of the block.
|
|
371
|
+
return { node: blockElement, offset: 0 };
|
|
372
|
+
};
|
|
373
|
+
|
|
374
|
+
/**
|
|
375
|
+
* Check if node `a` precedes node `b` in document order.
|
|
376
|
+
*
|
|
377
|
+
* @param a - First node.
|
|
378
|
+
* @param b - Second node.
|
|
379
|
+
* @return True if `a` comes before `b`.
|
|
380
|
+
*/
|
|
381
|
+
export const isNodeBefore = ( a: Node, b: Node ): boolean =>
|
|
382
|
+
a.compareDocumentPosition( b ) === Node.DOCUMENT_POSITION_FOLLOWING;
|