Skip to content

Conversation

@github-actions
Copy link
Contributor

@github-actions github-actions bot commented Jul 2, 2024

Release
@floating-ui/vue@1.1.1

Diff for packages/vue

Diff
diff --git a/packages/vue/CHANGELOG.md b/packages/vue/CHANGELOG.md
index 7bd9d286..c8b32a60 100644
--- a/packages/vue/CHANGELOG.md
+++ b/packages/vue/CHANGELOG.md
@@ -1,5 +1,21 @@
 # @floating-ui/vue
 
+## 1.1.1
+
+### Patch Changes
+
+- fix: ensure `MaybeReadonlyRefOrGetter` works in earlier versions of Vue
+
+## 1.1.0
+
+### Minor Changes
+
+- feat: support `MaybeReadonlyRefOrGetter` in `useFloating`
+
+### Patch Changes
+
+- Update dependencies: `@floating-ui/utils@0.2.4`
+
 ## 1.0.7
 
 ### Patch Changes
diff --git a/packages/vue/package.json b/packages/vue/package.json
index 367610f2..dbca4f27 100644
--- a/packages/vue/package.json
+++ b/packages/vue/package.json
@@ -1,6 +1,6 @@
 {
   "name": "@floating-ui/vue",
-  "version": "1.0.7",
+  "version": "1.1.1",
   "description": "Floating UI for Vue",
   "publishConfig": {
     "access": "public"
diff --git a/packages/vue/src/arrow.ts b/packages/vue/src/arrow.ts
index cc636e3f..215bee0c 100644
--- a/packages/vue/src/arrow.ts
+++ b/packages/vue/src/arrow.ts
@@ -1,9 +1,9 @@
 import type {Middleware} from '@floating-ui/dom';
 import {arrow as apply} from '@floating-ui/dom';
-import {unref} from 'vue-demi';
 
 import type {ArrowOptions} from './types';
 import {unwrapElement} from './utils/unwrapElement';
+import {toValue} from './utils/toValue';
 
 /**
  * Positions an inner element of the floating element such that it is centered to the reference element.
@@ -15,7 +15,7 @@ export function arrow(options: ArrowOptions): Middleware {
     name: 'arrow',
     options,
     fn(args) {
-      const element = unwrapElement(unref(options.element));
+      const element = unwrapElement(toValue(options.element));
 
       if (element == null) {
         return {};
diff --git a/packages/vue/src/types.ts b/packages/vue/src/types.ts
index a464239a..02aea117 100644
--- a/packages/vue/src/types.ts
+++ b/packages/vue/src/types.ts
@@ -53,6 +53,8 @@ export type {
 
 export type MaybeReadonlyRef<T> = T | Readonly<Ref<T>>;
 
+export type MaybeReadonlyRefOrGetter<T> = MaybeReadonlyRef<T> | (() => T);
+
 export type MaybeElement<T> = T | ComponentPublicInstance | null | undefined;
 
 export type UseFloatingOptions<T extends ReferenceElement = ReferenceElement> =
@@ -61,28 +63,28 @@ export type UseFloatingOptions<T extends ReferenceElement = ReferenceElement> =
      * Represents the open/close state of the floating element.
      * @default true
      */
-    open?: MaybeReadonlyRef<boolean | undefined>;
+    open?: MaybeReadonlyRefOrGetter<boolean | undefined>;
     /**
      * Where to place the floating element relative to its reference element.
      * @default 'bottom'
      */
-    placement?: MaybeReadonlyRef<Placement | undefined>;
+    placement?: MaybeReadonlyRefOrGetter<Placement | undefined>;
     /**
      * The type of CSS position property to use.
      * @default 'absolute'
      */
-    strategy?: MaybeReadonlyRef<Strategy | undefined>;
+    strategy?: MaybeReadonlyRefOrGetter<Strategy | undefined>;
     /**
      * These are plain objects that modify the positioning coordinates in some fashion, or provide useful data for the consumer to use.
      * @default undefined
      */
-    middleware?: MaybeReadonlyRef<Middleware[] | undefined>;
+    middleware?: MaybeReadonlyRefOrGetter<Middleware[] | undefined>;
     /**
      * Whether to use `transform` instead of `top` and `left` styles to
      * position the floating element (`floatingStyles`).
      * @default true
      */
-    transform?: MaybeReadonlyRef<boolean | undefined>;
+    transform?: MaybeReadonlyRefOrGetter<boolean | undefined>;
     /**
      * Callback to handle mounting/unmounting of the elements.
      * @default undefined
@@ -142,7 +144,7 @@ export type ArrowOptions = {
    * The arrow element or template ref to be positioned.
    * @required
    */
-  element: MaybeReadonlyRef<MaybeElement<Element>>;
+  element: MaybeReadonlyRefOrGetter<MaybeElement<Element>>;
   /**
    * The padding between the arrow element and the floating element edges. Useful when the floating element has rounded corners.
    * @default 0
diff --git a/packages/vue/src/useFloating.ts b/packages/vue/src/useFloating.ts
index 76b34ff4..904f0dc6 100644
--- a/packages/vue/src/useFloating.ts
+++ b/packages/vue/src/useFloating.ts
@@ -12,7 +12,6 @@ import {
   ref,
   shallowReadonly,
   shallowRef,
-  unref,
   watch,
 } from 'vue-demi';
 
@@ -24,6 +23,7 @@ import type {
 import {getDPR} from './utils/getDPR';
 import {roundByDPR} from './utils/roundByDPR';
 import {unwrapElement} from './utils/unwrapElement';
+import {toValue} from './utils/toValue';
 
 /**
  * Computes the `x` and `y` coordinates that will place the floating element next to a reference element when it is given a certain CSS positioning strategy.
@@ -38,11 +38,15 @@ export function useFloating<T extends ReferenceElement = ReferenceElement>(
   options: UseFloatingOptions<T> = {},
 ): UseFloatingReturn {
   const whileElementsMountedOption = options.whileElementsMounted;
-  const openOption = computed(() => unref(options.open) ?? true);
-  const middlewareOption = computed(() => unref(options.middleware));
-  const placementOption = computed(() => unref(options.placement) ?? 'bottom');
-  const strategyOption = computed(() => unref(options.strategy) ?? 'absolute');
-  const transformOption = computed(() => unref(options.transform) ?? true);
+  const openOption = computed(() => toValue(options.open) ?? true);
+  const middlewareOption = computed(() => toValue(options.middleware));
+  const placementOption = computed(
+    () => toValue(options.placement) ?? 'bottom',
+  );
+  const strategyOption = computed(
+    () => toValue(options.strategy) ?? 'absolute',
+  );
+  const transformOption = computed(() => toValue(options.transform) ?? true);
   const referenceElement = computed(() => unwrapElement(reference.value));
   const floatingElement = computed(() => unwrapElement(floating.value));
   const x = ref(0);
diff --git a/packages/vue/src/utils/toValue.ts b/packages/vue/src/utils/toValue.ts
new file mode 100644
index 00000000..632b5625
--- /dev/null
+++ b/packages/vue/src/utils/toValue.ts
@@ -0,0 +1,11 @@
+import {unref} from 'vue-demi';
+import type {Ref} from 'vue-demi';
+
+type MaybeRef<T> = T | Ref<T>;
+type MaybeRefOrGetter<T> = MaybeRef<T> | (() => T);
+
+type AnyFn = (...args: any[]) => any;
+
+export function toValue<T>(source: MaybeRefOrGetter<T>): T {
+  return typeof source === 'function' ? (source as AnyFn)() : unref(source);
+}
diff --git a/packages/vue/test/index.test.ts b/packages/vue/test/index.test.ts
index b105a09b..e98bbe03 100644
--- a/packages/vue/test/index.test.ts
+++ b/packages/vue/test/index.test.ts
@@ -141,6 +141,102 @@ describe('useFloating', () => {
     });
   });
 
+  test('updates floating coords when placement is a getter function', async () => {
+    const App = defineComponent({
+      name: 'App',
+      props: ['placement'],
+      setup(props: {placement?: Placement}) {
+        return setup({
+          placement: () => props.placement,
+          middleware: [offset(5)],
+        });
+      },
+      template: /* HTML */ `
+        <div ref="reference" />
+        <div ref="floating" />
+        <div data-testid="x">{{x}}</div>
+        <div data-testid="y">{{y}}</div>
+      `,
+    });
+
+    const {rerender, getByTestId} = render(App, {
+      props: {placement: 'bottom'},
+    });
+
+    await waitFor(() => {
+      expect(getByTestId('x').textContent).toBe('0');
+      expect(getByTestId('y').textContent).toBe('5');
+    });
+
+    await rerender({placement: 'right'});
+
+    await waitFor(() => {
+      expect(getByTestId('x').textContent).toBe('5');
+      expect(getByTestId('y').textContent).toBe('0');
+    });
+  });
+
+  test('updates floating coords when middleware is a getter function', async () => {
+    const App = defineComponent({
+      name: 'App',
+      props: ['middleware'],
+      setup(props) {
+        return setup({middleware: () => props.middleware});
+      },
+      template: /* HTML */ `
+        <div ref="reference" />
+        <div ref="floating" />
+        <div data-testid="x">{{x}}</div>
+        <div data-testid="y">{{y}}</div>
+      `,
+    });
+
+    const {rerender, getByTestId} = render(App, {
+      props: {middleware: []},
+    });
+
+    await waitFor(() => {
+      expect(getByTestId('x').textContent).toBe('0');
+      expect(getByTestId('y').textContent).toBe('0');
+    });
+
+    await rerender({middleware: [offset(10)]});
+
+    await waitFor(() => {
+      expect(getByTestId('x').textContent).toBe('0');
+      expect(getByTestId('y').textContent).toBe('10');
+    });
+  });
+
+  test('updates floating position when strategy is a getter function', async () => {
+    const App = defineComponent({
+      name: 'App',
+      props: ['strategy'],
+      setup(props: {strategy?: Strategy}) {
+        return setup({strategy: () => props.strategy});
+      },
+      template: /* HTML */ `
+        <div ref="reference" />
+        <div ref="floating" />
+        <div data-testid="position">{{strategy}}</div>
+      `,
+    });
+
+    const {rerender, getByTestId} = render(App, {
+      props: {strategy: 'absolute'},
+    });
+
+    await waitFor(() => {
+      expect(getByTestId('position').textContent).toBe('absolute');
+    });
+
+    await rerender({strategy: 'fixed'});
+
+    await waitFor(() => {
+      expect(getByTestId('position').textContent).toBe('fixed');
+    });
+  });
+
   test('resets `isPositioned` on open change', async () => {
     const App = defineComponent({
       name: 'App',
@@ -170,6 +266,35 @@ describe('useFloating', () => {
     });
   });
 
+  test('resets `isPositioned` on open change and open is a getter function', async () => {
+    const App = defineComponent({
+      name: 'App',
+      props: ['open'],
+      setup(props: {open?: boolean}) {
+        return setup({open: () => props.open});
+      },
+      template: /* HTML */ `
+        <div ref="reference" />
+        <div ref="floating" />
+        <div data-testid="isPositioned">{{isPositioned}}</div>
+      `,
+    });
+
+    const {rerender, getByTestId} = render(App, {
+      props: {open: true},
+    });
+
+    await waitFor(() => {
+      expect(getByTestId('isPositioned').textContent).toBe('true');
+    });
+
+    await rerender({open: false});
+
+    await waitFor(() => {
+      expect(getByTestId('isPositioned').textContent).toBe('false');
+    });
+  });
+
   test('fallbacks to default when placement becomes undefined', async () => {
     const App = defineComponent({
       name: 'App',

Full diff
1.0.7...1.1.1.

@DanielleHuisman DanielleHuisman marked this pull request as ready for review July 11, 2024 07:25
@DanielleHuisman DanielleHuisman enabled auto-merge (squash) July 11, 2024 07:25
@DanielleHuisman DanielleHuisman merged commit 99f48dd into main Jul 11, 2024
@DanielleHuisman DanielleHuisman deleted the upstream/vue-1.1.1 branch July 11, 2024 07:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants