base-ui-vue 0.4.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 +13 -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 +1 -1
- 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 +10 -3
- package/dist/decrement/NumberFieldDecrement.cjs.map +1 -1
- package/dist/decrement/NumberFieldDecrement.js +3 -2
- package/dist/decrement/NumberFieldDecrement.js.map +1 -1
- 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 +2 -2
- package/dist/fallback/AvatarFallback.cjs.map +1 -1
- package/dist/fallback/AvatarFallback.js +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 +3 -3
- package/dist/group/NumberFieldGroup.cjs.map +1 -1
- package/dist/group/NumberFieldGroup.js +1 -1
- 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 +3 -2
- package/dist/increment/NumberFieldIncrement.cjs.map +1 -1
- package/dist/increment/NumberFieldIncrement.js +2 -1
- package/dist/increment/NumberFieldIncrement.js.map +1 -1
- package/dist/index.cjs +60 -7
- package/dist/index.d.cts +3838 -1726
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +3838 -1726
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -3
- package/dist/index2.cjs +7475 -3089
- package/dist/index2.cjs.map +1 -1
- package/dist/index2.js +7215 -3106
- 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 +4 -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/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/usePopupAutoResize.ts +254 -0
- package/src/utils/usePopupViewport.ts +398 -0
- package/src/utils/usePositioner.ts +61 -0
- package/src/utils/useRenderElement.ts +3 -0
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
//#region \0rolldown/runtime.js
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __exportAll = (all, no_symbols) => {
|
|
7
|
+
let target = {};
|
|
8
|
+
for (var name in all) {
|
|
9
|
+
__defProp(target, name, {
|
|
10
|
+
get: all[name],
|
|
11
|
+
enumerable: true
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
if (!no_symbols) {
|
|
15
|
+
__defProp(target, Symbol.toStringTag, { value: "Module" });
|
|
16
|
+
}
|
|
17
|
+
return target;
|
|
18
|
+
};
|
|
19
|
+
var __copyProps = (to, from, except, desc) => {
|
|
20
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
21
|
+
for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
22
|
+
key = keys[i];
|
|
23
|
+
if (!__hasOwnProp.call(to, key) && key !== except) {
|
|
24
|
+
__defProp(to, key, {
|
|
25
|
+
get: ((k) => from[k]).bind(null, key),
|
|
26
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return to;
|
|
32
|
+
};
|
|
33
|
+
var __reExport = (target, mod, secondTarget) => (__copyProps(target, mod, "default"), secondTarget && __copyProps(secondTarget, mod, "default"));
|
|
34
|
+
|
|
35
|
+
//#endregion
|
|
36
|
+
export { __exportAll, __reExport };
|
package/package.json
CHANGED
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
ComponentPublicInstance,
|
|
3
|
+
ComputedRef,
|
|
4
|
+
InjectionKey,
|
|
5
|
+
MaybeRefOrGetter,
|
|
6
|
+
Ref,
|
|
7
|
+
TeleportProps,
|
|
8
|
+
} from 'vue'
|
|
9
|
+
import type { BaseUIComponentProps } from '../../utils/types'
|
|
10
|
+
import type {
|
|
11
|
+
RenderRef,
|
|
12
|
+
UseRenderElementComponentProps,
|
|
13
|
+
UseRenderElementReturn,
|
|
14
|
+
} from '../../utils/useRenderElement'
|
|
15
|
+
import { computed, inject, isRef, shallowRef, toValue } from 'vue'
|
|
16
|
+
import { mergeProps } from '../../merge-props/mergeProps'
|
|
17
|
+
import { EMPTY_OBJECT } from '../../utils/constants'
|
|
18
|
+
import { useBaseUiId } from '../../utils/useBaseUiId'
|
|
19
|
+
import { useMergedRefs } from '../../utils/useMergedRefs'
|
|
20
|
+
import { useRenderElement } from '../../utils/useRenderElement'
|
|
21
|
+
import { createAttribute } from '../utils/createAttribute'
|
|
22
|
+
|
|
23
|
+
export interface FloatingPortalState {}
|
|
24
|
+
|
|
25
|
+
export type FloatingPortalTarget = TeleportProps['to']
|
|
26
|
+
|
|
27
|
+
export type FloatingPortalContainer = MaybeRefOrGetter<FloatingPortalTarget>
|
|
28
|
+
|
|
29
|
+
export interface FloatingPortalProps<
|
|
30
|
+
State extends Record<string, any> = FloatingPortalState,
|
|
31
|
+
> extends BaseUIComponentProps<State> {
|
|
32
|
+
/**
|
|
33
|
+
* A parent element to render the portal element into.
|
|
34
|
+
*/
|
|
35
|
+
container?: FloatingPortalContainer
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface FloatingPortal {}
|
|
39
|
+
|
|
40
|
+
// eslint-disable-next-line ts/no-namespace
|
|
41
|
+
export namespace FloatingPortal {
|
|
42
|
+
export type State = FloatingPortalState
|
|
43
|
+
export type Target = FloatingPortalTarget
|
|
44
|
+
export type Container = FloatingPortalContainer
|
|
45
|
+
export type Props<
|
|
46
|
+
State extends Record<string, any> = FloatingPortalState,
|
|
47
|
+
> = FloatingPortalProps<State>
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export type FloatingPortalFocusManagerState = null | {
|
|
51
|
+
modal: boolean
|
|
52
|
+
open: boolean
|
|
53
|
+
onOpenChange: (
|
|
54
|
+
open: boolean,
|
|
55
|
+
data?: { reason?: string | undefined, event?: Event | undefined },
|
|
56
|
+
) => void
|
|
57
|
+
domReference: Element | null
|
|
58
|
+
closeOnFocusOut: boolean
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export interface FloatingPortalContext {
|
|
62
|
+
portalNode: Ref<HTMLElement | null>
|
|
63
|
+
setFocusManagerState: (
|
|
64
|
+
next:
|
|
65
|
+
| FloatingPortalFocusManagerState
|
|
66
|
+
| ((prev: FloatingPortalFocusManagerState) => FloatingPortalFocusManagerState),
|
|
67
|
+
) => void
|
|
68
|
+
beforeInsideRef: Ref<HTMLSpanElement | null>
|
|
69
|
+
afterInsideRef: Ref<HTMLSpanElement | null>
|
|
70
|
+
beforeOutsideRef: Ref<HTMLSpanElement | null>
|
|
71
|
+
afterOutsideRef: Ref<HTMLSpanElement | null>
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export const floatingPortalContextKey: InjectionKey<FloatingPortalContext>
|
|
75
|
+
= Symbol('FloatingPortalContext')
|
|
76
|
+
|
|
77
|
+
export function usePortalContext() {
|
|
78
|
+
return inject(floatingPortalContextKey, null)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function resolvePortalTarget(
|
|
82
|
+
target: MaybeRefOrGetter<FloatingPortalTarget> | undefined,
|
|
83
|
+
) {
|
|
84
|
+
if (target === undefined) {
|
|
85
|
+
return undefined
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const resolvedTarget = toValue(target)
|
|
89
|
+
|
|
90
|
+
return isRef(resolvedTarget) ? resolvedTarget.value : resolvedTarget
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export interface UseFloatingPortalNodeProps<
|
|
94
|
+
State extends Record<string, any> = FloatingPortalState,
|
|
95
|
+
> {
|
|
96
|
+
ref?: RenderRef | undefined
|
|
97
|
+
container?: FloatingPortalContainer | undefined
|
|
98
|
+
to?: MaybeRefOrGetter<FloatingPortalTarget> | undefined
|
|
99
|
+
componentProps?: UseRenderElementComponentProps<State> | undefined
|
|
100
|
+
elementProps?: MaybeRefOrGetter<Record<string, any> | undefined>
|
|
101
|
+
state?: MaybeRefOrGetter<State | undefined>
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export interface UseFloatingPortalNodeResult<
|
|
105
|
+
State extends Record<string, any> = FloatingPortalState,
|
|
106
|
+
> extends UseRenderElementReturn<State> {
|
|
107
|
+
portalNode: Ref<HTMLElement | null>
|
|
108
|
+
portalTarget: ComputedRef<FloatingPortalTarget>
|
|
109
|
+
shouldRender: ComputedRef<boolean>
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export function useFloatingPortalNode<
|
|
113
|
+
State extends Record<string, any> = FloatingPortalState,
|
|
114
|
+
>(
|
|
115
|
+
props: UseFloatingPortalNodeProps<State> = {},
|
|
116
|
+
): UseFloatingPortalNodeResult<State> {
|
|
117
|
+
const uniqueId = useBaseUiId()
|
|
118
|
+
const portalContext = usePortalContext()
|
|
119
|
+
const portalNode = shallowRef<HTMLElement | null>(null)
|
|
120
|
+
const portalAttribute = createAttribute('portal')
|
|
121
|
+
|
|
122
|
+
const state = computed(() =>
|
|
123
|
+
toValue(props.state) ?? (EMPTY_OBJECT as State),
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
const portalTarget = computed<FloatingPortalTarget>(() => {
|
|
127
|
+
const container = resolvePortalTarget(props.container)
|
|
128
|
+
|
|
129
|
+
if (container !== undefined) {
|
|
130
|
+
return container
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const explicitTarget = resolvePortalTarget(props.to)
|
|
134
|
+
|
|
135
|
+
if (explicitTarget !== undefined) {
|
|
136
|
+
return explicitTarget
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return portalContext?.portalNode.value ?? 'body'
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
const shouldRender = computed(() => uniqueId != null && portalTarget.value != null)
|
|
143
|
+
|
|
144
|
+
const portalProps = computed(() =>
|
|
145
|
+
mergeProps(
|
|
146
|
+
{
|
|
147
|
+
id: uniqueId,
|
|
148
|
+
[portalAttribute]: '',
|
|
149
|
+
},
|
|
150
|
+
toValue(props.elementProps),
|
|
151
|
+
),
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
const renderElement = useRenderElement({
|
|
155
|
+
componentProps: props.componentProps ?? EMPTY_OBJECT,
|
|
156
|
+
state,
|
|
157
|
+
props: portalProps,
|
|
158
|
+
defaultTagName: 'div',
|
|
159
|
+
ref: useMergedRefs<HTMLElement | ComponentPublicInstance>(
|
|
160
|
+
portalNode,
|
|
161
|
+
props.ref,
|
|
162
|
+
),
|
|
163
|
+
})
|
|
164
|
+
|
|
165
|
+
return {
|
|
166
|
+
...renderElement,
|
|
167
|
+
portalNode,
|
|
168
|
+
portalTarget,
|
|
169
|
+
shouldRender,
|
|
170
|
+
}
|
|
171
|
+
}
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { ComponentPublicInstance } from 'vue'
|
|
3
|
+
import type {
|
|
4
|
+
FloatingPortalFocusManagerState,
|
|
5
|
+
FloatingPortal as FloatingPortalTypes,
|
|
6
|
+
} from './FloatingPortal'
|
|
7
|
+
</script>
|
|
8
|
+
|
|
9
|
+
<script setup lang="ts">
|
|
10
|
+
import {
|
|
11
|
+
computed,
|
|
12
|
+
provide,
|
|
13
|
+
shallowRef,
|
|
14
|
+
unref,
|
|
15
|
+
useAttrs,
|
|
16
|
+
watch,
|
|
17
|
+
} from 'vue'
|
|
18
|
+
import { addEventListener } from '../../utils/addEventListener'
|
|
19
|
+
import { ownerVisuallyHidden } from '../../utils/constants'
|
|
20
|
+
import { createChangeEventDetails } from '../../utils/createBaseUIEventDetails'
|
|
21
|
+
import FocusGuard from '../../utils/FocusGuard.vue'
|
|
22
|
+
import { mergeCleanups } from '../../utils/mergeCleanups'
|
|
23
|
+
import { REASONS } from '../../utils/reasons'
|
|
24
|
+
import {
|
|
25
|
+
disableFocusInside,
|
|
26
|
+
enableFocusInside,
|
|
27
|
+
getNextTabbable,
|
|
28
|
+
getPreviousTabbable,
|
|
29
|
+
isOutsideEvent,
|
|
30
|
+
} from '../utils/tabbable'
|
|
31
|
+
import {
|
|
32
|
+
floatingPortalContextKey,
|
|
33
|
+
useFloatingPortalNode,
|
|
34
|
+
} from './FloatingPortal'
|
|
35
|
+
|
|
36
|
+
export interface FloatingPortalState {}
|
|
37
|
+
|
|
38
|
+
export interface FloatingPortalProps
|
|
39
|
+
extends FloatingPortalTypes.Props<FloatingPortalState> {
|
|
40
|
+
/**
|
|
41
|
+
* Vue Teleport target. Prefer `container` for React API parity.
|
|
42
|
+
*/
|
|
43
|
+
to?: FloatingPortalTypes.Target
|
|
44
|
+
/**
|
|
45
|
+
* Whether to render focus guards around the portal.
|
|
46
|
+
*/
|
|
47
|
+
renderGuards?: boolean
|
|
48
|
+
/**
|
|
49
|
+
* Disables Vue Teleport while preserving portal mounting behavior.
|
|
50
|
+
* @default false
|
|
51
|
+
*/
|
|
52
|
+
disabled?: boolean
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
type FocusGuardTemplateRef
|
|
56
|
+
= | Element
|
|
57
|
+
| ComponentPublicInstance
|
|
58
|
+
| { element?: HTMLSpanElement | null }
|
|
59
|
+
| null
|
|
60
|
+
|
|
61
|
+
defineOptions({
|
|
62
|
+
name: 'FloatingPortal',
|
|
63
|
+
inheritAttrs: false,
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
const props = withDefaults(defineProps<FloatingPortalProps>(), {
|
|
67
|
+
as: 'div',
|
|
68
|
+
disabled: false,
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
const attrs = useAttrs()
|
|
72
|
+
|
|
73
|
+
const state = computed<FloatingPortalState>(() => ({}))
|
|
74
|
+
const focusManagerState = shallowRef<FloatingPortalFocusManagerState>(null)
|
|
75
|
+
const focusInsideDisabled = shallowRef(false)
|
|
76
|
+
|
|
77
|
+
const beforeOutsideRef = shallowRef<HTMLSpanElement | null>(null)
|
|
78
|
+
const afterOutsideRef = shallowRef<HTMLSpanElement | null>(null)
|
|
79
|
+
const beforeInsideRef = shallowRef<HTMLSpanElement | null>(null)
|
|
80
|
+
const afterInsideRef = shallowRef<HTMLSpanElement | null>(null)
|
|
81
|
+
|
|
82
|
+
const {
|
|
83
|
+
portalNode,
|
|
84
|
+
portalTarget,
|
|
85
|
+
shouldRender,
|
|
86
|
+
tag,
|
|
87
|
+
mergedProps,
|
|
88
|
+
renderless,
|
|
89
|
+
ref: renderRef,
|
|
90
|
+
} = useFloatingPortalNode({
|
|
91
|
+
container: () => props.container,
|
|
92
|
+
to: () => props.to,
|
|
93
|
+
componentProps: props,
|
|
94
|
+
elementProps: computed(() => attrs),
|
|
95
|
+
state,
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
const shouldRenderGuards = computed(() => {
|
|
99
|
+
if (typeof props.renderGuards === 'boolean') {
|
|
100
|
+
return props.renderGuards
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const manager = focusManagerState.value
|
|
104
|
+
|
|
105
|
+
return !!manager && !manager.modal && manager.open && !!portalNode.value
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
function setFocusManagerState(
|
|
109
|
+
next:
|
|
110
|
+
| FloatingPortalFocusManagerState
|
|
111
|
+
| ((prev: FloatingPortalFocusManagerState) => FloatingPortalFocusManagerState),
|
|
112
|
+
) {
|
|
113
|
+
focusManagerState.value = typeof next === 'function'
|
|
114
|
+
? next(focusManagerState.value)
|
|
115
|
+
: next
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
watch(
|
|
119
|
+
[portalNode, () => focusManagerState.value?.modal],
|
|
120
|
+
([node, modal], _previous, onCleanup) => {
|
|
121
|
+
if (!node || modal) {
|
|
122
|
+
return
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function onFocus(event: FocusEvent) {
|
|
126
|
+
if (!node || !event.relatedTarget || !isOutsideEvent(event)) {
|
|
127
|
+
return
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (event.type === 'focusin') {
|
|
131
|
+
if (focusInsideDisabled.value) {
|
|
132
|
+
enableFocusInside(node)
|
|
133
|
+
focusInsideDisabled.value = false
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
else {
|
|
137
|
+
disableFocusInside(node)
|
|
138
|
+
focusInsideDisabled.value = true
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
onCleanup(mergeCleanups(
|
|
143
|
+
addEventListener(node, 'focusin', onFocus, true),
|
|
144
|
+
addEventListener(node, 'focusout', onFocus, true),
|
|
145
|
+
))
|
|
146
|
+
},
|
|
147
|
+
{ immediate: true },
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
watch(
|
|
151
|
+
[portalNode, () => focusManagerState.value?.open],
|
|
152
|
+
([node, open]) => {
|
|
153
|
+
if (!node || open !== true || !focusInsideDisabled.value) {
|
|
154
|
+
return
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
enableFocusInside(node)
|
|
158
|
+
focusInsideDisabled.value = false
|
|
159
|
+
},
|
|
160
|
+
{ immediate: true, flush: 'post' },
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
provide(floatingPortalContextKey, {
|
|
164
|
+
beforeOutsideRef,
|
|
165
|
+
afterOutsideRef,
|
|
166
|
+
beforeInsideRef,
|
|
167
|
+
afterInsideRef,
|
|
168
|
+
portalNode,
|
|
169
|
+
setFocusManagerState,
|
|
170
|
+
})
|
|
171
|
+
|
|
172
|
+
function handleBeforeOutsideFocus(event: FocusEvent) {
|
|
173
|
+
if (portalNode.value && isOutsideEvent(event, portalNode.value)) {
|
|
174
|
+
beforeInsideRef.value?.focus()
|
|
175
|
+
return
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const domReference = focusManagerState.value?.domReference ?? null
|
|
179
|
+
getPreviousTabbable(domReference)?.focus()
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function handleAfterOutsideFocus(event: FocusEvent) {
|
|
183
|
+
if (portalNode.value && isOutsideEvent(event, portalNode.value)) {
|
|
184
|
+
afterInsideRef.value?.focus()
|
|
185
|
+
return
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const manager = focusManagerState.value
|
|
189
|
+
const domReference = manager?.domReference ?? null
|
|
190
|
+
|
|
191
|
+
getNextTabbable(domReference)?.focus()
|
|
192
|
+
|
|
193
|
+
if (manager?.closeOnFocusOut) {
|
|
194
|
+
manager.onOpenChange(
|
|
195
|
+
false,
|
|
196
|
+
createChangeEventDetails(REASONS.focusOut, event),
|
|
197
|
+
)
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function setBeforeOutsideRef(value: FocusGuardTemplateRef) {
|
|
202
|
+
beforeOutsideRef.value = resolveFocusGuardElement(value)
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function setAfterOutsideRef(value: FocusGuardTemplateRef) {
|
|
206
|
+
afterOutsideRef.value = resolveFocusGuardElement(value)
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function resolveFocusGuardElement(value: FocusGuardTemplateRef) {
|
|
210
|
+
if (!value) {
|
|
211
|
+
return null
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
if (value instanceof HTMLSpanElement) {
|
|
215
|
+
return value
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
if ('element' in value) {
|
|
219
|
+
const element = unref(value.element)
|
|
220
|
+
return element instanceof HTMLSpanElement ? element : null
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
if ('$el' in value && value.$el instanceof HTMLSpanElement) {
|
|
224
|
+
return value.$el
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
return null
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
defineExpose({
|
|
231
|
+
element: portalNode,
|
|
232
|
+
})
|
|
233
|
+
</script>
|
|
234
|
+
|
|
235
|
+
<template>
|
|
236
|
+
<FocusGuard
|
|
237
|
+
v-if="shouldRenderGuards && portalNode"
|
|
238
|
+
:ref="setBeforeOutsideRef"
|
|
239
|
+
data-type="outside"
|
|
240
|
+
@focus="handleBeforeOutsideFocus"
|
|
241
|
+
/>
|
|
242
|
+
<span
|
|
243
|
+
v-if="shouldRenderGuards && portalNode"
|
|
244
|
+
:aria-owns="portalNode.id"
|
|
245
|
+
:style="ownerVisuallyHidden"
|
|
246
|
+
/>
|
|
247
|
+
<Teleport v-if="shouldRender" :to="portalTarget" :disabled="disabled">
|
|
248
|
+
<slot v-if="renderless" :ref="renderRef" :props="mergedProps" :state="state" />
|
|
249
|
+
<component :is="tag" v-else :ref="renderRef" v-bind="mergedProps">
|
|
250
|
+
<slot :state="state" />
|
|
251
|
+
</component>
|
|
252
|
+
</Teleport>
|
|
253
|
+
<FocusGuard
|
|
254
|
+
v-if="shouldRenderGuards && portalNode"
|
|
255
|
+
:ref="setAfterOutsideRef"
|
|
256
|
+
data-type="outside"
|
|
257
|
+
@focus="handleAfterOutsideFocus"
|
|
258
|
+
/>
|
|
259
|
+
</template>
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import type { ComputedRef, MaybeRefOrGetter } from 'vue'
|
|
2
|
+
import type { BaseUIChangeEventDetails } from '../../utils/createBaseUIEventDetails'
|
|
3
|
+
import type { TransitionStatus } from '../../utils/useTransitionStatus'
|
|
4
|
+
import type {
|
|
5
|
+
ContextData,
|
|
6
|
+
FloatingRootContext,
|
|
7
|
+
FloatingRootState,
|
|
8
|
+
FloatingTriggerMap,
|
|
9
|
+
ReferenceType,
|
|
10
|
+
} from '../types'
|
|
11
|
+
import { computed, shallowRef, toValue } from 'vue'
|
|
12
|
+
import { createEventEmitter } from '../utils/createEventEmitter'
|
|
13
|
+
import { isClickLikeEvent } from '../utils/event'
|
|
14
|
+
|
|
15
|
+
export interface CreateFloatingRootContextOptions {
|
|
16
|
+
open: MaybeRefOrGetter<boolean>
|
|
17
|
+
transitionStatus: MaybeRefOrGetter<TransitionStatus>
|
|
18
|
+
domReferenceElement: MaybeRefOrGetter<Element | null>
|
|
19
|
+
referenceElement: MaybeRefOrGetter<ReferenceType | null>
|
|
20
|
+
floatingElement: MaybeRefOrGetter<HTMLElement | null>
|
|
21
|
+
floatingId: MaybeRefOrGetter<string | undefined>
|
|
22
|
+
triggerElements: FloatingTriggerMap
|
|
23
|
+
nested?: boolean
|
|
24
|
+
onOpenChange?: (open: boolean, eventDetails: BaseUIChangeEventDetails<string, any>) => void
|
|
25
|
+
clearCloseTimer?: () => void
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function createFloatingRootContext(
|
|
29
|
+
options: CreateFloatingRootContextOptions,
|
|
30
|
+
): FloatingRootContext {
|
|
31
|
+
const state: { [K in keyof FloatingRootState]: ComputedRef<FloatingRootState[K]> } = {
|
|
32
|
+
open: computed(() => toValue(options.open)),
|
|
33
|
+
transitionStatus: computed(() => toValue(options.transitionStatus)),
|
|
34
|
+
domReferenceElement: computed(() => toValue(options.domReferenceElement)),
|
|
35
|
+
referenceElement: computed(() => toValue(options.referenceElement)),
|
|
36
|
+
floatingElement: computed(() => toValue(options.floatingElement)),
|
|
37
|
+
floatingId: computed(() => toValue(options.floatingId)),
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const context = {
|
|
41
|
+
dataRef: shallowRef<ContextData>({}),
|
|
42
|
+
events: createEventEmitter(),
|
|
43
|
+
nested: options.nested ?? false,
|
|
44
|
+
triggerElements: options.triggerElements,
|
|
45
|
+
clearCloseTimer: options.clearCloseTimer,
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const rootContext: FloatingRootContext = {
|
|
49
|
+
context,
|
|
50
|
+
useState(key) {
|
|
51
|
+
return state[key]
|
|
52
|
+
},
|
|
53
|
+
select<K extends keyof FloatingRootState>(key: K) {
|
|
54
|
+
return state[key].value
|
|
55
|
+
},
|
|
56
|
+
setOpen(open, eventDetails) {
|
|
57
|
+
options.onOpenChange?.(open, eventDetails)
|
|
58
|
+
},
|
|
59
|
+
dispatchOpenChange(open, eventDetails) {
|
|
60
|
+
syncOpenEvent(open, eventDetails.event)
|
|
61
|
+
|
|
62
|
+
context.events.emit('openchange', {
|
|
63
|
+
open,
|
|
64
|
+
reason: eventDetails.reason,
|
|
65
|
+
nativeEvent: eventDetails.event,
|
|
66
|
+
nested: context.nested,
|
|
67
|
+
triggerElement: eventDetails.trigger,
|
|
68
|
+
})
|
|
69
|
+
},
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function syncOpenEvent(open: boolean, event: Event | undefined) {
|
|
73
|
+
if (
|
|
74
|
+
!open
|
|
75
|
+
|| !state.open.value
|
|
76
|
+
|| (event != null && isClickLikeEvent(event))
|
|
77
|
+
) {
|
|
78
|
+
context.dataRef.value.openEvent = open ? event : undefined
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return rootContext
|
|
83
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import type { InjectionKey } from 'vue'
|
|
2
|
+
import type { FloatingNodeType, FloatingTreeType } from '../types'
|
|
3
|
+
import { inject, onScopeDispose, provide } from 'vue'
|
|
4
|
+
import { useBaseUiId } from '../../utils/useBaseUiId'
|
|
5
|
+
import { FloatingTreeStore } from './FloatingTreeStore'
|
|
6
|
+
|
|
7
|
+
const floatingNodeContextKey: InjectionKey<FloatingNodeType | null>
|
|
8
|
+
= Symbol('FloatingNodeContext')
|
|
9
|
+
const floatingTreeContextKey: InjectionKey<FloatingTreeType | null>
|
|
10
|
+
= Symbol('FloatingTreeContext')
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Returns the parent node id for nested floating elements, if available.
|
|
14
|
+
*/
|
|
15
|
+
export function useFloatingParentNodeId() {
|
|
16
|
+
return inject(floatingNodeContextKey, null)?.id ?? null
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Returns the nearest floating tree context, if available.
|
|
21
|
+
*/
|
|
22
|
+
export function useFloatingTree(externalTree?: FloatingTreeStore) {
|
|
23
|
+
const contextTree = inject(floatingTreeContextKey, null)
|
|
24
|
+
return externalTree ?? contextTree
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Registers a node into the nearest floating tree, returning its id.
|
|
29
|
+
*/
|
|
30
|
+
export function useFloatingNodeId(externalTree?: FloatingTreeStore) {
|
|
31
|
+
const id = useBaseUiId()
|
|
32
|
+
const tree = useFloatingTree(externalTree)
|
|
33
|
+
const parentId = useFloatingParentNodeId()
|
|
34
|
+
|
|
35
|
+
if (id) {
|
|
36
|
+
const node = { id, parentId }
|
|
37
|
+
tree?.addNode(node)
|
|
38
|
+
onScopeDispose(() => {
|
|
39
|
+
tree?.removeNode(node)
|
|
40
|
+
})
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return id
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Provides the current floating node id and parent id to nested floating elements.
|
|
48
|
+
*/
|
|
49
|
+
export function provideFloatingNode(id: string | undefined) {
|
|
50
|
+
const parentId = useFloatingParentNodeId()
|
|
51
|
+
provide(floatingNodeContextKey, {
|
|
52
|
+
id,
|
|
53
|
+
parentId,
|
|
54
|
+
})
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Provides a floating tree store to descendants, creating one when needed.
|
|
59
|
+
*/
|
|
60
|
+
export function provideFloatingTree(externalTree?: FloatingTreeStore) {
|
|
61
|
+
const tree = externalTree ?? new FloatingTreeStore()
|
|
62
|
+
provide(floatingTreeContextKey, tree)
|
|
63
|
+
return tree
|
|
64
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { Ref } from 'vue'
|
|
2
|
+
import type { FloatingEvents, FloatingNodeType } from '../types'
|
|
3
|
+
import { shallowRef } from 'vue'
|
|
4
|
+
import { createEventEmitter } from '../utils/createEventEmitter'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Stores and manages floating elements in a tree structure.
|
|
8
|
+
*/
|
|
9
|
+
export class FloatingTreeStore {
|
|
10
|
+
readonly nodesRef: Ref<FloatingNodeType[]> = shallowRef([])
|
|
11
|
+
|
|
12
|
+
readonly events: FloatingEvents = createEventEmitter()
|
|
13
|
+
|
|
14
|
+
addNode(node: FloatingNodeType) {
|
|
15
|
+
this.nodesRef.value = [...this.nodesRef.value, node]
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
removeNode(node: FloatingNodeType) {
|
|
19
|
+
this.nodesRef.value = this.nodesRef.value.filter(item => item !== node)
|
|
20
|
+
}
|
|
21
|
+
}
|