@@ -24,8 +24,32 @@ export default function register(Component, tagName, propNames, options) {
24
24
) ;
25
25
}
26
26
27
+ function ContextProvider ( props ) {
28
+ this . getChildContext = ( ) => props . context ;
29
+ // eslint-disable-next-line no-unused-vars
30
+ const { context, children, ...rest } = props ;
31
+ return cloneElement ( children , rest ) ;
32
+ }
33
+
27
34
function connectedCallback ( ) {
28
- this . _vdom = toVdom ( this , this . _vdomComponent ) ;
35
+ // Obtain a reference to the previous context by pinging the nearest
36
+ // higher up node that was rendered with Preact. If one Preact component
37
+ // higher up receives our ping, it will set the `detail` property of
38
+ // our custom event. This works because events are dispatched
39
+ // synchronously.
40
+ const event = new CustomEvent ( '_preact' , {
41
+ detail : { } ,
42
+ bubbles : true ,
43
+ cancelable : true ,
44
+ } ) ;
45
+ this . dispatchEvent ( event ) ;
46
+ const context = event . detail . context ;
47
+
48
+ this . _vdom = h (
49
+ ContextProvider ,
50
+ { context } ,
51
+ toVdom ( this , true , this . _vdomComponent )
52
+ ) ;
29
53
( this . hasAttribute ( 'hydrate' ) ? hydrate : render ) ( this . _vdom , this . _root ) ;
30
54
}
31
55
@@ -46,7 +70,32 @@ function disconnectedCallback() {
46
70
render ( ( this . _vdom = null ) , this . _root ) ;
47
71
}
48
72
49
- function toVdom ( element , nodeName ) {
73
+ /**
74
+ * Pass an event listener to each `<slot>` that "forwards" the current
75
+ * context value to the rendered child. The child will trigger a custom
76
+ * event, where will add the context value to. Because events work
77
+ * synchronously, the child can immediately pull of the value right
78
+ * after having fired the event.
79
+ */
80
+ function Slot ( props , context ) {
81
+ const ref = ( r ) => {
82
+ if ( ! r ) {
83
+ this . ref . removeEventListener ( '_preact' , this . _listener ) ;
84
+ } else {
85
+ this . ref = r ;
86
+ if ( ! this . _listener ) {
87
+ this . _listener = ( event ) => {
88
+ event . stopPropagation ( ) ;
89
+ event . detail . context = context ;
90
+ } ;
91
+ r . addEventListener ( '_preact' , this . _listener ) ;
92
+ }
93
+ }
94
+ } ;
95
+ return h ( 'slot' , { ...props , ref } ) ;
96
+ }
97
+
98
+ function toVdom ( element , wrap , nodeName ) {
50
99
if ( element . nodeType === 3 ) return element . data ;
51
100
if ( element . nodeType !== 1 ) return null ;
52
101
let children = [ ] ,
@@ -62,14 +111,17 @@ function toVdom(element, nodeName) {
62
111
}
63
112
64
113
for ( i = cn . length ; i -- ; ) {
65
- const vnode = toVdom ( cn [ i ] ) ;
114
+ const vnode = toVdom ( cn [ i ] , false ) ;
66
115
// Move slots correctly
67
116
const name = cn [ i ] . slot ;
68
117
if ( name ) {
69
- props [ name ] = h ( 'slot' , { name } , vnode ) ;
118
+ props [ name ] = h ( Slot , { name } , vnode ) ;
70
119
} else {
71
120
children [ i ] = vnode ;
72
121
}
73
122
}
74
- return h ( nodeName || element . nodeName . toLowerCase ( ) , props , children ) ;
123
+
124
+ // Only wrap the topmost node with a slot
125
+ const wrappedChildren = wrap ? h ( Slot , null , children ) : children ;
126
+ return h ( nodeName || element . nodeName . toLowerCase ( ) , props , wrappedChildren ) ;
75
127
}
0 commit comments