///|
/// The browser `FormData` payload supplied to React form Actions.
///
/// Convert it with `to_dom_form_data` when the complete file-aware dom-ffi
/// `FormData` API is needed.
#external
pub type ReactFormData
///|
/// Returns the same browser object as dom-ffi `FormData`, without copying its
/// entries. This preserves the React Action callback API while exposing
/// file-aware `FormData` operations.
///
/// [FormData](https://developer.mozilla.org/en-US/docs/Web/API/FormData)
pub fn ReactFormData::to_dom_form_data(self : ReactFormData) -> @dom.FormData = "%identity"
///|
/// Returns a string form field, or `None` when the field is absent or is a
/// non-string value such as a File.
pub fn ReactFormData::get_string(self : ReactFormData, key : String) -> String? {
match self.to_dom_form_data().get(key) {
Some(@dom.FormDataValue::Text(value)) => Some(value)
_ => None
}
}
///|
/// A typed React `action` or `formAction` property.
struct FormAction {
js_value : JsObscure
}
///|
fn FormAction::from_js_value(value : JsObscure) -> FormAction {
FormAction::{ js_value: value }
}
///|
fn FormAction::to_js_value(self : FormAction) -> JsObscure {
self.js_value
}
///|
/// Wraps a `use_action_state` dispatcher for a form `action` or submit-control
/// `formAction` property.
pub fn form_action(dispatch : (ReactFormData) -> Unit) -> FormAction {
FormAction::from_js_value(@dom.v_to_js_obscure(dispatch))
}
///|
/// Converts a MoonBit async form Action into a JavaScript Promise-returning
/// React Action. Rejections are forwarded to React by the official
/// `moonbitlang/async/js_async` bridge.
pub fn async_form_action(action : async (ReactFormData) -> Unit) -> FormAction {
let js_action = fn(data : ReactFormData) {
@js_async.Promise::from_async(async fn() { action(data) })
}
FormAction::from_js_value(@dom.v_to_js_obscure(js_action))
}
///|
/// Stores state managed by an asynchronous React Action. The MoonBit async
/// reducer is exported as a JavaScript Promise so React can keep `is_pending`
/// true until it settles and can serialize queued Actions.
pub fn[S, A] use_async_action_state(
initial : S,
action : async (S, A) -> S,
) -> (S, (A) -> Unit, Bool) {
let js_action = fn(previous : S, payload : A) {
@js_async.Promise::from_async(async fn() { action(previous, payload) })
}
let pair = react_use_action_state(
@dom.v_to_js_obscure(js_action),
any_to_js_value(initial),
)
let state : S = any_from_js_value(pair.get("0"))
let dispatch = fn1_from_js_obscure(pair.get("1"))
let is_pending : Bool = any_from_js_value(pair.get("2"))
(state, fn(value : A) { dispatch(any_to_js_value(value)) }, is_pending)
}
///|
extern "js" fn react_use_optimistic(
value : JsObscure,
reducer : JsObscure,
) -> JsObscure =
#| (value, reducer) => globalThis.React.useOptimistic(value, reducer)
///|
/// Returns a temporary optimistic state and a typed dispatcher. React restores
/// the supplied base value when the surrounding Action finishes unless the
/// owner commits a new base value.
pub fn[S, A] use_optimistic(
value : S,
reducer : (S, A) -> S,
) -> (S, (A) -> Unit) {
let pair = react_use_optimistic(
any_to_js_value(value),
any_to_js_value(reducer),
)
let state : S = any_from_js_value(pair.get("0"))
let dispatch = fn1_from_js_obscure(pair.get("1"))
(state, fn(value : A) { dispatch(any_to_js_value(value)) })
}
///|
extern "js" fn react_use_form_status() -> JsObscure =
#| () => globalThis.ReactDOM.useFormStatus()
///|
/// Status for the nearest parent React form. A component calling this Hook
/// must be rendered below the form rather than in the same component that
/// creates it.
struct FormStatus {
js_value : JsObscure
}
///|
/// Reads React's submission status for the nearest parent form.
///
/// Call this Hook from a component rendered inside the form. A component that
/// creates the form cannot observe that same form's status with this Hook.
pub fn use_form_status() -> FormStatus {
FormStatus::{ js_value: react_use_form_status() }
}
///|
extern "js" fn form_status_pending(status : JsObscure) -> Bool =
#| (status) => status.pending === true
///|
/// Returns whether the nearest parent form Action is currently pending.
pub fn FormStatus::pending(self : FormStatus) -> Bool {
form_status_pending(self.js_value)
}
///|
extern "js" fn form_status_data(status : JsObscure) -> JsObscure =
#| (status) => status.data ?? null
///|
/// Returns the submitted data while the parent form Action is pending.
pub fn FormStatus::data(self : FormStatus) -> ReactFormData? {
let value = form_status_data(self.js_value)
if value.is_nil() {
None
} else {
Some(@dom.js_obscure_to_v(value))
}
}
///|
extern "js" fn form_status_method(status : JsObscure) -> String =
#| (status) => status.method ?? "get"
///|
/// Returns the HTTP method used by the pending parent-form submission.
/// React reports `"get"` when no active submission supplies another method.
pub fn FormStatus::submission_method(self : FormStatus) -> String {
form_status_method(self.js_value)
}
///|
extern "js" fn form_status_action(status : JsObscure) -> JsObscure =
#| (status) => status.action ?? null
///|
/// Returns the Action function associated with the pending submission, or
/// `None` when the parent form has no active Action submission.
pub fn FormStatus::action(self : FormStatus) -> FormAction? {
let value = form_status_action(self.js_value)
if value.is_nil() {
None
} else {
Some(FormAction::from_js_value(value))
}
}