{
  "version": 3,
  "sources": ["../../src/utils/crdt-user-selections.ts"],
  "sourcesContent": ["/**\n * WordPress dependencies\n */\nimport { select } from '@wordpress/data';\nimport { Y } from '@wordpress/sync';\n// @ts-ignore No exported types for block editor store selectors.\nimport { store as blockEditorStore } from '@wordpress/block-editor';\n\n/**\n * Internal dependencies\n */\nimport { CRDT_RECORD_MAP_KEY } from '../sync';\nimport type { YPostRecord } from './crdt';\nimport type { YBlock, YBlocks } from './crdt-blocks';\nimport { getRootMap, richTextOffsetToHtmlIndex } from './crdt-utils';\nimport type {\n\tAbsoluteBlockIndexPath,\n\tWPBlockSelection,\n\tSelectionState,\n\tSelectionNone,\n\tSelectionCursor,\n\tSelectionInOneBlock,\n\tSelectionInMultipleBlocks,\n\tSelectionWholeBlock,\n\tSelectionDirection,\n\tCursorPosition,\n} from '../types';\n\n/**\n * The type of selection.\n */\nexport enum SelectionType {\n\tNone = 'none',\n\tCursor = 'cursor',\n\tSelectionInOneBlock = 'selection-in-one-block',\n\tSelectionInMultipleBlocks = 'selection-in-multiple-blocks',\n\tWholeBlock = 'whole-block',\n}\n\n/**\n * Converts WordPress block editor selection to a SelectionState.\n *\n * Uses getBlockPathForLocalClientId to locate blocks in the Yjs document by\n * their tree position (index path) rather than clientId, since clientIds may\n * differ between the block-editor store and the Yjs document (e.g. in \"Show\n * Template\" mode).\n *\n * @param selectionStart             - The start position of the selection\n * @param selectionEnd               - The end position of the selection\n * @param yDoc                       - The Yjs document\n * @param options                    - Optional parameters\n * @param options.selectionDirection - The direction of the selection (forward or backward)\n * @return The SelectionState\n */\nexport function getSelectionState(\n\tselectionStart: WPBlockSelection,\n\tselectionEnd: WPBlockSelection,\n\tyDoc: Y.Doc,\n\toptions?: { selectionDirection?: SelectionDirection }\n): SelectionState {\n\tconst { selectionDirection } = options ?? {};\n\tconst ymap = getRootMap< YPostRecord >( yDoc, CRDT_RECORD_MAP_KEY );\n\tconst yBlocks = ymap.get( 'blocks' );\n\n\tconst isSelectionEmpty = Object.keys( selectionStart ).length === 0;\n\tconst noSelection: SelectionNone = {\n\t\ttype: SelectionType.None,\n\t};\n\n\tif ( isSelectionEmpty || ! yBlocks ) {\n\t\t// Case 1: No selection, or no blocks in the document.\n\t\treturn noSelection;\n\t}\n\n\t// When the page initially loads, selectionStart can contain an empty object `{}`.\n\tconst isSelectionInOneBlock =\n\t\tselectionStart.clientId === selectionEnd.clientId;\n\tconst isCursorOnly =\n\t\tisSelectionInOneBlock && selectionStart.offset === selectionEnd.offset;\n\tconst isSelectionAWholeBlock =\n\t\tisSelectionInOneBlock &&\n\t\tselectionStart.offset === undefined &&\n\t\tselectionEnd.offset === undefined;\n\n\tif ( isSelectionAWholeBlock ) {\n\t\t// Case 2: A whole block is selected.\n\t\tconst path = getBlockPathForLocalClientId( selectionStart.clientId );\n\t\tconst blockPosition = path\n\t\t\t? createRelativePositionForBlockPath( path, yBlocks )\n\t\t\t: null;\n\n\t\tif ( ! blockPosition ) {\n\t\t\treturn noSelection;\n\t\t}\n\n\t\treturn {\n\t\t\ttype: SelectionType.WholeBlock,\n\t\t\tblockPosition,\n\t\t};\n\t} else if ( isCursorOnly ) {\n\t\t// Case 3: Cursor only, no text selected\n\t\tconst cursorPosition = getCursorPosition( selectionStart, yBlocks );\n\n\t\tif ( ! cursorPosition ) {\n\t\t\t// If we can't find the cursor position in block text, treat it as a non-selection.\n\t\t\treturn noSelection;\n\t\t}\n\n\t\treturn {\n\t\t\ttype: SelectionType.Cursor,\n\t\t\tcursorPosition,\n\t\t};\n\t} else if ( isSelectionInOneBlock ) {\n\t\t// Case 4: Selection in a single block\n\t\tconst cursorStartPosition = getCursorPosition(\n\t\t\tselectionStart,\n\t\t\tyBlocks\n\t\t);\n\t\tconst cursorEndPosition = getCursorPosition( selectionEnd, yBlocks );\n\n\t\tif ( ! cursorStartPosition || ! cursorEndPosition ) {\n\t\t\t// If we can't find the cursor positions in block text, treat it as a non-selection.\n\t\t\treturn noSelection;\n\t\t}\n\n\t\treturn {\n\t\t\ttype: SelectionType.SelectionInOneBlock,\n\t\t\tcursorStartPosition,\n\t\t\tcursorEndPosition,\n\t\t\tselectionDirection,\n\t\t};\n\t}\n\n\t// Case 5: Selection in multiple blocks\n\tconst cursorStartPosition = getCursorPosition( selectionStart, yBlocks );\n\tconst cursorEndPosition = getCursorPosition( selectionEnd, yBlocks );\n\tif ( ! cursorStartPosition || ! cursorEndPosition ) {\n\t\t// If we can't find the cursor positions in block text, treat it as a non-selection.\n\t\treturn noSelection;\n\t}\n\n\treturn {\n\t\ttype: SelectionType.SelectionInMultipleBlocks,\n\t\tcursorStartPosition,\n\t\tcursorEndPosition,\n\t\tselectionDirection,\n\t};\n}\n\n/**\n * Get the cursor position from a selection.\n *\n * @param selection - The selection.\n * @param blocks    - The blocks to search through.\n * @return The cursor position, or null if not found.\n */\nfunction getCursorPosition(\n\tselection: WPBlockSelection,\n\tblocks: YBlocks\n): CursorPosition | null {\n\tconst path = getBlockPathForLocalClientId( selection.clientId );\n\tconst block = path ? findBlockByPath( path, blocks ) : null;\n\tif (\n\t\t! block ||\n\t\t! selection.attributeKey ||\n\t\tundefined === selection.offset\n\t) {\n\t\treturn null;\n\t}\n\n\tconst attributes = block.get( 'attributes' );\n\tconst currentYText = attributes?.get( selection.attributeKey );\n\n\t// If the attribute is not a Y.Text, return null.\n\tif ( ! ( currentYText instanceof Y.Text ) ) {\n\t\treturn null;\n\t}\n\n\tconst relativePosition = Y.createRelativePositionFromTypeIndex(\n\t\tcurrentYText,\n\t\trichTextOffsetToHtmlIndex( currentYText.toString(), selection.offset )\n\t);\n\n\treturn {\n\t\trelativePosition,\n\t\tabsoluteOffset: selection.offset,\n\t};\n}\n\n/**\n * Resolves a local block-editor clientId to its index path relative to the\n * post content blocks. This allows finding the corresponding block in the Yjs\n * document even when clientIds differ (e.g. in \"Show Template\" mode where\n * blocks are cloned).\n *\n * In template mode, the block tree includes template parts and wrapper blocks\n * around a core/post-content block. The Yjs document only contains the post\n * content blocks, so we stop the upward walk when the parent is\n * core/post-content (its inner blocks correspond to the Yjs root blocks).\n *\n * @param clientId - The local block-editor clientId to resolve.\n * @return The index path from root, or null if not resolvable.\n */\nexport function getBlockPathForLocalClientId(\n\tclientId: string\n): AbsoluteBlockIndexPath | null {\n\tconst { getBlockIndex, getBlockRootClientId, getBlockName } =\n\t\tselect( blockEditorStore );\n\n\tconst path: AbsoluteBlockIndexPath = [];\n\tlet current: string | null = clientId;\n\twhile ( current ) {\n\t\tconst index = getBlockIndex( current );\n\t\tif ( index === -1 ) {\n\t\t\treturn null;\n\t\t}\n\t\tpath.unshift( index );\n\t\tconst parent = getBlockRootClientId( current );\n\t\tif ( ! parent ) {\n\t\t\tbreak;\n\t\t}\n\t\t// If the parent is core/post-content, stop here \u2014 the Yjs doc\n\t\t// root blocks correspond to post-content's inner blocks.\n\t\tconst parentName = getBlockName( parent );\n\t\tif ( parentName === 'core/post-content' ) {\n\t\t\tbreak;\n\t\t}\n\t\tcurrent = parent;\n\t}\n\treturn path.length > 0 ? path : null;\n}\n\n/**\n * Find a block by navigating a tree index path in the Yjs block hierarchy.\n *\n * @param path   - The index path, e.g. [0, 1] for blocks[0].innerBlocks[1].\n * @param blocks - The root-level Yjs blocks array.\n * @return The block Y.Map if found, null otherwise.\n */\nfunction findBlockByPath(\n\tpath: AbsoluteBlockIndexPath,\n\tblocks: YBlocks\n): YBlock | null {\n\tlet currentBlocks = blocks;\n\tfor ( let i = 0; i < path.length; i++ ) {\n\t\tif ( path[ i ] >= currentBlocks.length ) {\n\t\t\treturn null;\n\t\t}\n\t\tconst block = currentBlocks.get( path[ i ] );\n\t\tif ( ! block ) {\n\t\t\treturn null;\n\t\t}\n\t\tif ( i === path.length - 1 ) {\n\t\t\treturn block;\n\t\t}\n\t\tcurrentBlocks =\n\t\t\tblock.get( 'innerBlocks' ) ?? ( new Y.Array() as YBlocks );\n\t}\n\treturn null;\n}\n\n/**\n * Create a Y.RelativePosition for a block by navigating a tree index path.\n *\n * @param path   - The index path, e.g. [0, 1] for blocks[0].innerBlocks[1].\n * @param blocks - The root-level Yjs blocks array.\n * @return A Y.RelativePosition for the block, or null if the path is invalid.\n */\nfunction createRelativePositionForBlockPath(\n\tpath: AbsoluteBlockIndexPath,\n\tblocks: YBlocks\n): Y.RelativePosition | null {\n\tlet currentBlocks = blocks;\n\tfor ( let i = 0; i < path.length; i++ ) {\n\t\tif ( path[ i ] >= currentBlocks.length ) {\n\t\t\treturn null;\n\t\t}\n\t\tif ( i === path.length - 1 ) {\n\t\t\treturn Y.createRelativePositionFromTypeIndex(\n\t\t\t\tcurrentBlocks,\n\t\t\t\tpath[ i ]\n\t\t\t);\n\t\t}\n\t\tconst block = currentBlocks.get( path[ i ] );\n\t\tcurrentBlocks =\n\t\t\tblock?.get( 'innerBlocks' ) ?? ( new Y.Array() as YBlocks );\n\t}\n\treturn null;\n}\n\n/**\n * Check if two selection states are equal.\n *\n * @param selection1 - The first selection state.\n * @param selection2 - The second selection state.\n * @return True if the selection states are equal, false otherwise.\n */\nexport function areSelectionsStatesEqual(\n\tselection1: SelectionState,\n\tselection2: SelectionState\n): boolean {\n\tif ( selection1.type !== selection2.type ) {\n\t\treturn false;\n\t}\n\n\tswitch ( selection1.type ) {\n\t\tcase SelectionType.None:\n\t\t\treturn true;\n\n\t\tcase SelectionType.Cursor:\n\t\t\treturn areCursorPositionsEqual(\n\t\t\t\tselection1.cursorPosition,\n\t\t\t\t( selection2 as SelectionCursor ).cursorPosition\n\t\t\t);\n\n\t\tcase SelectionType.SelectionInOneBlock:\n\t\t\treturn (\n\t\t\t\tareCursorPositionsEqual(\n\t\t\t\t\tselection1.cursorStartPosition,\n\t\t\t\t\t( selection2 as SelectionInOneBlock ).cursorStartPosition\n\t\t\t\t) &&\n\t\t\t\tareCursorPositionsEqual(\n\t\t\t\t\tselection1.cursorEndPosition,\n\t\t\t\t\t( selection2 as SelectionInOneBlock ).cursorEndPosition\n\t\t\t\t) &&\n\t\t\t\tselection1.selectionDirection ===\n\t\t\t\t\t( selection2 as SelectionInOneBlock ).selectionDirection\n\t\t\t);\n\n\t\tcase SelectionType.SelectionInMultipleBlocks:\n\t\t\treturn (\n\t\t\t\tareCursorPositionsEqual(\n\t\t\t\t\tselection1.cursorStartPosition,\n\t\t\t\t\t( selection2 as SelectionInMultipleBlocks )\n\t\t\t\t\t\t.cursorStartPosition\n\t\t\t\t) &&\n\t\t\t\tareCursorPositionsEqual(\n\t\t\t\t\tselection1.cursorEndPosition,\n\t\t\t\t\t( selection2 as SelectionInMultipleBlocks )\n\t\t\t\t\t\t.cursorEndPosition\n\t\t\t\t) &&\n\t\t\t\tselection1.selectionDirection ===\n\t\t\t\t\t( selection2 as SelectionInMultipleBlocks )\n\t\t\t\t\t\t.selectionDirection\n\t\t\t);\n\t\tcase SelectionType.WholeBlock:\n\t\t\treturn Y.compareRelativePositions(\n\t\t\t\tselection1.blockPosition,\n\t\t\t\t( selection2 as SelectionWholeBlock ).blockPosition\n\t\t\t);\n\n\t\tdefault:\n\t\t\treturn false;\n\t}\n}\n\n/**\n * Check if two cursor positions are equal.\n *\n * @param cursorPosition1 - The first cursor position.\n * @param cursorPosition2 - The second cursor position.\n * @return True if the cursor positions are equal, false otherwise.\n */\nfunction areCursorPositionsEqual(\n\tcursorPosition1: CursorPosition,\n\tcursorPosition2: CursorPosition\n): boolean {\n\tconst isRelativePositionEqual = Y.compareRelativePositions(\n\t\tcursorPosition1.relativePosition,\n\t\tcursorPosition2.relativePosition\n\t);\n\n\t// Ensure a change in calculated absolute offset results in a treating the cursor as modified.\n\t// This is necessary because Y.Text relative positions can remain the same after text changes.\n\tconst isAbsoluteOffsetEqual =\n\t\tcursorPosition1.absoluteOffset === cursorPosition2.absoluteOffset;\n\n\treturn isRelativePositionEqual && isAbsoluteOffsetEqual;\n}\n"],
  "mappings": "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",
  "names": ["import_sync", "SelectionType", "cursorStartPosition", "cursorEndPosition", "blockEditorStore"]
}
