Packages
lustre
1.0.0
5.7.1
5.7.0
5.6.0
5.5.2
5.5.1
5.5.0
5.4.0
5.3.5
5.3.4
5.3.3
5.3.2
5.3.1
5.3.0
5.2.1
5.2.0
5.1.1
5.1.0
5.0.3
5.0.2
5.0.1
5.0.0
4.6.4
4.6.3
4.6.2
4.6.1
4.6.0
4.5.1
4.5.0
4.4.4
4.4.3
4.4.1
4.4.0
4.3.6
4.3.5
4.3.4
4.3.3
4.3.2
4.3.1
4.3.0
4.2.6
4.2.5
4.2.4
4.2.3
4.2.2
4.2.1
4.2.0
4.1.8
4.1.7
4.1.6
4.1.5
4.1.4
4.1.3
4.1.2
4.1.1
4.1.0
4.0.0
4.0.0-rc1
4.0.0-rc.2
3.1.4
3.1.3
3.1.2
3.1.1
3.1.0
3.0.12
3.0.11
3.0.10
3.0.9
3.0.8
3.0.7
3.0.6
3.0.5
3.0.4
3.0.3
3.0.2
3.0.1
3.0.0
3.0.0-rc.8
3.0.0-rc.7
3.0.0-rc.6
3.0.0-rc.5
3.0.0-rc.4
3.0.0-rc.3
3.0.0-rc.2
3.0.0-rc.1
2.0.1
2.0.0
1.3.0
1.2.0
1.1.0
1.0.0
Create HTML templates, single page applications, Web Components, and real-time server components in Gleam!
Current section
Files
Jump to
Current section
Files
src/ffi.mjs
import * as React from 'react'
import * as ReactDOM from 'react-dom'
import * as Gleam from './gleam.mjs'
import * as Cmd from './lustre/cmd.mjs'
// -----------------------------------------------------------------------------
export const mount = ({ init, update, render }, selector) => {
const root = document.querySelector(selector)
if (!root) {
console.warn([
'[lustre] Oops, it looks like I couldn\'t find an element on the ',
'page matching the selector "' + selector + '".',
'',
'Hint: make sure you aren\'t running your script before the rest of ',
'the HTML document has been parsed! you can add the `defer` attribute ',
'to your script tag to make sure that can\'t happen.'
].join('\n'))
return new Gleam.Error()
}
// OK this looks sus, what's going on here? We want to be able to return the
// dispatch function given to us from `useReducer` so that the rest of our
// Gleam code *outside* the application and dispatch commands.
//
// That's great but because `useReducer` is part of the callback passed to
// `createElement` we can't just save it to a variable and return it. We
// immediately render the app, so this all happens synchronously, so there's
// no chance of accidentally returning `null` and our Gleam code consuming it
// as if it was a function.
let dispatch = null
const App = React.createElement(() => {
const [[state, cmds], $dispatch] = React.useReducer(([state, _], action) => update(state, action), init)
const el = render(state)
if (dispatch === null) dispatch = $dispatch
React.useEffect(() => {
for (const cmd of Cmd.to_list(cmds)) {
cmd($dispatch)
}
})
return typeof el == 'string'
? el
: el($dispatch)
})
ReactDOM.render(App, root)
return new Gleam.Ok(dispatch)
}
// -----------------------------------------------------------------------------
export const node = (tag, attributes, children) => (dispatch) => {
const props = attributes.toArray().map(attr => {
switch (attr.constructor.name) {
case "Attribute":
case "Property":
return [attr.name, attr.value]
case "Event":
return ['on' + capitalise(attr.name), (e) => attr.handler(e, dispatch)]
// This should Never Happen™️ but if it does we don't want everything
// to explode, so we'll print a friendly error, ignore the attribute
// and carry on as normal.
default: {
console.warn([
'[lustre] Oops, I\'m not sure how to handle attributes with ',
'the type "' + attr.constructor.name + '". Did you try calling ',
'this function from JavaScript by mistake?',
'',
'If not, it might be an error in lustre itself. Please open ',
'an issue at https://github.com/hayleigh-dot-dev/gleam-lustre/issues'
].join('\n'))
return []
}
}
})
return React.createElement(tag,
// React expects an object of "props" where the keys are attribute names
// like "class" or "onClick" and their corresponding values. Lustre's API
// works with lists, though.
//
// The snippet above converts our Gleam list of attributes to a JavaScript
// array of key/value pairs. This below turns that array into an object.
Object.fromEntries(props),
// Recursively pass down the dispatch function to all children. Text nodes
// – constructed below – aren't functions
...children.toArray().map(child => typeof child === 'function'
? child(dispatch)
: child
)
)
}
export const stateful = (init, render) => (dispatch) => {
const [state, setState] = React.useState(init)
return render(state, setState)(dispatch)
}
export const fragment = (children) => (dispatch) => {
return React.createElement(React.Fragment, null,
...children.toArray().map(child => typeof child === 'function'
? child(dispatch)
: child
)
)
}
// This is just an identity function! We need it because we want to trick Gleam
// into converting a `String` into an `Element(action)` .
export const text = (content) => content
// -----------------------------------------------------------------------------
export const map = (element, f) => (dispatch) => {
return element(action => dispatch(f(action)))
}
// -----------------------------------------------------------------------------
export const object = (entries) => Object.fromEntries(entries)
const capitalise = s => s && s[0].toUpperCase() + s.slice(1)