@@ -253,6 +253,24 @@ function normalizeCellAttrsForSingleCell(attrs: Record<string, unknown>): Record
253253 } ;
254254}
255255
256+ function normalizeClonedRowInsertCellAttrs (
257+ sourceAttrs : Record < string , unknown > ,
258+ fromHeaderToBody : boolean ,
259+ ) : Record < string , unknown > {
260+ const normalizedAttrs : Record < string , unknown > = {
261+ ...sourceAttrs ,
262+ rowspan : 1 ,
263+ } ;
264+
265+ // Header rows can carry explicit `borders: null` to suppress drawing.
266+ // Drop that sentinel when cloning into body cells so tableCell defaults apply.
267+ if ( fromHeaderToBody && normalizedAttrs . borders == null ) {
268+ delete normalizedAttrs . borders ;
269+ }
270+
271+ return normalizedAttrs ;
272+ }
273+
256274type ExpandMergedCellParams = {
257275 tr : Transaction ;
258276 tablePos : number ;
@@ -617,11 +635,11 @@ function insertRowInTable(
617635 }
618636
619637 const colspan = ( ( sourceCell . attrs as Record < string , unknown > ) . colspan as number ) || 1 ;
620- const targetCellType = sourceCell . type . name === 'tableHeader' ? defaultCellType : sourceCell . type ;
621- const newCell = targetCellType . createAndFill ( {
622- ... ( sourceCell . attrs as Record < string , unknown > ) ,
623- rowspan : 1 ,
624- } ) ;
638+ const fromHeaderToBody = sourceCell . type . name === 'tableHeader' ;
639+ const targetCellType = fromHeaderToBody ? defaultCellType : sourceCell . type ;
640+ const newCell = targetCellType . createAndFill (
641+ normalizeClonedRowInsertCellAttrs ( sourceCell . attrs as Record < string , unknown > , fromHeaderToBody ) ,
642+ ) ;
625643 if ( newCell ) newCells . push ( newCell ) ;
626644 col += colspan ;
627645 }
@@ -643,6 +661,148 @@ function insertRowInTable(
643661 return true ;
644662}
645663
664+ function addColumnToTableForSplit (
665+ tr : Transaction ,
666+ tablePos : number ,
667+ col : number ,
668+ splitRowStart : number ,
669+ splitRowEnd : number ,
670+ ) : void {
671+ const tableNode = tr . doc . nodeAt ( tablePos ) ;
672+ if ( ! tableNode || tableNode . type . name !== 'table' ) return ;
673+ const map = TableMap . get ( tableNode ) ;
674+ const tableStart = tablePos + 1 ;
675+ const mapStart = tr . mapping . maps . length ;
676+ const widenedOutsideSplit = new Set < number > ( ) ;
677+
678+ for ( let row = 0 ; row < map . height ; row ++ ) {
679+ const index = row * map . width + col ;
680+ const pos = map . map [ index ] ;
681+ const cell = tableNode . nodeAt ( pos ) ;
682+ if ( ! cell ) continue ;
683+
684+ const inSplitRows = row >= splitRowStart && row < splitRowEnd ;
685+ if ( ! inSplitRows && col > 0 ) {
686+ const leftPos = map . map [ index - 1 ] ! ;
687+ const leftCell = tableNode . nodeAt ( leftPos ) ;
688+ if ( leftCell && ! widenedOutsideSplit . has ( leftPos ) ) {
689+ tr . setNodeMarkup (
690+ tr . mapping . slice ( mapStart ) . map ( tableStart + leftPos ) ,
691+ null ,
692+ addColSpan ( leftCell . attrs as Record < string , unknown > , col - map . colCount ( leftPos ) ) ,
693+ ) ;
694+ widenedOutsideSplit . add ( leftPos ) ;
695+ }
696+ row += ( ( cell . attrs ?. rowspan as number ) || 1 ) - 1 ;
697+ continue ;
698+ }
699+
700+ if ( col > 0 && map . map [ index - 1 ] === pos ) {
701+ tr . setNodeMarkup (
702+ tr . mapping . slice ( mapStart ) . map ( tableStart + pos ) ,
703+ null ,
704+ addColSpan ( cell . attrs as Record < string , unknown > , col - map . colCount ( pos ) ) ,
705+ ) ;
706+ row += ( ( ( cell . attrs as Record < string , unknown > ) . rowspan as number ) || 1 ) - 1 ;
707+ } else {
708+ const refType = col > 0 ? ( tableNode . nodeAt ( map . map [ index - 1 ] ) ?. type ?? cell . type ) : cell . type ;
709+ const cellPos = map . positionAt ( row , col , tableNode ) ;
710+ tr . insert ( tr . mapping . slice ( mapStart ) . map ( tableStart + cellPos ) , refType . createAndFill ( ) ! ) ;
711+ row += ( ( cell . attrs ?. rowspan as number ) || 1 ) - 1 ;
712+ }
713+ }
714+ }
715+
716+ function insertRowInTableForSplit (
717+ tr : Transaction ,
718+ tablePos : number ,
719+ sourceRowIndex : number ,
720+ insertIndex : number ,
721+ splitColStart : number ,
722+ splitColEnd : number ,
723+ schema : Editor [ 'state' ] [ 'schema' ] ,
724+ ) : boolean {
725+ const tableNode = tr . doc . nodeAt ( tablePos ) ;
726+ if ( ! tableNode || tableNode . type . name !== 'table' ) return false ;
727+
728+ const rowCount = tableNode . childCount ;
729+ if ( rowCount === 0 ) return false ;
730+
731+ const map = TableMap . get ( tableNode ) ;
732+ const boundedInsertIndex = Math . max ( 0 , Math . min ( insertIndex , rowCount ) ) ;
733+ const boundedSourceRowIndex = Math . max ( 0 , Math . min ( sourceRowIndex , rowCount - 1 ) ) ;
734+ const sourceRow = tableNode . child ( boundedSourceRowIndex ) ;
735+ if ( ! sourceRow ) return false ;
736+
737+ const rowType = schema . nodes . tableRow ;
738+ const defaultCellType = schema . nodes . tableCell ;
739+ if ( ! rowType || ! defaultCellType ) return false ;
740+
741+ const newCells : import ( 'prosemirror-model' ) . Node [ ] = [ ] ;
742+ const cellsToExtend = new Map < number , Record < string , unknown > > ( ) ;
743+
744+ for ( let col = 0 ; col < map . width ; ) {
745+ if ( boundedInsertIndex > 0 && boundedInsertIndex < map . height ) {
746+ const indexAbove = ( boundedInsertIndex - 1 ) * map . width + col ;
747+ const indexAtInsert = boundedInsertIndex * map . width + col ;
748+
749+ if ( map . map [ indexAbove ] === map . map [ indexAtInsert ] ) {
750+ const spanningPos = map . map [ indexAbove ] ;
751+ const spanningCell = tableNode . nodeAt ( spanningPos ) ;
752+ if ( spanningCell ) {
753+ const spanningAttrs = spanningCell . attrs as Record < string , unknown > ;
754+ const rowspan = ( spanningAttrs . rowspan as number ) || 1 ;
755+ const colspan = ( spanningAttrs . colspan as number ) || 1 ;
756+ cellsToExtend . set ( tablePos + 1 + spanningPos , { ...spanningAttrs , rowspan : rowspan + 1 } ) ;
757+ col += colspan ;
758+ continue ;
759+ }
760+ }
761+ }
762+
763+ const sourceMapIndex = boundedSourceRowIndex * map . width + col ;
764+ const sourceCellPos = map . map [ sourceMapIndex ] ;
765+ const sourceCell = tableNode . nodeAt ( sourceCellPos ) ?? sourceRow . firstChild ;
766+ if ( ! sourceCell ) {
767+ col += 1 ;
768+ continue ;
769+ }
770+
771+ const sourceAttrs = sourceCell . attrs as Record < string , unknown > ;
772+ const colspan = ( sourceAttrs . colspan as number ) || 1 ;
773+ const overlapsSplitRange = col < splitColEnd && col + colspan > splitColStart ;
774+
775+ if ( ! overlapsSplitRange ) {
776+ const sourceRowspan = ( sourceAttrs . rowspan as number ) || 1 ;
777+ cellsToExtend . set ( tablePos + 1 + sourceCellPos , { ...sourceAttrs , rowspan : sourceRowspan + 1 } ) ;
778+ col += colspan ;
779+ continue ;
780+ }
781+
782+ const fromHeaderToBody = sourceCell . type . name === 'tableHeader' ;
783+ const targetCellType = fromHeaderToBody ? defaultCellType : sourceCell . type ;
784+ const newCell = targetCellType . createAndFill ( normalizeClonedRowInsertCellAttrs ( sourceAttrs , fromHeaderToBody ) ) ;
785+ if ( newCell ) newCells . push ( newCell ) ;
786+ col += colspan ;
787+ }
788+
789+ for ( const [ pos , attrs ] of cellsToExtend . entries ( ) ) {
790+ tr . setNodeMarkup ( pos , null , attrs ) ;
791+ }
792+
793+ if ( newCells . length === 0 ) return true ;
794+
795+ const newRow = rowType . createAndFill ( null , newCells ) ;
796+ if ( ! newRow ) return false ;
797+
798+ let insertPos = tablePos + 1 ;
799+ for ( let row = 0 ; row < boundedInsertIndex ; row ++ ) {
800+ insertPos += tableNode . child ( row ) . nodeSize ;
801+ }
802+ tr . insert ( insertPos , newRow ) ;
803+ return true ;
804+ }
805+
646806// ---------------------------------------------------------------------------
647807// Batch 2 — Table lifecycle + layout
648808// ---------------------------------------------------------------------------
@@ -2241,7 +2401,7 @@ export function tablesSplitCellAdapter(
22412401
22422402 for ( let columnOffset = 0 ; columnOffset < additionalColumns ; columnOffset ++ ) {
22432403 const insertColumnIndex = columnIndex + currentColspan + columnOffset ;
2244- addColumnToTable ( tr , tablePos , insertColumnIndex ) ;
2404+ addColumnToTableForSplit ( tr , tablePos , insertColumnIndex , rowIndex , rowIndex + targetRows ) ;
22452405 updatedGrid = insertGridColumnWidth ( updatedGrid , insertColumnIndex ) ?? updatedGrid ;
22462406 }
22472407
@@ -2254,7 +2414,15 @@ export function tablesSplitCellAdapter(
22542414 const insertIndex = rowIndex + currentRowspan + rowOffset ;
22552415 const boundedInsertIndex = Math . max ( 0 , Math . min ( insertIndex , currentTableNode . childCount ) ) ;
22562416 const sourceRowIndex = Math . max ( 0 , Math . min ( boundedInsertIndex - 1 , currentTableNode . childCount - 1 ) ) ;
2257- const didInsertRow = insertRowInTable ( tr , tablePos , sourceRowIndex , boundedInsertIndex , schema ) ;
2417+ const didInsertRow = insertRowInTableForSplit (
2418+ tr ,
2419+ tablePos ,
2420+ sourceRowIndex ,
2421+ boundedInsertIndex ,
2422+ columnIndex ,
2423+ columnIndex + targetColumns ,
2424+ schema ,
2425+ ) ;
22582426
22592427 if ( ! didInsertRow ) {
22602428 return toTableFailure ( 'INVALID_TARGET' , 'Cell split could not insert required rows.' ) ;
0 commit comments