Skip to content

Feature to easily access the reference of the root/first/<user defined> HTML element of a component to ease the use of certain JS libraries #9299

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

Open
webJose opened this issue Oct 7, 2023 · 0 comments

Comments

@webJose
Copy link
Contributor

webJose commented Oct 7, 2023

Describe the problem

I'll expose the problem using Floating-UI (FUI).

FUI is an amazing library that is very simple to use. However, using it inside a Svelte component library is not simple at all. Why? Because it works with HTML elements, which are hidden by Svelte (and to be fair, others too). Let's take a look at an example: A button component that wants to show a tooltip.

We start by examining FUI's requirement for placement: We need the HTML element reference of the button, and the HTML reference of the DIV that makes up the tooltip. The latter requires the position to be set equal to the strategy (absolute or fixed). Trivial stuff because the natural place to place the FUI code is inside the Tooltip component. I'll place code that uses Bootstrap.

<div
    class="tooltip show bs-tooltip-{bsPos}"
    style:top={`${y}px`}
    style:left={`${x}px`}
    bind:this={tooltipEl}
    transition:fade={{ duration: 350 }}
>
    <div class="tooltip-inner rounded-3" class:shadow>
        <slot />
    </div>
    <div
        class="tooltip-arrow"
        style:top={arrowY !== undefined ? `${arrowY}px` : ""}
        style:left={arrowX !== undefined ? `${arrowX}px` : ""}
        bind:this={arrowEl}
    />
</div>

Let us not concern ourselves about the JS part. Not important. Just note the bind:this={tooltipEl}. We need a similar reference from the button. However, the button element is enclosed inside the Button component:

<button
    class="btn btn-{outlineClass}{buttonType} {sizeClass}"
    class:active
    {disabled}
    on:click
    bind:this={buttonEl}
    use:ft={{ ...fuiTriggerOpts, showFn: show.show, hideFn: show.hide }}
>
    <slot />
</button>

Since Svelte does not have the feature I'm asking for, I have to willingly expose a reference of the button HTML element so the Tooltip component can receive it. Now imagine having to do this on every component in the library.

Describe the proposed solution

It would be great if Svelte provided a built-in mechanism that automagically exported the root/first HTML element reference so one could pick it up by binding to a prop. It would be great if I could just do:

<Button bind:rootref={buttonEl} ... >
    I have a tooltip!
</Button>
<Tooltip referenceEl={buttonEl} ... >
    I am the tooltip for the button.
</Tooltip>

The automatic rootref property would be a reference to the first element in the component's markup, and yes, it would have to be smart enough to sort the IF blocks that may be on top, making rootref change reactively to changes in the markup.

To complete the feature, the developer could use syntax to mark the element that is desired as the "root reference". You guys are the experts, but I imagine something like:

<button>
    <span rootbind>...</span>
<button>

A simple keyword that I apply to the one element I want, but that can be applied in different IF branches. The rule would be: After deciding on the HTML elements that will be visible, there must be among them at least zero, and at most one element marked with rootbind.

Finally, in order to not pollute RAM with a bunch of references that aren't in use, this could only get activated for component instances that have the bind:rootref binding.

Alternatives considered

Reference Prop on Every Component

The most obvious one is to export a reference to the root HTML element of every component in the library. This pollutes RAM as only a few will be really needed.

Slots

I have also considered using slots. This one is quite interesting but note that it also has the same RAM issue as every component instance spends a variable to hold on to a ref that may or may not be used.

            <Button fuiTriggerOpts={{ hideTriggers: Triggers.All, showTriggers: Triggers.All }}>
                <svelte:fragment slot="content">
                    Focus or hover me!
                </svelte:fragment>
                <Tooltip shadow={true} slot="floatUi" placement="top-end" let:reference referenceEl={reference}>
                    <div class="fancy-tooltip">
                        <div class="big">
                            <Icon name="keyboard" /> + <Icon name="mouse-solid" />
                        </div>
                        <p>I appear or disappear on <strong>keyboard focus</strong> or <strong>mouse hover</strong> events.</p>
                    </div>
                </Tooltip>
            </Button>

This is my attempt with slots. The floatUi slot provides a reference slot variable that holds the reference to the <button> element. This technique is kind of nice, but it has problems:

  1. There can only be one floating user interface element per "parent" component. What if I wanted a tooltip AND a menu on click? I cannot since there's only one slot for floating UI.
  2. Every Button component in the entire application will be creating the HTML element reference, wasting RAM in most cases as it is only needed when pairing with a floating UI piece.
  3. Every component in the component library would need to be extended with this kind of setup (a content slot and a floating slot) so they too can have floating UI's associated with them.

Context

This is an idea I haven't tested: Every component that could be used as the reference element of a FUI would expose its "root element" reference as context with setContext(). Then, FUI components like Tooltip can then use getContext() to obtain the reference and do the FUI calculations for position with it.

Again, waste of RAM here. There will be a LOT of contexts everywhere that won't be in use.

Importance

would make my life easier

@webJose webJose changed the title Feature to easily access the HTML element reference of the root/first/<user defined> of a component to ease the use of certain JS libraries Feature to easily access the reference of the root/first/<user defined> HTML element of a component to ease the use of certain JS libraries Oct 7, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant