Coder Social home page Coder Social logo

svelte-floating-ui's Introduction

Svelte Floating UI

🎈Svelte Floating UI

npm version npm downloads license

Floating UI for Svelte with actions. No wrapper components or component bindings required!

npm i svelte-floating-ui @floating-ui/core

Usage

createFloatingActions takes an optional options object for configuring the content placement. The content action also takes an optional options object for updating the options of the content placement.

createFloatingActions also returns an update method as it's third value which can be used to manually update the content position.

Example

<script lang="ts">
  import { offset, flip, shift } from "svelte-floating-ui/dom";
  import { createFloatingActions } from "svelte-floating-ui";

  const [ floatingRef, floatingContent ] = createFloatingActions({
    strategy: "absolute",
    placement: "top",
    middleware: [
      offset(6),
      flip(),
      shift(),
    ]
  });

  let showTooltip: boolean = false;
</script>

<button
  on:mouseenter={() => showTooltip = true}
  on:mouseleave={() => showTooltip = false}
  use:floatingRef
>Hover me</button>

{#if showTooltip}
  <div style="position:absolute" use:floatingContent>
    Tooltip
  </div>
{/if}

API

Setting Floating UI options

Floating UI options can be set statically when creating the actions, or dynamically on the content action.

If both are set, then the dynamic options will be merged with the initial options.

<script>
  // set once and no longer updated
  const [ floatingRef, floatingContent ] = createFloatingActions(initOptions);
</script>

<!-- will be merged with initOptions -->
<div use:floatingContent={ dynamicOptions }/>

Updating the Floating UI position

The content element's position can be manually updated by using the third value returned by createFloatingActions. This method takes an optional options object which will be merged with the initial options.

<script>
  // Get update method
  const [ floatingRef, floatingContent, update] = createFloatingActions(initOptions);

  update(updateOptions)
</script>

You can use autoUpdate options directly in initOptions for createFloatingActions or floatingContent, but not in update

<script>
  import { offset, flip, shift } from "svelte-floating-ui/dom";
  import { createFloatingActions } from "svelte-floating-ui";

  const [ floatingRef, floatingContent ] = createFloatingActions({
    strategy: "absolute",
    placement: "top",
    middleware: [
      offset(6),
      flip(),
      shift(),
    ],
    autoUpdate: { // or false to disable everything
      ancestorResize: false,
      elementResize: false
    }
  });
</script>

What values can autoUpdate have?

Partial<Options>

/**
* false: Don't initialize autoUpdate;
* true: Standard autoUpdate values from the documentation;
* object: All as in the autoUpdate documentation. Your parameters are added to the default ones;
* @default true
*/
autoUpdate?: boolean | Partial<Options>

Svelte Floating UI allows you to use the floatingRef (reference node) like VirtualElement

Svelte stores allow you to make these elements reactive and provide full support for them in the Svelte Floating UI

This is an example of creating a tooltip that runs behind the mouse cursor:

<script lang='ts'>
  import type { ClientRectObject, VirtualElement } from 'svelte-floating-ui/core'
  import { createFloatingActions } from 'svelte-floating-ui'
  import { writable } from 'svelte/store'
  
  const [floatingRef, floatingContent] = createFloatingActions({
    strategy: 'fixed', //or absolute
  })

  let x = 0
  let y = 0

  const mousemove = (ev: MouseEvent) => {
    x = ev.clientX
    y = ev.clientY
  }

  $: getBoundingClientRect = ():ClientRectObject => {
    return {
      x,
      y,
      top: y,
      left: x,
      bottom: y,
      right: x,
      width: 0,
      height: 0
    }
  }
  
  const virtualElement = writable<VirtualElement>({ getBoundingClientRect })

  $: virtualElement.set({ getBoundingClientRect })

  floatingRef(virtualElement)
</script>

<svelte:window on:mousemove={mousemove}/>

<main>
  <h2 use:floatingContent>Magic</h2>
</main>

Applying custom styles on compute

To apply styles manually, you can pass the onComputed option to createFloatingActions. This is a function that recieves a ComputePositionReturn. This function is called every time the tooltip's position is computed.

See Arrow Middleware for an example on it's usage.

Arrow Middleware

For convenience, a custom Arrow middleware is provided. Rather than accepting an HTMLElement, this takes a Writable<HTMLElement>. Otherwise, this middleware works exactly as the regular Floating UI one, including needing to manually set the arrow styles.

To set the styles, you can pass the onComputed option. The below implementation is copied from the Floating UI Tutorial.

<script>
  import { writable } from "svelte/store";
  import { arrow } from "svelte-floating-ui";

  const arrowRef = writable(null);
  const [ floatingRef, floatingContent, update] = createFloatingActions({
    strategy: "absolute",
    placement: "bottom",
    middleware: [
      arrow({ element: arrowRef })
    ],
    onComputed({ placement, middlewareData }) {
      const { x, y } = middlewareData.arrow;
      const staticSide = {
        top: 'bottom',
        right: 'left',
        bottom: 'top',
        left: 'right',
      }[placement.split('-')[0]];

      Object.assign($arrowRef.style, {
        left: x != null ? `${x}px` : "",
        top: y != null ? `${y}px` : "",
        [staticSide]: "-4px"
      });
    }
  });
</script>

<button
  on:mouseenter={() => showTooltip = true}
  on:mouseleave={() => showTooltip = false}
  use:floatingRef
>Hover me</button>

{#if showTooltip}
  <div class="tooltip" use:floatingContent>
    Tooltip this is some longer text than the button
    <div class="arrow" bind:this={$arrowRef} />
  </div>
{/if}

Thanks to TehNut/svelte-floating-ui for the foundation for this package

svelte-floating-ui's People

Contributors

fedorovvvv avatar priithansen avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

svelte-floating-ui's Issues

Add support for virtual elements

Hi,

Virtual elements are not supported since ReferenceAction only accepts HTMLElement.

The type signature for ReferenceAction is:

type ReferenceAction = (node: HTMLElement) => void;

For instance, in svelte-popperjs the type signature is:

type ReferenceAction = (
	node: Element | VirtualElement | Readable<VirtualElement>
) => {
	destroy?(): void;
};

Do you plan to add support for virtual elements?

A way to destroy the auto update function that ties to the scrolling

In this demo, I have a button that triggers a floating tooltip when mouse enter, and remove the tooltip when mouse leave. Obviously I want the onComputed() to run when the tooltip is showing. However, there doesn't seem to be an easy way to turn it off on-demand when the tooltip is hidden. As a result, you can see the console message keeps printing.

I know you mentioned in #8 that autoUpdate() is unavoidable when the tooltip is out of view. But what I'm asking is a way to completely reset the whole floating-ui back to before the first call to floatingContent().

Or is this request more appropriate for the upstream?

<script lang="ts">
    import { createFloatingActions } from 'svelte-floating-ui'

    let showTooltip = false

    const [floatingRef, floatingContent] = createFloatingActions({
        onComputed({ placement, middlewareData }) {
            if (!showTooltip) {
                console.log('I dont want to see this')
            }
        },
    })
</script>

<div style="overflow:auto; height: 500px; width:500px; border: 1px solid;">
    <div style="width:500px; height: 1200px; display:flex; align-items: center; justify-content: center;">
        <button on:mouseenter={() => (showTooltip = true)} on:mouseleave={() => (showTooltip = false)} use:floatingRef>Hello</button>

        {#if showTooltip}
            <div style="position:absolute; border: 1px solid; padding:4px;" use:floatingContent>Tooltip is a long one</div>
        {/if}
    </div>
</div>

Module not found in webpack 5

I have this error when i compile with webpack 5 :

Module not found: Error: Can't resolve './core' in '/var/www/svelte-app/node_modules/svelte-floating-ui'

Any idea ?
thank you

Does not work properly with svelte-portal

When attempting to use svelte-floating-ui with svelte-portal the floating element initially renders in the wrong position. Resizing the window or closing and opening the floating UI fixes it (at least, closing and opening controlled by display: none/block;). If I were to make an educated guess I would say that the floating ui calcualtions are being done before the floating element is moved to the portal.

Here is some example code:

<script lang="ts">
  import Portal from 'svelte-portal';
  import { offset, flip, shift } from '@floating-ui/dom';
  import { createFloatingActions } from 'svelte-floating-ui';

  const [floatingRef, floatingContent] = createFloatingActions({
    strategy: 'absolute',
    placement: 'bottom-end',
    middleware: [offset(6), flip(), shift()],
  });

  const ariaMenuButtonId = 'menu-button';
  const ariaMenuId = 'menu';
  let open = false;
</script>

<button
  id={ariaMenuButtonId}
  type="submit"
  class="h-10 rounded-sm"
  aria-haspopup="true"
  aria-controls={ariaMenuId}
  on:click={() => {
    open = !open;
  }}
  use:floatingRef>Open</button
>

<Portal>
  <ul
    id={ariaMenuId}
    role="menu"
    aria-labelledby={ariaMenuButtonId}
    class="menu w-20"
    class:menu-open={open}
    use:floatingContent
  >
    <li role="none"><a href="/#" role="menuitem">Menu Item</a></li>
  </ul>
</Portal>

<style>
  .menu {
    display: none;
  }

  .menu-open {
    display: block;
  }
</style>

TypeScript error on floating-ui >= 1.6.0

Hey, there seems to be a type mismatch, when upgrading dependencies to:

  • "@floating-ui/core": "1.6.0",
  • "@floating-ui/dom": "1.6.1",

https://github.com/fedorovvvv/svelte-floating-ui/blob/a1f7bee81ed60a2dcbe143dfc4b0cefd48cde22a/src/lib/index.ts#L40C13-L40C72

Argument of type 'ComputeConfig' is not assignable to parameter of type 'Partial<{ placement?: Placement; strategy?: Strategy; middleware?: (false | { name: string; options?: any; fn: (state: { platform: Platform; placement: Placement; ... 6 more ...; elements: Elements; }) => Promisable<...>; })[]; platform?: Platform; }>'.
  Types of property 'platform' are incompatible.

Are there any plans to bump the dependencies?

Arrow not working in svelte 4 with typescript

I tried implementing Arrow in the following manner as per README:

<script lang="ts">
	import { fade } from 'svelte/transition';
	import { offset, flip, shift } from 'svelte-floating-ui/dom';
	import { createFloatingActions, arrow } from 'svelte-floating-ui';
	import { writable } from 'svelte/store';

	const arrowRef = writable(null) as unknown as HTMLElement;
	const [floatingRef, floatingContent] = createFloatingActions({
		strategy: 'absolute',
		placement: 'bottom',
		middleware: [offset(6), flip(), shift(), arrow({ element: arrowRef })],
		onComputed({ placement, middlewareData }) {
			const { x, y } = middlewareData.arrow;
			const staticSide = {
				top: 'bottom',
				right: 'left',
				bottom: 'top',
				left: 'right',
			}[placement.split('-')[0]];

			Object.assign($arrowRef!.style, {
				left: x != null ? `${x}px` : '',
				top: y != null ? `${y}px` : '',
				[staticSide]: '-4px',
			});
		},
	});

	export let tip: string;

	let showTooltip = true;
</script>

<!-- svelte-ignore a11y-no-static-element-interactions -->
<div
	class="sp-survey--tooltip--wrapper"
	use:floatingRef
	on:mouseenter={() => (showTooltip = true)}
	on:mouseleave={() => (showTooltip = false)}>
	{#if showTooltip}
		<div
			style="position:absolute"
			use:floatingContent
			class="sp-survey--tooltip--content"
			transition:fade|global>
			{tip}

			<div
				class="arrow"
				bind:this={$arrowRef} />
		</div>
	{/if}
	<slot />
</div>

<style>
	.sp-survey--tooltip--content {
		background-color: #000;
		color: #fff;
		padding: 0.5rem;
		border-radius: 0.25rem;
		font-size: 0.75rem;
		opacity: 0.8;
		z-index: 9999;
		overflow: auto;
		display: inline-block;
	}

	.arrow {
		position: absolute;
		bottom: 100%;
		left: 50%;
		margin-left: -0.5rem;
		border-width: 0.5rem;
		border-style: solid;
		border-color: transparent transparent #000 transparent;
		z-index: 9999;
	}
</style>

Under staticSide under Object.assign function I get the following error:

A computed property name must be of type 'string', 'number', 'symbol', or 'any'.

My x and y are also giving me an error of :

Property 'x' (or y) does not exist on type '(Partial<Coords> & { centerOffset: number; alignmentOffset?: number | undefined; }) | undefined'.ts

when I bind $arrowRef, the error message states:

No overload matches this call. Overload 1 of 2, '(store: SvelteStore<any>): any', gave the following error. Argument of type 'HTMLElement' is not assignable to parameter of type 'SvelteStore<any>'. Property 'subscribe' is missing in type 'HTMLElement' but required in type 'SvelteStore<any>'. Overload 2 of 2, '(store: SvelteStore<any> | null | undefined): any', gave the following error. Argument of type 'HTMLElement' is not assignable to parameter of type 'SvelteStore<any>'.ts(2769)

The arrow is not showing in the preview.

Do You have any suggestions?

missing exports condition using svelteKit V2

After the migration to svelteKit 2.x, I am getting the following message on starting the dev server:

WARNING: The following packages have a svelte field in their package.json but no exports condition for svelte.
 
[email protected]

Please see https://github.com/sveltejs/vite-plugin-svelte/blob/main/docs/faq.md#missing-exports-condition for details.

Do you mind fixing this?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.