Skip to content

Commit e037390

Browse files
feat: flaoting/vue
1 parent a38508a commit e037390

File tree

18 files changed

+464
-52
lines changed

18 files changed

+464
-52
lines changed

packages/vue-primitives/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@
8484
"@vue/shared": "^3.4.31"
8585
},
8686
"dependencies": {
87+
"@floating-ui/dom": "^1.6.10",
8788
"@floating-ui/vue": "^1.1.4",
8889
"aria-hidden": "^1.2.4"
8990
},
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import {
2+
type Middleware,
3+
type Padding,
4+
arrow as arrowCore,
5+
} from '@floating-ui/dom'
6+
7+
import { type MaybeRefOrGetter, toValue } from 'vue'
8+
9+
export interface ArrowOptions {
10+
/**
11+
* The arrow element or template ref to be positioned.
12+
* @required
13+
*/
14+
element: MaybeRefOrGetter<Element | undefined>
15+
/**
16+
* The padding between the arrow element and the floating element edges. Useful when the floating element has rounded corners.
17+
* @default 0
18+
*/
19+
padding?: Padding
20+
}
21+
22+
/**
23+
* Positions an inner element of the floating element such that it is centered to the reference element.
24+
* @param options The arrow options.
25+
* @see https://floating-ui.com/docs/arrow
26+
*/
27+
export function arrow(options: ArrowOptions): Middleware {
28+
return {
29+
name: 'arrow',
30+
options,
31+
fn(state) {
32+
const element = toValue(options.element)
33+
34+
if (element == null) {
35+
return {}
36+
}
37+
38+
return arrowCore({ element, padding: options.padding }).fn(state)
39+
},
40+
}
41+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
export { arrow } from './arrow.ts'
2+
export type * from './types'
3+
export { useFloating } from './useFloating.ts'
4+
5+
export {
6+
autoPlacement,
7+
flip,
8+
hide,
9+
inline,
10+
limitShift,
11+
offset,
12+
shift,
13+
size,
14+
autoUpdate,
15+
computePosition,
16+
detectOverflow,
17+
getOverflowAncestors,
18+
platform,
19+
} from '@floating-ui/dom'
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
import type {
2+
ComputePositionConfig,
3+
ComputePositionReturn,
4+
VirtualElement,
5+
} from '@floating-ui/dom'
6+
7+
import type { CSSProperties, MaybeRefOrGetter, Ref } from 'vue'
8+
import type { MutableRefObject } from '../hooks'
9+
10+
export type { ArrowOptions } from './arrow'
11+
export type {
12+
AlignedPlacement,
13+
Alignment,
14+
AutoPlacementOptions,
15+
AutoUpdateOptions,
16+
Axis,
17+
Boundary,
18+
ClientRectObject,
19+
ComputePositionConfig,
20+
ComputePositionReturn,
21+
Coords,
22+
Derivable,
23+
DetectOverflowOptions,
24+
Dimensions,
25+
ElementContext,
26+
ElementRects,
27+
Elements,
28+
FlipOptions,
29+
FloatingElement,
30+
HideOptions,
31+
InlineOptions,
32+
Length,
33+
Middleware,
34+
MiddlewareArguments,
35+
MiddlewareData,
36+
MiddlewareReturn,
37+
MiddlewareState,
38+
NodeScroll,
39+
OffsetOptions,
40+
Padding,
41+
Placement,
42+
Platform,
43+
Rect,
44+
ReferenceElement,
45+
RootBoundary,
46+
ShiftOptions,
47+
Side,
48+
SideObject,
49+
SizeOptions,
50+
Strategy,
51+
VirtualElement,
52+
} from '@floating-ui/dom'
53+
54+
type Prettify<T> = {
55+
[K in keyof T]: T[K];
56+
} & {}
57+
58+
type ToRef<T> = {
59+
[P in keyof T]: Ref<T[P]>;
60+
}
61+
62+
export type UseFloatingData = Prettify<ComputePositionReturn & { isPositioned: boolean }>
63+
64+
export type ReferenceType = Element | VirtualElement
65+
66+
export type UseFloatingReturn<RT extends ReferenceType> = Prettify<ToRef<UseFloatingData> & {
67+
/**
68+
* Update the position of the floating element, re-rendering the component
69+
* if required.
70+
*/
71+
update: () => void
72+
/**
73+
* Pre-configured positioning styles to apply to the floating element.
74+
*/
75+
floatingStyles: Ref<CSSProperties>
76+
/**
77+
* Object containing the reference and floating refs and reactive setters.
78+
*/
79+
refs: {
80+
/**
81+
* A React ref to the reference element.
82+
*/
83+
reference: MutableRefObject<ReferenceType | undefined>
84+
/**
85+
* A React ref to the floating element.
86+
*/
87+
floating: MutableRefObject<HTMLElement | undefined>
88+
/**
89+
* A callback to set the reference element (reactive).
90+
*/
91+
setReference: (node: RT | undefined) => void
92+
/**
93+
* A callback to set the floating element (reactive).
94+
*/
95+
setFloating: (node: HTMLElement | undefined) => void
96+
}
97+
/**
98+
* Object containing the reference and floating elements.
99+
*/
100+
elements: {
101+
reference: Ref<ReferenceType | undefined>
102+
floating: Ref<HTMLElement | undefined>
103+
}
104+
}>
105+
106+
export type UseFloatingCofnig = ComputePositionConfig
107+
108+
export type UseFloatingOptions<RT extends ReferenceType = ReferenceType> = Prettify<{
109+
/**
110+
* The `open` state of the floating element to synchronize with the
111+
* `isPositioned` value.
112+
* @default false
113+
*/
114+
open?: MaybeRefOrGetter<boolean>
115+
/**
116+
* A callback invoked when both the reference and floating elements are
117+
* mounted, and cleaned up when either is unmounted. This is useful for
118+
* setting up event listeners (e.g. pass `autoUpdate`).
119+
*/
120+
whileElementsMounted?: (
121+
reference: RT,
122+
floating: HTMLElement,
123+
update: () => void,
124+
) => () => void
125+
/**
126+
* Object containing the reference and floating elements.
127+
*/
128+
elements?: {
129+
reference?: Ref<RT | undefined>
130+
floating?: Ref<HTMLElement | undefined>
131+
}
132+
/**
133+
* Whether to use `transform` for positioning instead of `top` and `left`
134+
* (layout) in the `floatingStyles` object.
135+
* @default false
136+
*/
137+
transform?: boolean
138+
}>
Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
import { computePosition } from '@floating-ui/dom'
2+
import {
3+
type CSSProperties,
4+
type MaybeRefOrGetter,
5+
computed,
6+
shallowRef,
7+
toValue,
8+
watch,
9+
watchEffect,
10+
watchSyncEffect,
11+
} from 'vue'
12+
13+
import { useRef } from '../hooks/useRef.ts'
14+
import type {
15+
ComputePositionConfig,
16+
MiddlewareData,
17+
ReferenceType,
18+
UseFloatingCofnig,
19+
UseFloatingOptions,
20+
UseFloatingReturn,
21+
} from './types.ts'
22+
import { roundByDPR } from './utils/roundByDPR.ts'
23+
import { getDPR } from './utils/getDPR.ts'
24+
25+
/**
26+
* Computes the `x` and `y` coordinates that will place the floating element next to a reference element when it is given a certain CSS positioning strategy.
27+
* @param options The floating options.
28+
* @param config The floating configuration.
29+
* @see https://floating-ui.com/docs/vue
30+
*/
31+
export function useFloating<RT extends ReferenceType = ReferenceType>(
32+
options: UseFloatingOptions<RT> = {},
33+
config: MaybeRefOrGetter<UseFloatingCofnig> = {},
34+
): UseFloatingReturn<RT> {
35+
let configValue: UseFloatingCofnig
36+
37+
watchEffect(() => {
38+
const shouldUpdate = configValue !== undefined
39+
configValue = toValue(config)
40+
41+
if (shouldUpdate) {
42+
update()
43+
}
44+
})
45+
46+
const {
47+
transform = true,
48+
whileElementsMounted,
49+
open,
50+
elements: {
51+
reference: externalReference,
52+
floating: externalFloating,
53+
} = {},
54+
} = options
55+
56+
const x = shallowRef(0)
57+
const y = shallowRef(0)
58+
const strategy = shallowRef(configValue!.strategy ?? 'absolute')
59+
const placement = shallowRef(configValue!.placement ?? 'bottom')
60+
const middlewareData = shallowRef<MiddlewareData>({})
61+
const isPositioned = shallowRef(false)
62+
63+
const referenceRef = useRef<ReferenceType>()
64+
const floatingRef = useRef<HTMLElement>()
65+
66+
const _reference = shallowRef<RT>()
67+
const _floating = shallowRef<HTMLElement>()
68+
69+
function setReference(node: RT | undefined) {
70+
if (node !== referenceRef.current) {
71+
referenceRef.current = node
72+
_reference.value = node
73+
}
74+
}
75+
76+
function setFloating(node: HTMLElement | undefined) {
77+
if (node !== floatingRef.current) {
78+
floatingRef.current = node
79+
_floating.value = node
80+
}
81+
}
82+
83+
const referenceEl = computed(() => externalReference?.value || _reference.value)
84+
const floatingEl = computed(() => externalFloating?.value || _floating.value)
85+
86+
function update() {
87+
if (!referenceRef.current || !floatingRef.current)
88+
return
89+
90+
const config: ComputePositionConfig = {
91+
placement: configValue.placement,
92+
strategy: configValue.strategy,
93+
middleware: configValue.middleware,
94+
}
95+
96+
if (configValue.platform)
97+
config.platform = configValue.platform
98+
99+
computePosition(referenceRef.current, floatingRef.current, config).then(
100+
(data) => {
101+
x.value = data.x
102+
y.value = data.y
103+
strategy.value = data.strategy
104+
placement.value = data.placement
105+
middlewareData.value = data.middlewareData
106+
isPositioned.value = true
107+
},
108+
)
109+
}
110+
111+
watch(() => toValue(open), (openVal) => {
112+
if (openVal === false && isPositioned.value)
113+
isPositioned.value = false
114+
})
115+
116+
watch([referenceEl, floatingEl], ([referenceEl, floatingEl], __, onCleanup) => {
117+
if (referenceEl)
118+
referenceRef.current = referenceEl
119+
120+
if (floatingEl)
121+
floatingRef.current = floatingEl
122+
123+
if (!referenceEl || !floatingEl)
124+
return
125+
126+
if (!whileElementsMounted) {
127+
update()
128+
return
129+
}
130+
131+
onCleanup(whileElementsMounted(referenceEl, floatingEl, update))
132+
})
133+
134+
const floatingStyles = computed<CSSProperties>(() => {
135+
const initialStyles = {
136+
position: strategy.value,
137+
left: 0,
138+
top: 0,
139+
}
140+
141+
const floating = floatingEl.value
142+
if (!floating)
143+
return initialStyles
144+
145+
const xVal = roundByDPR(floating, x.value)
146+
const yVal = roundByDPR(floating, y.value)
147+
148+
if (transform) {
149+
return {
150+
...initialStyles,
151+
transform: `translate(${xVal}px, ${yVal}px)`,
152+
...(getDPR(floating) >= 1.5 && { willChange: 'transform' }),
153+
}
154+
}
155+
156+
return {
157+
position: strategy.value,
158+
left: `${xVal}px`,
159+
top: `${yVal}px`,
160+
}
161+
})
162+
163+
return {
164+
x,
165+
y,
166+
strategy,
167+
placement,
168+
middlewareData,
169+
isPositioned,
170+
floatingStyles,
171+
refs: {
172+
reference: referenceRef,
173+
floating: floatingRef,
174+
setReference,
175+
setFloating,
176+
},
177+
elements: {
178+
reference: referenceEl,
179+
floating: floatingEl,
180+
},
181+
update,
182+
}
183+
}

0 commit comments

Comments
 (0)