-import { inject } from 'vue'
-import { SELECT_ITEM_INJECTION_KEY } from './SelectItem.vue'
+import { injectSelectItemContext } from './SelectItem.vue'
import { Primitive, type PrimitiveProps } from '@/Primitive'
export interface SelectItemIndicatorProps extends PrimitiveProps {}
@@ -8,11 +7,11 @@ const props = withDefaults(defineProps
(), {
as: 'span',
})
-const itemContext = inject(SELECT_ITEM_INJECTION_KEY)
+const itemContext = injectSelectItemContext()
-
+
diff --git a/packages/radix-vue/src/Select/SelectItemText.vue b/packages/radix-vue/src/Select/SelectItemText.vue
index ad011a1f7..3d7cc8682 100644
--- a/packages/radix-vue/src/Select/SelectItemText.vue
+++ b/packages/radix-vue/src/Select/SelectItemText.vue
@@ -1,8 +1,8 @@
@@ -57,12 +56,14 @@ export default {
-
+
-
+
diff --git a/packages/radix-vue/src/Select/SelectLabel.vue b/packages/radix-vue/src/Select/SelectLabel.vue
index 0728e118c..c5ea31c10 100644
--- a/packages/radix-vue/src/Select/SelectLabel.vue
+++ b/packages/radix-vue/src/Select/SelectLabel.vue
@@ -7,16 +7,13 @@ export interface SelectLabelProps extends PrimitiveProps {
diff --git a/packages/radix-vue/src/Select/SelectProvider.vue b/packages/radix-vue/src/Select/SelectProvider.vue
index 4cdb5cea6..fe6e3ded0 100644
--- a/packages/radix-vue/src/Select/SelectProvider.vue
+++ b/packages/radix-vue/src/Select/SelectProvider.vue
@@ -1,12 +1,12 @@
diff --git a/packages/radix-vue/src/Select/SelectRoot.vue b/packages/radix-vue/src/Select/SelectRoot.vue
index 54eecf513..c590b4e88 100644
--- a/packages/radix-vue/src/Select/SelectRoot.vue
+++ b/packages/radix-vue/src/Select/SelectRoot.vue
@@ -1,8 +1,8 @@
@@ -47,7 +47,7 @@ watch(currentElement, () => {
ref="primitiveElement"
@auto-scroll="
() => {
- const { viewport, selectedItem } = contentContext!;
+ const { viewport, selectedItem } = contentContext;
if (viewport?.value && selectedItem?.value) {
viewport.value.scrollTop = viewport.value.scrollTop + selectedItem.value.offsetHeight;
}
diff --git a/packages/radix-vue/src/Select/SelectScrollUpButton.vue b/packages/radix-vue/src/Select/SelectScrollUpButton.vue
index dd03a2669..70a902a13 100644
--- a/packages/radix-vue/src/Select/SelectScrollUpButton.vue
+++ b/packages/radix-vue/src/Select/SelectScrollUpButton.vue
@@ -1,17 +1,17 @@
@@ -43,7 +43,7 @@ watch(currentElement, () => {
v-if="canScrollUp"
ref="primitiveElement"
@auto-scroll="() => {
- const { viewport, selectedItem } = contentContext!;
+ const { viewport, selectedItem } = contentContext;
if (viewport?.value && selectedItem?.value) {
viewport.value.scrollTop = viewport.value.scrollTop - selectedItem.value.offsetHeight;
}
diff --git a/packages/radix-vue/src/Select/SelectTrigger.vue b/packages/radix-vue/src/Select/SelectTrigger.vue
index eef72d18e..cd7cc4ea2 100644
--- a/packages/radix-vue/src/Select/SelectTrigger.vue
+++ b/packages/radix-vue/src/Select/SelectTrigger.vue
@@ -5,10 +5,9 @@ export interface SelectTriggerProps extends PrimitiveProps {
@@ -40,7 +40,7 @@ onMounted(() => {
:as-child="asChild"
:style="{ pointerEvents: 'none' }"
>
-
+
{{ placeholder }}
diff --git a/packages/radix-vue/src/Select/SelectViewport.vue b/packages/radix-vue/src/Select/SelectViewport.vue
index 7ed6777c2..6bb56629a 100644
--- a/packages/radix-vue/src/Select/SelectViewport.vue
+++ b/packages/radix-vue/src/Select/SelectViewport.vue
@@ -1,32 +1,24 @@
@@ -50,7 +49,7 @@ const context = inject(SLIDER_INJECTION_KEY)
event.preventDefault();
// Touch devices have a delay before focusing so won't focus if touch immediately moves
// away from target (sliding). We want thumb to focus regardless.
- if (context!.thumbElements.value.includes(target)) {
+ if (rootContext.thumbElements.value.includes(target)) {
target.focus();
}
else {
diff --git a/packages/radix-vue/src/Slider/SliderRange.vue b/packages/radix-vue/src/Slider/SliderRange.vue
index ea9895fbe..0dbbbc71f 100644
--- a/packages/radix-vue/src/Slider/SliderRange.vue
+++ b/packages/radix-vue/src/Slider/SliderRange.vue
@@ -3,27 +3,27 @@ export interface SliderRangeProps extends PrimitiveProps {}
import type { DataOrientation, Direction } from '../shared/types'
-import { useCollection, useDirection, useFormControl } from '@/shared'
+import { createContext, useCollection, useDirection, useFormControl } from '@/shared'
export interface SliderRootProps extends PrimitiveProps {
name?: string
@@ -16,7 +16,12 @@ export interface SliderRootProps extends PrimitiveProps {
minStepsBetweenThumbs?: number
}
-export interface SliderProvideValue {
+export type SliderRootEmits = {
+ 'update:modelValue': [payload: number[] | undefined]
+ 'valueCommit': [payload: number[]]
+}
+
+export interface SliderRootContext {
orientation: Ref
disabled: Ref
min: Ref
@@ -26,13 +31,8 @@ export interface SliderProvideValue {
thumbElements: Ref
}
-export type SliderRootEmits = {
- 'update:modelValue': [payload: number[] | undefined]
- 'valueCommit': [payload: number[]]
-}
-
-export const SLIDER_INJECTION_KEY
- = Symbol() as InjectionKey
+export const [injectSliderRootContext, provideSliderRootContext]
+ = createContext('SliderRoot')
export default {
inheritAttrs: false,
@@ -42,7 +42,7 @@ export default {
diff --git a/packages/radix-vue/src/Slider/SliderVertical.vue b/packages/radix-vue/src/Slider/SliderVertical.vue
index 81561be28..16857ed3a 100644
--- a/packages/radix-vue/src/Slider/SliderVertical.vue
+++ b/packages/radix-vue/src/Slider/SliderVertical.vue
@@ -1,9 +1,9 @@
diff --git a/packages/radix-vue/src/Tabs/TabsContent.vue b/packages/radix-vue/src/Tabs/TabsContent.vue
index 0bf4db6a1..f81b8beb4 100644
--- a/packages/radix-vue/src/Tabs/TabsContent.vue
+++ b/packages/radix-vue/src/Tabs/TabsContent.vue
@@ -6,19 +6,19 @@ export interface TabsContentProps extends PrimitiveProps {
-
+
diff --git a/packages/radix-vue/src/Tabs/TabsRoot.vue b/packages/radix-vue/src/Tabs/TabsRoot.vue
index 927fc2e47..431b5b248 100644
--- a/packages/radix-vue/src/Tabs/TabsRoot.vue
+++ b/packages/radix-vue/src/Tabs/TabsRoot.vue
@@ -1,11 +1,21 @@
@@ -38,25 +38,25 @@ const isSelected = computed(() => props.value === context?.modelValue.value)
:data-state="isSelected ? 'active' : 'inactive'"
:disabled="disabled"
:data-disabled="disabled ? '' : undefined"
- :data-orientation="context?.orientation.value"
+ :data-orientation="rootContext.orientation.value"
@mousedown.left="(event) => {
// only call handler if it's the left button (mousedown gets triggered by all mouse buttons)
// but not when the control key is pressed (avoiding MacOS right click)
if (!disabled && event.ctrlKey === false) {
- context?.changeModelValue(value);
+ rootContext.changeModelValue(value);
}
else {
// prevent focus to avoid accidental activation
event.preventDefault();
}
}"
- @keydown.enter.space="context?.changeModelValue(value)"
+ @keydown.enter.space="rootContext.changeModelValue(value)"
@focus="() => {
// handle 'automatic' activation if necessary
// ie. activate tab following focus
- const isAutomaticActivation = context?.activationMode !== 'manual';
+ const isAutomaticActivation = rootContext.activationMode !== 'manual';
if (!isSelected && !disabled && isAutomaticActivation) {
- context?.changeModelValue(value);
+ rootContext.changeModelValue(value);
}
}"
>
diff --git a/packages/radix-vue/src/Toast/FocusProxy.vue b/packages/radix-vue/src/Toast/FocusProxy.vue
index 10a17e748..712306e70 100644
--- a/packages/radix-vue/src/Toast/FocusProxy.vue
+++ b/packages/radix-vue/src/Toast/FocusProxy.vue
@@ -1,13 +1,12 @@
@@ -17,7 +16,7 @@ const context = inject(TOAST_PROVIDER_INJECTION_KEY)
style="position: 'fixed'"
@focus="(event) => {
const prevFocusedElement = event.relatedTarget as HTMLElement | null;
- const isFocusFromOutsideViewport = !context!.viewport.value?.contains(prevFocusedElement);
+ const isFocusFromOutsideViewport = !providerContext.viewport.value?.contains(prevFocusedElement);
if (isFocusFromOutsideViewport) emits('focusFromOutsideViewport');
}"
>
diff --git a/packages/radix-vue/src/Toast/ToastAnnounce.vue b/packages/radix-vue/src/Toast/ToastAnnounce.vue
index 9069bfd4a..cd051397c 100644
--- a/packages/radix-vue/src/Toast/ToastAnnounce.vue
+++ b/packages/radix-vue/src/Toast/ToastAnnounce.vue
@@ -1,11 +1,11 @@
@@ -18,7 +17,7 @@ const interactiveContext = inject(TOAST_INTERACTIVE_CONTEXT)
diff --git a/packages/radix-vue/src/Toast/ToastProvider.vue b/packages/radix-vue/src/Toast/ToastProvider.vue
index b3ca997b2..8afe9c140 100644
--- a/packages/radix-vue/src/Toast/ToastProvider.vue
+++ b/packages/radix-vue/src/Toast/ToastProvider.vue
@@ -1,7 +1,8 @@
@@ -142,7 +140,7 @@ provide(TOAST_INTERACTIVE_CONTEXT, {
{{ announceTextContent }}
-
+
{
pointerStartRef = { x: event.clientX, y: event.clientY };
@@ -167,8 +165,8 @@ provide(TOAST_INTERACTIVE_CONTEXT, {
const x = event.clientX - pointerStartRef.x;
const y = event.clientY - pointerStartRef.y;
const hasSwipeMoveStarted = Boolean(swipeDeltaRef);
- const isHorizontalSwipe = ['left', 'right'].includes(context!.swipeDirection.value);
- const clamp = ['left', 'up'].includes(context!.swipeDirection.value)
+ const isHorizontalSwipe = ['left', 'right'].includes(providerContext.swipeDirection.value);
+ const clamp = ['left', 'up'].includes(providerContext.swipeDirection.value)
? Math.min
: Math.max;
const clampedX = isHorizontalSwipe ? clamp(0, x) : 0;
@@ -180,7 +178,7 @@ provide(TOAST_INTERACTIVE_CONTEXT, {
swipeDeltaRef = delta;
handleAndDispatchCustomEvent(TOAST_SWIPE_MOVE, (ev: SwipeEvent) => emits('swipeMove', ev), eventDetail);
}
- else if (isDeltaInDirection(delta, context!.swipeDirection.value, moveStartBuffer)) {
+ else if (isDeltaInDirection(delta, providerContext.swipeDirection.value, moveStartBuffer)) {
swipeDeltaRef = delta;
handleAndDispatchCustomEvent(TOAST_SWIPE_START, (ev: SwipeEvent) => emits('swipeStart', ev), eventDetail);
(event.target as HTMLElement).setPointerCapture(event.pointerId);
@@ -203,7 +201,7 @@ provide(TOAST_INTERACTIVE_CONTEXT, {
const toast = event.currentTarget;
const eventDetail = { originalEvent: event, delta };
if (
- isDeltaInDirection(delta, context!.swipeDirection.value, context!.swipeThreshold.value)
+ isDeltaInDirection(delta, providerContext.swipeDirection.value, providerContext.swipeThreshold.value)
) {
handleAndDispatchCustomEvent(TOAST_SWIPE_END, (ev: SwipeEvent) => emits('swipeEnd', ev), eventDetail);
}
diff --git a/packages/radix-vue/src/Toast/ToastViewport.vue b/packages/radix-vue/src/Toast/ToastViewport.vue
index b4a9ac12e..9db254780 100644
--- a/packages/radix-vue/src/Toast/ToastViewport.vue
+++ b/packages/radix-vue/src/Toast/ToastViewport.vue
@@ -1,8 +1,8 @@
diff --git a/packages/radix-vue/src/ToggleGroup/ToggleGroupItem.vue b/packages/radix-vue/src/ToggleGroup/ToggleGroupItem.vue
index 6b48d4c81..773609183 100644
--- a/packages/radix-vue/src/ToggleGroup/ToggleGroupItem.vue
+++ b/packages/radix-vue/src/ToggleGroup/ToggleGroupItem.vue
@@ -8,8 +8,8 @@ export interface ToggleGroupItemProps extends ToggleProps {
context?.modelValue.value?.includes(props.value))
v-bind="props"
:disabled="disabled"
:pressed="
- context?.type === 'single'
- ? context?.modelValue.value === value
- : context?.modelValue.value?.includes(value)
+ rootContext.type === 'single'
+ ? rootContext.modelValue.value === value
+ : rootContext.modelValue.value?.includes(value)
"
- @update:pressed="context?.changeModelValue(value)"
+ @update:pressed="rootContext.changeModelValue(value)"
>
diff --git a/packages/radix-vue/src/ToggleGroup/ToggleGroupRoot.vue b/packages/radix-vue/src/ToggleGroup/ToggleGroupRoot.vue
index e918b1d53..6af9b154d 100644
--- a/packages/radix-vue/src/ToggleGroup/ToggleGroupRoot.vue
+++ b/packages/radix-vue/src/ToggleGroup/ToggleGroupRoot.vue
@@ -1,9 +1,9 @@
diff --git a/packages/radix-vue/src/Toolbar/ToolbarSeparator.vue b/packages/radix-vue/src/Toolbar/ToolbarSeparator.vue
index 19dd87589..f9713aaaf 100644
--- a/packages/radix-vue/src/Toolbar/ToolbarSeparator.vue
+++ b/packages/radix-vue/src/Toolbar/ToolbarSeparator.vue
@@ -1,18 +1,17 @@
diff --git a/packages/radix-vue/src/Toolbar/ToolbarToggleGroup.vue b/packages/radix-vue/src/Toolbar/ToolbarToggleGroup.vue
index b168054b4..977c4d4a9 100644
--- a/packages/radix-vue/src/Toolbar/ToolbarToggleGroup.vue
+++ b/packages/radix-vue/src/Toolbar/ToolbarToggleGroup.vue
@@ -1,6 +1,5 @@
diff --git a/packages/radix-vue/src/Tooltip/TooltipContentHoverable.vue b/packages/radix-vue/src/Tooltip/TooltipContentHoverable.vue
index 3fe6da59f..16887d3c8 100644
--- a/packages/radix-vue/src/Tooltip/TooltipContentHoverable.vue
+++ b/packages/radix-vue/src/Tooltip/TooltipContentHoverable.vue
@@ -1,7 +1,7 @@
@@ -106,11 +106,11 @@ onMounted(() => {
emits('pointerDownOutside', event)
}"
@focus-outside.prevent
- @dismiss="context.onClose()"
+ @dismiss="rootContext.onClose()"
>
-
+
{{ ariaLabel }}
diff --git a/packages/radix-vue/src/Tooltip/TooltipProvider.vue b/packages/radix-vue/src/Tooltip/TooltipProvider.vue
index 6daf82a0e..0a3d64f7f 100644
--- a/packages/radix-vue/src/Tooltip/TooltipProvider.vue
+++ b/packages/radix-vue/src/Tooltip/TooltipProvider.vue
@@ -1,7 +1,7 @@
@@ -49,9 +49,9 @@ onMounted(() => {
{
@@ -59,21 +59,21 @@ onMounted(() => {
if (
!hasPointerMoveOpened && !providerContext.isPointerInTransitRef.value
) {
- context.onTriggerEnter();
+ rootContext.onTriggerEnter();
hasPointerMoveOpened = true;
}
}"
@pointerleave="(event) => {
- context.onTriggerLeave();
+ rootContext.onTriggerLeave();
hasPointerMoveOpened = false;
}"
@pointerdown="handlePointerDown"
@focus="() => {
- if (!isPointerDown) context.onOpen()
+ if (!isPointerDown) rootContext.onOpen()
}"
- @blur="context.onClose()"
+ @blur="rootContext.onClose()"
@click="() => {
- if (!context.disableClosingTrigger.value) context.onClose()
+ if (!rootContext.disableClosingTrigger.value) rootContext.onClose()
}"
>
diff --git a/packages/radix-vue/src/shared/createContext.ts b/packages/radix-vue/src/shared/createContext.ts
index 550f3c2db..6f5d68099 100644
--- a/packages/radix-vue/src/shared/createContext.ts
+++ b/packages/radix-vue/src/shared/createContext.ts
@@ -1,25 +1,50 @@
import { type InjectionKey, inject, provide } from 'vue'
-export function createContext(rootComponentName: string) {
- const injectionKey: InjectionKey = Symbol(
- `${rootComponentName}Context`,
- )
+/**
+ * @param providerComponentName - The name(s) of the component(s) providing the context.
+ *
+ * There are situations where context can come from multiple components. In such cases, you might need to give an array of component names to provide your context, instead of just a single string.
+ *
+ * @param contextName The description for injection key symbol.
+ */
+export function createContext(
+ providerComponentName: string | string[],
+ contextName?: string,
+) {
+ const symbolDescription
+ = typeof providerComponentName === 'string' && !contextName
+ ? `${providerComponentName}Context`
+ : contextName
+
+ const injectionKey: InjectionKey = Symbol(symbolDescription)
/**
- * @throws When failed to inject context and fallback not specified.
+ * @param fallback The context value to return if the injection fails.
+ *
+ * @throws When context injection failed and no fallback is specified.
* This happens when the component injecting the context is not a child of the root component providing the context.
*/
- const injectContext = (
- fallback?: T,
- ): T extends null ? ContextValue | null : ContextValue => {
+ const injectContext = <
+ T extends ContextValue | null | undefined = ContextValue,
+ >(
+ fallback?: T,
+ ): T extends null ? ContextValue | null : ContextValue => {
const context = inject(injectionKey, fallback)
if (context)
return context
if (context === null)
- return null as any
+ return context as any
- throw new Error(`Component must be used within ${rootComponentName}`)
+ throw new Error(
+ `Injection \`${injectionKey.toString()}\` not found. Component must be used within ${
+ Array.isArray(providerComponentName)
+ ? `one of the following components: ${providerComponentName.join(
+ ', ',
+ )}`
+ : `\`${providerComponentName}\``
+ }`,
+ )
}
const provideContext = (contextValue: ContextValue) => {
diff --git a/playground/vue3/package.json b/playground/vue3/package.json
index b756a2721..b4021281e 100644
--- a/playground/vue3/package.json
+++ b/playground/vue3/package.json
@@ -22,7 +22,7 @@
"@vitejs/plugin-vue": "^4.1.0",
"autoprefixer": "^10.4.14",
"eslint": "^8.43.0",
- "postcss": "^8.4.24",
+ "postcss": "^8.4.31",
"tailwindcss": "^3.3.2",
"typescript": "^5.0.2",
"unplugin-vue-components": "^0.25.1",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 0a193c2c4..9d0bb2a14 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -76,7 +76,7 @@ importers:
version: 10.4.1(vue@3.3.4)
autoprefixer:
specifier: ^10.4.14
- version: 10.4.14(postcss@8.4.26)
+ version: 10.4.14(postcss@8.4.31)
eslint:
specifier: ^8.43.0
version: 8.43.0
@@ -84,8 +84,8 @@ importers:
specifier: ^0.16.2
version: 0.16.2(@types/node@20.4.7)(vite@4.3.9)
postcss:
- specifier: ^8.4.26
- version: 8.4.26
+ specifier: ^8.4.31
+ version: 8.4.31
tailwindcss:
specifier: ^3.3.3
version: 3.3.3(ts-node@10.9.1)
@@ -3205,7 +3205,7 @@ packages:
resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==}
dev: true
- /autoprefixer@10.4.14(postcss@8.4.26):
+ /autoprefixer@10.4.14(postcss@8.4.31):
resolution: {integrity: sha512-FQzyfOsTlwVzjHxKEqRIAdJx9niO6VCBCoEwax/VLSoQF29ggECcPuBqUMZ+u8jCZOPSy8b8/8KnuFbp0SaFZQ==}
engines: {node: ^10 || ^12 || >=14}
hasBin: true
@@ -3217,7 +3217,7 @@ packages:
fraction.js: 4.2.1
normalize-range: 0.1.2
picocolors: 1.0.0
- postcss: 8.4.26
+ postcss: 8.4.31
postcss-value-parser: 4.2.0
dev: true
@@ -3819,7 +3819,7 @@ packages:
dev: true
/concat-map@0.0.1:
- resolution: {integrity: sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=}
+ resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
dev: true
/concat-stream@1.6.2:
@@ -7195,6 +7195,7 @@ packages:
pathe: 1.1.1
pkg-types: 1.0.3
ufo: 1.3.0
+ dev: false
/mlly@1.4.2:
resolution: {integrity: sha512-i/Ykufi2t1EZ6NaPLdfnZk2AX8cs0d+mTzVKuPfqPKPatxLApaBoxJQ9x1/uckXtrS/U5oisPMDkNs0yQTaBRg==}
@@ -7952,15 +7953,6 @@ packages:
source-map: 0.6.1
dev: true
- /postcss@8.4.26:
- resolution: {integrity: sha512-jrXHFF8iTloAenySjM/ob3gSj7pCu0Ji49hnjqzsgSRa50hkWCKD0HQ+gMNJkW38jBI68MpAAg7ZWwHwX8NMMw==}
- engines: {node: ^10 || ^12 || >=14}
- dependencies:
- nanoid: 3.3.6
- picocolors: 1.0.0
- source-map-js: 1.0.2
- dev: true
-
/postcss@8.4.31:
resolution: {integrity: sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==}
engines: {node: ^10 || ^12 || >=14}
@@ -9605,7 +9597,7 @@ packages:
dependencies:
cac: 6.7.14
debug: 4.3.4
- mlly: 1.4.1
+ mlly: 1.4.2
pathe: 1.1.1
picocolors: 1.0.0
source-map: 0.6.1
@@ -9628,7 +9620,7 @@ packages:
dependencies:
cac: 6.7.14
debug: 4.3.4
- mlly: 1.4.1
+ mlly: 1.4.2
pathe: 1.1.1
picocolors: 1.0.0
vite: 4.3.9(@types/node@18.16.18)
@@ -10251,7 +10243,6 @@ packages:
resolution: {directory: packages/radix-vue, type: directory}
id: file:packages/radix-vue
name: radix-vue
- version: 0.4.1
dependencies:
'@floating-ui/dom': 1.4.2
'@floating-ui/vue': 1.0.1(vue@3.3.4)