From 06b235a64a2f87ab0cbf1e79145ecfdec9a48860 Mon Sep 17 00:00:00 2001 From: abdel-17 Date: Sat, 27 Apr 2024 18:21:13 +0300 Subject: [PATCH 1/8] drop boxing --- src/lib/box.svelte.ts | 68 ------------- src/lib/hooks/use-floating.svelte.ts | 137 +++++++++++++-------------- src/lib/types.ts | 53 +++++------ src/routes/+page.svelte | 13 +-- 4 files changed, 99 insertions(+), 172 deletions(-) delete mode 100644 src/lib/box.svelte.ts diff --git a/src/lib/box.svelte.ts b/src/lib/box.svelte.ts deleted file mode 100644 index c36d314a..00000000 --- a/src/lib/box.svelte.ts +++ /dev/null @@ -1,68 +0,0 @@ -import type { Getter } from './types.js'; - -export type ReadableBox = { - readonly value: T; -}; - -export type WritableBox = { - value: T; -}; - -class StateBox { - value = $state(); - - constructor(initialValue: T) { - this.value = initialValue; - } -} - -class DerivedBox { - #getter: Getter; - - constructor(getter: Getter) { - this.#getter = getter; - } - - readonly value = $derived.by(() => this.#getter()); -} - -class ReadonlyBox { - #box: ReadableBox; - - constructor(box: ReadableBox) { - this.#box = box; - } - - get value() { - return this.#box.value; - } -} - -/** - * Creates a writable box. - * - * @returns A box with a `value` property which can be set to a new value. - * Useful to pass state to other functions. - */ -export function box(): WritableBox; - -/** - * Creates a writable box with an initial value. - * - * @param initialValue The initial value of the box. - * @returns A box with a `value` property which can be set to a new value. - * Useful to pass state to other functions. - */ -export function box(initialValue: T): WritableBox; - -export function box(initialValue?: T): WritableBox { - return new StateBox(initialValue); -} - -box.derived = function (getter: Getter): ReadableBox { - return new DerivedBox(getter); -}; - -box.readonly = function (box: ReadableBox): ReadableBox { - return new ReadonlyBox(box); -}; diff --git a/src/lib/hooks/use-floating.svelte.ts b/src/lib/hooks/use-floating.svelte.ts index 42a56ef0..4b450b9b 100644 --- a/src/lib/hooks/use-floating.svelte.ts +++ b/src/lib/hooks/use-floating.svelte.ts @@ -1,4 +1,3 @@ -import { box } from '$lib/box.svelte.js'; import type { UseFloatingOptions, UseFloatingReturn } from '$lib/types.js'; import { getDPR, noop, roundByDPR, styleObjectToString } from '$lib/utils.js'; import type { MiddlewareData, ReferenceElement } from '@floating-ui/dom'; @@ -11,129 +10,125 @@ import { computePosition } from '@floating-ui/dom'; */ export function useFloating( options: UseFloatingOptions = {} -): UseFloatingReturn { - const openOption = box.derived(() => options.open ?? true); +): UseFloatingReturn { + const openOption = $derived(options.open ?? true); const onOpenChangeOption = options.onOpenChange ?? noop; - const placementOption = box.derived(() => options.placement ?? 'bottom'); - const strategyOption = box.derived(() => options.strategy ?? 'absolute'); - const middlewareOption = box.derived(() => options.middleware); - const transformOption = box.derived(() => options.transform ?? true); - const referenceElement = box.derived(() => options.elements?.reference); - const floatingElement = box.derived(() => options.elements?.floating); + const placementOption = $derived(options.placement ?? 'bottom'); + const strategyOption = $derived(options.strategy ?? 'absolute'); + const middlewareOption = $derived(options.middleware); + const transformOption = $derived(options.transform ?? true); + const elements = $derived(options.elements ?? {}); const whileElementsMountedOption = options.whileElementsMounted; - const x = box(0); - const y = box(0); - const strategy = box(strategyOption.value); - const placement = box(placementOption.value); - const middlewareData = box({}); - const isPositioned = box(false); - const floatingStyles = box.derived(() => { + let x = $state(0); + let y = $state(0); + let strategy = $state(strategyOption); + let placement = $state(placementOption); + let middlewareData = $state({}); + let isPositioned = $state(false); + + const floatingStyles = $derived.by(() => { const initialStyles = { - position: strategy.value, + position: strategy, left: '0', top: '0' }; - if (!floatingElement.value) { + const { floating } = elements; + if (floating == null) { return styleObjectToString(initialStyles); } - const xVal = roundByDPR(floatingElement.value, x.value); - const yVal = roundByDPR(floatingElement.value, y.value); + const xVal = roundByDPR(floating, x); + const yVal = roundByDPR(floating, y); - if (transformOption.value) { + if (transformOption) { return styleObjectToString({ ...initialStyles, transform: `translate(${xVal}px, ${yVal}px)`, - ...(getDPR(floatingElement.value) >= 1.5 && { willChange: 'transform' }) + ...(getDPR(floating) >= 1.5 && { willChange: 'transform' }) }); } return styleObjectToString({ - position: strategy.value, + position: strategy, left: `${xVal}px`, top: `${yVal}px` }); }); - let whileElementsMountedCleanup: (() => void) | undefined; - function update() { - if ( - referenceElement.value === null || - referenceElement.value === undefined || - floatingElement.value === null || - floatingElement.value === undefined - ) { + const { reference, floating } = elements; + if (reference == null || floating == null) { return; } - computePosition(referenceElement.value, floatingElement.value, { - middleware: middlewareOption.value, - placement: placementOption.value, - strategy: strategyOption.value + computePosition(reference, floating, { + middleware: middlewareOption, + placement: placementOption, + strategy: strategyOption }).then((position) => { - x.value = position.x; - y.value = position.y; - strategy.value = position.strategy; - placement.value = position.placement; - middlewareData.value = position.middlewareData; - isPositioned.value = true; + x = position.x; + y = position.y; + strategy = position.strategy; + placement = position.placement; + middlewareData = position.middlewareData; + isPositioned = true; }); } function attach() { - cleanup(); - if (whileElementsMountedOption === undefined) { update(); return; } - if (referenceElement.value != null && floatingElement.value != null) { - whileElementsMountedCleanup = whileElementsMountedOption( - referenceElement.value, - floatingElement.value, - update - ); - return; + const { floating, reference } = elements; + if (reference != null && floating != null) { + return whileElementsMountedOption(reference, floating, update); } } function reset() { - if (!openOption.value) { - isPositioned.value = false; - } - } - - function cleanup() { - if (typeof whileElementsMountedCleanup === 'function') { - whileElementsMountedCleanup(); - whileElementsMountedCleanup = undefined; + if (!openOption) { + isPositioned = false; } } $effect.pre(update); $effect.pre(attach); $effect.pre(reset); - $effect(() => cleanup); return { - x: box.readonly(x), - y: box.readonly(y), - strategy: box.readonly(strategy), - placement: box.readonly(placement), - middlewareData: box.readonly(middlewareData), - isPositioned: box.readonly(isPositioned), - floatingStyles, + get x() { + return x; + }, + get y() { + return y; + }, + get strategy() { + return strategy; + }, + get placement() { + return placement; + }, + get middlewareData() { + return middlewareData; + }, + get isPositioned() { + return isPositioned; + }, + get floatingStyles() { + return floatingStyles; + }, update, context: { - open: openOption, + get open() { + return openOption; + }, onOpenChange: onOpenChangeOption, - elements: { - reference: box.readonly(referenceElement), - floating: box.readonly(floatingElement) + get elements() { + return elements; } } }; diff --git a/src/lib/types.ts b/src/lib/types.ts index c076ba98..4f8a7f41 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -1,4 +1,3 @@ -import type { ReadableBox } from '$lib/box.svelte.js'; import type { FloatingElement, Middleware, @@ -17,58 +16,58 @@ export interface UseFloatingOptions void; + readonly onOpenChange?: (open: boolean, event?: Event, reason?: OpenChangeReason) => void; /** * Where to place the floating element relative to its reference element. * @default 'bottom' */ - placement?: Placement; + readonly placement?: Placement; /** * The type of CSS position property to use. * @default 'absolute' */ - strategy?: Strategy; + readonly strategy?: Strategy; /** * These are plain objects that modify the positioning coordinates in some fashion, or provide useful data for the consumer to use. * @default undefined */ - middleware?: Array; + readonly middleware?: Array; /** * Whether to use `transform` instead of `top` and `left` styles to * position the floating element (`floatingStyles`). * @default true */ - transform?: boolean; + readonly transform?: boolean; /** * The reference and floating elements. */ - elements?: { + readonly elements?: { /** * The reference element. */ - reference?: T | null; + readonly reference?: T | null; /** * The floating element which is anchored to the reference element. */ - floating?: FloatingElement | null; + readonly floating?: FloatingElement | null; }; /** * Callback to handle mounting/unmounting of the elements. * @default undefined */ - whileElementsMounted?: ( + readonly whileElementsMounted?: ( reference: T, floating: FloatingElement, update: () => void @@ -86,77 +85,77 @@ type OpenChangeReason = | 'list-navigation' | 'safe-polygon'; -export interface FloatingContext { +export interface FloatingContext { /** * Represents the open/close state of the floating element. */ - open: ReadableBox; + readonly open: boolean; /** * Event handler that can be invoked whenever the open state changes. */ - onOpenChange: (open: boolean, event?: Event, reason?: OpenChangeReason) => void; + readonly onOpenChange: (open: boolean, event?: Event, reason?: OpenChangeReason) => void; /** * The reference and floating elements. */ - elements: { + readonly elements: { /** * The reference element. */ - reference: ReadableBox; + readonly reference?: T | null; /** * The floating element which is anchored to the reference element. */ - floating: ReadableBox; + readonly floating?: FloatingElement | null; }; } -export interface UseFloatingReturn { +export interface UseFloatingReturn { /** * The x-coord of the floating element. */ - x: ReadableBox; + readonly x: number; /** * The y-coord of the floating element. */ - y: ReadableBox; + readonly y: number; /** * The stateful placement, which can be different from the initial `placement` passed as options. */ - placement: ReadableBox; + readonly placement: Placement; /** * The type of CSS position property to use. */ - strategy: ReadableBox; + readonly strategy: Strategy; /** * Additional data from middleware. */ - middlewareData: ReadableBox; + readonly middlewareData: MiddlewareData; /** * The boolean that let you know if the floating element has been positioned. */ - isPositioned: ReadableBox; + readonly isPositioned: boolean; /** * CSS styles to apply to the floating element to position it. */ - floatingStyles: ReadableBox; + readonly floatingStyles: string; /** * The function to update floating position manually. */ - update: () => void; + readonly update: () => void; /** * Context object containing internal logic to alter the behavior of the floating element. * Commonly used to inject into others hooks. */ - context: FloatingContext; + readonly context: FloatingContext; } diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index 6004cec0..2c3a6dc2 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -2,10 +2,11 @@ import { useFloating, type Placement, autoUpdate, offset } from '$lib/index.js'; const elements = $state<{ reference?: HTMLElement; floating?: HTMLElement }>({}); - let placement = $state('bottom'); - - const { floatingStyles, x, y } = useFloating({ + const floating = useFloating({ + get placement() { + return placement; + }, middleware: [offset(5)], elements, whileElementsMounted: autoUpdate @@ -19,8 +20,8 @@ -

{x.value}

-

{y.value}

+

{floating.x}

+

{floating.y}

-
Floating
+
Floating
From 7abb201ad5f36d77e616455b306416f4b7c384a7 Mon Sep 17 00:00:00 2001 From: abdel-17 Date: Sat, 27 Apr 2024 20:56:45 +0300 Subject: [PATCH 2/8] use classes --- src/lib/hooks/use-floating.svelte.ts | 247 ++++++++++++++++++--------- src/lib/index.ts | 4 +- src/lib/types.ts | 92 +--------- 3 files changed, 177 insertions(+), 166 deletions(-) diff --git a/src/lib/hooks/use-floating.svelte.ts b/src/lib/hooks/use-floating.svelte.ts index 4b450b9b..a17db6b3 100644 --- a/src/lib/hooks/use-floating.svelte.ts +++ b/src/lib/hooks/use-floating.svelte.ts @@ -1,48 +1,50 @@ -import type { UseFloatingOptions, UseFloatingReturn } from '$lib/types.js'; +import type { FloatingElements, OpenChangeReason, UseFloatingOptions } from '$lib/types.js'; import { getDPR, noop, roundByDPR, styleObjectToString } from '$lib/utils.js'; +import type { Strategy } from '@floating-ui/dom'; +import type { Placement } from '@floating-ui/dom'; import type { MiddlewareData, ReferenceElement } from '@floating-ui/dom'; import { computePosition } from '@floating-ui/dom'; -/** - * Hook for managing floating elements. - * Aims to keep as much parity with `@floating-ui/react` as possible. - * For now see: https://floating-ui.com/docs/useFloating for API documentation. - */ -export function useFloating( - options: UseFloatingOptions = {} -): UseFloatingReturn { - const openOption = $derived(options.open ?? true); - const onOpenChangeOption = options.onOpenChange ?? noop; - const placementOption = $derived(options.placement ?? 'bottom'); - const strategyOption = $derived(options.strategy ?? 'absolute'); - const middlewareOption = $derived(options.middleware); - const transformOption = $derived(options.transform ?? true); - const elements = $derived(options.elements ?? {}); - const whileElementsMountedOption = options.whileElementsMounted; - - let x = $state(0); - let y = $state(0); - let strategy = $state(strategyOption); - let placement = $state(placementOption); - let middlewareData = $state({}); - let isPositioned = $state(false); - - const floatingStyles = $derived.by(() => { +class FloatingState { + readonly #options: UseFloatingOptions; + + constructor(options: UseFloatingOptions) { + this.#options = options; + this.placement = this.placementOption; + this.strategy = this.strategyOption; + } + + open = $derived.by(() => this.#options.open ?? true); + onOpenChange = $derived.by(() => this.#options.onOpenChange ?? noop); + placementOption = $derived.by(() => this.#options.placement ?? 'bottom'); + strategyOption = $derived.by(() => this.#options.strategy ?? 'absolute'); + middleware = $derived.by(() => this.#options.middleware); + transform = $derived.by(() => this.#options.transform ?? true); + elements = $derived.by(() => this.#options.elements ?? {}); + whileElementsMounted = $derived.by(() => this.#options.whileElementsMounted); + + x = $state(0); + y = $state(0); + placement: Placement = $state('bottom'); + strategy: Strategy = $state('absolute'); + middlewareData: MiddlewareData = $state.frozen({}); + isPositioned = $state(false); + floatingStyles = $derived.by(() => { const initialStyles = { - position: strategy, + position: this.strategy, left: '0', top: '0' }; - const { floating } = elements; + const { floating } = this.elements; if (floating == null) { return styleObjectToString(initialStyles); } - const xVal = roundByDPR(floating, x); - const yVal = roundByDPR(floating, y); + const xVal = roundByDPR(floating, this.x); + const yVal = roundByDPR(floating, this.y); - if (transformOption) { + if (this.transform) { return styleObjectToString({ ...initialStyles, transform: `translate(${xVal}px, ${yVal}px)`, @@ -51,47 +53,164 @@ export function useFloating( } return styleObjectToString({ - position: strategy, + position: this.strategyOption, left: `${xVal}px`, top: `${yVal}px` }); }); +} + +export class FloatingContext { + readonly #state: FloatingState; + + constructor(state: FloatingState) { + this.#state = state; + } + + /** + * Represents the open/close state of the floating element. + * @default true + */ + get open(): boolean { + return this.#state.open; + } + + /** + * Event handler that can be invoked whenever the open state changes. + */ + get onOpenChange(): (open: boolean, event?: Event, reason?: OpenChangeReason) => void { + return this.#state.onOpenChange; + } + + /** + * The reference and floating elements. + */ + get elements(): FloatingElements { + return this.#state.elements; + } +} + +export class UseFloatingReturn { + readonly #state: FloatingState; + readonly #context: FloatingContext; + readonly #update: () => void; + + constructor(state: FloatingState, update: () => void) { + this.#state = state; + this.#context = new FloatingContext(state); + this.#update = update; + } + + /** + * The x-coord of the floating element. + */ + get x(): number { + return this.#state.x; + } + + /** + * The y-coord of the floating element. + */ + get y(): number { + return this.#state.y; + } + + /** + * The stateful placement, which can be different from the initial `placement` passed as options. + */ + get placement(): Placement { + return this.#state.placement; + } + + /** + * The type of CSS position property to use. + */ + get strategy(): Strategy { + return this.#state.strategy; + } + + /** + * Additional data from middleware. + */ + get middlewareData(): MiddlewareData { + return this.#state.middlewareData; + } + + /** + * The boolean that let you know if the floating element has been positioned. + */ + get isPositioned(): boolean { + return this.#state.isPositioned; + } + + /** + * CSS styles to apply to the floating element to position it. + */ + get floatingStyles(): string { + return this.#state.floatingStyles; + } + + /** + * The function to update floating position manually. + */ + get update(): () => void { + return this.#update; + } + + /** + * Context object containing internal logic to alter the behavior of the floating element. + * Commonly used to inject into others hooks. + */ + get context(): FloatingContext { + return this.#context; + } +} + +/** + * Hook for managing floating elements. + * Aims to keep as much parity with `@floating-ui/react` as possible. + * For now see: https://floating-ui.com/docs/useFloating for API documentation. + */ +export function useFloating( + options: UseFloatingOptions = {} +): UseFloatingReturn { + const state = new FloatingState(options); function update() { - const { reference, floating } = elements; + const { reference, floating } = state.elements; if (reference == null || floating == null) { return; } computePosition(reference, floating, { - middleware: middlewareOption, - placement: placementOption, - strategy: strategyOption + middleware: state.middleware, + placement: state.placementOption, + strategy: state.strategyOption }).then((position) => { - x = position.x; - y = position.y; - strategy = position.strategy; - placement = position.placement; - middlewareData = position.middlewareData; - isPositioned = true; + state.x = position.x; + state.y = position.y; + state.strategy = position.strategy; + state.placement = position.placement; + state.middlewareData = position.middlewareData; + state.isPositioned = true; }); } function attach() { - if (whileElementsMountedOption === undefined) { + if (state.whileElementsMounted === undefined) { update(); return; } - const { floating, reference } = elements; + const { floating, reference } = state.elements; if (reference != null && floating != null) { - return whileElementsMountedOption(reference, floating, update); + return state.whileElementsMounted(reference, floating, update); } } function reset() { - if (!openOption) { - isPositioned = false; + if (!state.open) { + state.isPositioned = false; } } @@ -99,37 +218,5 @@ export function useFloating( $effect.pre(attach); $effect.pre(reset); - return { - get x() { - return x; - }, - get y() { - return y; - }, - get strategy() { - return strategy; - }, - get placement() { - return placement; - }, - get middlewareData() { - return middlewareData; - }, - get isPositioned() { - return isPositioned; - }, - get floatingStyles() { - return floatingStyles; - }, - update, - context: { - get open() { - return openOption; - }, - onOpenChange: onOpenChangeOption, - get elements() { - return elements; - } - } - }; + return new UseFloatingReturn(state, update); } diff --git a/src/lib/index.ts b/src/lib/index.ts index 25820456..7c243338 100644 --- a/src/lib/index.ts +++ b/src/lib/index.ts @@ -1,3 +1,3 @@ export * from '@floating-ui/dom'; -export { useFloating } from '$lib/hooks/use-floating.svelte.js'; -export { type UseFloatingOptions, type UseFloatingReturn } from '$lib/types.js'; +export { useFloating, type UseFloatingReturn } from '$lib/hooks/use-floating.svelte.js'; +export { type UseFloatingOptions } from '$lib/types.js'; diff --git a/src/lib/types.ts b/src/lib/types.ts index 4f8a7f41..0e813da6 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -1,14 +1,11 @@ import type { FloatingElement, Middleware, - MiddlewareData, Placement, ReferenceElement, Strategy } from '@floating-ui/dom'; -export type Getter = () => T; - export type Expand = T extends infer U ? { [K in keyof U]: U[K] } : never; export interface UseFloatingOptions { @@ -51,17 +48,7 @@ export interface UseFloatingOptions; /** * Callback to handle mounting/unmounting of the elements. @@ -74,7 +61,7 @@ export interface UseFloatingOptions () => void; } -type OpenChangeReason = +export type OpenChangeReason = | 'outside-press' | 'escape-key' | 'ancestor-scroll' @@ -85,77 +72,14 @@ type OpenChangeReason = | 'list-navigation' | 'safe-polygon'; -export interface FloatingContext { - /** - * Represents the open/close state of the floating element. - */ - readonly open: boolean; - - /** - * Event handler that can be invoked whenever the open state changes. - */ - readonly onOpenChange: (open: boolean, event?: Event, reason?: OpenChangeReason) => void; - +export type FloatingElements = { /** - * The reference and floating elements. - */ - readonly elements: { - /** - * The reference element. - */ - readonly reference?: T | null; - - /** - * The floating element which is anchored to the reference element. - */ - readonly floating?: FloatingElement | null; - }; -} - -export interface UseFloatingReturn { - /** - * The x-coord of the floating element. + * The reference element. */ - readonly x: number; + readonly reference?: T | null; /** - * The y-coord of the floating element. + * The floating element which is anchored to the reference element. */ - readonly y: number; - - /** - * The stateful placement, which can be different from the initial `placement` passed as options. - */ - readonly placement: Placement; - - /** - * The type of CSS position property to use. - */ - readonly strategy: Strategy; - - /** - * Additional data from middleware. - */ - readonly middlewareData: MiddlewareData; - - /** - * The boolean that let you know if the floating element has been positioned. - */ - readonly isPositioned: boolean; - - /** - * CSS styles to apply to the floating element to position it. - */ - readonly floatingStyles: string; - - /** - * The function to update floating position manually. - */ - readonly update: () => void; - - /** - * Context object containing internal logic to alter the behavior of the floating element. - * Commonly used to inject into others hooks. - */ - readonly context: FloatingContext; -} + readonly floating?: FloatingElement | null; +}; From 267619dbe8e40128e69d4c49cb315292143f769b Mon Sep 17 00:00:00 2001 From: hugos68 Date: Sat, 27 Apr 2024 22:30:56 +0200 Subject: [PATCH 3/8] Updated tests, merged dev --- src/lib/hooks/use-floating.test.svelte.ts | 68 +++++++++++------------ 1 file changed, 34 insertions(+), 34 deletions(-) diff --git a/src/lib/hooks/use-floating.test.svelte.ts b/src/lib/hooks/use-floating.test.svelte.ts index 87439447..4eaba8ff 100644 --- a/src/lib/hooks/use-floating.test.svelte.ts +++ b/src/lib/hooks/use-floating.test.svelte.ts @@ -34,7 +34,7 @@ describe('useFloating', () => { it_in_effect('updates floating coordinates on middleware change', async () => { const middleware: Middleware[] = $state([]); - const { x, y } = useFloating({ + const floating = useFloating({ ...test_config(), get middleware() { return middleware; @@ -42,21 +42,21 @@ describe('useFloating', () => { }); await vi.waitFor(() => { - expect(x.value).toBe(0); - expect(y.value).toBe(0); + expect(floating.x).toBe(0); + expect(floating.y).toBe(0); }); middleware.push(offset(5)); await vi.waitFor(() => { - expect(x.value).toBe(0); - expect(y.value).toBe(5); + expect(floating.x).toBe(0); + expect(floating.y).toBe(5); }); }); it_in_effect('updates floating coordinates on placement change', async () => { let placement: Placement = $state('bottom'); - const { x, y } = useFloating({ + const floating = useFloating({ ...test_config(), middleware: [offset(5)], get placement() { @@ -65,21 +65,21 @@ describe('useFloating', () => { }); await vi.waitFor(() => { - expect(x.value).toBe(0); - expect(y.value).toBe(5); + expect(floating.x).toBe(0); + expect(floating.y).toBe(5); }); placement = 'top'; await vi.waitFor(() => { - expect(x.value).toBe(0); - expect(y.value).toBe(-5); + expect(floating.x).toBe(0); + expect(floating.y).toBe(-5); }); }); it_in_effect('updates `floatingStyles` on strategy change', async () => { let strategy: Strategy = $state('absolute'); - const { floatingStyles } = useFloating({ + const floating = useFloating({ ...test_config(), get strategy() { return strategy; @@ -87,51 +87,51 @@ describe('useFloating', () => { }); await vi.waitFor(() => { - expect(floatingStyles.value).toContain('position: absolute'); + expect(floating.floatingStyles).toContain('position: absolute'); }); strategy = 'fixed'; await vi.waitFor(() => { - expect(floatingStyles.value).toContain('position: fixed'); + expect(floating.floatingStyles).toContain('position: fixed'); }); }); it_in_effect('updates `isPositioned` when position is computed', async () => { - const { x, y, isPositioned } = useFloating({ + const floating = useFloating({ ...test_config(), middleware: [offset(5)] }); - expect(x.value).toBe(0); - expect(y.value).toBe(0); - expect(isPositioned.value).toBe(false); + expect(floating.x).toBe(0); + expect(floating.y).toBe(0); + expect(floating.isPositioned).toBe(false); await vi.waitFor(() => { - expect(x.value).toBe(0); - expect(y.value).toBe(5); - expect(isPositioned.value).toBe(true); + expect(floating.x).toBe(0); + expect(floating.y).toBe(5); + expect(floating.isPositioned).toBe(true); }); }); it_in_effect('updates `isPositioned` to `false` when `open` is set to `false`', async () => { let open = $state(true); - const { isPositioned } = useFloating({ + const floating = useFloating({ ...test_config(), get open() { return open; } }); - expect(isPositioned.value).toBe(false); + expect(floating.isPositioned).toBe(false); await vi.waitFor(() => { - expect(isPositioned.value).toBe(true); + expect(floating.isPositioned).toBe(true); }); open = false; await vi.waitFor(() => { - expect(isPositioned.value).toBe(false); + expect(floating.isPositioned).toBe(false); }); }); it_in_effect( @@ -139,7 +139,7 @@ describe('useFloating', () => { async () => { let placement: Placement | undefined = $state('top'); - const { x, y } = useFloating({ + const floating = useFloating({ ...test_config(), middleware: [offset(5)], get placement() { @@ -148,15 +148,15 @@ describe('useFloating', () => { }); await vi.waitFor(() => { - expect(x.value).toBe(0); - expect(y.value).toBe(-5); + expect(floating.x).toBe(0); + expect(floating.y).toBe(-5); }); placement = undefined; await vi.waitFor(() => { - expect(x.value).toBe(0); - expect(y.value).toBe(5); + expect(floating.x).toBe(0); + expect(floating.y).toBe(5); }); } ); @@ -165,7 +165,7 @@ describe('useFloating', () => { async () => { let strategy: Strategy | undefined = $state('fixed'); - const { floatingStyles } = useFloating({ + const floating = useFloating({ ...test_config(), get strategy() { return strategy; @@ -173,13 +173,13 @@ describe('useFloating', () => { }); await vi.waitFor(() => { - expect(floatingStyles.value).toContain('position: fixed'); + expect(floating.floatingStyles).toContain('position: fixed'); }); strategy = undefined; await vi.waitFor(() => { - expect(floatingStyles.value).toContain('position: absolute'); + expect(floating.floatingStyles).toContain('position: absolute'); }); } ); @@ -239,7 +239,7 @@ describe('useFloating', () => { expect(whileElementsMountedCleanup).toHaveBeenCalledTimes(1); }); it_in_effect('correctly assigns `middlewareData` from `middleware`', async () => { - const { middlewareData } = useFloating({ + const floating = useFloating({ ...test_config(), middleware: [ { @@ -250,7 +250,7 @@ describe('useFloating', () => { }); await vi.waitFor(() => { - expect(middlewareData.value).toEqual({ test: { content: 'Content' } }); + expect(floating.middlewareData).toEqual({ test: { content: 'Content' } }); }); }); }); From f5c5fd86474ce2150ec0acf539d61e84aafffef5 Mon Sep 17 00:00:00 2001 From: hugos68 Date: Sat, 27 Apr 2024 22:32:59 +0200 Subject: [PATCH 4/8] Outdated doc change --- src/lib/hooks/use-floating.svelte.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/lib/hooks/use-floating.svelte.ts b/src/lib/hooks/use-floating.svelte.ts index a17db6b3..512ddfe1 100644 --- a/src/lib/hooks/use-floating.svelte.ts +++ b/src/lib/hooks/use-floating.svelte.ts @@ -168,8 +168,6 @@ export class UseFloatingReturn { /** * Hook for managing floating elements. - * Aims to keep as much parity with `@floating-ui/react` as possible. - * For now see: https://floating-ui.com/docs/useFloating for API documentation. */ export function useFloating( options: UseFloatingOptions = {} From 60196e2d881b6fdcce73d68976f9cf99e71c6204 Mon Sep 17 00:00:00 2001 From: hugos68 Date: Sat, 27 Apr 2024 22:50:28 +0200 Subject: [PATCH 5/8] Removed generic --- src/lib/hooks/use-floating.svelte.ts | 6 +++--- src/lib/types.ts | 10 +++++----- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/lib/hooks/use-floating.svelte.ts b/src/lib/hooks/use-floating.svelte.ts index 512ddfe1..c848af2e 100644 --- a/src/lib/hooks/use-floating.svelte.ts +++ b/src/lib/hooks/use-floating.svelte.ts @@ -5,10 +5,10 @@ import type { Placement } from '@floating-ui/dom'; import type { MiddlewareData, ReferenceElement } from '@floating-ui/dom'; import { computePosition } from '@floating-ui/dom'; -class FloatingState { - readonly #options: UseFloatingOptions; +class FloatingState { + readonly #options: UseFloatingOptions; - constructor(options: UseFloatingOptions) { + constructor(options: UseFloatingOptions) { this.#options = options; this.placement = this.placementOption; this.strategy = this.strategyOption; diff --git a/src/lib/types.ts b/src/lib/types.ts index 0e813da6..aa90bf8a 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -8,7 +8,7 @@ import type { export type Expand = T extends infer U ? { [K in keyof U]: U[K] } : never; -export interface UseFloatingOptions { +export interface UseFloatingOptions { /** * Represents the open/close state of the floating element. * @default true @@ -48,14 +48,14 @@ export interface UseFloatingOptions; + readonly elements?: FloatingElements; /** * Callback to handle mounting/unmounting of the elements. * @default undefined */ readonly whileElementsMounted?: ( - reference: T, + reference: ReferenceElement, floating: FloatingElement, update: () => void ) => () => void; @@ -72,11 +72,11 @@ export type OpenChangeReason = | 'list-navigation' | 'safe-polygon'; -export type FloatingElements = { +export type FloatingElements = { /** * The reference element. */ - readonly reference?: T | null; + readonly reference?: ReferenceElement | null; /** * The floating element which is anchored to the reference element. From 794a42913144943c53339cefd773f7b4795ba533 Mon Sep 17 00:00:00 2001 From: hugos68 Date: Sun, 28 Apr 2024 11:44:39 +0200 Subject: [PATCH 6/8] Removed generics for now --- src/lib/hooks/use-floating.svelte.ts | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/src/lib/hooks/use-floating.svelte.ts b/src/lib/hooks/use-floating.svelte.ts index c848af2e..01812963 100644 --- a/src/lib/hooks/use-floating.svelte.ts +++ b/src/lib/hooks/use-floating.svelte.ts @@ -60,10 +60,10 @@ class FloatingState { }); } -export class FloatingContext { - readonly #state: FloatingState; +export class FloatingContext { + readonly #state: FloatingState; - constructor(state: FloatingState) { + constructor(state: FloatingState) { this.#state = state; } @@ -85,17 +85,17 @@ export class FloatingContext { /** * The reference and floating elements. */ - get elements(): FloatingElements { + get elements(): FloatingElements { return this.#state.elements; } } -export class UseFloatingReturn { - readonly #state: FloatingState; - readonly #context: FloatingContext; +export class UseFloatingReturn { + readonly #state: FloatingState; + readonly #context: FloatingContext; readonly #update: () => void; - constructor(state: FloatingState, update: () => void) { + constructor(state: FloatingState, update: () => void) { this.#state = state; this.#context = new FloatingContext(state); this.#update = update; @@ -161,7 +161,7 @@ export class UseFloatingReturn { * Context object containing internal logic to alter the behavior of the floating element. * Commonly used to inject into others hooks. */ - get context(): FloatingContext { + get context(): FloatingContext { return this.#context; } } @@ -169,9 +169,7 @@ export class UseFloatingReturn { /** * Hook for managing floating elements. */ -export function useFloating( - options: UseFloatingOptions = {} -): UseFloatingReturn { +export function useFloating(options: UseFloatingOptions = {}): UseFloatingReturn { const state = new FloatingState(options); function update() { From 57d4f8b1d8d34585ab89faf494cd470cd89d99cf Mon Sep 17 00:00:00 2001 From: hugos68 Date: Sun, 28 Apr 2024 11:47:04 +0200 Subject: [PATCH 7/8] Removed redundant import --- src/lib/hooks/use-floating.svelte.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/hooks/use-floating.svelte.ts b/src/lib/hooks/use-floating.svelte.ts index 01812963..ad2a6694 100644 --- a/src/lib/hooks/use-floating.svelte.ts +++ b/src/lib/hooks/use-floating.svelte.ts @@ -2,7 +2,7 @@ import type { FloatingElements, OpenChangeReason, UseFloatingOptions } from '$li import { getDPR, noop, roundByDPR, styleObjectToString } from '$lib/utils.js'; import type { Strategy } from '@floating-ui/dom'; import type { Placement } from '@floating-ui/dom'; -import type { MiddlewareData, ReferenceElement } from '@floating-ui/dom'; +import type { MiddlewareData } from '@floating-ui/dom'; import { computePosition } from '@floating-ui/dom'; class FloatingState { From 4e1f4623748bda29384680ebbda0ad412d10fc31 Mon Sep 17 00:00:00 2001 From: hugos68 Date: Sun, 28 Apr 2024 11:48:11 +0200 Subject: [PATCH 8/8] Unified imports --- src/lib/hooks/use-floating.svelte.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/lib/hooks/use-floating.svelte.ts b/src/lib/hooks/use-floating.svelte.ts index ad2a6694..aa143630 100644 --- a/src/lib/hooks/use-floating.svelte.ts +++ b/src/lib/hooks/use-floating.svelte.ts @@ -1,9 +1,11 @@ import type { FloatingElements, OpenChangeReason, UseFloatingOptions } from '$lib/types.js'; import { getDPR, noop, roundByDPR, styleObjectToString } from '$lib/utils.js'; -import type { Strategy } from '@floating-ui/dom'; -import type { Placement } from '@floating-ui/dom'; -import type { MiddlewareData } from '@floating-ui/dom'; -import { computePosition } from '@floating-ui/dom'; +import { + computePosition, + type Strategy, + type Placement, + type MiddlewareData +} from '@floating-ui/dom'; class FloatingState { readonly #options: UseFloatingOptions;