Hyperapp support for Web Components
componentApp(
componentName, // component name
elem => {
init?, // passed to hyperApp
style?, // component stylesheet
view, // passed to hyperApp
subscriptions?, // passed to hyperApp
dispatch?, // passed to hyperApp
externalState?, // state transform for the `onStateChange` event
methods?, // element methods
properties?, // element properties
cloneCSS? // boolean for cloning host CSS
}
)
import componentApp from "https://cdn.skypack.dev/hyperappwebcomponent"
HyperappWebComponent provides four mechanisms for communicating between the host and the component:
Here the component notifies the host whenever it's state changes. If externalState
is provided, the component will invoke it on the state before attaching it to the event detail
property:
componentApp('counter-', elem => {
return {
init: { counter: 0},
style: `button { color: red !important; }`,
view: s => html`
<div class="counter">
<h1>${s.counter}</h1>
<button onclick=${s => ({ ...s, counter: s.counter + 1 }}>+</button>
</div>`,
externalState: s => ({ counterValue: s.counter }),
}
})
componentApp('game-', elem => {
const CounterStateChange = (s, ev) => ({...s, counterValue: ev.detail.counterValue})
return {
init: { counterValue: 0 },
view: state => html`<counter- onstateChange=${CounterStateChange} />`,
}
})
game
recieves a notification on a counter
state change and here merges that state into it's own.
externalState
allows the component to only expose a part or a modified version of it's state to the outside. If omitted it defaults to s=>s
. To prevent exposing the state use s=>undefined
ev.srcElement
contains the source component
Here the component triggers a host event using an Effect:
componentApp('game-', elem => {
const CounterStateChange = (s, ev) => [
{ ...s, counterValue: ev.detail.counterValue },
ev.detail.counterValue==10 && elem.events.finish.effect
]
return {
init: { counterValue: 0 },
view: s => html`<counter- onstateChange=${CounterStateChange} />`,
}
})
componentApp('flow-', elem => {
return {
init: { mode: 'start' },
view: s => html`<game- onfinish=${s => ({ ...s, mode: 'finish' }} />`
}
})
elem.events.finish.effect
triggers the finish
event on the host (with a payload if provided) when counterValue
reaches 10
Here the host invoke a method on the component:
componentApp('score-', elem => {
return {
init: { count: 0 },
view: s => html`${s.count}`,
methods: {
addPoint: s => ({ ...s, count: s.count + 1 }),
},
}
})
componentApp('flow-', elem => {
const getSubComponent = name => elem.shadowRoot.querySelector(name);
const Action = s => {
const score = getSubComponent('score-');
return [ s, score && (dispatch => score.addPoint()) ];
}
return {
view: s => html`
<score- />
<button onclick=${Action}></button>`
}
})
Action
invokes score's method addPoint
dispatching the attached action.
Note the check for score
is not necessary here, but in actions triggered from init
the element does not exist yet.
componentApp('score-', elem => {
init: { scoreCount: 0 },
view: s => html`${s.scoreCount}`,
properties: {
counter: (s, v) => ({...s, scoreCount: Number(v) })
}
})
componentApp('flow-', elem => {
init: { flowCount: 0 },
view: s => html`
<score- counter=${s.flowCount}/>
<button onclick=${s => ({...s, flowCount: flowCount+1 })}></button>`
})
Every time the button is clicked, score's property counter
changes trigerring the attached action in score.