const logicalChildrenPropname = createSymbolOrFallback ( '_blazorLogicalChildren' ) ; const logicalParentPropname = createSymbolOrFallback ( '_blazorLogicalParent' ) ; const logicalEndSiblingPropname = createSymbolOrFallback ( '_blazorLogicalEnd' ) ; export function toLogicalRootCommentElement ( start , end ) { if ( ! start . parentNode ) { throw new Error ( `Comment not connected to the DOM ${ start . textContent }` ) ; } const parent = start . parentNode ; const parentLogicalElement = toLogicalElement ( parent , true ) ; const children = getLogicalChildrenArray ( parentLogicalElement ) ; Array . from ( parent . childNodes ) . forEach ( n => children . push ( n as unknown as LogicalElement ) ) ; start [ logicalParentPropname ] = parentLogicalElement ; if ( end ) { start [ logicalEndSiblingPropname ] = end ; toLogicalElement ( end , true ) ; } return toLogicalElement ( start , true ) ; } export function toLogicalElement ( element , allowExistingContents ? ) { if ( element . childNodes . length > 0 && ! allowExistingContents ) { throw new Error ( 'New logical elements must start empty, or allowExistingContents must be true' ) ; } element [ logicalChildrenPropname ] = [ ] ; return element as unknown as LogicalElement ; } export function createAndInsertLogicalContainer ( parent , childIndex ) { const containerElement = document . createComment ( '!' ) ; insertLogicalChild ( containerElement , parent , childIndex ) ; return containerElement as any as LogicalElement ; } export function insertLogicalChild ( child , parent , childIndex ) { const childAsLogicalElement = child as any as LogicalElement ; if ( child instanceof Comment ) { const existingGrandchildren = getLogicalChildrenArray ( childAsLogicalElement ) ; if ( existingGrandchildren && getLogicalChildrenArray ( childAsLogicalElement ) . length > 0 ) { throw new Error ( 'Not implemented: inserting non-empty logical container' ) ; } } if ( getLogicalParent ( childAsLogicalElement ) ) { throw new Error ( 'Not implemented: moving existing logical children' ) ; } const newSiblings = getLogicalChildrenArray ( parent ) ; if ( childIndex < newSiblings . length ) { const nextSibling = newSiblings [ childIndex ] as any as Node ; nextSibling . parentNode ! . insertBefore ( child , nextSibling ) ; newSiblings . splice ( childIndex , 0 , childAsLogicalElement ) ; } else { appendDomNode ( child , parent ) ; newSiblings . push ( childAsLogicalElement ) ; } childAsLogicalElement [ logicalParentPropname ] = parent ; if ( ! ( logicalChildrenPropname in childAsLogicalElement ) ) { childAsLogicalElement [ logicalChildrenPropname ] = [ ] ; } } export function removeLogicalChild ( parent , childIndex ) { const childrenArray = getLogicalChildrenArray ( parent ) ; const childToRemove = childrenArray . splice ( childIndex , 1 ) [ 0 ] ; if ( childToRemove instanceof Comment ) { const grandchildrenArray = getLogicalChildrenArray ( childToRemove ) ; while ( grandchildrenArray . length > 0 ) { removeLogicalChild ( childToRemove , 0 ) ; } } const domNodeToRemove = childToRemove as any as Node ; domNodeToRemove . parentNode ! . removeChild ( domNodeToRemove ) ; } export function getLogicalParent ( element ) { return ( element [ logicalParentPropname ] as LogicalElement ) || null ; } export function getLogicalSiblingEnd ( element ) { return ( element [ logicalEndSiblingPropname ] as LogicalElement ) || null ; } export function getLogicalChild ( parent , childIndex ) { return getLogicalChildrenArray ( parent ) [ childIndex ] ; } export function isSvgElement ( element ) { return getClosestDomElement ( element ) . namespaceURI === 'http://www.w3.org/2000/svg' ; } export function getLogicalChildrenArray ( element ) { return element [ logicalChildrenPropname ] as LogicalElement [ ] ; } export function permuteLogicalChildren ( parent , permutationList ) { const siblings = getLogicalChildrenArray ( parent ) ; permutationList . forEach ( ( listEntry ) => { listEntry . moveRangeStart = siblings [ listEntry . fromSiblingIndex ] ; listEntry . moveRangeEnd = findLastDomNodeInRange ( listEntry . moveRangeStart ) ; } ) ; permutationList . forEach ( ( listEntry ) => { const marker = listEntry . moveToBeforeMarker = document . createComment ( 'marker' ) ; const insertBeforeNode = siblings [ listEntry . toSiblingIndex + 1 ] as any as Node ; if ( insertBeforeNode ) { insertBeforeNode . parentNode ! . insertBefore ( marker , insertBeforeNode ) ; } else { appendDomNode ( marker , parent ) ; } } ) ; permutationList . forEach ( ( listEntry ) => { const insertBefore = listEntry . moveToBeforeMarker ! ; const parentDomNode = insertBefore . parentNode ! ; const elementToMove = listEntry . moveRangeStart ! ; const moveEndNode = listEntry . moveRangeEnd ! ; let nextToMove = elementToMove as any as Node | null ; while ( nextToMove ) { const nextNext = nextToMove . nextSibling ; parentDomNode . insertBefore ( nextToMove , insertBefore ) ; if ( nextToMove === moveEndNode ) { break ; } else { nextToMove = nextNext ; } } parentDomNode . removeChild ( insertBefore ) ; } ) ; permutationList . forEach ( ( listEntry ) => { siblings [ listEntry . toSiblingIndex ] = listEntry . moveRangeStart ! ; } ) ; } export function getClosestDomElement ( logicalElement ) { if ( logicalElement instanceof Element ) { return logicalElement ; } else if ( logicalElement instanceof Comment ) { return logicalElement . parentNode ! as Element ; } else { throw new Error ( 'Not a valid logical element' ) ; } } export interface PermutationListEntry { fromSiblingIndex : number ; toSiblingIndex : number ; } interface PermutationListEntryWithTrackingData extends PermutationListEntry { moveRangeStart ? : LogicalElement ; moveRangeEnd ? : Node ; moveToBeforeMarker ? : Node ; } function getLogicalNextSibling ( element ) { const siblings = getLogicalChildrenArray ( getLogicalParent ( element ) ! ) ; const siblingIndex = Array . prototype . indexOf . call ( siblings , element ) ; return siblings [ siblingIndex + 1 ] || null ; } function appendDomNode ( child , parent ) { if ( parent instanceof Element ) { parent . appendChild ( child ) ; } else if ( parent instanceof Comment ) { const parentLogicalNextSibling = getLogicalNextSibling ( parent ) as any as Node ; if ( parentLogicalNextSibling ) { parentLogicalNextSibling . parentNode ! . insertBefore ( child , parentLogicalNextSibling ) ; } else { appendDomNode ( child , getLogicalParent ( parent ) ! ) ; } } else { throw new Error ( `Cannot append node because the parent is not a valid logical element. Parent: ${ parent }` ) ; } } function findLastDomNodeInRange ( element ) { if ( element instanceof Element ) { return element ; } const nextSibling = getLogicalNextSibling ( element ) ; if ( nextSibling ) { return ( nextSibling as any as Node ) . previousSibling ; } else { const logicalParent = getLogicalParent ( element ) ! ; return logicalParent instanceof Element ? logicalParent . lastChild : findLastDomNodeInRange ( logicalParent ) ; } } function createSymbolOrFallback ( fallback ) { return typeof Symbol === 'function' ? Symbol ( ) : fallback ; } export interface LogicalElement { LogicalElement__DO_NOT_IMPLEMENT : any ; }