@vielzeug/floatit 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,84 @@
1
+ # @vielzeug/floatit
2
+
3
+ > Lightweight floating-element positioning for tooltips, dropdowns, popovers, and menus.
4
+
5
+ [![npm version](https://img.shields.io/npm/v/@vielzeug/floatit)](https://www.npmjs.com/package/@vielzeug/floatit) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
6
+
7
+ **Floatit** is a zero-dependency DOM positioning engine used by Vielzeug components and available as a standalone package. It gives you a small, composable API for computing and applying floating positions, plus middleware for offsetting, flipping, shifting, sizing, and auto-updating.
8
+
9
+ ## Installation
10
+
11
+ ```sh
12
+ pnpm add @vielzeug/floatit
13
+ # npm install @vielzeug/floatit
14
+ # yarn add @vielzeug/floatit
15
+ ```
16
+
17
+ ## Quick Start
18
+
19
+ ```ts
20
+ import { autoUpdate, flip, offset, positionFloat, shift } from '@vielzeug/floatit';
21
+
22
+ const reference = document.querySelector('#trigger')!;
23
+ const floating = document.querySelector('#tooltip')!;
24
+
25
+ await positionFloat(reference, floating, {
26
+ placement: 'top',
27
+ middleware: [offset(8), flip(), shift({ padding: 6 })],
28
+ });
29
+
30
+ const cleanup = autoUpdate(reference, floating, () => {
31
+ positionFloat(reference, floating, {
32
+ placement: 'top',
33
+ middleware: [offset(8), flip(), shift({ padding: 6 })],
34
+ });
35
+ });
36
+
37
+ // later
38
+ cleanup();
39
+ ```
40
+
41
+ ## Features
42
+
43
+ - ✅ **`positionFloat()`** — compute and apply `left` / `top` styles in one call
44
+ - ✅ **`computePosition()`** — low-level `{ x, y, placement }` API when you want to control rendering yourself
45
+ - ✅ **Composable middleware** — `offset`, `flip`, `shift`, and `size`
46
+ - ✅ **`autoUpdate()`** — keeps floating UI aligned on scroll, resize, and element resizes
47
+ - ✅ **Typed placement model** — `top`, `bottom-start`, `left-end`, and related helpers
48
+ - ✅ **Zero dependencies** — pure DOM APIs only
49
+
50
+ ## API Summary
51
+
52
+ | Export | Description |
53
+ |---|---|
54
+ | `positionFloat(reference, floating, options?)` | Compute and apply the floating position |
55
+ | `computePosition(reference, floating, config?)` | Return `{ x, y, placement }` without mutating the DOM |
56
+ | `autoUpdate(reference, floating, update, options?)` | Re-run positioning when layout conditions change |
57
+ | `offset(value)` | Add distance between reference and floating element |
58
+ | `flip(options?)` | Flip to the opposite side when the preferred side overflows |
59
+ | `shift(options?)` | Clamp the floating element inside the viewport |
60
+ | `size(options?)` | Provide available dimensions and resize hooks |
61
+
62
+ ## Usage Notes
63
+
64
+ - `strategy` is part of the public option types but is not currently applied internally; position from viewport coordinates (typically with `position: fixed` CSS).
65
+ - Middleware runs in order; the common chain is `offset()`, `flip()`, `shift()`, then `size()`.
66
+ - Call the cleanup returned by `autoUpdate()` when the floating UI closes.
67
+ - Use `autoUpdate(..., { observeFloating: false })` when observing floating-size changes would cause unnecessary update loops.
68
+ - Use `computePosition()` when you want to apply transforms or animations yourself.
69
+
70
+ ## Documentation
71
+
72
+ Full docs at **[vielzeug.dev/floatit](https://vielzeug.dev/floatit)**
73
+
74
+ | | |
75
+ |---|---|
76
+ | [Overview](https://vielzeug.dev/floatit/) | Installation, quick start, and feature overview |
77
+ | [Usage Guide](https://vielzeug.dev/floatit/usage) | Placement, middleware, and lifecycle patterns |
78
+ | [API Reference](https://vielzeug.dev/floatit/api) | Complete function signatures and types |
79
+ | [Examples](https://vielzeug.dev/floatit/examples) | Tooltips, dropdowns, and framework recipes |
80
+
81
+ ## License
82
+
83
+ MIT © [Helmuth Saatkamp](https://github.com/helmuthdu) — Part of the [Vielzeug](https://github.com/helmuthdu/vielzeug) monorepo.
84
+
@@ -0,0 +1,2 @@
1
+ var e={bottom:`top`,left:`right`,right:`left`,top:`bottom`};function t(e){return e.split(`-`)[0]}function n(e){return e.split(`-`)[1]??null}function r({height:e,width:t,x:n,y:r}){return{height:e,width:t,x:n,y:r}}function i(e,t,n,r){return e===`start`?t:e===`end`?t+n-r:t+(n-r)/2}function a(e,r,a){let o=t(e),s=n(e);return o===`top`?{x:i(s,r.x,r.width,a.width),y:r.y-a.height}:o===`bottom`?{x:i(s,r.x,r.width,a.width),y:r.y+r.height}:o===`left`?{x:r.x-a.width,y:i(s,r.y,r.height,a.height)}:{x:r.x+r.width,y:i(s,r.y,r.height,a.height)}}function o(e,t,n={}){let{middleware:i=[],placement:o=`bottom`}=n,s=i.filter(Boolean),c=o,l=0,u=()=>{let n=r(e.getBoundingClientRect()),i=r(t.getBoundingClientRect()),o={...a(c,n,i),elements:{floating:t,reference:e},placement:c,rects:{floating:i,reference:n}};for(let e of s)o=e.fn(o);return o.placement!==c&&l<1?(c=o.placement,l++,u()):{placement:o.placement,x:o.x,y:o.y}};return Promise.resolve(u())}function s(e){return{fn(n){let r=t(n.placement);return{...n,x:n.x+(r===`right`?e:r===`left`?-e:0),y:n.y+(r===`bottom`?e:r===`top`?-e:0)}},name:`offset`}}function c(r={}){let{padding:i=0}=r;return{fn(r){let{placement:a,rects:{floating:o},x:s,y:c}=r,l=t(a),u=window.innerWidth,d=window.innerHeight;if(!(l===`top`&&c<i||l===`bottom`&&c+o.height>d-i||l===`left`&&s<i||l===`right`&&s+o.width>u-i))return r;let f=n(a),p=e[l],m=f?`${p}-${f}`:p;return{...r,placement:m}},name:`flip`}}function l(e={}){let{padding:t=0}=e;return{fn(e){let{rects:{floating:n},x:r,y:i}=e,a=window.innerWidth,o=window.innerHeight;return{...e,x:Math.min(Math.max(r,t),a-n.width-t),y:Math.min(Math.max(i,t),o-n.height-t)}},name:`shift`}}function u(e={}){let{apply:t,padding:n=0}=e;return{fn(e){return t?.({availableHeight:window.innerHeight-n*2,availableWidth:window.innerWidth-n*2,elements:e.elements}),e},name:`size`}}function d(e,t,n,{observeFloating:r=!0,observeVisualViewport:i=!0}={}){let a=e=>{e.composedPath().includes(t)||n()};window.addEventListener(`scroll`,a,{capture:!0,passive:!0}),window.addEventListener(`resize`,n,{passive:!0});let o=i?window.visualViewport:null;o?.addEventListener(`resize`,n,{passive:!0}),o?.addEventListener(`scroll`,n,{passive:!0});let s=new ResizeObserver(n);return s.observe(e),r&&s.observe(t),()=>{window.removeEventListener(`scroll`,a,{capture:!0}),window.removeEventListener(`resize`,n),o?.removeEventListener(`resize`,n),o?.removeEventListener(`scroll`,n),s.disconnect()}}function f(e,t,n={}){return o(e,t,{strategy:`fixed`,...n}).then(({placement:e,x:n,y:r})=>(t.style.left=`${n}px`,t.style.top=`${r}px`,e))}exports.autoUpdate=d,exports.computePosition=o,exports.flip=c,exports.offset=s,exports.positionFloat=f,exports.shift=l,exports.size=u;
2
+ //# sourceMappingURL=floatit.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"floatit.cjs","names":[],"sources":["../src/floatit.ts"],"sourcesContent":["/** @vielzeug/floatit — Lightweight floating element positioning. */\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\nexport type Side = 'top' | 'bottom' | 'left' | 'right';\nexport type Alignment = 'start' | 'end';\nexport type Placement = Side | `${Side}-${Alignment}`;\nexport type Strategy = 'fixed' | 'absolute';\n\ninterface Rect {\n x: number;\n y: number;\n width: number;\n height: number;\n}\n\nexport interface MiddlewareState {\n x: number;\n y: number;\n placement: Placement;\n rects: { floating: Rect; reference: Rect };\n elements: { floating: HTMLElement; reference: Element };\n}\n\nexport interface Middleware {\n name: string;\n fn: (state: MiddlewareState) => MiddlewareState;\n}\n\nexport interface ComputePositionConfig {\n placement?: Placement;\n strategy?: Strategy;\n middleware?: Array<Middleware | null | undefined | false>;\n}\n\nexport interface ComputePositionResult {\n x: number;\n y: number;\n placement: Placement;\n}\n\n// ─── Internal helpers ─────────────────────────────────────────────────────────\n\nconst OPPOSITE: Record<Side, Side> = { bottom: 'top', left: 'right', right: 'left', top: 'bottom' };\n\nfunction getSide(p: Placement): Side {\n return p.split('-')[0] as Side;\n}\n\nfunction getAlign(p: Placement): Alignment | null {\n return (p.split('-')[1] as Alignment) ?? null;\n}\n\nfunction toRect({ height, width, x, y }: DOMRect): Rect {\n return { height, width, x, y };\n}\n\nfunction crossCoord(align: Alignment | null, start: number, span: number, floatSpan: number): number {\n return align === 'start' ? start : align === 'end' ? start + span - floatSpan : start + (span - floatSpan) / 2;\n}\n\n/** Compute the base x/y for a floating element relative to a reference element. */\nfunction baseCoords(placement: Placement, ref: Rect, float: Rect): { x: number; y: number } {\n const side = getSide(placement);\n const align = getAlign(placement);\n\n if (side === 'top') return { x: crossCoord(align, ref.x, ref.width, float.width), y: ref.y - float.height };\n\n if (side === 'bottom') return { x: crossCoord(align, ref.x, ref.width, float.width), y: ref.y + ref.height };\n\n if (side === 'left') return { x: ref.x - float.width, y: crossCoord(align, ref.y, ref.height, float.height) };\n\n /* right */ return { x: ref.x + ref.width, y: crossCoord(align, ref.y, ref.height, float.height) };\n}\n\n// ─── computePosition ──────────────────────────────────────────────────────────\n\n/**\n * Computes the position of a floating element relative to a reference element.\n * Returns a Promise for API compatibility.\n */\nexport function computePosition(\n reference: Element,\n floating: HTMLElement,\n config: ComputePositionConfig = {},\n): Promise<ComputePositionResult> {\n const { middleware = [], placement: initial = 'bottom' } = config;\n const mws = middleware.filter(Boolean) as Middleware[];\n\n let activePlacement = initial;\n let resets = 0;\n\n const run = (): ComputePositionResult => {\n const refRect = toRect(reference.getBoundingClientRect());\n const floatRect = toRect(floating.getBoundingClientRect());\n const base = baseCoords(activePlacement, refRect, floatRect);\n\n let state: MiddlewareState = {\n ...base,\n elements: { floating, reference },\n placement: activePlacement,\n rects: { floating: floatRect, reference: refRect },\n };\n\n for (const mw of mws) state = mw.fn(state);\n\n // If a middleware (e.g. flip) changed the placement, restart once with the new one.\n if (state.placement !== activePlacement && resets < 1) {\n activePlacement = state.placement;\n resets++;\n\n return run();\n }\n\n return { placement: state.placement, x: state.x, y: state.y };\n };\n\n return Promise.resolve(run());\n}\n\n// ─── Middlewares ──────────────────────────────────────────────────────────────\n\n/** Adds a gap (in px) between the reference and the floating element. */\nexport function offset(value: number): Middleware {\n return {\n fn(state) {\n const side = getSide(state.placement);\n\n return {\n ...state,\n x: state.x + (side === 'right' ? value : side === 'left' ? -value : 0),\n y: state.y + (side === 'bottom' ? value : side === 'top' ? -value : 0),\n };\n },\n name: 'offset',\n };\n}\n\nexport interface FlipOptions {\n /** Minimum distance from the viewport edge before flipping (px). */\n padding?: number;\n}\n\n/** Flips the floating element to the opposite side when it would overflow the viewport. */\nexport function flip(options: FlipOptions = {}): Middleware {\n const { padding = 0 } = options;\n\n return {\n fn(state) {\n const {\n placement,\n rects: { floating },\n x,\n y,\n } = state;\n const side = getSide(placement);\n const vw = window.innerWidth;\n const vh = window.innerHeight;\n const overflows =\n (side === 'top' && y < padding) ||\n (side === 'bottom' && y + floating.height > vh - padding) ||\n (side === 'left' && x < padding) ||\n (side === 'right' && x + floating.width > vw - padding);\n\n if (!overflows) return state;\n\n const align = getAlign(placement);\n const opp = OPPOSITE[side];\n const flipped = (align ? `${opp}-${align}` : opp) as Placement;\n\n return { ...state, placement: flipped };\n },\n name: 'flip',\n };\n}\n\nexport interface ShiftOptions {\n /** Minimum distance to maintain from the viewport edges (px). */\n padding?: number;\n}\n\n/** Shifts the floating element along its axis to keep it within the viewport. */\nexport function shift(options: ShiftOptions = {}): Middleware {\n const { padding = 0 } = options;\n\n return {\n fn(state) {\n const {\n rects: { floating },\n x,\n y,\n } = state;\n const vw = window.innerWidth;\n const vh = window.innerHeight;\n\n return {\n ...state,\n x: Math.min(Math.max(x, padding), vw - floating.width - padding),\n y: Math.min(Math.max(y, padding), vh - floating.height - padding),\n };\n },\n name: 'shift',\n };\n}\n\nexport interface SizeApplyArgs {\n availableWidth: number;\n availableHeight: number;\n elements: { floating: HTMLElement; reference: Element };\n}\n\nexport interface SizeOptions {\n /** Minimum distance to maintain from the viewport edges (px). */\n padding?: number;\n /** Called with available dimensions — use to resize the floating element. */\n apply?: (args: SizeApplyArgs) => void;\n}\n\n/** Provides available width/height and optionally resizes the floating element. */\nexport function size(options: SizeOptions = {}): Middleware {\n const { apply, padding = 0 } = options;\n\n return {\n fn(state) {\n apply?.({\n availableHeight: window.innerHeight - padding * 2,\n availableWidth: window.innerWidth - padding * 2,\n elements: state.elements,\n });\n\n return state;\n },\n name: 'size',\n };\n}\n\n// ─── autoUpdate ───────────────────────────────────────────────────────────────\n\nexport interface AutoUpdateOptions {\n /**\n * Whether to observe size changes on the floating element itself.\n * Set to `false` for virtual-scroll dropdowns whose outer dimensions are\n * managed entirely by the caller (e.g. width set via `size()` middleware),\n * to avoid a ResizeObserver feedback loop.\n * Defaults to `true`.\n */\n observeFloating?: boolean;\n /**\n * Whether to observe `window.visualViewport` resize/scroll changes.\n * Helps keep floating UI aligned during pinch-zoom and virtual keyboard changes.\n * Defaults to `true`.\n */\n observeVisualViewport?: boolean;\n}\n\n/**\n * Automatically calls `update` whenever the floating element's position may have\n * changed (viewport resize, scroll events, or reference / floating element resize).\n * Returns a cleanup function.\n */\nexport function autoUpdate(\n reference: Element,\n floating: HTMLElement,\n update: () => void,\n { observeFloating = true, observeVisualViewport = true }: AutoUpdateOptions = {},\n): () => void {\n // Scroll events inside the floating element itself (e.g. a dropdown scrolling\n // its own options list) must never trigger repositioning.\n // Use composedPath() instead of e.target — shadow DOM retargets e.target to\n // the shadow host at the window listener boundary, so contains(e.target) would\n // miss scrolls that originated inside a shadow root.\n const scrollHandler = (e: Event) => {\n if (e.composedPath().includes(floating)) return;\n\n update();\n };\n\n window.addEventListener('scroll', scrollHandler, { capture: true, passive: true });\n window.addEventListener('resize', update, { passive: true });\n\n const vv = observeVisualViewport ? window.visualViewport : null;\n\n vv?.addEventListener('resize', update, { passive: true });\n vv?.addEventListener('scroll', update, { passive: true });\n\n const ro = new ResizeObserver(update);\n\n ro.observe(reference);\n\n if (observeFloating) ro.observe(floating);\n\n return () => {\n window.removeEventListener('scroll', scrollHandler, { capture: true } as EventListenerOptions);\n window.removeEventListener('resize', update);\n vv?.removeEventListener('resize', update);\n vv?.removeEventListener('scroll', update);\n ro.disconnect();\n };\n}\n\n// ─── positionFloat ────────────────────────────────────────────────────────────\n\nexport interface FloatOptions {\n /** Preferred placement relative to the reference element. */\n placement?: Placement;\n /** Positioning strategy. Defaults to `'fixed'`. */\n strategy?: Strategy;\n /** Middleware to modify positioning behavior. */\n middleware?: Array<Middleware | null | undefined | false>;\n}\n\n/**\n * Computes and applies the floating position to a floating element.\n * Sets `left`/`top` inline styles and returns the resolved placement.\n *\n * @example\n * ```ts\n * positionFloat(reference, floating, {\n * placement: 'top',\n * middleware: [offset(8), flip(), shift({ padding: 6 })],\n * }).then(placement => el.dataset.placement = placement);\n * ```\n */\nexport function positionFloat(\n reference: Element,\n floating: HTMLElement,\n options: FloatOptions = {},\n): Promise<Placement> {\n return computePosition(reference, floating, {\n strategy: 'fixed',\n ...options,\n }).then(({ placement, x, y }) => {\n floating.style.left = `${x}px`;\n floating.style.top = `${y}px`;\n\n return placement;\n });\n}\n"],"mappings":"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"}
@@ -0,0 +1,118 @@
1
+ /** @vielzeug/floatit — Lightweight floating element positioning. */
2
+ export type Side = 'top' | 'bottom' | 'left' | 'right';
3
+ export type Alignment = 'start' | 'end';
4
+ export type Placement = Side | `${Side}-${Alignment}`;
5
+ export type Strategy = 'fixed' | 'absolute';
6
+ interface Rect {
7
+ x: number;
8
+ y: number;
9
+ width: number;
10
+ height: number;
11
+ }
12
+ export interface MiddlewareState {
13
+ x: number;
14
+ y: number;
15
+ placement: Placement;
16
+ rects: {
17
+ floating: Rect;
18
+ reference: Rect;
19
+ };
20
+ elements: {
21
+ floating: HTMLElement;
22
+ reference: Element;
23
+ };
24
+ }
25
+ export interface Middleware {
26
+ name: string;
27
+ fn: (state: MiddlewareState) => MiddlewareState;
28
+ }
29
+ export interface ComputePositionConfig {
30
+ placement?: Placement;
31
+ strategy?: Strategy;
32
+ middleware?: Array<Middleware | null | undefined | false>;
33
+ }
34
+ export interface ComputePositionResult {
35
+ x: number;
36
+ y: number;
37
+ placement: Placement;
38
+ }
39
+ /**
40
+ * Computes the position of a floating element relative to a reference element.
41
+ * Returns a Promise for API compatibility.
42
+ */
43
+ export declare function computePosition(reference: Element, floating: HTMLElement, config?: ComputePositionConfig): Promise<ComputePositionResult>;
44
+ /** Adds a gap (in px) between the reference and the floating element. */
45
+ export declare function offset(value: number): Middleware;
46
+ export interface FlipOptions {
47
+ /** Minimum distance from the viewport edge before flipping (px). */
48
+ padding?: number;
49
+ }
50
+ /** Flips the floating element to the opposite side when it would overflow the viewport. */
51
+ export declare function flip(options?: FlipOptions): Middleware;
52
+ export interface ShiftOptions {
53
+ /** Minimum distance to maintain from the viewport edges (px). */
54
+ padding?: number;
55
+ }
56
+ /** Shifts the floating element along its axis to keep it within the viewport. */
57
+ export declare function shift(options?: ShiftOptions): Middleware;
58
+ export interface SizeApplyArgs {
59
+ availableWidth: number;
60
+ availableHeight: number;
61
+ elements: {
62
+ floating: HTMLElement;
63
+ reference: Element;
64
+ };
65
+ }
66
+ export interface SizeOptions {
67
+ /** Minimum distance to maintain from the viewport edges (px). */
68
+ padding?: number;
69
+ /** Called with available dimensions — use to resize the floating element. */
70
+ apply?: (args: SizeApplyArgs) => void;
71
+ }
72
+ /** Provides available width/height and optionally resizes the floating element. */
73
+ export declare function size(options?: SizeOptions): Middleware;
74
+ export interface AutoUpdateOptions {
75
+ /**
76
+ * Whether to observe size changes on the floating element itself.
77
+ * Set to `false` for virtual-scroll dropdowns whose outer dimensions are
78
+ * managed entirely by the caller (e.g. width set via `size()` middleware),
79
+ * to avoid a ResizeObserver feedback loop.
80
+ * Defaults to `true`.
81
+ */
82
+ observeFloating?: boolean;
83
+ /**
84
+ * Whether to observe `window.visualViewport` resize/scroll changes.
85
+ * Helps keep floating UI aligned during pinch-zoom and virtual keyboard changes.
86
+ * Defaults to `true`.
87
+ */
88
+ observeVisualViewport?: boolean;
89
+ }
90
+ /**
91
+ * Automatically calls `update` whenever the floating element's position may have
92
+ * changed (viewport resize, scroll events, or reference / floating element resize).
93
+ * Returns a cleanup function.
94
+ */
95
+ export declare function autoUpdate(reference: Element, floating: HTMLElement, update: () => void, { observeFloating, observeVisualViewport }?: AutoUpdateOptions): () => void;
96
+ export interface FloatOptions {
97
+ /** Preferred placement relative to the reference element. */
98
+ placement?: Placement;
99
+ /** Positioning strategy. Defaults to `'fixed'`. */
100
+ strategy?: Strategy;
101
+ /** Middleware to modify positioning behavior. */
102
+ middleware?: Array<Middleware | null | undefined | false>;
103
+ }
104
+ /**
105
+ * Computes and applies the floating position to a floating element.
106
+ * Sets `left`/`top` inline styles and returns the resolved placement.
107
+ *
108
+ * @example
109
+ * ```ts
110
+ * positionFloat(reference, floating, {
111
+ * placement: 'top',
112
+ * middleware: [offset(8), flip(), shift({ padding: 6 })],
113
+ * }).then(placement => el.dataset.placement = placement);
114
+ * ```
115
+ */
116
+ export declare function positionFloat(reference: Element, floating: HTMLElement, options?: FloatOptions): Promise<Placement>;
117
+ export {};
118
+ //# sourceMappingURL=floatit.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"floatit.d.ts","sourceRoot":"","sources":["../src/floatit.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ var e={bottom:`top`,left:`right`,right:`left`,top:`bottom`};function t(e){return e.split(`-`)[0]}function n(e){return e.split(`-`)[1]??null}function r({height:e,width:t,x:n,y:r}){return{height:e,width:t,x:n,y:r}}function i(e,t,n,r){return e===`start`?t:e===`end`?t+n-r:t+(n-r)/2}function a(e,r,a){let o=t(e),s=n(e);return o===`top`?{x:i(s,r.x,r.width,a.width),y:r.y-a.height}:o===`bottom`?{x:i(s,r.x,r.width,a.width),y:r.y+r.height}:o===`left`?{x:r.x-a.width,y:i(s,r.y,r.height,a.height)}:{x:r.x+r.width,y:i(s,r.y,r.height,a.height)}}function o(e,t,n={}){let{middleware:i=[],placement:o=`bottom`}=n,s=i.filter(Boolean),c=o,l=0,u=()=>{let n=r(e.getBoundingClientRect()),i=r(t.getBoundingClientRect()),o={...a(c,n,i),elements:{floating:t,reference:e},placement:c,rects:{floating:i,reference:n}};for(let e of s)o=e.fn(o);return o.placement!==c&&l<1?(c=o.placement,l++,u()):{placement:o.placement,x:o.x,y:o.y}};return Promise.resolve(u())}function s(e){return{fn(n){let r=t(n.placement);return{...n,x:n.x+(r===`right`?e:r===`left`?-e:0),y:n.y+(r===`bottom`?e:r===`top`?-e:0)}},name:`offset`}}function c(r={}){let{padding:i=0}=r;return{fn(r){let{placement:a,rects:{floating:o},x:s,y:c}=r,l=t(a),u=window.innerWidth,d=window.innerHeight;if(!(l===`top`&&c<i||l===`bottom`&&c+o.height>d-i||l===`left`&&s<i||l===`right`&&s+o.width>u-i))return r;let f=n(a),p=e[l],m=f?`${p}-${f}`:p;return{...r,placement:m}},name:`flip`}}function l(e={}){let{padding:t=0}=e;return{fn(e){let{rects:{floating:n},x:r,y:i}=e,a=window.innerWidth,o=window.innerHeight;return{...e,x:Math.min(Math.max(r,t),a-n.width-t),y:Math.min(Math.max(i,t),o-n.height-t)}},name:`shift`}}function u(e={}){let{apply:t,padding:n=0}=e;return{fn(e){return t?.({availableHeight:window.innerHeight-n*2,availableWidth:window.innerWidth-n*2,elements:e.elements}),e},name:`size`}}function d(e,t,n,{observeFloating:r=!0,observeVisualViewport:i=!0}={}){let a=e=>{e.composedPath().includes(t)||n()};window.addEventListener(`scroll`,a,{capture:!0,passive:!0}),window.addEventListener(`resize`,n,{passive:!0});let o=i?window.visualViewport:null;o?.addEventListener(`resize`,n,{passive:!0}),o?.addEventListener(`scroll`,n,{passive:!0});let s=new ResizeObserver(n);return s.observe(e),r&&s.observe(t),()=>{window.removeEventListener(`scroll`,a,{capture:!0}),window.removeEventListener(`resize`,n),o?.removeEventListener(`resize`,n),o?.removeEventListener(`scroll`,n),s.disconnect()}}function f(e,t,n={}){return o(e,t,{strategy:`fixed`,...n}).then(({placement:e,x:n,y:r})=>(t.style.left=`${n}px`,t.style.top=`${r}px`,e))}export{d as autoUpdate,o as computePosition,c as flip,s as offset,f as positionFloat,l as shift,u as size};
2
+ //# sourceMappingURL=floatit.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"floatit.js","names":[],"sources":["../src/floatit.ts"],"sourcesContent":["/** @vielzeug/floatit — Lightweight floating element positioning. */\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\nexport type Side = 'top' | 'bottom' | 'left' | 'right';\nexport type Alignment = 'start' | 'end';\nexport type Placement = Side | `${Side}-${Alignment}`;\nexport type Strategy = 'fixed' | 'absolute';\n\ninterface Rect {\n x: number;\n y: number;\n width: number;\n height: number;\n}\n\nexport interface MiddlewareState {\n x: number;\n y: number;\n placement: Placement;\n rects: { floating: Rect; reference: Rect };\n elements: { floating: HTMLElement; reference: Element };\n}\n\nexport interface Middleware {\n name: string;\n fn: (state: MiddlewareState) => MiddlewareState;\n}\n\nexport interface ComputePositionConfig {\n placement?: Placement;\n strategy?: Strategy;\n middleware?: Array<Middleware | null | undefined | false>;\n}\n\nexport interface ComputePositionResult {\n x: number;\n y: number;\n placement: Placement;\n}\n\n// ─── Internal helpers ─────────────────────────────────────────────────────────\n\nconst OPPOSITE: Record<Side, Side> = { bottom: 'top', left: 'right', right: 'left', top: 'bottom' };\n\nfunction getSide(p: Placement): Side {\n return p.split('-')[0] as Side;\n}\n\nfunction getAlign(p: Placement): Alignment | null {\n return (p.split('-')[1] as Alignment) ?? null;\n}\n\nfunction toRect({ height, width, x, y }: DOMRect): Rect {\n return { height, width, x, y };\n}\n\nfunction crossCoord(align: Alignment | null, start: number, span: number, floatSpan: number): number {\n return align === 'start' ? start : align === 'end' ? start + span - floatSpan : start + (span - floatSpan) / 2;\n}\n\n/** Compute the base x/y for a floating element relative to a reference element. */\nfunction baseCoords(placement: Placement, ref: Rect, float: Rect): { x: number; y: number } {\n const side = getSide(placement);\n const align = getAlign(placement);\n\n if (side === 'top') return { x: crossCoord(align, ref.x, ref.width, float.width), y: ref.y - float.height };\n\n if (side === 'bottom') return { x: crossCoord(align, ref.x, ref.width, float.width), y: ref.y + ref.height };\n\n if (side === 'left') return { x: ref.x - float.width, y: crossCoord(align, ref.y, ref.height, float.height) };\n\n /* right */ return { x: ref.x + ref.width, y: crossCoord(align, ref.y, ref.height, float.height) };\n}\n\n// ─── computePosition ──────────────────────────────────────────────────────────\n\n/**\n * Computes the position of a floating element relative to a reference element.\n * Returns a Promise for API compatibility.\n */\nexport function computePosition(\n reference: Element,\n floating: HTMLElement,\n config: ComputePositionConfig = {},\n): Promise<ComputePositionResult> {\n const { middleware = [], placement: initial = 'bottom' } = config;\n const mws = middleware.filter(Boolean) as Middleware[];\n\n let activePlacement = initial;\n let resets = 0;\n\n const run = (): ComputePositionResult => {\n const refRect = toRect(reference.getBoundingClientRect());\n const floatRect = toRect(floating.getBoundingClientRect());\n const base = baseCoords(activePlacement, refRect, floatRect);\n\n let state: MiddlewareState = {\n ...base,\n elements: { floating, reference },\n placement: activePlacement,\n rects: { floating: floatRect, reference: refRect },\n };\n\n for (const mw of mws) state = mw.fn(state);\n\n // If a middleware (e.g. flip) changed the placement, restart once with the new one.\n if (state.placement !== activePlacement && resets < 1) {\n activePlacement = state.placement;\n resets++;\n\n return run();\n }\n\n return { placement: state.placement, x: state.x, y: state.y };\n };\n\n return Promise.resolve(run());\n}\n\n// ─── Middlewares ──────────────────────────────────────────────────────────────\n\n/** Adds a gap (in px) between the reference and the floating element. */\nexport function offset(value: number): Middleware {\n return {\n fn(state) {\n const side = getSide(state.placement);\n\n return {\n ...state,\n x: state.x + (side === 'right' ? value : side === 'left' ? -value : 0),\n y: state.y + (side === 'bottom' ? value : side === 'top' ? -value : 0),\n };\n },\n name: 'offset',\n };\n}\n\nexport interface FlipOptions {\n /** Minimum distance from the viewport edge before flipping (px). */\n padding?: number;\n}\n\n/** Flips the floating element to the opposite side when it would overflow the viewport. */\nexport function flip(options: FlipOptions = {}): Middleware {\n const { padding = 0 } = options;\n\n return {\n fn(state) {\n const {\n placement,\n rects: { floating },\n x,\n y,\n } = state;\n const side = getSide(placement);\n const vw = window.innerWidth;\n const vh = window.innerHeight;\n const overflows =\n (side === 'top' && y < padding) ||\n (side === 'bottom' && y + floating.height > vh - padding) ||\n (side === 'left' && x < padding) ||\n (side === 'right' && x + floating.width > vw - padding);\n\n if (!overflows) return state;\n\n const align = getAlign(placement);\n const opp = OPPOSITE[side];\n const flipped = (align ? `${opp}-${align}` : opp) as Placement;\n\n return { ...state, placement: flipped };\n },\n name: 'flip',\n };\n}\n\nexport interface ShiftOptions {\n /** Minimum distance to maintain from the viewport edges (px). */\n padding?: number;\n}\n\n/** Shifts the floating element along its axis to keep it within the viewport. */\nexport function shift(options: ShiftOptions = {}): Middleware {\n const { padding = 0 } = options;\n\n return {\n fn(state) {\n const {\n rects: { floating },\n x,\n y,\n } = state;\n const vw = window.innerWidth;\n const vh = window.innerHeight;\n\n return {\n ...state,\n x: Math.min(Math.max(x, padding), vw - floating.width - padding),\n y: Math.min(Math.max(y, padding), vh - floating.height - padding),\n };\n },\n name: 'shift',\n };\n}\n\nexport interface SizeApplyArgs {\n availableWidth: number;\n availableHeight: number;\n elements: { floating: HTMLElement; reference: Element };\n}\n\nexport interface SizeOptions {\n /** Minimum distance to maintain from the viewport edges (px). */\n padding?: number;\n /** Called with available dimensions — use to resize the floating element. */\n apply?: (args: SizeApplyArgs) => void;\n}\n\n/** Provides available width/height and optionally resizes the floating element. */\nexport function size(options: SizeOptions = {}): Middleware {\n const { apply, padding = 0 } = options;\n\n return {\n fn(state) {\n apply?.({\n availableHeight: window.innerHeight - padding * 2,\n availableWidth: window.innerWidth - padding * 2,\n elements: state.elements,\n });\n\n return state;\n },\n name: 'size',\n };\n}\n\n// ─── autoUpdate ───────────────────────────────────────────────────────────────\n\nexport interface AutoUpdateOptions {\n /**\n * Whether to observe size changes on the floating element itself.\n * Set to `false` for virtual-scroll dropdowns whose outer dimensions are\n * managed entirely by the caller (e.g. width set via `size()` middleware),\n * to avoid a ResizeObserver feedback loop.\n * Defaults to `true`.\n */\n observeFloating?: boolean;\n /**\n * Whether to observe `window.visualViewport` resize/scroll changes.\n * Helps keep floating UI aligned during pinch-zoom and virtual keyboard changes.\n * Defaults to `true`.\n */\n observeVisualViewport?: boolean;\n}\n\n/**\n * Automatically calls `update` whenever the floating element's position may have\n * changed (viewport resize, scroll events, or reference / floating element resize).\n * Returns a cleanup function.\n */\nexport function autoUpdate(\n reference: Element,\n floating: HTMLElement,\n update: () => void,\n { observeFloating = true, observeVisualViewport = true }: AutoUpdateOptions = {},\n): () => void {\n // Scroll events inside the floating element itself (e.g. a dropdown scrolling\n // its own options list) must never trigger repositioning.\n // Use composedPath() instead of e.target — shadow DOM retargets e.target to\n // the shadow host at the window listener boundary, so contains(e.target) would\n // miss scrolls that originated inside a shadow root.\n const scrollHandler = (e: Event) => {\n if (e.composedPath().includes(floating)) return;\n\n update();\n };\n\n window.addEventListener('scroll', scrollHandler, { capture: true, passive: true });\n window.addEventListener('resize', update, { passive: true });\n\n const vv = observeVisualViewport ? window.visualViewport : null;\n\n vv?.addEventListener('resize', update, { passive: true });\n vv?.addEventListener('scroll', update, { passive: true });\n\n const ro = new ResizeObserver(update);\n\n ro.observe(reference);\n\n if (observeFloating) ro.observe(floating);\n\n return () => {\n window.removeEventListener('scroll', scrollHandler, { capture: true } as EventListenerOptions);\n window.removeEventListener('resize', update);\n vv?.removeEventListener('resize', update);\n vv?.removeEventListener('scroll', update);\n ro.disconnect();\n };\n}\n\n// ─── positionFloat ────────────────────────────────────────────────────────────\n\nexport interface FloatOptions {\n /** Preferred placement relative to the reference element. */\n placement?: Placement;\n /** Positioning strategy. Defaults to `'fixed'`. */\n strategy?: Strategy;\n /** Middleware to modify positioning behavior. */\n middleware?: Array<Middleware | null | undefined | false>;\n}\n\n/**\n * Computes and applies the floating position to a floating element.\n * Sets `left`/`top` inline styles and returns the resolved placement.\n *\n * @example\n * ```ts\n * positionFloat(reference, floating, {\n * placement: 'top',\n * middleware: [offset(8), flip(), shift({ padding: 6 })],\n * }).then(placement => el.dataset.placement = placement);\n * ```\n */\nexport function positionFloat(\n reference: Element,\n floating: HTMLElement,\n options: FloatOptions = {},\n): Promise<Placement> {\n return computePosition(reference, floating, {\n strategy: 'fixed',\n ...options,\n }).then(({ placement, x, y }) => {\n floating.style.left = `${x}px`;\n floating.style.top = `${y}px`;\n\n return placement;\n });\n}\n"],"mappings":"AA2CA,IAAM,EAA+B,CAAE,OAAQ,MAAO,KAAM,QAAS,MAAO,OAAQ,IAAK,SAAU,CAEnG,SAAS,EAAQ,EAAoB,CACnC,OAAO,EAAE,MAAM,IAAI,CAAC,GAGtB,SAAS,EAAS,EAAgC,CAChD,OAAQ,EAAE,MAAM,IAAI,CAAC,IAAoB,KAG3C,SAAS,EAAO,CAAE,SAAQ,QAAO,IAAG,KAAoB,CACtD,MAAO,CAAE,SAAQ,QAAO,IAAG,IAAG,CAGhC,SAAS,EAAW,EAAyB,EAAe,EAAc,EAA2B,CACnG,OAAO,IAAU,QAAU,EAAQ,IAAU,MAAQ,EAAQ,EAAO,EAAY,GAAS,EAAO,GAAa,EAI/G,SAAS,EAAW,EAAsB,EAAW,EAAuC,CAC1F,IAAM,EAAO,EAAQ,EAAU,CACzB,EAAQ,EAAS,EAAU,CAQrB,OANR,IAAS,MAAc,CAAE,EAAG,EAAW,EAAO,EAAI,EAAG,EAAI,MAAO,EAAM,MAAM,CAAE,EAAG,EAAI,EAAI,EAAM,OAAQ,CAEvG,IAAS,SAAiB,CAAE,EAAG,EAAW,EAAO,EAAI,EAAG,EAAI,MAAO,EAAM,MAAM,CAAE,EAAG,EAAI,EAAI,EAAI,OAAQ,CAExG,IAAS,OAAe,CAAE,EAAG,EAAI,EAAI,EAAM,MAAO,EAAG,EAAW,EAAO,EAAI,EAAG,EAAI,OAAQ,EAAM,OAAO,CAAE,CAE1F,CAAE,EAAG,EAAI,EAAI,EAAI,MAAO,EAAG,EAAW,EAAO,EAAI,EAAG,EAAI,OAAQ,EAAM,OAAO,CAAE,CASpG,SAAgB,EACd,EACA,EACA,EAAgC,EAAE,CACF,CAChC,GAAM,CAAE,aAAa,EAAE,CAAE,UAAW,EAAU,UAAa,EACrD,EAAM,EAAW,OAAO,QAAQ,CAElC,EAAkB,EAClB,EAAS,EAEP,MAAmC,CACvC,IAAM,EAAU,EAAO,EAAU,uBAAuB,CAAC,CACnD,EAAY,EAAO,EAAS,uBAAuB,CAAC,CAGtD,EAAyB,CAC3B,GAHW,EAAW,EAAiB,EAAS,EAAU,CAI1D,SAAU,CAAE,WAAU,YAAW,CACjC,UAAW,EACX,MAAO,CAAE,SAAU,EAAW,UAAW,EAAS,CACnD,CAED,IAAK,IAAM,KAAM,EAAK,EAAQ,EAAG,GAAG,EAAM,CAU1C,OAPI,EAAM,YAAc,GAAmB,EAAS,GAClD,EAAkB,EAAM,UACxB,IAEO,GAAK,EAGP,CAAE,UAAW,EAAM,UAAW,EAAG,EAAM,EAAG,EAAG,EAAM,EAAG,EAG/D,OAAO,QAAQ,QAAQ,GAAK,CAAC,CAM/B,SAAgB,EAAO,EAA2B,CAChD,MAAO,CACL,GAAG,EAAO,CACR,IAAM,EAAO,EAAQ,EAAM,UAAU,CAErC,MAAO,CACL,GAAG,EACH,EAAG,EAAM,GAAK,IAAS,QAAU,EAAQ,IAAS,OAAS,CAAC,EAAQ,GACpE,EAAG,EAAM,GAAK,IAAS,SAAW,EAAQ,IAAS,MAAQ,CAAC,EAAQ,GACrE,EAEH,KAAM,SACP,CASH,SAAgB,EAAK,EAAuB,EAAE,CAAc,CAC1D,GAAM,CAAE,UAAU,GAAM,EAExB,MAAO,CACL,GAAG,EAAO,CACR,GAAM,CACJ,YACA,MAAO,CAAE,YACT,IACA,KACE,EACE,EAAO,EAAQ,EAAU,CACzB,EAAK,OAAO,WACZ,EAAK,OAAO,YAOlB,GAAI,EALD,IAAS,OAAS,EAAI,GACtB,IAAS,UAAY,EAAI,EAAS,OAAS,EAAK,GAChD,IAAS,QAAU,EAAI,GACvB,IAAS,SAAW,EAAI,EAAS,MAAQ,EAAK,GAEjC,OAAO,EAEvB,IAAM,EAAQ,EAAS,EAAU,CAC3B,EAAM,EAAS,GACf,EAAW,EAAQ,GAAG,EAAI,GAAG,IAAU,EAE7C,MAAO,CAAE,GAAG,EAAO,UAAW,EAAS,EAEzC,KAAM,OACP,CASH,SAAgB,EAAM,EAAwB,EAAE,CAAc,CAC5D,GAAM,CAAE,UAAU,GAAM,EAExB,MAAO,CACL,GAAG,EAAO,CACR,GAAM,CACJ,MAAO,CAAE,YACT,IACA,KACE,EACE,EAAK,OAAO,WACZ,EAAK,OAAO,YAElB,MAAO,CACL,GAAG,EACH,EAAG,KAAK,IAAI,KAAK,IAAI,EAAG,EAAQ,CAAE,EAAK,EAAS,MAAQ,EAAQ,CAChE,EAAG,KAAK,IAAI,KAAK,IAAI,EAAG,EAAQ,CAAE,EAAK,EAAS,OAAS,EAAQ,CAClE,EAEH,KAAM,QACP,CAiBH,SAAgB,EAAK,EAAuB,EAAE,CAAc,CAC1D,GAAM,CAAE,QAAO,UAAU,GAAM,EAE/B,MAAO,CACL,GAAG,EAAO,CAOR,OANA,IAAQ,CACN,gBAAiB,OAAO,YAAc,EAAU,EAChD,eAAgB,OAAO,WAAa,EAAU,EAC9C,SAAU,EAAM,SACjB,CAAC,CAEK,GAET,KAAM,OACP,CA2BH,SAAgB,EACd,EACA,EACA,EACA,CAAE,kBAAkB,GAAM,wBAAwB,IAA4B,EAAE,CACpE,CAMZ,IAAM,EAAiB,GAAa,CAC9B,EAAE,cAAc,CAAC,SAAS,EAAS,EAEvC,GAAQ,EAGV,OAAO,iBAAiB,SAAU,EAAe,CAAE,QAAS,GAAM,QAAS,GAAM,CAAC,CAClF,OAAO,iBAAiB,SAAU,EAAQ,CAAE,QAAS,GAAM,CAAC,CAE5D,IAAM,EAAK,EAAwB,OAAO,eAAiB,KAE3D,GAAI,iBAAiB,SAAU,EAAQ,CAAE,QAAS,GAAM,CAAC,CACzD,GAAI,iBAAiB,SAAU,EAAQ,CAAE,QAAS,GAAM,CAAC,CAEzD,IAAM,EAAK,IAAI,eAAe,EAAO,CAMrC,OAJA,EAAG,QAAQ,EAAU,CAEjB,GAAiB,EAAG,QAAQ,EAAS,KAE5B,CACX,OAAO,oBAAoB,SAAU,EAAe,CAAE,QAAS,GAAM,CAAyB,CAC9F,OAAO,oBAAoB,SAAU,EAAO,CAC5C,GAAI,oBAAoB,SAAU,EAAO,CACzC,GAAI,oBAAoB,SAAU,EAAO,CACzC,EAAG,YAAY,EA2BnB,SAAgB,EACd,EACA,EACA,EAAwB,EAAE,CACN,CACpB,OAAO,EAAgB,EAAW,EAAU,CAC1C,SAAU,QACV,GAAG,EACJ,CAAC,CAAC,MAAM,CAAE,YAAW,IAAG,QACvB,EAAS,MAAM,KAAO,GAAG,EAAE,IAC3B,EAAS,MAAM,IAAM,GAAG,EAAE,IAEnB,GACP"}
package/dist/index.cjs ADDED
@@ -0,0 +1 @@
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require(`./floatit.cjs`);exports.autoUpdate=e.autoUpdate,exports.computePosition=e.computePosition,exports.flip=e.flip,exports.offset=e.offset,exports.positionFloat=e.positionFloat,exports.shift=e.shift,exports.size=e.size;
@@ -0,0 +1,2 @@
1
+ export * from './floatit';
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,WAAW,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1 @@
1
+ import{autoUpdate as e,computePosition as t,flip as n,offset as r,positionFloat as i,shift as a,size as o}from"./floatit.js";export{e as autoUpdate,t as computePosition,n as flip,r as offset,i as positionFloat,a as shift,o as size};
package/package.json ADDED
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "@vielzeug/floatit",
3
+ "version": "2.0.0",
4
+ "type": "module",
5
+ "files": [
6
+ "dist"
7
+ ],
8
+ "main": "./dist/index.cjs",
9
+ "module": "./dist/index.js",
10
+ "types": "dist/index.d.ts",
11
+ "exports": {
12
+ ".": {
13
+ "source": "./src/index.ts",
14
+ "types": "./dist/index.d.ts",
15
+ "import": "./dist/index.js",
16
+ "require": "./dist/index.cjs"
17
+ }
18
+ },
19
+ "scripts": {
20
+ "build": "vite build && pnpm run build:types",
21
+ "build:types": "tsc -p tsconfig.declarations.json",
22
+ "fix": "eslint --fix src",
23
+ "lint": "eslint src",
24
+ "prepublishOnly": "pnpm run build",
25
+ "test": "vitest"
26
+ },
27
+ "publishConfig": {
28
+ "access": "public",
29
+ "registry": "https://registry.npmjs.org/"
30
+ },
31
+ "dependencies": {},
32
+ "devDependencies": {
33
+ "@types/node": "^25.5.0",
34
+ "typescript": "~6.0.2",
35
+ "vite": "^8.0.2",
36
+ "vitest": "^4.1.1"
37
+ }
38
+ }