Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
ghivert committed Jul 20, 2024
1 parent 573f533 commit 6bfcdf7
Show file tree
Hide file tree
Showing 5 changed files with 151 additions and 22 deletions.
4 changes: 0 additions & 4 deletions src/events.ffi.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -142,10 +142,6 @@ export function pageY(event) {
return event.pageY
}

export function relatedTarget(event) {
return event.relatedTarget
}

export function screenX(event) {
return event.screenX
}
Expand Down
29 changes: 24 additions & 5 deletions src/main.gleam
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import gleam/int
import gleam/io
import gleam/option
import react
import react/attribute as a
import react/client
Expand All @@ -23,12 +25,28 @@ pub fn root() {
react.strict_mode([app()])
}

@external(javascript, "./react.ffi.mjs", "nativeLog")
fn native_log(value: a) -> a

fn counter() {
use <- react.component__("Counter")
use _, ref <- react.forward_ref_("Counter")
let #(counting, set_counting) = react.use_state_(0)
html.button([e.on_click(fn(_) { set_counting(fn(count) { count + 1 }) })], [
html.text("count is " <> int.to_string(counting)),
])
html.button(
[
a.ref(ref),
e.on_click(fn(_) {
case react.get_current(ref) {
option.None -> Nil
option.Some(_) -> {
native_log(ref)
Nil
}
}
set_counting(fn(count) { count + 1 })
}),
],
[html.text("count is " <> int.to_string(counting))],
)
}

fn nav_links() {
Expand All @@ -48,11 +66,12 @@ fn nav_links() {
pub fn app() {
let counter = counter()
use <- react.component__("App")
let ref = react.use_ref()
react.fragment([
nav_links(),
html.h1([], [html.text("Vite + Gleam + React")]),
html.div([a.class("card")], [
counter(),
counter(Nil, ref),
html.p([], [
html.text("Edit "),
html.code([], [html.text("src/main.gleam")]),
Expand Down
39 changes: 39 additions & 0 deletions src/react.ffi.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,29 @@ export function render(root, children) {
return root.render(children)
}

export function addForwardRef(Component) {
return new Proxy(Component, {
apply(target, _this, argumentsList) {
const props_ = argumentsList[0]
const ref = argumentsList[1]
const props = { ...props_, ref }
return jsx(React.forwardRef(target), props)
},
})
}

export function addChildrenForwardRef(Component) {
return new Proxy(Component, {
apply(target, _this, argumentsList) {
const props_ = argumentsList[0]
const ref = argumentsList[1]
const children = argumentsList[2]
const props = { ...props_, ref }
return jsx(React.forwardRef(withRefChildren(target), props, children))
},
})
}

export function withChildren(Component) {
return new Proxy(Component, {
apply(target, _, argumentsList) {
Expand All @@ -32,6 +55,17 @@ export function withChildren(Component) {
})
}

export function withRefChildren(Component) {
return new Proxy(Component, {
apply(target, _, argumentsList) {
const props = argumentsList[0]
const ref = argumentsList[1]
const children = gleam.List.fromArray(props.children)
return target(props, ref, children)
},
})
}

// Extract children from props to give it to function.
// This exist because `component` has shape `fn(props, children) -> Component`.
export function addChildrenProxy(Component) {
Expand Down Expand Up @@ -155,3 +189,8 @@ export function convertStyle(styles) {
export function innerHTML(html) {
return { __html: html }
}

export function nativeLog(a) {
console.log(a)
return a
}
83 changes: 75 additions & 8 deletions src/react.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,11 @@ import react/internals/coerce.{coerce}

// Component creation

/// Default Node in React. Use `component`-family functions to create components.
pub type Component

/// Create a React component, with a `name`, and a `render` function. `render`
/// will accept props, and a list of children.
pub fn component(
name name: String,
render render: fn(props, List(Component)) -> Component,
Expand All @@ -15,18 +18,40 @@ pub fn component(
|> add_children_proxy
}

/// Create a React component, with a `name`, and a `render` function. This
/// component does not accept children.
pub fn component_(name name: String, render render: fn(props) -> Component) {
render
|> set_function_name(name)
|> add_proxy
}

/// Create a React component, with a `name` and a `render` function. This
/// component does not accept children nor props.
pub fn component__(name name: String, render render: fn() -> Component) {
render
|> set_function_name(name)
|> add_empty_proxy
}

pub fn forward_ref(
name name: String,
render render: fn(props, Ref(ref), List(Component)) -> Component,
) {
render
|> set_function_name(name)
|> add_children_forward_ref
}

pub fn forward_ref_(
name name: String,
render render: fn(props, Ref(ref)) -> Component,
) {
render
|> set_function_name(name)
|> add_forward_ref
}

@external(javascript, "react", "memo")
pub fn memo(
component: fn(props, List(Component)) -> Component,
Expand Down Expand Up @@ -60,11 +85,6 @@ pub fn suspense(props: Suspense, children: List(Component)) -> Component
@external(javascript, "react", "useCallback")
pub fn use_callback(fun: function, dependencies: dependencies) -> a

pub type Context(a)

@external(javascript, "react", "useContext")
pub fn use_context(context: Context(a)) -> a

@external(javascript, "react", "useDebugValue")
pub fn use_debug_value(value: a) -> Nil

Expand Down Expand Up @@ -99,9 +119,6 @@ pub fn use_reducer_(
init: fn(initializer) -> state,
) -> #(state, fn(action) -> Nil)

@external(javascript, "react", "useRef")
pub fn use_ref(initial_value: a) -> Ref(a)

@external(javascript, "react", "useState")
pub fn use_state(initial_value: a) -> #(a, fn(a) -> Nil)

Expand All @@ -119,16 +136,56 @@ pub fn use_transition() -> #(Bool, fn() -> Nil)

// Refs

/// A Ref is a mutable data stored in React, persisted across renders.
/// They allow to keep track of a DOM node, a component data, or to store a
/// mutable variable in the component, outside of every component lifecycle.
pub type Ref(a)

/// Set the current value of a ref, overriding its existing content.
@external(javascript, "./react.ffi.mjs", "setCurrent")
pub fn set_current(of ref: Ref(a), with value: a) -> Nil

/// Get the current value of a ref.
@external(javascript, "./react.ffi.mjs", "getCurrent")
pub fn get_current(from ref: Ref(a)) -> a

/// Most used ref you'll want to create. They're automatically created to `None`,
/// and can be passed to `attribute.ref` or `use_imperative_handle`.
/// You probably don't want the ref value to be anything than `Option(a)`, unless
/// you have really strong reasons.
pub fn use_ref() {
use_ref_(option.None)
}

/// Use `use_ref` if you're trying to acquire a reference to a child or to a
/// component. Use `use_ref_` when you want to keep track of a data, like if
/// you're doing some side-effects, in conjuction with `get_current` and
/// `set_current`.
@external(javascript, "react", "useRef")
pub fn use_ref_(initial_value: a) -> Ref(a)

/// Use `use_imperative_handle` when you want to customize the data stored in
/// a ref. It's mostly used in conjuction with `forward_ref`.
pub fn use_imperative_handle(ref, handler, dependencies) {
use_imperative_handle_(ref, fn() { option.Some(handler()) }, dependencies)
}

/// Use `use_imperative_handle` by default, unless you really know what you're
/// doing.
@external(javascript, "react", "useImperativeHandle")
pub fn use_imperative_handle_(
ref: Ref(a),
handler: fn() -> a,
dependencies: b,
) -> Nil

// Contexts

pub type Context(a)

@external(javascript, "react", "useContext")
pub fn use_context(context: Context(a)) -> a

@external(javascript, "react", "createContext")
pub fn create_context(default_value default_value: Option(a)) -> Context(a)

Expand Down Expand Up @@ -173,6 +230,16 @@ fn add_proxy(a: fn(props) -> Component) -> fn(props) -> Component
@external(javascript, "./react.ffi.mjs", "addEmptyProxy")
fn add_empty_proxy(a: fn() -> Component) -> fn() -> Component

@external(javascript, "./react.ffi.mjs", "addChildrenForwardRef")
fn add_children_forward_ref(
a: fn(props, Ref(ref), List(Component)) -> Component,
) -> fn(props, Ref(ref), List(Component)) -> Component

@external(javascript, "./react.ffi.mjs", "addForwardRef")
fn add_forward_ref(
a: fn(props, Ref(ref)) -> Component,
) -> fn(props, Ref(ref)) -> Component

@external(javascript, "./react.ffi.mjs", "addChildrenProxy")
fn add_children_proxy(
a: fn(props, List(Component)) -> Component,
Expand Down
18 changes: 13 additions & 5 deletions src/react/attribute.gleam
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import gleam/dynamic
import gleam/option
import gleam/string
import react
import react/internals/attribute
Expand Down Expand Up @@ -36,13 +37,20 @@ pub fn dangerously_set_inner_html(inner_html: InnerHTML) {
attribute.attribute("dangerouslySetInnerHTML", inner_html)
}

/// A ref object from `react.use_ref`. Your ref will be filled with the DOM element for this node.
pub fn ref(ref: react.Ref(a)) {
attribute.attribute("ref", ref)
/// A ref object from `react.use_ref`. Your ref will be filled with the DOM
/// element for this node. Contrarily to JS React, when using a Ref, you're
/// forced to use an optional type here. Because when using this function, you
/// want to get a reference from a real DOM node, meaning at the initialization
/// of the reference, you won't have any data. Use `ref_` when you want full
/// control over the ref you send to the Component.
pub fn ref(ref: react.Ref(option.Option(a))) {
attribute.attribute("ref", fn(dom_ref) {
react.set_current(ref, option.Some(dom_ref))
})
}

/// A ref callback function. The callback will be provided with the DOM element for this node.
pub fn ref_(ref: fn(dynamic.Dynamic) -> Nil) {
/// A ref callback function. The callback will be provided with the DOM element for this node. Use this function to get control on the ref provided by the DOM node or the component.
pub fn ref_(ref: fn(a) -> Nil) {
attribute.attribute("ref", ref)
}

Expand Down

0 comments on commit 6bfcdf7

Please sign in to comment.