@@ -750,11 +750,75 @@ class VirtualElement {
750750}
751751
752752
753+ /**
754+ * A "pass thru" virtual node whose children are managed by a render and an
755+ * unrender callback. The intent of this flavor of virtual node is to make
756+ * it easy to blend other kinds of virtualdom (eg React) into Phosphor's
757+ * virtualdom.
758+ *
759+ * #### Notes
760+ * User code will not typically create a `VirtualElementPass` node directly.
761+ * Instead, the `hpass()` function will be used to create an element tree.
762+ */
763+ export
764+ class VirtualElementPass {
765+ /**
766+ * The type of the node.
767+ *
768+ * This value can be used as a type guard for discriminating the
769+ * `VirtualNode` union type.
770+ */
771+ readonly type : 'passthru' = 'passthru' ;
772+
773+ /**
774+ * Construct a new virtual element pass thru node.
775+ *
776+ * @param tag - the tag of the parent element of this node. Once the parent
777+ * element is rendered, it will be passed as an argument to
778+ * renderer.render
779+ *
780+ * @param attrs - attributes that will assigned to the
781+ * parent element
782+ *
783+ * @param renderer - an object with render and unrender
784+ * functions, each of which should take a single argument of type
785+ * HTMLElement and return nothing. If null, the parent element
786+ * will be rendered barren without any children.
787+ */
788+ constructor ( readonly tag : string , readonly attrs : ElementAttrs , readonly renderer : VirtualElementPass . IRenderer | null ) { }
789+
790+ render ( host : HTMLElement ) : void {
791+ // skip actual render if renderer is null
792+ if ( this . renderer ) {
793+ this . renderer . render ( host ) ;
794+ }
795+ }
796+
797+ unrender ( host : HTMLElement ) : void {
798+ // skip actual unrender if renderer is null
799+ if ( this . renderer ) {
800+ this . renderer . unrender ( host ) ;
801+ }
802+ }
803+ }
804+
805+
806+ /**
807+ * The namespace for the VirtualElementPass class statics.
808+ */
809+ export namespace VirtualElementPass {
810+ export type IRenderer = {
811+ render : ( host : HTMLElement ) => void ,
812+ unrender : ( host : HTMLElement ) => void
813+ } ;
814+ }
815+
816+
753817/**
754818 * A type alias for a general virtual node.
755819 */
756820export
757- type VirtualNode = VirtualElement | VirtualText ;
821+ type VirtualNode = VirtualElement | VirtualElementPass | VirtualText ;
758822
759823
760824/**
@@ -792,6 +856,8 @@ export function h(tag: string): VirtualElement {
792856 children . push ( arg ) ;
793857 } else if ( arg instanceof VirtualElement ) {
794858 children . push ( arg ) ;
859+ } else if ( arg instanceof VirtualElementPass ) {
860+ children . push ( arg ) ;
795861 } else if ( arg instanceof Array ) {
796862 extend ( children , arg ) ;
797863 } else if ( i === 1 && arg && typeof arg === 'object' ) {
@@ -808,6 +874,8 @@ export function h(tag: string): VirtualElement {
808874 array . push ( child ) ;
809875 } else if ( child instanceof VirtualElement ) {
810876 array . push ( child ) ;
877+ } else if ( child instanceof VirtualElementPass ) {
878+ array . push ( child ) ;
811879 }
812880 }
813881 }
@@ -934,6 +1002,41 @@ namespace h {
9341002}
9351003
9361004
1005+ /**
1006+ * Create a new "pass thru" virtual element node.
1007+ *
1008+ * @param tag - The tag name for the parent element.
1009+ *
1010+ * @param attrs - The attributes for the parent element, if any.
1011+ *
1012+ * @param renderer - an object with render and unrender functions, if any.
1013+ *
1014+ * @returns A new "pass thru" virtual element node for the given parameters.
1015+ *
1016+ */
1017+ export function hpass ( tag : string , renderer ?: VirtualElementPass . IRenderer ) : VirtualElementPass ;
1018+ export function hpass ( tag : string , attrs : ElementAttrs , renderer ?: VirtualElementPass . IRenderer ) : VirtualElementPass ;
1019+ export function hpass ( tag : string ) : VirtualElementPass {
1020+ let attrs : ElementAttrs = { } ;
1021+ let renderer : VirtualElementPass . IRenderer | null = null ;
1022+
1023+ if ( arguments . length === 2 ) {
1024+ const arg = arguments [ 1 ] ;
1025+
1026+ if ( "render" in arg && "unrender" in arg ) {
1027+ renderer = arg ;
1028+ } else {
1029+ attrs = arg ;
1030+ }
1031+ } else if ( arguments . length === 3 ) {
1032+ attrs = arguments [ 1 ] ;
1033+ renderer = arguments [ 2 ] ;
1034+ }
1035+
1036+ return new VirtualElementPass ( tag , attrs , renderer ) ;
1037+ }
1038+
1039+
9371040/**
9381041 * The namespace for the virtual DOM rendering functions.
9391042 */
@@ -952,8 +1055,10 @@ namespace VirtualDOM {
9521055 *
9531056 * If virtual diffing is desired, use the `render` function instead.
9541057 */
955- export
956- function realize ( node : VirtualElement ) : HTMLElement {
1058+ export function realize ( node : VirtualText ) : Text ;
1059+ export function realize ( node : VirtualElement ) : HTMLElement ;
1060+ export function realize ( node : VirtualElementPass ) : HTMLElement ;
1061+ export function realize ( node : VirtualNode ) : HTMLElement | Text {
9571062 return Private . createDOMNode ( node ) ;
9581063 }
9591064
@@ -990,14 +1095,12 @@ namespace Private {
9901095 /**
9911096 * A weak mapping of host element to virtual DOM content.
9921097 */
993- export
994- const hostMap = new WeakMap < HTMLElement , ReadonlyArray < VirtualNode > > ( ) ;
1098+ export const hostMap = new WeakMap < HTMLElement , ReadonlyArray < VirtualNode > > ( ) ;
9951099
9961100 /**
9971101 * Cast a content value to a content array.
9981102 */
999- export
1000- function asContentArray ( value : VirtualNode | ReadonlyArray < VirtualNode > | null ) : ReadonlyArray < VirtualNode > {
1103+ export function asContentArray ( value : VirtualNode | ReadonlyArray < VirtualNode > | null ) : ReadonlyArray < VirtualNode > {
10011104 if ( ! value ) {
10021105 return [ ] ;
10031106 }
@@ -1010,32 +1113,42 @@ namespace Private {
10101113 /**
10111114 * Create a new DOM element for a virtual node.
10121115 */
1013- export
1014- function createDOMNode ( node : VirtualText ) : Text ;
1015- export
1016- function createDOMNode ( node : VirtualElement ) : HTMLElement ;
1017- export
1018- function createDOMNode ( node : VirtualNode ) : HTMLElement | Text ;
1019- export
1020- function createDOMNode ( node : VirtualNode ) : HTMLElement | Text {
1021- // Create a text node for a virtual text node.
1022- if ( node . type === 'text' ) {
1023- return document . createTextNode ( node . content ) ;
1024- }
1116+ export function createDOMNode ( node : VirtualText ) : Text ;
1117+ export function createDOMNode ( node : VirtualElement ) : HTMLElement ;
1118+ export function createDOMNode ( node : VirtualElementPass ) : HTMLElement ;
1119+ export function createDOMNode ( node : VirtualNode ) : HTMLElement | Text ;
1120+ export function createDOMNode ( node : VirtualNode , host : HTMLElement | null ) : HTMLElement | Text ;
1121+ export function createDOMNode ( node : VirtualNode , host : HTMLElement | null , before : Node | null ) : HTMLElement | Text ;
1122+ export function createDOMNode ( node : VirtualNode ) : HTMLElement | Text {
1123+ let host = arguments [ 1 ] || null ;
1124+ const before = arguments [ 2 ] || null ;
1125+
1126+ if ( host ) {
1127+ host . insertBefore ( createDOMNode ( node ) , before ) ;
1128+ } else {
1129+ // Create a text node for a virtual text node.
1130+ if ( node . type === 'text' ) {
1131+ return document . createTextNode ( node . content ) ;
1132+ }
10251133
1026- // Create the HTML element with the specified tag.
1027- let element = document . createElement ( node . tag ) ;
1134+ // Create the HTML element with the specified tag.
1135+ host = document . createElement ( node . tag ) ;
10281136
1029- // Add the attributes for the new element.
1030- addAttrs ( element , node . attrs ) ;
1137+ // Add the attributes for the new element.
1138+ addAttrs ( host , node . attrs ) ;
10311139
1032- // Recursively populate the element with child content.
1033- for ( let i = 0 , n = node . children . length ; i < n ; ++ i ) {
1034- element . appendChild ( createDOMNode ( node . children [ i ] ) ) ;
1140+ if ( node . type === 'passthru' ) {
1141+ node . render ( host ) ;
1142+ return host ;
1143+ }
1144+
1145+ // Recursively populate the element with child content.
1146+ for ( let i = 0 , n = node . children . length ; i < n ; ++ i ) {
1147+ createDOMNode ( node . children [ i ] , host ) ;
1148+ }
10351149 }
10361150
1037- // Return the populated element.
1038- return element ;
1151+ return host ;
10391152 }
10401153
10411154 /**
@@ -1044,8 +1157,7 @@ namespace Private {
10441157 * This is the core "diff" algorithm. There is no explicit "patch"
10451158 * phase. The host is patched at each step as the diff progresses.
10461159 */
1047- export
1048- function updateContent ( host : HTMLElement , oldContent : ReadonlyArray < VirtualNode > , newContent : ReadonlyArray < VirtualNode > ) : void {
1160+ export function updateContent ( host : HTMLElement , oldContent : ReadonlyArray < VirtualNode > , newContent : ReadonlyArray < VirtualNode > ) : void {
10491161 // Bail early if the content is identical.
10501162 if ( oldContent === newContent ) {
10511163 return ;
@@ -1068,7 +1180,7 @@ namespace Private {
10681180
10691181 // If the old content is exhausted, create a new node.
10701182 if ( i >= oldCopy . length ) {
1071- host . appendChild ( createDOMNode ( newContent [ i ] ) ) ;
1183+ createDOMNode ( newContent [ i ] , host ) ;
10721184 continue ;
10731185 }
10741186
@@ -1089,11 +1201,19 @@ namespace Private {
10891201 continue ;
10901202 }
10911203
1092- // If the old or new node is a text node, the other node is now
1093- // known to be an element node, so create and insert a new node.
1094- if ( oldVNode . type === 'text' || newVNode . type === 'text' ) {
1204+ // Handle the case of passthru update.
1205+ if ( oldVNode . type === 'passthru' && newVNode . type === 'passthru' ) {
1206+ newVNode . render ( currElem as HTMLElement ) ;
1207+ currElem = currElem ! . nextSibling ;
1208+ continue ;
1209+ }
1210+
1211+ // If the types of the old and new nodes differ,
1212+ // create and insert a new node.
1213+ if ( oldVNode . type === 'text' || newVNode . type === 'text' ||
1214+ oldVNode . type === 'passthru' || newVNode . type === 'passthru' ) {
10951215 ArrayExt . insert ( oldCopy , i , newVNode ) ;
1096- host . insertBefore ( createDOMNode ( newVNode ) , currElem ) ;
1216+ createDOMNode ( newVNode , host , currElem ) ;
10971217 continue ;
10981218 }
10991219
@@ -1126,14 +1246,14 @@ namespace Private {
11261246 let oldKey = oldVNode . attrs . key ;
11271247 if ( oldKey && oldKey !== newKey ) {
11281248 ArrayExt . insert ( oldCopy , i , newVNode ) ;
1129- host . insertBefore ( createDOMNode ( newVNode ) , currElem ) ;
1249+ createDOMNode ( newVNode , host , currElem ) ;
11301250 continue ;
11311251 }
11321252
11331253 // If the tags are different, create a new node.
11341254 if ( oldVNode . tag !== newVNode . tag ) {
11351255 ArrayExt . insert ( oldCopy , i , newVNode ) ;
1136- host . insertBefore ( createDOMNode ( newVNode ) , currElem ) ;
1256+ createDOMNode ( newVNode , host , currElem ) ;
11371257 continue ;
11381258 }
11391259
@@ -1149,9 +1269,32 @@ namespace Private {
11491269 currElem = currElem ! . nextSibling ;
11501270 }
11511271
1272+ // Cleanup stale DOM
1273+ removeContent ( host , oldCopy , newCount , true ) ;
1274+ }
1275+
1276+ /**
1277+ * Handle cleanup of stale vdom and its associated DOM. Stale nodes are
1278+ * traversed recursively and any needed explicit cleanup is carried out (
1279+ * in particular, the unrender callback of VirtualElementPass nodes). The
1280+ * stale children of the top level node are removed using removeChild.
1281+ */
1282+ function removeContent ( host : HTMLElement , oldContent : ReadonlyArray < VirtualNode > , newCount : number , _sentinel = false ) {
11521283 // Dispose of the old nodes pushed to the end of the host.
1153- for ( let i = oldCopy . length - newCount ; i > 0 ; -- i ) {
1154- host . removeChild ( host . lastChild ! ) ;
1284+ for ( let i = oldContent . length - 1 ; i >= newCount ; -- i ) {
1285+ const oldNode = oldContent [ i ] ;
1286+ const child = ( _sentinel ? host . lastChild : host . childNodes [ i ] ) as HTMLElement ;
1287+
1288+ // recursively clean up host children
1289+ if ( oldNode . type === 'text' ) { } else if ( oldNode . type === 'passthru' ) {
1290+ oldNode . unrender ( child ! ) ;
1291+ } else {
1292+ removeContent ( child ! , oldNode . children , 0 ) ;
1293+ }
1294+
1295+ if ( _sentinel ) {
1296+ host . removeChild ( child ! ) ;
1297+ }
11551298 }
11561299 }
11571300
0 commit comments