@@ -1600,91 +1600,80 @@ function Layout({ saveUpdateHook, sendEvent, loadImportSource }) {
1600
1600
}
1601
1601
}
1602
1602
1603
- function Element ( { model, key } ) {
1603
+ function Element ( { model } ) {
1604
1604
if ( model . importSource ) {
1605
1605
return html `< ${ ImportedElement } model =${ model } /> ` ;
1606
1606
} else {
1607
1607
return html `< ${ StandardElement } model =${ model } /> ` ;
1608
1608
}
1609
1609
}
1610
1610
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
-
1626
1611
function StandardElement ( { model } ) {
1627
1612
const config = react . useContext ( LayoutConfigContext ) ;
1628
1613
const children = elementChildren ( model . children ) ;
1629
1614
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 ) ;
1635
1619
}
1636
1620
1637
1621
function ImportedElement ( { model } ) {
1638
1622
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 ) ;
1640
1649
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 ) ;
1645
1651
1646
1652
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
+ } ;
1655
1657
} , [ ] ) ;
1656
1658
1657
1659
// 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 ) ) ;
1670
1661
1671
- react . useEffect (
1672
- ( ) => ( ) =>
1673
- importSource . then ( ( { unmountElement } ) =>
1674
- unmountElement ( mountPoint . current )
1675
- ) ,
1676
- [ ]
1677
- ) ;
1678
1662
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 [ ] ;
1684
1668
} 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
+ } ) ;
1688
1677
}
1689
1678
}
1690
1679
@@ -1715,34 +1704,46 @@ function eventHandler(sendEvent, eventSpec) {
1715
1704
return value ;
1716
1705
}
1717
1706
} ) ;
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" ] ,
1725
1710
} ) ;
1726
1711
} ;
1727
1712
}
1728
1713
1729
- function loadFromImportSource ( config , importSource ) {
1714
+ function loadImportSource$1 ( config , importSource ) {
1730
1715
return config
1731
1716
. loadImportSource ( importSource . source , importSource . sourceType )
1732
1717
. 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" ) {
1738
1719
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
+ } ,
1743
1742
} ;
1744
1743
} else {
1745
- console . error ( `${ module } does not expose the required interfaces` ) ;
1744
+ console . error (
1745
+ `${ importSource . source } did not export a function 'bind'`
1746
+ ) ;
1746
1747
}
1747
1748
} ) ;
1748
1749
}
@@ -1767,16 +1768,6 @@ function useForceUpdate() {
1767
1768
return react . useCallback ( ( ) => updateState ( { } ) , [ ] ) ;
1768
1769
}
1769
1770
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
-
1780
1771
function mountLayout ( mountElement , layoutProps ) {
1781
1772
reactDom . render ( react . createElement ( Layout , layoutProps ) , mountElement ) ;
1782
1773
}
0 commit comments