@@ -1600,91 +1600,80 @@ function Layout({ saveUpdateHook, sendEvent, loadImportSource }) {
16001600 }
16011601}
16021602
1603- function Element ( { model, key } ) {
1603+ function Element ( { model } ) {
16041604 if ( model . importSource ) {
16051605 return html `< ${ ImportedElement } model =${ model } /> ` ;
16061606 } else {
16071607 return html `< ${ StandardElement } model =${ model } /> ` ;
16081608 }
16091609}
16101610
1611- function elementChildren ( modelChildren ) {
1612- if ( ! modelChildren ) {
1613- return [ ] ;
1614- } else {
1615- return modelChildren . map ( ( child ) => {
1616- switch ( typeof child ) {
1617- case "object" :
1618- return html `< ${ Element } key =${ child . key } model=${ child } /> ` ;
1619- case "string" :
1620- return child ;
1621- }
1622- } ) ;
1623- }
1624- }
1625-
16261611function StandardElement ( { model } ) {
16271612 const config = react . useContext ( LayoutConfigContext ) ;
16281613 const children = elementChildren ( model . children ) ;
16291614 const attributes = elementAttributes ( model , config . sendEvent ) ;
1630- if ( model . children && model . children . length ) {
1631- return html `< ${ model . tagName } ...${ attributes } > ${ children } </ /> ` ;
1632- } else {
1633- return html `< ${ model . tagName } ...${ attributes } /> ` ;
1634- }
1615+ // Use createElement here to avoid warning about variable numbers of children not
1616+ // having keys. Warning about this must now be the responsibility of the server
1617+ // providing the models instead of the client rendering them.
1618+ return react . createElement ( model . tagName , attributes , ...children ) ;
16351619}
16361620
16371621function ImportedElement ( { model } ) {
16381622 const config = react . useContext ( LayoutConfigContext ) ;
1639- config . sendEvent ;
1623+
1624+ const importSourceFallback = model . importSource . fallback ;
1625+ const [ importSource , setImportSource ] = react . useState ( null ) ;
1626+
1627+ if ( ! importSource ) {
1628+ // load the import source in the background
1629+ loadImportSource$1 ( config , model . importSource ) . then ( setImportSource ) ;
1630+
1631+ // display a fallback if one was given
1632+ if ( ! importSourceFallback ) {
1633+ return html `< div /> ` ;
1634+ } else if ( typeof importSourceFallback == "string" ) {
1635+ return html `< div > ${ importSourceFallback } </ div > ` ;
1636+ } else {
1637+ return html `< ${ StandardElement } model =${ importSourceFallback } /> ` ;
1638+ }
1639+ } else {
1640+ return html `< ${ RenderImportedElement }
1641+ model =${ model }
1642+ importSource=${ importSource }
1643+ /> ` ;
1644+ }
1645+ }
1646+
1647+ function RenderImportedElement ( ) {
1648+ react . useContext ( LayoutConfigContext ) ;
16401649 const mountPoint = react . useRef ( null ) ;
1641- const fallback = model . importSource . fallback ;
1642- const importSource = useConst ( ( ) =>
1643- loadFromImportSource ( config , model . importSource )
1644- ) ;
1650+ const sourceBinding = react . useRef ( null ) ;
16451651
16461652 react . useEffect ( ( ) => {
1647- if ( fallback ) {
1648- importSource . then ( ( ) => {
1649- reactDom . unmountComponentAtNode ( mountPoint . current ) ;
1650- if ( mountPoint . current . children ) {
1651- mountPoint . current . removeChild ( mountPoint . current . children [ 0 ] ) ;
1652- }
1653- } ) ;
1654- }
1653+ sourceBinding . current = importSource . bind ( mountPoint . current ) ;
1654+ return ( ) => {
1655+ sourceBinding . current . unmount ( ) ;
1656+ } ;
16551657 } , [ ] ) ;
16561658
16571659 // this effect must run every time in case the model has changed
1658- react . useEffect ( ( ) => {
1659- importSource . then ( ( { createElement, renderElement } ) => {
1660- renderElement (
1661- createElement (
1662- model . tagName ,
1663- elementAttributes ( model , config . sendEvent ) ,
1664- model . children
1665- ) ,
1666- mountPoint . current
1667- ) ;
1668- } ) ;
1669- } ) ;
1660+ react . useEffect ( ( ) => sourceBinding . current . render ( model ) ) ;
16701661
1671- react . useEffect (
1672- ( ) => ( ) =>
1673- importSource . then ( ( { unmountElement } ) =>
1674- unmountElement ( mountPoint . current )
1675- ) ,
1676- [ ]
1677- ) ;
16781662
1679- if ( ! fallback ) {
1680- return html ` < div ref = ${ mountPoint } / > ` ;
1681- } else if ( typeof fallback == "string" ) {
1682- // need the second div there so we can removeChild above
1683- return html ` < div ref = ${ mountPoint } > < div > ${ fallback } </ div > </ div > ` ;
1663+ }
1664+
1665+ function elementChildren ( modelChildren ) {
1666+ if ( ! modelChildren ) {
1667+ return [ ] ;
16841668 } else {
1685- return html `< div ref =${ mountPoint } >
1686- < ${ StandardElement } model =${ fallback } />
1687- </ div > ` ;
1669+ return modelChildren . map ( ( child ) => {
1670+ switch ( typeof child ) {
1671+ case "object" :
1672+ return html `< ${ Element } key =${ child . key } model=${ child } /> ` ;
1673+ case "string" :
1674+ return child ;
1675+ }
1676+ } ) ;
16881677 }
16891678}
16901679
@@ -1715,34 +1704,46 @@ function eventHandler(sendEvent, eventSpec) {
17151704 return value ;
17161705 }
17171706 } ) ;
1718- new Promise ( ( resolve , reject ) => {
1719- const msg = {
1720- data : data ,
1721- target : eventSpec [ "target" ] ,
1722- } ;
1723- sendEvent ( msg ) ;
1724- resolve ( msg ) ;
1707+ sendEvent ( {
1708+ data : data ,
1709+ target : eventSpec [ "target" ] ,
17251710 } ) ;
17261711 } ;
17271712}
17281713
1729- function loadFromImportSource ( config , importSource ) {
1714+ function loadImportSource$1 ( config , importSource ) {
17301715 return config
17311716 . loadImportSource ( importSource . source , importSource . sourceType )
17321717 . then ( ( module ) => {
1733- if (
1734- typeof module . createElement == "function" &&
1735- typeof module . renderElement == "function" &&
1736- typeof module . unmountElement == "function"
1737- ) {
1718+ if ( typeof module . bind == "function" ) {
17381719 return {
1739- createElement : ( type , props , children ) =>
1740- module . createElement ( module [ type ] , props , children , config ) ,
1741- renderElement : module . renderElement ,
1742- unmountElement : module . unmountElement ,
1720+ bind : ( node ) => {
1721+ const binding = module . bind ( node , config ) ;
1722+ if (
1723+ typeof binding . render == "function" &&
1724+ typeof binding . unmount == "function"
1725+ ) {
1726+ return {
1727+ render : ( model ) => {
1728+ binding . render (
1729+ module [ model . tagName ] ,
1730+ elementAttributes ( model , config . sendEvent ) ,
1731+ model . children
1732+ ) ;
1733+ } ,
1734+ unmount : binding . unmount ,
1735+ } ;
1736+ } else {
1737+ console . error (
1738+ `${ importSource . source } returned an impropper binding`
1739+ ) ;
1740+ }
1741+ } ,
17431742 } ;
17441743 } else {
1745- console . error ( `${ module } does not expose the required interfaces` ) ;
1744+ console . error (
1745+ `${ importSource . source } did not export a function 'bind'`
1746+ ) ;
17461747 }
17471748 } ) ;
17481749}
@@ -1767,16 +1768,6 @@ function useForceUpdate() {
17671768 return react . useCallback ( ( ) => updateState ( { } ) , [ ] ) ;
17681769}
17691770
1770- function useConst ( func ) {
1771- const ref = react . useRef ( ) ;
1772-
1773- if ( ! ref . current ) {
1774- ref . current = func ( ) ;
1775- }
1776-
1777- return ref . current ;
1778- }
1779-
17801771function mountLayout ( mountElement , layoutProps ) {
17811772 reactDom . render ( react . createElement ( Layout , layoutProps ) , mountElement ) ;
17821773}
0 commit comments