import { isInstance , sha256hex , tagConstructor } from "@adpt/utils" ; import { InternalError } from "./error" ; import { AdaptElement , AdaptMountedElement , ElementPredicate , GenericInstance , GenericInstanceMethods , isMountedElement , KeyPath , } from "./jsx" ; import { findMummyUrn , registerObject } from "./reanimate" ; export interface Handle < I extends object = GenericInstance > extends Readonly < Partial < BuildId > > { readonly associated : boolean ; readonly target : AdaptElement | null | undefined ; readonly origTarget : AdaptElement | null | undefined ; readonly mountedOrig : AdaptMountedElement | null | undefined ; readonly name ? : string ; nextMounted ( pred ? ) : AdaptMountedElement | null | undefined ; replaceTarget ( child , buildId ) : void ; } /**
 * Extracts the instance type associated with a  Handle .
 *  public export type HandleInstanceType < H extends Handle > = H extends Handle < infer I > ? I : never ; export function isHandle < I extends object = GenericInstance > ( val ) { return isHandleImpl ( val ) ; } export interface HandleInternal < I extends object = GenericInstance > extends Handle < I > { unresolvedTarget ? : KeyPath | null ; associate ( el ) : void ; targetReplaced ( buildId ) : boolean ; } export interface BuildId { buildNum : number ; } export function isHandleInternal < I extends object = GenericInstance > ( val ) { return isHandleImpl ( val ) ; } export function getInternalHandle < I extends object = GenericInstance > ( el ) { const hand = el . props . handle ; if ( ! isHandleInternal ( hand ) ) throw new InternalError ( `handle is not a HandleImpl` ) ; return hand ; } let nextId = 0 ; const id = Symbol . for ( "AdaptHandleId" ) ; const origElement = Symbol . for ( "AdaptHandleOrigElement" ) ; interface HandleOptions { name ? : string ; target ? : KeyPath ; } export const handleSignature = sha256hex ( "This is an Adapt.Handle" ) ; export interface HandleObj { __adaptIsHandle : string ; name ? : string ; target : string [ ] | null ; urn : string ; } export function isHandleObj ( val ) { return ( val as any ) . __adaptIsHandle === handleSignature ; } remarks returns internal function findFirst ( start , pred ) { let hand = start ; while ( true ) { if ( ! isHandleImpl ( hand ) ) { throw new InternalError ( `Handle is not a HandleImpl` ) ; } const orig = hand . origTarget ; if ( orig === undefined ) { throw new InternalError ( `Handle chain has undefined origTarget` ) ; } if ( pred ( hand ) ) return orig ; if ( hand . childElement == null ) { return hand . childElement ; } hand = hand . childElement . props . handle ; if ( hand == null ) { throw new InternalError ( `no Handle present on Element in child chain` ) ; } } } returns internal function findLast ( start ) { let hand = start ; while ( true ) { if ( ! isHandleImpl ( hand ) ) { throw new InternalError ( `Handle is not a HandleImpl` ) ; } const orig = hand . origTarget ; if ( orig === undefined ) { throw new InternalError ( `Handle chain has undefined origTarget` ) ; } if ( hand . childElement === undefined ) return orig ; if ( hand . childElement === null ) return null ; hand = hand . childElement . props . handle ; if ( hand == null ) { throw new InternalError ( `no Handle present on Element in child chain` ) ; } } } class HandleImpl implements HandleInternal { readonly name ? : string ; unresolvedTarget ? : KeyPath | null ; [ origElement ] ? : AdaptElement | null ; [ id ] : number ; buildNum ? : number ; childElement ? : AdaptElement | null ; constructor ( opts ) { this [ id ] = nextId ++ ; if ( opts . name ) this . name = opts . name ; if ( opts . target !== undefined ) { this . unresolvedTarget = opts . target ; if ( opts . target === null ) this . associate ( null ) ; } } associate = ( el ) => { const orig = this [ origElement ] ; if ( orig !== undefined ) { const path = isMountedElement ( orig ) ? orig . path : "<not mounted>" ; throw new Error ( `Cannot associate a Handle with more than one AdaptElement. ` + `Original element type ${ orig && orig . componentName }, ` + `path: ${ path }, ` + `second association element type ${ el && el . componentName }` ) ; } this [ origElement ] = el ; } ; get associated ( ) : boolean { return this [ origElement ] !== undefined ; } replaceTarget = ( el , buildId ) => { const orig = this [ origElement ] ; if ( orig == null ) { throw new Error ( `A Handle must first be associated with an ` + `Element before replaceTarget can be called` ) ; } if ( this . buildNum === undefined || buildId . buildNum > this . buildNum ) { this . buildNum = buildId . buildNum ; if ( el === this [ origElement ] ) return ; this . childElement = el ; return ; } if ( this . buildNum === buildId . buildNum ) { throw new Error ( `Cannot call replaceTarget on a Handle more than once` ) ; } throw new Error ( `Cannot call replaceTarget on a Handle with an ` + `older build iteration. ${ this . origDebug ( ) } ` + `(this.buildNum=${ this . buildNum } ` + `buildId.buildNum=${ buildId . buildNum })` ) ; } ; targetReplaced ( buildId ) { return this . buildNum === buildId . buildNum && this . childElement !== undefined ; } get id ( ) { return this [ id ] ; } get origTarget ( ) : AdaptElement | null | undefined { const orig = this [ origElement ] ; return orig ; } get mountedOrig ( ) : AdaptMountedElement | null | undefined { return this . nextMounted ( ) ; } nextMounted ( pred = ( ) => true ) { const elem = findFirst ( this , ( hand ) => { return isMountedElement ( hand . origTarget ) && pred ( hand . origTarget ) ; } ) as AdaptMountedElement ; if ( elem == null ) return elem ; if ( ! isMountedElement ( elem ) ) return undefined ; return elem ; } get target ( ) : AdaptElement | null | undefined { if ( this . origTarget === undefined ) return undefined ; return findLast ( this ) ; } toString ( ) { return `Handle(${ this . id })` ; } toJSON ( ) { const el = this . target ; const target = isMountedElement ( el ) ? el . keyPath : null ; const ret = { __adaptIsHandle : handleSignature , target , urn : handleUrn } ; if ( this . name !== undefined ) ret . name = this . name ; return ret ; } origDebug ( ) { const orig = this [ origElement ] ; if ( orig === undefined ) return "Original element: <unassociated>" ; if ( orig === null ) return "Original element: <null>" ; const path = isMountedElement ( orig ) ? orig . path : "<not mounted>" ; const name = orig . componentName || "<anonymous>" ; return `Original element type ${ name }, path: ${ path }` ; } } tagConstructor ( HandleImpl , "adapt" ) ; function isHandleImpl ( val ) { return isInstance ( val , HandleImpl , "adapt" ) ; } param name export function handle < I extends object = GenericInstance > ( name ? ) { return new HandleImpl ( { name } ) ; } registerObject ( HandleImpl , "HandleImpl" , module ) ; export const handleUrn = findMummyUrn ( HandleImpl ) ;