LexiFi / ocaml-vdom

Elm architecture and (V)DOM for OCaml
MIT License
197 stars 13 forks source link

Tippy content #34

Closed nickbetteridge closed 4 years ago

nickbetteridge commented 4 years ago

I'm trying to modify Tippy to accept ('model, 'msg) Vdom.app as content instead of a string and I'm really having problems in defining the types and writing (a) the Tippy value record and (b) the handler for Vdom_blit.register

I have a tippybinding.mli of

type t
val t_of_js : Ojs.t -> t
val t_to_js : t -> Ojs.t
type props = { trigger: string option }
type options = { content: Js_browser.Element.t; trigger: string option }
val create : Js_browser.Element.t -> options -> t [@@js.global "tippy"]
val set_content : t -> Js_browser.Element.t -> unit [@@js.call "setContent"]
val set_props : t -> props -> unit [@@js.call "setProps"]
val destroy : t -> unit

and a tippy.mli of

val init: unit -> unit
type trigger = MouseEnter | Focus | Focusin | Click | Manual
val tooltip : ?trigger:trigger list -> content : ('model, 'msg) Vdom.app -> 'a Vdom.vdom

Is this a sensible approach or should I force a user to use a Vdom_blit.Custom.t for the contents?

mlasson commented 4 years ago

Hello Nick !

It seems that what you are proposing would be to run a whole vdom application inside the content element. It may be possible to hack something that does that but it would be super painful especially if you want the "tooltip app" to communicate with the "main app". I would not go that way.

Ideally, one would probably like to have the following API instead:

val tooltip : ?trigger:trigger list -> content:'a Vdom.vdom list -> 'a Vdom.vdom

That would be great and allow to have interactive tooltips that could send 'a messages (eg. with buttons inside the tooltip).

Unfortunately, there is no simple way to use the tippy library with "vdom content" because tippy moves the content element out of its parent which breaks invariants of the vdom blitter: the content element will be moved inside a div that is not controlled by the "blitter" and all event triggered inside the content element won't be caught by the blitter's event handler.

I think that if you want to have an interactive tooltip library whose content is a vdom node you could use the lower-level popper.js library and reimplement in vdom part of the tooltip logic. You would need to use a custom element to represent the popper instance (and dispose it when it's not needed). Popper will help to attach the parent of the custom element to the parent's parent. In order to have access to the parent's parent, you will need to use the "Vdom_blit.Custom.after_redraw" callback to create the instance (because before the first redraw, the parent of the custom element has not yet been inserted to the dom ...). The content of the parent will be the inside of the tooltip and parent of the parent will be the target of the tooltip.

As you can see, it is not super easy ... and it will require some work to implement the trigger (and untrigger) of the tooltip (ie. all the features that are implemented by tippy and not by popper).

To conclude, I would say that either you stick to string/html content and use tippy (and sacrifice all the benifits of vdom inside the tooltip) or you use popper.js to implement small tooltip library in vdom.

Out of curiosity, what are you trying to achieve with that ?

nickbetteridge commented 4 years ago

Hi Marc, Thanks for your comments - I was trying to do some svg popups with buttons. I'll look into the Popper.js api and tooltips (thanks :)) and see if I can get it to do what I need. I did do an orphan vdom component a few days ago using tippy, which resolved to an Element.t instead of a string in the bindings, and this seemed to work although I didn't check any of the msg'ing. I'll get back to this as soon as I can and report progress. Thanks again

mlasson commented 4 years ago

Yes, I made that for you (and for convincing myself that this was indeed doable) https://github.com/mlasson/tooltips .

But I am sure there are some caveat with this approach (I would not be surprise that you'll need to all popper instance's update method after all update of the dom). Ie when you insert a new element that may move the target, you'll need to call the update method to move the popup. This could be done with an "infinite" after_redraw (an after_redraw callback that register itself until the custom elements is disposed).

I close this issue, good luck !

nickbetteridge commented 4 years ago

Merci!