How many contexts are too many contexts? A question about designing a Svelte library with Floating-UI

318 views Asked by At

I am in the process of creating a Svelte library for a customized Bootstrap theme. The idea is to only use the CSS and ditch all of the JavaScript.

I am currently in the floating parts of the library. Things like menus and tooltips. I started with tooltips and I am having a hard time zeroing on a design I like.

Floating-UI Requirement

The Floating-UI library requires the html elements of the reference and the target elements to perform the calculations. No problem with bind:this except for a scenario where I have dozens of components that may show a floating UI. That would fill the parent with dozens of variables to hold the references, or an array which is not ideal in my head because it is then not easy to know which is which.

Also, I will be needing show boolean variables for every floating UI in the dozens as well.

So I played with the idea of slots, and I have been partly successful in creating markup like this:

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

The advantages:

  • I don't pollute the parent component's script tag with variables to hold references. Instead, I get the parent reference from a slot variable and pass it to the tooltip.
  • I made the button declare its own show variable for the purposes of handling showing and hiding the tooltip or whatever floating UI is given to it.
  • It doesn't complicate things too much (ignore the fuiTrigger things you see for now).

The disadvantages:

  • A maximum of one floating UI can be paired with the element (button in this case). If I wanted a tooltip and a menu, I cannot.
  • I have to repeat this type of slot construction for every component I suspect can have a floating UI associated to it. If I'm fair about it, it could potentially be any component: Icons, buttons, splitters, resizers, checkboxes, radios, etc.

So here comes my next idea: What if controls published a reference to its root element as context using setContext()? That would allow me to code the Tooltip and other floating UI elements with getContext() and then I don't have to pass references around or having them in variables. But then it means that pretty much every control in the library would do this setContext() thing (see the last disadvantage).

My main question is: Would this be abusing the context system? Is this too many contexts? A complex page could probably reach 100 contexts.

While that's my main question, I'll be happy to hear completely different alternatives to approach the original problem easier.

1

There are 1 answers

5
superbadcodemonkey On

This idea sounds analogous to the dynamic between <label>'s for and <input>'s id attribute. You can't use id inside a Svelte component though, because they should be reusable and id's must be unique by definition.

The problem with context in this context (ha) is that you can only set context for the current component, and only read context set by parent(s). That means you'd have to establish some form of parent-child relation (through slots) between the controls and floats.
This complicates your markup significantly and as already you mentioned requires explicitly built-in support in the target component and gets tricky with multiple 'floats' per component.

I played around with use:-directives and funky higher-order-components for a bit, but in the end I realized the simplest solution is usually the best one:

<script>
    let linkTargets = {};
</script>

<Button bind:this={linkTargets.focusButton}>
    Focus me!
</Button>

<Tooltip target={linkTargets.focusButton}>
    I only appear or disappear on <strong>keyboard focus</strong> events.
</Tooltip>

Just like an array you only need one variable to hold all the references, but by using an object you get descriptive key names, just like for and id :)