aboutsummaryrefslogtreecommitdiff
path: root/src/lustre.mjs
diff options
context:
space:
mode:
Diffstat (limited to 'src/lustre.mjs')
-rw-r--r--src/lustre.mjs111
1 files changed, 111 insertions, 0 deletions
diff --git a/src/lustre.mjs b/src/lustre.mjs
new file mode 100644
index 0000000..2352f01
--- /dev/null
+++ b/src/lustre.mjs
@@ -0,0 +1,111 @@
+import * as React from 'react'
+import * as ReactDOM from 'react-dom'
+import * as Gleam from './gleam.mjs'
+
+// -----------------------------------------------------------------------------
+
+export const mount = ({ init, update, view }, 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()
+ }
+
+ const App = React.createElement(() => {
+ const [state, dispatch] = React.useReducer(update, init)
+
+ return view(state)(dispatch)
+ })
+
+ ReactDOM.render(App, root)
+
+ return new Gleam.Ok()
+}
+
+// -----------------------------------------------------------------------------
+
+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)))
+}
+
+
+// -----------------------------------------------------------------------------
+
+const capitalise = s => s && s[0].toUpperCase() + s.slice(1)