-
-
Notifications
You must be signed in to change notification settings - Fork 80
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Dialog with input element does not behave correctly #200
Comments
In case this helps, here's the code I use for a dialog. In short, I make it a component. // /src/app/element/dialog.gleam
import gleam/dict.{type Dict}
import gleam/dynamic.{type DecodeErrors, type Decoder, type Dynamic}
import gleam/float
import gleam/javascript/promise
import gleam/json
import gleam/option.{type Option, None, Some}
import gleam/result
import lustre
import lustre/attribute.{type Attribute} as attr
import lustre/effect.{type Effect}
import lustre/element.{type Element}
import lustre/element/html
import lustre/event
const dialog_element_name = "quorvz-dialog"
const close_event_name = "close"
pub fn register() -> Result(Nil, lustre.Error) {
let dialog = lustre.component(init, update, view, on_attr_change())
lustre.register(dialog, dialog_element_name)
}
pub opaque type DialogProps(a) {
Props(class: String, open: Bool, on_close: Option(a))
}
pub fn props() -> DialogProps(a) {
Props(class: "", open: False, on_close: None)
}
pub fn with_class(props: DialogProps(a), class: String) -> DialogProps(a) {
Props(..props, class:)
}
pub fn with_open(props: DialogProps(a), open: Bool) -> DialogProps(a) {
Props(..props, open:)
}
pub fn with_on_close(props: DialogProps(a), on_close: a) {
Props(..props, on_close: Some(on_close))
}
pub fn dialog(props: DialogProps(a), children: List(Element(a))) -> Element(a) {
let Props(class:, open:, on_close:) = props
let open = case open {
True -> "true"
False -> "false"
}
let on_close = case on_close {
None -> attr.none()
Some(on_close) -> on_close_handler(on_close)
}
element.element(
dialog_element_name,
[attr.class(class), attr.attribute("open", open), on_close],
children,
)
}
fn on_close_handler(on_close) {
event.on(close_event_name, fn(_) { Ok(on_close) })
}
pub opaque type Model {
Model(id: String, class: String)
}
pub opaque type Message {
ClassAttributeChanged(String)
OpenAttributeChanged(Bool)
UserTriggeredEscapeOnDialogElement
}
fn init(_: Nil) -> #(Model, Effect(Message)) {
let id = float.random() |> float.to_string()
#(Model(id:, class: ""), effect.none())
}
fn on_attr_change() -> Dict(String, Decoder(Message)) {
dict.from_list([#("class", class_attr_decoder), #("open", open_attr_decoder)])
}
fn class_attr_decoder(value: Dynamic) -> Result(Message, DecodeErrors) {
use class <- result.try(dynamic.string(value))
Ok(ClassAttributeChanged(class))
}
fn open_attr_decoder(value: Dynamic) -> Result(Message, DecodeErrors) {
use open <- result.try(dynamic.string(value))
let open = case open {
"true" -> True
_ -> False
}
Ok(OpenAttributeChanged(open))
}
fn update(model: Model, msg: Message) -> #(Model, Effect(Message)) {
case msg {
ClassAttributeChanged(class) -> #(Model(..model, class:), effect.none())
UserTriggeredEscapeOnDialogElement -> #(
model,
event.emit(close_event_name, json.null()),
)
OpenAttributeChanged(open) -> #(
model,
set_dialog_open_state(model.id, open),
)
}
}
fn set_dialog_open_state(id: String, open: Bool) -> Effect(Message) {
use _ <- effect.from
// We need to wait because the open attribute change notification comes in before
// lustre has had a chance to render the DOM
let _ = {
use _ <- promise.await(promise.wait(0))
promise.resolve(do_set_dialog_open_state(dialog_element_name, id:, open:))
}
Nil
}
@external(javascript, "../../app.ffi.mjs", "setDialogOpenState")
fn do_set_dialog_open_state(
element_name element_name: String,
id id: String,
open open: Bool,
) -> Nil
fn view(model: Model) -> Element(Message) {
let Model(id:, class:) = model
html.dialog([attr.id(id), attr.class(class), on_escape_keydown()], [
html.slot([]),
])
}
fn on_escape_keydown() {
event.on("keydown", fn(e) {
use key <- result.try(dynamic.field("key", dynamic.string)(e))
case key {
"Escape" -> {
event.prevent_default(e)
Ok(UserTriggeredEscapeOnDialogElement)
}
_ -> Error([dynamic.DecodeError(expected: "", found: "", path: [])])
}
})
} // /src/app.ffi.mjs
export function setDialogOpenState(elementName, id, open) {
const dialogs = document.querySelectorAll(elementName);
dialogs.forEach((dialog) => {
const el = dialog.shadowRoot.getElementById(id);
if (el instanceof HTMLDialogElement) {
if (open) {
el.showModal();
} else {
el.close();
}
}
});
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
The first example does not work properly. On every input event, the dialog is closed.
In the second example, updating the value via ffi instead provides a workaround, so that the dialog with the input element does work properly (i.e. it is neither closed upon re-rendering nor does dragging the input slider "get stuck/interrupted" or similar).
The third example does work as well, it does not use a dialog at all.
First example (not working):
Second example (working, workaround ffi):
Third example (working, no dialog):
Please let me know, if I can provide any further information.
The text was updated successfully, but these errors were encountered: