base-ui-vue 0.3.0 → 0.5.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/dist/arrow/TooltipArrow.cjs +609 -0
- package/dist/arrow/TooltipArrow.cjs.map +1 -0
- package/dist/arrow/TooltipArrow.js +478 -0
- package/dist/arrow/TooltipArrow.js.map +1 -0
- package/dist/button/Button.cjs +5 -317
- package/dist/button/Button.cjs.map +1 -1
- package/dist/button/Button.js +3 -249
- package/dist/button/Button.js.map +1 -1
- package/dist/button/ToolbarButton.cjs +19 -6
- package/dist/button/ToolbarButton.cjs.map +1 -1
- package/dist/button/ToolbarButton.js +3 -2
- package/dist/button/ToolbarButton.js.map +1 -1
- package/dist/checkbox/index.cjs +20 -9
- package/dist/checkbox/index.cjs.map +1 -1
- package/dist/checkbox/index.js +7 -2
- package/dist/checkbox/index.js.map +1 -1
- package/dist/checkbox-group/CheckboxGroup.cjs +14 -13
- package/dist/checkbox-group/CheckboxGroup.cjs.map +1 -1
- package/dist/checkbox-group/CheckboxGroup.js +2 -1
- package/dist/checkbox-group/CheckboxGroup.js.map +1 -1
- package/dist/components/FloatingPortal.cjs +66 -0
- package/dist/components/FloatingPortal.cjs.map +1 -0
- package/dist/components/FloatingPortal.js +55 -0
- package/dist/components/FloatingPortal.js.map +1 -0
- package/dist/components/FloatingRootStore.cjs +101 -0
- package/dist/components/FloatingRootStore.cjs.map +1 -0
- package/dist/components/FloatingRootStore.js +84 -0
- package/dist/components/FloatingRootStore.js.map +1 -0
- package/dist/components/FloatingTree.cjs +33 -0
- package/dist/components/FloatingTree.cjs.map +1 -0
- package/dist/components/FloatingTree.js +22 -0
- package/dist/components/FloatingTree.js.map +1 -0
- package/dist/composite/composite.cjs +6 -0
- package/dist/composite/composite.js +1 -1
- package/dist/content/ScrollAreaContent.cjs +3 -3
- package/dist/content/ScrollAreaContent.cjs.map +1 -1
- package/dist/content/ScrollAreaContent.js +1 -1
- package/dist/control/FieldControl.cjs +3 -3
- package/dist/control/FieldControl.cjs.map +1 -1
- package/dist/control/FieldControl.js +1 -1
- package/dist/control/SliderControl.cjs +3 -3
- package/dist/control/SliderControl.cjs.map +1 -1
- package/dist/control/SliderControl.js +3 -3
- package/dist/corner/ScrollAreaCorner.cjs +3 -3
- package/dist/corner/ScrollAreaCorner.cjs.map +1 -1
- package/dist/corner/ScrollAreaCorner.js +1 -1
- package/dist/decrement/NumberFieldDecrement.cjs +868 -0
- package/dist/decrement/NumberFieldDecrement.cjs.map +1 -0
- package/dist/decrement/NumberFieldDecrement.js +701 -0
- package/dist/decrement/NumberFieldDecrement.js.map +1 -0
- package/dist/description/FieldDescription.cjs +2 -2
- package/dist/description/FieldDescription.cjs.map +1 -1
- package/dist/description/FieldDescription.js +1 -1
- package/dist/error/FieldError.cjs +2 -2
- package/dist/error/FieldError.cjs.map +1 -1
- package/dist/error/FieldError.js +1 -1
- package/dist/fallback/AvatarFallback.cjs +4 -48
- package/dist/fallback/AvatarFallback.cjs.map +1 -1
- package/dist/fallback/AvatarFallback.js +4 -42
- package/dist/fallback/AvatarFallback.js.map +1 -1
- package/dist/floating-ui-vue/index.js +638 -0
- package/dist/floating-ui-vue/index.js.map +1 -0
- package/dist/form/Form.cjs +3 -3
- package/dist/form/Form.cjs.map +1 -1
- package/dist/form/Form.js +1 -1
- package/dist/group/NumberFieldGroup.cjs +72 -0
- package/dist/group/NumberFieldGroup.cjs.map +1 -0
- package/dist/group/NumberFieldGroup.js +67 -0
- package/dist/group/NumberFieldGroup.js.map +1 -0
- package/dist/group/ToolbarGroup.cjs +2 -2
- package/dist/group/ToolbarGroup.cjs.map +1 -1
- package/dist/group/ToolbarGroup.js +1 -1
- package/dist/header/AccordionHeader.cjs +2 -2
- package/dist/header/AccordionHeader.cjs.map +1 -1
- package/dist/header/AccordionHeader.js +1 -1
- package/dist/hooks/useDismiss.cjs +502 -0
- package/dist/hooks/useDismiss.cjs.map +1 -0
- package/dist/hooks/useHoverFloatingInteraction.cjs +233 -0
- package/dist/hooks/useHoverFloatingInteraction.cjs.map +1 -0
- package/dist/image/AvatarImage.cjs +4 -4
- package/dist/image/AvatarImage.cjs.map +1 -1
- package/dist/image/AvatarImage.js +1 -1
- package/dist/increment/NumberFieldIncrement.cjs +113 -0
- package/dist/increment/NumberFieldIncrement.cjs.map +1 -0
- package/dist/increment/NumberFieldIncrement.js +108 -0
- package/dist/increment/NumberFieldIncrement.js.map +1 -0
- package/dist/index.cjs +80 -8
- package/dist/index.d.cts +4328 -1600
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +4328 -1600
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -4
- package/dist/index2.cjs +9435 -3236
- package/dist/index2.cjs.map +1 -1
- package/dist/index2.js +8905 -3049
- package/dist/index2.js.map +1 -1
- package/dist/rolldown-runtime.js +36 -0
- package/package.json +1 -1
- package/src/floating-ui-vue/components/FloatingPortal.ts +171 -0
- package/src/floating-ui-vue/components/FloatingPortal.vue +259 -0
- package/src/floating-ui-vue/components/FloatingRootStore.ts +83 -0
- package/src/floating-ui-vue/components/FloatingTree.ts +64 -0
- package/src/floating-ui-vue/components/FloatingTreeStore.ts +21 -0
- package/src/floating-ui-vue/hooks/useDismiss.ts +689 -0
- package/src/floating-ui-vue/hooks/useHoverFloatingInteraction.ts +244 -0
- package/src/floating-ui-vue/hooks/useHoverInteractionSharedState.ts +125 -0
- package/src/floating-ui-vue/hooks/useHoverShared.ts +53 -0
- package/src/floating-ui-vue/index.ts +48 -0
- package/src/floating-ui-vue/middleware/arrow.ts +114 -0
- package/src/floating-ui-vue/types.ts +117 -0
- package/src/floating-ui-vue/utils/composite.ts +20 -0
- package/src/floating-ui-vue/utils/constants.ts +2 -0
- package/src/floating-ui-vue/utils/createAttribute.ts +3 -0
- package/src/floating-ui-vue/utils/createEventEmitter.ts +20 -0
- package/src/floating-ui-vue/utils/element.ts +59 -0
- package/src/floating-ui-vue/utils/event.ts +15 -0
- package/src/floating-ui-vue/utils/nodes.ts +14 -0
- package/src/floating-ui-vue/utils/tabbable.ts +309 -0
- package/src/index.ts +7 -1
- package/src/number-field/decrement/NumberFieldDecrement.vue +109 -0
- package/src/number-field/group/NumberFieldGroup.vue +47 -0
- package/src/number-field/increment/NumberFieldIncrement.vue +109 -0
- package/src/number-field/index.ts +42 -0
- package/src/number-field/input/NumberFieldInput.vue +455 -0
- package/src/number-field/root/NumberFieldRoot.vue +626 -0
- package/src/number-field/root/NumberFieldRootContext.ts +94 -0
- package/src/number-field/root/useNumberFieldButton.ts +171 -0
- package/src/number-field/scrub-area/NumberFieldScrubArea.vue +359 -0
- package/src/number-field/scrub-area/NumberFieldScrubAreaContext.ts +26 -0
- package/src/number-field/scrub-area-cursor/NumberFieldScrubAreaCursor.vue +75 -0
- package/src/number-field/utils/constants.ts +4 -0
- package/src/number-field/utils/getViewportRect.ts +34 -0
- package/src/number-field/utils/parse.ts +248 -0
- package/src/number-field/utils/stateAttributesMapping.ts +9 -0
- package/src/number-field/utils/subscribeToVisualViewportResize.ts +27 -0
- package/src/number-field/utils/types.ts +24 -0
- package/src/number-field/utils/validate.ts +120 -0
- package/src/otp-field/index.ts +22 -0
- package/src/otp-field/input/OtpFieldInput.vue +336 -0
- package/src/otp-field/root/OtpFieldRoot.vue +583 -0
- package/src/otp-field/root/OtpFieldRootContext.ts +81 -0
- package/src/otp-field/utils/otp.ts +135 -0
- package/src/otp-field/utils/stateAttributesMapping.ts +16 -0
- package/src/radio/index.ts +28 -0
- package/src/radio/indicator/RadioIndicator.vue +90 -0
- package/src/radio/indicator/RadioIndicatorDataAttributes.ts +54 -0
- package/src/radio/root/RadioRoot.vue +544 -0
- package/src/radio/root/RadioRootContext.ts +18 -0
- package/src/radio/root/RadioRootDataAttributes.ts +46 -0
- package/src/radio/utils/stateAttributesMapping.ts +29 -0
- package/src/radio-group/RadioGroup.vue +381 -0
- package/src/radio-group/RadioGroupContext.ts +42 -0
- package/src/radio-group/RadioGroupDataAttributes.ts +6 -0
- package/src/radio-group/index.ts +10 -0
- package/src/tabs/index.ts +38 -0
- package/src/tabs/indicator/TabsIndicator.vue +201 -0
- package/src/tabs/indicator/TabsIndicatorCssVars.ts +26 -0
- package/src/tabs/indicator/TabsIndicatorDataAttributes.ts +12 -0
- package/src/tabs/indicator/prehydrationScript.min.ts +5 -0
- package/src/tabs/indicator/prehydrationScript.template.js +87 -0
- package/src/tabs/list/TabsList.vue +201 -0
- package/src/tabs/list/TabsListContext.ts +29 -0
- package/src/tabs/list/TabsListDataAttributes.ts +12 -0
- package/src/tabs/panel/TabsPanel.vue +167 -0
- package/src/tabs/panel/TabsPanelDataAttributes.ts +28 -0
- package/src/tabs/root/TabsRoot.vue +468 -0
- package/src/tabs/root/TabsRootContext.ts +56 -0
- package/src/tabs/root/TabsRootDataAttributes.ts +12 -0
- package/src/tabs/root/stateAttributesMapping.ts +9 -0
- package/src/tabs/tab/TabsTab.vue +274 -0
- package/src/tabs/tab/TabsTabDataAttributes.ts +20 -0
- package/src/tabs/utils/areTabValuesEqual.ts +6 -0
- package/src/tooltip/arrow/TooltipArrow.vue +116 -0
- package/src/tooltip/arrow/TooltipArrowDataAttributes.ts +31 -0
- package/src/tooltip/index.ts +54 -0
- package/src/tooltip/popup/TooltipPopup.vue +142 -0
- package/src/tooltip/popup/TooltipPopupDataAttributes.ts +35 -0
- package/src/tooltip/portal/TooltipPortal.vue +83 -0
- package/src/tooltip/portal/TooltipPortalContext.ts +19 -0
- package/src/tooltip/positioner/TooltipPositioner.vue +221 -0
- package/src/tooltip/positioner/TooltipPositionerContext.ts +23 -0
- package/src/tooltip/positioner/TooltipPositionerCssVars.ts +27 -0
- package/src/tooltip/positioner/TooltipPositionerDataAttributes.ts +26 -0
- package/src/tooltip/provider/TooltipProvider.vue +78 -0
- package/src/tooltip/provider/TooltipProviderContext.ts +19 -0
- package/src/tooltip/root/TooltipRoot.vue +401 -0
- package/src/tooltip/root/TooltipRootContext.ts +71 -0
- package/src/tooltip/store/TooltipHandle.ts +215 -0
- package/src/tooltip/trigger/TooltipTrigger.vue +343 -0
- package/src/tooltip/trigger/TooltipTriggerDataAttributes.ts +12 -0
- package/src/tooltip/utils/constants.ts +2 -0
- package/src/tooltip/viewport/TooltipViewport.vue +149 -0
- package/src/tooltip/viewport/TooltipViewportCssVars.ts +14 -0
- package/src/tooltip/viewport/TooltipViewportDataAttributes.ts +26 -0
- package/src/utils/FloatingPortalLite.vue +66 -0
- package/src/utils/FocusGuard.vue +36 -0
- package/src/utils/adaptiveOriginMiddleware.ts +79 -0
- package/src/utils/addEventListener.ts +16 -0
- package/src/utils/constants.ts +11 -0
- package/src/utils/createBaseUIEventDetails.ts +2 -0
- package/src/utils/detectBrowser.ts +15 -0
- package/src/utils/formatNumber.ts +35 -2
- package/src/utils/getCssDimensions.ts +19 -0
- package/src/utils/getDisabledMountTransitionStyles.ts +9 -0
- package/src/utils/hideMiddleware.ts +19 -0
- package/src/utils/mergeCleanups.ts +7 -0
- package/src/utils/platform.ts +18 -0
- package/src/utils/popupStateMapping.ts +103 -0
- package/src/utils/reasons.ts +4 -0
- package/src/utils/serializeValue.ts +17 -0
- package/src/utils/useAnchorPositioning.ts +644 -0
- package/src/utils/useInterval.ts +45 -0
- package/src/utils/usePopupAutoResize.ts +254 -0
- package/src/utils/usePopupViewport.ts +398 -0
- package/src/utils/usePositioner.ts +61 -0
- package/src/utils/usePressAndHold.ts +260 -0
- package/src/utils/useRenderElement.ts +3 -0
- package/src/utils/useValueChanged.ts +21 -0
|
@@ -0,0 +1,644 @@
|
|
|
1
|
+
import type { Rect } from '@floating-ui/utils'
|
|
2
|
+
import type { ComputedRef, CSSProperties, MaybeRefOrGetter, Ref, ShallowRef } from 'vue'
|
|
3
|
+
import type {
|
|
4
|
+
AutoUpdateOptions,
|
|
5
|
+
Boundary as FloatingBoundary,
|
|
6
|
+
Middleware,
|
|
7
|
+
MiddlewareState,
|
|
8
|
+
Padding,
|
|
9
|
+
Side as PhysicalSide,
|
|
10
|
+
Placement,
|
|
11
|
+
Strategy,
|
|
12
|
+
VirtualElement,
|
|
13
|
+
} from '../floating-ui-vue'
|
|
14
|
+
import {
|
|
15
|
+
getAlignment,
|
|
16
|
+
getSide,
|
|
17
|
+
getSideAxis,
|
|
18
|
+
} from '@floating-ui/utils'
|
|
19
|
+
import { computed, shallowRef, toValue, watchEffect } from 'vue'
|
|
20
|
+
import { useDirection } from '../direction-provider/DirectionContext'
|
|
21
|
+
import {
|
|
22
|
+
autoUpdate,
|
|
23
|
+
flip,
|
|
24
|
+
limitShift,
|
|
25
|
+
offset,
|
|
26
|
+
shift,
|
|
27
|
+
size,
|
|
28
|
+
useFloating,
|
|
29
|
+
} from '../floating-ui-vue'
|
|
30
|
+
import { arrow } from '../floating-ui-vue/middleware/arrow'
|
|
31
|
+
import { DEFAULT_SIDES } from './adaptiveOriginMiddleware'
|
|
32
|
+
import { hide } from './hideMiddleware'
|
|
33
|
+
import { ownerDocument, ownerWindow } from './owner'
|
|
34
|
+
|
|
35
|
+
export type Side = 'top' | 'bottom' | 'left' | 'right' | 'inline-end' | 'inline-start'
|
|
36
|
+
export type Align = 'start' | 'center' | 'end'
|
|
37
|
+
export type Boundary = 'clipping-ancestors' | Element | Element[] | Rect
|
|
38
|
+
export type OffsetFunction = (data: {
|
|
39
|
+
side: Side
|
|
40
|
+
align: Align
|
|
41
|
+
anchor: { width: number, height: number }
|
|
42
|
+
positioner: { width: number, height: number }
|
|
43
|
+
}) => number
|
|
44
|
+
|
|
45
|
+
interface SideFlipMode {
|
|
46
|
+
/**
|
|
47
|
+
* How to avoid collisions on the side axis.
|
|
48
|
+
* - `'flip'`: If there is not enough space, place the popup on the opposite side.
|
|
49
|
+
* - `'none'`: Keep the preferred side even if it overflows.
|
|
50
|
+
*/
|
|
51
|
+
side?: 'flip' | 'none'
|
|
52
|
+
/**
|
|
53
|
+
* How to avoid collisions on the align axis.
|
|
54
|
+
* - `'flip'`: If there is not enough space, swap `'start'` and `'end'` alignment.
|
|
55
|
+
* - `'shift'`: Keep the alignment and shift the popup to fit within the boundary.
|
|
56
|
+
* - `'none'`: Keep the preferred alignment even if it overflows.
|
|
57
|
+
*/
|
|
58
|
+
align?: 'flip' | 'shift' | 'none'
|
|
59
|
+
/**
|
|
60
|
+
* If both sides on the preferred axis do not fit, determines whether to fallback
|
|
61
|
+
* to a side on the perpendicular axis and which logical side to prefer.
|
|
62
|
+
* - `'start'`: Prefer the logical start side on the perpendicular axis.
|
|
63
|
+
* - `'end'`: Prefer the logical end side on the perpendicular axis.
|
|
64
|
+
* - `'none'`: Do not fallback to the perpendicular axis.
|
|
65
|
+
*/
|
|
66
|
+
fallbackAxisSide?: 'start' | 'end' | 'none'
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
interface SideShiftMode {
|
|
70
|
+
/**
|
|
71
|
+
* How to avoid collisions on the side axis.
|
|
72
|
+
* - `'shift'`: Keep the preferred side and shift the popup to fit within the boundary.
|
|
73
|
+
* - `'none'`: Keep the preferred side even if it overflows.
|
|
74
|
+
*/
|
|
75
|
+
side?: 'shift' | 'none'
|
|
76
|
+
/**
|
|
77
|
+
* How to avoid collisions on the align axis.
|
|
78
|
+
* - `'shift'`: Keep the alignment and shift the popup to fit within the boundary.
|
|
79
|
+
* - `'none'`: Keep the preferred alignment even if it overflows.
|
|
80
|
+
*/
|
|
81
|
+
align?: 'shift' | 'none'
|
|
82
|
+
/**
|
|
83
|
+
* If both sides on the preferred axis do not fit, determines whether to fallback
|
|
84
|
+
* to a side on the perpendicular axis and which logical side to prefer.
|
|
85
|
+
* - `'start'`: Prefer the logical start side on the perpendicular axis.
|
|
86
|
+
* - `'end'`: Prefer the logical end side on the perpendicular axis.
|
|
87
|
+
* - `'none'`: Do not fallback to the perpendicular axis.
|
|
88
|
+
*/
|
|
89
|
+
fallbackAxisSide?: 'start' | 'end' | 'none'
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export type CollisionAvoidance = SideFlipMode | SideShiftMode
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Used by regular popups that usually aren't scrollable and are allowed to
|
|
96
|
+
* freely flip to any axis of placement.
|
|
97
|
+
*/
|
|
98
|
+
export const POPUP_COLLISION_AVOIDANCE: CollisionAvoidance = {
|
|
99
|
+
fallbackAxisSide: 'end',
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Used for dropdowns that usually strictly prefer top/bottom placements and
|
|
104
|
+
* use `var(--available-height)` to limit their height.
|
|
105
|
+
*/
|
|
106
|
+
export const DROPDOWN_COLLISION_AVOIDANCE: CollisionAvoidance = {
|
|
107
|
+
fallbackAxisSide: 'none',
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export interface UseAnchorPositioningSharedParameters {
|
|
111
|
+
/**
|
|
112
|
+
* An element to position the popup against.
|
|
113
|
+
* By default, the popup will be positioned against the trigger.
|
|
114
|
+
*/
|
|
115
|
+
anchor?: MaybeRefOrGetter<Element | VirtualElement | null | undefined>
|
|
116
|
+
/**
|
|
117
|
+
* Determines which CSS `position` property to use.
|
|
118
|
+
* @default 'absolute'
|
|
119
|
+
*/
|
|
120
|
+
positionMethod?: MaybeRefOrGetter<Strategy | undefined>
|
|
121
|
+
/**
|
|
122
|
+
* Which side of the anchor element to align the popup against.
|
|
123
|
+
* May automatically change to avoid collisions.
|
|
124
|
+
* @default 'bottom'
|
|
125
|
+
*/
|
|
126
|
+
side?: MaybeRefOrGetter<Side | undefined>
|
|
127
|
+
/**
|
|
128
|
+
* Distance between the anchor and the popup in pixels.
|
|
129
|
+
* Also accepts a function that returns the distance to read the dimensions of the anchor
|
|
130
|
+
* and positioner elements, along with its side and alignment.
|
|
131
|
+
*
|
|
132
|
+
* The function takes a `data` object parameter with the following properties:
|
|
133
|
+
* - `data.anchor`: the dimensions of the anchor element with properties `width` and `height`.
|
|
134
|
+
* - `data.positioner`: the dimensions of the positioner element with properties `width` and `height`.
|
|
135
|
+
* - `data.side`: which side of the anchor element the positioner is aligned against.
|
|
136
|
+
* - `data.align`: how the positioner is aligned relative to the specified side.
|
|
137
|
+
*
|
|
138
|
+
* @example
|
|
139
|
+
* ```vue
|
|
140
|
+
* <TooltipPositioner
|
|
141
|
+
* :side-offset="({ side, anchor }) => side === 'top' ? anchor.height : 0"
|
|
142
|
+
* />
|
|
143
|
+
* ```
|
|
144
|
+
*
|
|
145
|
+
* @default 0
|
|
146
|
+
*/
|
|
147
|
+
sideOffset?: MaybeRefOrGetter<number | OffsetFunction | undefined>
|
|
148
|
+
/**
|
|
149
|
+
* How to align the popup relative to the specified side.
|
|
150
|
+
* @default 'center'
|
|
151
|
+
*/
|
|
152
|
+
align?: MaybeRefOrGetter<Align | undefined>
|
|
153
|
+
/**
|
|
154
|
+
* Additional offset along the alignment axis in pixels.
|
|
155
|
+
* Also accepts a function that returns the offset to read the dimensions of the anchor
|
|
156
|
+
* and positioner elements, along with its side and alignment.
|
|
157
|
+
*
|
|
158
|
+
* The function takes a `data` object parameter with the following properties:
|
|
159
|
+
* - `data.anchor`: the dimensions of the anchor element with properties `width` and `height`.
|
|
160
|
+
* - `data.positioner`: the dimensions of the positioner element with properties `width` and `height`.
|
|
161
|
+
* - `data.side`: which side of the anchor element the positioner is aligned against.
|
|
162
|
+
* - `data.align`: how the positioner is aligned relative to the specified side.
|
|
163
|
+
*
|
|
164
|
+
* @example
|
|
165
|
+
* ```vue
|
|
166
|
+
* <TooltipPositioner
|
|
167
|
+
* :align-offset="({ align, positioner }) => align === 'start' ? positioner.width : 0"
|
|
168
|
+
* />
|
|
169
|
+
* ```
|
|
170
|
+
*
|
|
171
|
+
* @default 0
|
|
172
|
+
*/
|
|
173
|
+
alignOffset?: MaybeRefOrGetter<number | OffsetFunction | undefined>
|
|
174
|
+
/**
|
|
175
|
+
* An element or a rectangle that delimits the area that the popup is confined to.
|
|
176
|
+
* @default 'clipping-ancestors'
|
|
177
|
+
*/
|
|
178
|
+
collisionBoundary?: MaybeRefOrGetter<Boundary | undefined>
|
|
179
|
+
/**
|
|
180
|
+
* Additional space to maintain from the edge of the collision boundary.
|
|
181
|
+
* @default 5
|
|
182
|
+
*/
|
|
183
|
+
collisionPadding?: MaybeRefOrGetter<Padding | undefined>
|
|
184
|
+
/**
|
|
185
|
+
* Whether to maintain the popup in the viewport after
|
|
186
|
+
* the anchor element was scrolled out of view.
|
|
187
|
+
* @default false
|
|
188
|
+
*/
|
|
189
|
+
sticky?: MaybeRefOrGetter<boolean | undefined>
|
|
190
|
+
/**
|
|
191
|
+
* Minimum distance to maintain between the arrow and the edges of the popup.
|
|
192
|
+
*
|
|
193
|
+
* Use it to prevent the arrow element from hanging out of the rounded corners of a popup.
|
|
194
|
+
* @default 5
|
|
195
|
+
*/
|
|
196
|
+
arrowPadding?: MaybeRefOrGetter<Padding | undefined>
|
|
197
|
+
/**
|
|
198
|
+
* Whether to disable the popup from tracking any layout shift of its positioning anchor.
|
|
199
|
+
* @default false
|
|
200
|
+
*/
|
|
201
|
+
disableAnchorTracking?: MaybeRefOrGetter<boolean | undefined>
|
|
202
|
+
/**
|
|
203
|
+
* Determines how to handle collisions when positioning the popup.
|
|
204
|
+
*/
|
|
205
|
+
collisionAvoidance?: MaybeRefOrGetter<CollisionAvoidance | undefined>
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
export interface UseAnchorPositioningParameters extends UseAnchorPositioningSharedParameters {
|
|
209
|
+
keepMounted?: MaybeRefOrGetter<boolean | undefined>
|
|
210
|
+
mounted: MaybeRefOrGetter<boolean>
|
|
211
|
+
shiftCrossAxis?: MaybeRefOrGetter<boolean | undefined>
|
|
212
|
+
lazyFlip?: MaybeRefOrGetter<boolean | undefined>
|
|
213
|
+
adaptiveOrigin?: MaybeRefOrGetter<Middleware | undefined>
|
|
214
|
+
autoUpdateOptions?: MaybeRefOrGetter<Partial<AutoUpdateOptions> | undefined>
|
|
215
|
+
positionerSizeVars?: MaybeRefOrGetter<boolean | undefined>
|
|
216
|
+
/**
|
|
217
|
+
* Optional middleware that can replace the measured reference rect before offsets and collision
|
|
218
|
+
* middleware run.
|
|
219
|
+
*/
|
|
220
|
+
inline?: MaybeRefOrGetter<Middleware | undefined>
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
export interface UseAnchorPositioningReturnValue {
|
|
224
|
+
positionerRef: ShallowRef<HTMLElement | null>
|
|
225
|
+
positionerStyles: ComputedRef<CSSProperties>
|
|
226
|
+
arrowStyles: ComputedRef<CSSProperties>
|
|
227
|
+
arrowRef: Ref<HTMLElement | null>
|
|
228
|
+
arrowUncentered: ComputedRef<boolean>
|
|
229
|
+
side: ComputedRef<Side>
|
|
230
|
+
align: ComputedRef<Align>
|
|
231
|
+
physicalSide: ComputedRef<PhysicalSide>
|
|
232
|
+
anchorHidden: ComputedRef<boolean>
|
|
233
|
+
isPositioned: ComputedRef<boolean>
|
|
234
|
+
update: () => void
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Provides standardized anchor positioning behavior for floating elements.
|
|
239
|
+
* Wraps Floating UI's Vue `useFloating` composable.
|
|
240
|
+
*/
|
|
241
|
+
export function useAnchorPositioning(
|
|
242
|
+
params: UseAnchorPositioningParameters,
|
|
243
|
+
): UseAnchorPositioningReturnValue {
|
|
244
|
+
const direction = useDirection()
|
|
245
|
+
const isRtl = computed(() => direction.value === 'rtl')
|
|
246
|
+
const mounted = computed(() => toValue(params.mounted))
|
|
247
|
+
const keepMounted = computed(() => toValue(params.keepMounted) ?? false)
|
|
248
|
+
const sideParam = computed(() => toValue(params.side) ?? 'bottom')
|
|
249
|
+
const alignParam = computed(() => toValue(params.align) ?? 'center')
|
|
250
|
+
const positionMethod = computed(() => toValue(params.positionMethod) ?? 'absolute')
|
|
251
|
+
const adaptiveOriginMiddleware = computed(() => toValue(params.adaptiveOrigin))
|
|
252
|
+
const positionerSizeVars = computed(() => toValue(params.positionerSizeVars) ?? true)
|
|
253
|
+
const collisionAvoidance = computed(() =>
|
|
254
|
+
toValue(params.collisionAvoidance) ?? POPUP_COLLISION_AVOIDANCE,
|
|
255
|
+
)
|
|
256
|
+
|
|
257
|
+
const mountSide = shallowRef<PhysicalSide | null>(null)
|
|
258
|
+
const positionerRef = shallowRef<HTMLElement | null>(null)
|
|
259
|
+
const arrowRef = shallowRef<HTMLElement | null>(null)
|
|
260
|
+
const transformOrigin = shallowRef<string | undefined>(undefined)
|
|
261
|
+
const sizeData = shallowRef<{
|
|
262
|
+
availableWidth?: number
|
|
263
|
+
availableHeight?: number
|
|
264
|
+
anchorWidth?: number
|
|
265
|
+
anchorHeight?: number
|
|
266
|
+
positionerWidth?: number
|
|
267
|
+
positionerHeight?: number
|
|
268
|
+
}>({})
|
|
269
|
+
|
|
270
|
+
watchEffect(() => {
|
|
271
|
+
if (!mounted.value && mountSide.value !== null) {
|
|
272
|
+
mountSide.value = null
|
|
273
|
+
}
|
|
274
|
+
})
|
|
275
|
+
|
|
276
|
+
const requestedPhysicalSide = computed<PhysicalSide>(() =>
|
|
277
|
+
mountSide.value ?? getPhysicalSide(sideParam.value, isRtl.value),
|
|
278
|
+
)
|
|
279
|
+
|
|
280
|
+
const placement = computed<Placement>(() => {
|
|
281
|
+
if (alignParam.value === 'center') {
|
|
282
|
+
return requestedPhysicalSide.value
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
return `${requestedPhysicalSide.value}-${alignParam.value}` as Placement
|
|
286
|
+
})
|
|
287
|
+
|
|
288
|
+
const reference = computed(() => toValue(params.anchor) ?? null)
|
|
289
|
+
|
|
290
|
+
const collisionPadding = computed(() =>
|
|
291
|
+
normalizeCollisionPadding(toValue(params.collisionPadding) ?? 5, sideParam.value),
|
|
292
|
+
)
|
|
293
|
+
|
|
294
|
+
const commonCollisionProps = computed(() => ({
|
|
295
|
+
boundary: getFloatingBoundary(toValue(params.collisionBoundary)),
|
|
296
|
+
padding: collisionPadding.value,
|
|
297
|
+
}))
|
|
298
|
+
|
|
299
|
+
const autoUpdateOptions = computed<AutoUpdateOptions>(() => ({
|
|
300
|
+
elementResize:
|
|
301
|
+
!toValue(params.disableAnchorTracking) && typeof ResizeObserver !== 'undefined',
|
|
302
|
+
layoutShift:
|
|
303
|
+
!toValue(params.disableAnchorTracking) && typeof IntersectionObserver !== 'undefined',
|
|
304
|
+
...toValue(params.autoUpdateOptions),
|
|
305
|
+
}))
|
|
306
|
+
|
|
307
|
+
const middleware = computed<Middleware[]>(() => {
|
|
308
|
+
const sideOffset = toValue(params.sideOffset) ?? 0
|
|
309
|
+
const alignOffset = toValue(params.alignOffset) ?? 0
|
|
310
|
+
const arrowPadding = toValue(params.arrowPadding) ?? 5
|
|
311
|
+
const sticky = toValue(params.sticky) ?? false
|
|
312
|
+
const shiftCrossAxis = toValue(params.shiftCrossAxis) ?? false
|
|
313
|
+
const avoidance = collisionAvoidance.value
|
|
314
|
+
const collisionAvoidanceSide = avoidance.side ?? 'flip'
|
|
315
|
+
const collisionAvoidanceAlign = avoidance.align ?? 'flip'
|
|
316
|
+
const collisionAvoidanceFallbackAxisSide = avoidance.fallbackAxisSide ?? 'end'
|
|
317
|
+
const shiftDisabled
|
|
318
|
+
= collisionAvoidanceAlign === 'none' && collisionAvoidanceSide !== 'shift'
|
|
319
|
+
const crossAxisShiftEnabled
|
|
320
|
+
= !shiftDisabled && (sticky || shiftCrossAxis || collisionAvoidanceSide === 'shift')
|
|
321
|
+
|
|
322
|
+
const flipMiddleware = collisionAvoidanceSide === 'none'
|
|
323
|
+
? null
|
|
324
|
+
: flip({
|
|
325
|
+
...commonCollisionProps.value,
|
|
326
|
+
padding: expandCollisionPadding(collisionPadding.value, 1),
|
|
327
|
+
mainAxis: !shiftCrossAxis && collisionAvoidanceSide === 'flip',
|
|
328
|
+
crossAxis: collisionAvoidanceAlign === 'flip' ? 'alignment' : false,
|
|
329
|
+
fallbackAxisSideDirection: collisionAvoidanceFallbackAxisSide,
|
|
330
|
+
})
|
|
331
|
+
|
|
332
|
+
const shiftMiddleware = shiftDisabled
|
|
333
|
+
? null
|
|
334
|
+
: shift((data) => {
|
|
335
|
+
const html = ownerDocument(data.elements.floating)?.documentElement
|
|
336
|
+
|
|
337
|
+
return {
|
|
338
|
+
...commonCollisionProps.value,
|
|
339
|
+
rootBoundary: shiftCrossAxis && html
|
|
340
|
+
? { x: 0, y: 0, width: html.clientWidth, height: html.clientHeight }
|
|
341
|
+
: undefined,
|
|
342
|
+
mainAxis: collisionAvoidanceAlign !== 'none',
|
|
343
|
+
crossAxis: crossAxisShiftEnabled,
|
|
344
|
+
limiter: sticky || shiftCrossAxis
|
|
345
|
+
? undefined
|
|
346
|
+
: limitShift((limitData) => {
|
|
347
|
+
if (!arrowRef.value) {
|
|
348
|
+
return {}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
const { width, height } = arrowRef.value.getBoundingClientRect()
|
|
352
|
+
const sideAxis = getSideAxis(getSide(limitData.placement))
|
|
353
|
+
const arrowSize = sideAxis === 'y' ? width : height
|
|
354
|
+
const offsetAmount = sideAxis === 'y'
|
|
355
|
+
? collisionPadding.value.left + collisionPadding.value.right
|
|
356
|
+
: collisionPadding.value.top + collisionPadding.value.bottom
|
|
357
|
+
|
|
358
|
+
return {
|
|
359
|
+
offset: arrowSize / 2 + offsetAmount / 2,
|
|
360
|
+
}
|
|
361
|
+
}),
|
|
362
|
+
}
|
|
363
|
+
})
|
|
364
|
+
|
|
365
|
+
const middlewareItems: Array<Middleware | null | undefined> = [
|
|
366
|
+
toValue(params.inline),
|
|
367
|
+
offset((state) => {
|
|
368
|
+
const data = getOffsetData(state, sideParam.value, isRtl.value)
|
|
369
|
+
const sideAxis = typeof sideOffset === 'function' ? sideOffset(data) : sideOffset
|
|
370
|
+
const alignAxis = typeof alignOffset === 'function' ? alignOffset(data) : alignOffset
|
|
371
|
+
|
|
372
|
+
return {
|
|
373
|
+
mainAxis: sideAxis,
|
|
374
|
+
crossAxis: alignAxis,
|
|
375
|
+
alignmentAxis: alignAxis,
|
|
376
|
+
}
|
|
377
|
+
}),
|
|
378
|
+
]
|
|
379
|
+
|
|
380
|
+
if (
|
|
381
|
+
collisionAvoidanceSide === 'shift'
|
|
382
|
+
|| collisionAvoidanceAlign === 'shift'
|
|
383
|
+
|| alignParam.value === 'center'
|
|
384
|
+
) {
|
|
385
|
+
middlewareItems.push(shiftMiddleware, flipMiddleware)
|
|
386
|
+
}
|
|
387
|
+
else {
|
|
388
|
+
middlewareItems.push(flipMiddleware, shiftMiddleware)
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
middlewareItems.push(
|
|
392
|
+
size({
|
|
393
|
+
...commonCollisionProps.value,
|
|
394
|
+
apply({ elements: { floating }, availableWidth, availableHeight, rects }) {
|
|
395
|
+
if (!mounted.value) {
|
|
396
|
+
return
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
const dpr = ownerWindow(floating).devicePixelRatio || 1
|
|
400
|
+
const { x, y, width, height } = rects.reference
|
|
401
|
+
const anchorWidth = (Math.round((x + width) * dpr) - Math.round(x * dpr)) / dpr
|
|
402
|
+
const anchorHeight = (Math.round((y + height) * dpr) - Math.round(y * dpr)) / dpr
|
|
403
|
+
|
|
404
|
+
sizeData.value = {
|
|
405
|
+
availableWidth,
|
|
406
|
+
availableHeight,
|
|
407
|
+
anchorWidth,
|
|
408
|
+
anchorHeight,
|
|
409
|
+
positionerWidth: rects.floating.width,
|
|
410
|
+
positionerHeight: rects.floating.height,
|
|
411
|
+
}
|
|
412
|
+
},
|
|
413
|
+
}),
|
|
414
|
+
arrow(() => ({
|
|
415
|
+
element:
|
|
416
|
+
arrowRef.value
|
|
417
|
+
|| ownerDocument(positionerRef.value)?.createElement('div')
|
|
418
|
+
|| undefined,
|
|
419
|
+
padding: arrowPadding,
|
|
420
|
+
offsetParent: 'floating',
|
|
421
|
+
})),
|
|
422
|
+
{
|
|
423
|
+
name: 'transformOrigin',
|
|
424
|
+
fn(state) {
|
|
425
|
+
const { elements, middlewareData, placement: renderedPlacement, rects, y } = state
|
|
426
|
+
const currentRenderedSide = getSide(renderedPlacement)
|
|
427
|
+
const currentRenderedAxis = getSideAxis(currentRenderedSide)
|
|
428
|
+
const arrowEl = arrowRef.value
|
|
429
|
+
const arrowX = middlewareData.arrow?.x || 0
|
|
430
|
+
const arrowY = middlewareData.arrow?.y || 0
|
|
431
|
+
const arrowWidth = arrowEl?.clientWidth || 0
|
|
432
|
+
const arrowHeight = arrowEl?.clientHeight || 0
|
|
433
|
+
const transformX = arrowX + arrowWidth / 2
|
|
434
|
+
const transformY = arrowY + arrowHeight / 2
|
|
435
|
+
const shiftY = Math.abs(middlewareData.shift?.y || 0)
|
|
436
|
+
const halfAnchorHeight = rects.reference.height / 2
|
|
437
|
+
const sideOffsetValue = typeof sideOffset === 'function'
|
|
438
|
+
? sideOffset(getOffsetData(state, sideParam.value, isRtl.value))
|
|
439
|
+
: sideOffset
|
|
440
|
+
const isOverlappingAnchor = shiftY > sideOffsetValue
|
|
441
|
+
|
|
442
|
+
const adjacentTransformOrigin = {
|
|
443
|
+
top: `${transformX}px calc(100% + ${sideOffsetValue}px)`,
|
|
444
|
+
bottom: `${transformX}px ${-sideOffsetValue}px`,
|
|
445
|
+
left: `calc(100% + ${sideOffsetValue}px) ${transformY}px`,
|
|
446
|
+
right: `${-sideOffsetValue}px ${transformY}px`,
|
|
447
|
+
}[currentRenderedSide]
|
|
448
|
+
const overlapTransformOrigin
|
|
449
|
+
= `${transformX}px ${rects.reference.y + halfAnchorHeight - y}px`
|
|
450
|
+
|
|
451
|
+
transformOrigin.value
|
|
452
|
+
= crossAxisShiftEnabled && currentRenderedAxis === 'y' && isOverlappingAnchor
|
|
453
|
+
? overlapTransformOrigin
|
|
454
|
+
: adjacentTransformOrigin
|
|
455
|
+
|
|
456
|
+
elements.floating.style.setProperty('--transform-origin', transformOrigin.value)
|
|
457
|
+
|
|
458
|
+
return {}
|
|
459
|
+
},
|
|
460
|
+
},
|
|
461
|
+
hide,
|
|
462
|
+
toValue(params.adaptiveOrigin),
|
|
463
|
+
)
|
|
464
|
+
|
|
465
|
+
return middlewareItems.filter((item): item is Middleware => item != null)
|
|
466
|
+
})
|
|
467
|
+
|
|
468
|
+
const floating = useFloating(reference, positionerRef, {
|
|
469
|
+
open: () => keepMounted.value ? mounted.value : undefined,
|
|
470
|
+
placement,
|
|
471
|
+
strategy: positionMethod,
|
|
472
|
+
middleware,
|
|
473
|
+
transform: () => adaptiveOriginMiddleware.value == null,
|
|
474
|
+
whileElementsMounted(referenceEl, floatingEl, update) {
|
|
475
|
+
return autoUpdate(referenceEl, floatingEl, update, autoUpdateOptions.value)
|
|
476
|
+
},
|
|
477
|
+
})
|
|
478
|
+
|
|
479
|
+
const renderedPhysicalSide = computed(() => getSide(floating.placement.value))
|
|
480
|
+
const side = computed<Side>(() =>
|
|
481
|
+
getLogicalSide(sideParam.value, renderedPhysicalSide.value, isRtl.value),
|
|
482
|
+
)
|
|
483
|
+
const align = computed<Align>(() =>
|
|
484
|
+
(getAlignment(floating.placement.value) as Align | undefined) ?? 'center',
|
|
485
|
+
)
|
|
486
|
+
const anchorHidden = computed(() =>
|
|
487
|
+
Boolean(floating.middlewareData.value.hide?.referenceHidden),
|
|
488
|
+
)
|
|
489
|
+
const arrowStyles = computed<CSSProperties>(() => ({
|
|
490
|
+
position: 'absolute',
|
|
491
|
+
left: toCssPixel(floating.middlewareData.value.arrow?.x),
|
|
492
|
+
top: toCssPixel(floating.middlewareData.value.arrow?.y),
|
|
493
|
+
}))
|
|
494
|
+
const arrowUncentered = computed(() => {
|
|
495
|
+
const centerOffset = floating.middlewareData.value.arrow?.centerOffset
|
|
496
|
+
return typeof centerOffset === 'number' && centerOffset !== 0
|
|
497
|
+
})
|
|
498
|
+
const resolvedPosition = computed<Strategy>(() =>
|
|
499
|
+
floating.isPositioned.value ? positionMethod.value : 'fixed',
|
|
500
|
+
)
|
|
501
|
+
const positionerStyles = computed<CSSProperties>(() => {
|
|
502
|
+
const adaptiveOriginData = floating.middlewareData.value.adaptiveOrigin as
|
|
503
|
+
| typeof DEFAULT_SIDES
|
|
504
|
+
| undefined
|
|
505
|
+
const { sideX, sideY } = adaptiveOriginData || DEFAULT_SIDES
|
|
506
|
+
const baseStyles: CSSProperties = adaptiveOriginMiddleware.value
|
|
507
|
+
? {
|
|
508
|
+
position: resolvedPosition.value,
|
|
509
|
+
[sideX]: toCssPixel(floating.x.value),
|
|
510
|
+
[sideY]: toCssPixel(floating.y.value),
|
|
511
|
+
}
|
|
512
|
+
: {
|
|
513
|
+
...floating.floatingStyles.value,
|
|
514
|
+
position: resolvedPosition.value,
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
const styles: CSSProperties = {
|
|
518
|
+
...baseStyles,
|
|
519
|
+
'--available-width': toCssPixel(sizeData.value.availableWidth),
|
|
520
|
+
'--available-height': toCssPixel(sizeData.value.availableHeight),
|
|
521
|
+
'--anchor-width': toCssPixel(sizeData.value.anchorWidth),
|
|
522
|
+
'--anchor-height': toCssPixel(sizeData.value.anchorHeight),
|
|
523
|
+
'--transform-origin': transformOrigin.value,
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
if (positionerSizeVars.value) {
|
|
527
|
+
styles['--positioner-width'] = toCssPixel(sizeData.value.positionerWidth)
|
|
528
|
+
styles['--positioner-height'] = toCssPixel(sizeData.value.positionerHeight)
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
if (!floating.isPositioned.value) {
|
|
532
|
+
styles.opacity = 0
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
return styles
|
|
536
|
+
})
|
|
537
|
+
|
|
538
|
+
watchEffect(() => {
|
|
539
|
+
if (!toValue(params.lazyFlip) || !mounted.value || !floating.isPositioned.value) {
|
|
540
|
+
return
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
if (mountSide.value !== renderedPhysicalSide.value) {
|
|
544
|
+
mountSide.value = renderedPhysicalSide.value
|
|
545
|
+
}
|
|
546
|
+
})
|
|
547
|
+
|
|
548
|
+
return {
|
|
549
|
+
positionerRef,
|
|
550
|
+
positionerStyles,
|
|
551
|
+
arrowStyles,
|
|
552
|
+
arrowRef,
|
|
553
|
+
arrowUncentered,
|
|
554
|
+
side,
|
|
555
|
+
align,
|
|
556
|
+
physicalSide: renderedPhysicalSide,
|
|
557
|
+
anchorHidden,
|
|
558
|
+
isPositioned: computed(() => floating.isPositioned.value),
|
|
559
|
+
update: floating.update,
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
function getLogicalSide(sideParam: Side, renderedSide: PhysicalSide, isRtl: boolean): Side {
|
|
564
|
+
const isLogicalSideParam = sideParam === 'inline-start' || sideParam === 'inline-end'
|
|
565
|
+
const logicalRight = isRtl ? 'inline-start' : 'inline-end'
|
|
566
|
+
const logicalLeft = isRtl ? 'inline-end' : 'inline-start'
|
|
567
|
+
|
|
568
|
+
return {
|
|
569
|
+
top: 'top',
|
|
570
|
+
right: isLogicalSideParam ? logicalRight : 'right',
|
|
571
|
+
bottom: 'bottom',
|
|
572
|
+
left: isLogicalSideParam ? logicalLeft : 'left',
|
|
573
|
+
}[renderedSide] as Side
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
function getPhysicalSide(side: Side, isRtl: boolean): PhysicalSide {
|
|
577
|
+
return {
|
|
578
|
+
'top': 'top',
|
|
579
|
+
'right': 'right',
|
|
580
|
+
'bottom': 'bottom',
|
|
581
|
+
'left': 'left',
|
|
582
|
+
'inline-end': isRtl ? 'left' : 'right',
|
|
583
|
+
'inline-start': isRtl ? 'right' : 'left',
|
|
584
|
+
}[side] as PhysicalSide
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
function getOffsetData(state: MiddlewareState, sideParam: Side, isRtl: boolean) {
|
|
588
|
+
const { rects, placement } = state
|
|
589
|
+
|
|
590
|
+
return {
|
|
591
|
+
side: getLogicalSide(sideParam, getSide(placement), isRtl),
|
|
592
|
+
align: (getAlignment(placement) as Align | undefined) ?? 'center',
|
|
593
|
+
anchor: { width: rects.reference.width, height: rects.reference.height },
|
|
594
|
+
positioner: { width: rects.floating.width, height: rects.floating.height },
|
|
595
|
+
} as const
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
function normalizeCollisionPadding(padding: Padding, sideParam: Side) {
|
|
599
|
+
const bias = 1
|
|
600
|
+
const biasTop = sideParam === 'bottom' ? bias : 0
|
|
601
|
+
const biasBottom = sideParam === 'top' ? bias : 0
|
|
602
|
+
const biasLeft = sideParam === 'right' ? bias : 0
|
|
603
|
+
const biasRight = sideParam === 'left' ? bias : 0
|
|
604
|
+
|
|
605
|
+
if (typeof padding === 'number') {
|
|
606
|
+
return {
|
|
607
|
+
top: padding + biasTop,
|
|
608
|
+
right: padding + biasRight,
|
|
609
|
+
bottom: padding + biasBottom,
|
|
610
|
+
left: padding + biasLeft,
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
return {
|
|
615
|
+
top: (padding.top || 0) + biasTop,
|
|
616
|
+
right: (padding.right || 0) + biasRight,
|
|
617
|
+
bottom: (padding.bottom || 0) + biasBottom,
|
|
618
|
+
left: (padding.left || 0) + biasLeft,
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
function expandCollisionPadding(
|
|
623
|
+
padding: ReturnType<typeof normalizeCollisionPadding>,
|
|
624
|
+
amount: number,
|
|
625
|
+
) {
|
|
626
|
+
return {
|
|
627
|
+
top: padding.top + amount,
|
|
628
|
+
right: padding.right + amount,
|
|
629
|
+
bottom: padding.bottom + amount,
|
|
630
|
+
left: padding.left + amount,
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
function getFloatingBoundary(boundary: Boundary | undefined): FloatingBoundary | undefined {
|
|
635
|
+
if (boundary === 'clipping-ancestors') {
|
|
636
|
+
return 'clippingAncestors'
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
return boundary
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
function toCssPixel(value: number | undefined) {
|
|
643
|
+
return value === undefined ? undefined : `${value}px`
|
|
644
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { onUnmounted } from 'vue'
|
|
2
|
+
|
|
3
|
+
type IntervalId = number
|
|
4
|
+
|
|
5
|
+
const EMPTY = 0 as IntervalId
|
|
6
|
+
|
|
7
|
+
export class Interval {
|
|
8
|
+
currentId: IntervalId = EMPTY
|
|
9
|
+
|
|
10
|
+
static create() {
|
|
11
|
+
return new Interval()
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Repeatedly executes `fn` every `delay` ms, clearing any previously scheduled interval.
|
|
16
|
+
*/
|
|
17
|
+
start(delay: number, fn: () => void) {
|
|
18
|
+
this.clear()
|
|
19
|
+
this.currentId = setInterval(fn, delay) as unknown as IntervalId
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
isStarted() {
|
|
23
|
+
return this.currentId !== EMPTY
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
clear = () => {
|
|
27
|
+
if (this.currentId !== EMPTY) {
|
|
28
|
+
clearInterval(this.currentId)
|
|
29
|
+
this.currentId = EMPTY
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* A `setInterval` with automatic cleanup on unmount.
|
|
36
|
+
*/
|
|
37
|
+
export function useInterval() {
|
|
38
|
+
const interval = Interval.create()
|
|
39
|
+
|
|
40
|
+
onUnmounted(() => {
|
|
41
|
+
interval.clear()
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
return interval
|
|
45
|
+
}
|