diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4f6c4808d10..98b5e6005c5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -43,7 +43,7 @@ jobs: strategy: matrix: - node: [18.x, 20.x, 22.x] + node: [20.x, 22.x] fail-fast: false steps: @@ -79,6 +79,6 @@ jobs: - name: browser tests run: pnpm run test:browser - - if: matrix.node == '18.x' + - if: matrix.node == '22.x' name: Playground Smoke Test run: cd playgrounds && bash ./build.sh diff --git a/eslint.config.js b/eslint.config.js index 0068d11c1e8..0017f6cd640 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -1,6 +1,8 @@ import { resolve } from 'node:path' import { fileURLToPath } from 'node:url' import antfu from '@antfu/eslint-config' +import { createSimplePlugin } from 'eslint-factory' +import { createAutoInsert } from 'eslint-plugin-unimport' const dir = fileURLToPath(new URL('.', import.meta.url)) const restricted = [ @@ -122,4 +124,33 @@ export default antfu( 'no-restricted-imports': 'off', }, }, + createAutoInsert({ + imports: [ + { + from: 'vue', + name: 'shallowRef', + }, + { + from: 'vue', + name: 'ref', + as: 'deepRef', + }, + ], + }), + createSimplePlugin({ + name: 'no-ref', + exclude: ['**/*.md', '**/*.md/**'], + create(context) { + return { + CallExpression(node) { + if (node.callee.type === 'Identifier' && node.callee.name === 'ref') { + context.report({ + node, + message: 'Usage of ref() is restricted. Use shallowRef() or deepRef() instead.', + }) + } + }, + } + }, + }), ) diff --git a/package.json b/package.json index 57f9527ed2a..a7ee050c499 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@vueuse/monorepo", "type": "module", - "version": "12.6.1", + "version": "12.7.0", "private": true, "packageManager": "pnpm@10.4.0", "description": "Collection of essential Vue Composition Utilities", @@ -67,7 +67,9 @@ "consola": "catalog:", "esbuild-register": "catalog:", "eslint": "catalog:", + "eslint-factory": "^0.1.2", "eslint-plugin-format": "catalog:", + "eslint-plugin-unimport": "^0.1.2", "export-size": "catalog:", "fake-indexeddb": "catalog:", "firebase": "catalog:", diff --git a/packages/.test/mount.ts b/packages/.test/mount.ts index 35a2cd7b18d..a4fd0e1b455 100644 --- a/packages/.test/mount.ts +++ b/packages/.test/mount.ts @@ -1,5 +1,5 @@ import type { InjectionKey, Ref } from 'vue' -import { createApp, defineComponent, h, provide, ref } from 'vue' +import { createApp, defineComponent, h, provide, shallowRef } from 'vue' type InstanceType = V extends { new (...arg: any[]): infer X } ? X : never type VM = InstanceType & { unmount: () => void } @@ -38,7 +38,7 @@ export function useInjectedSetup(setup: () => V) { const Provider = defineComponent({ components: Comp, setup() { - provide(Key, ref(1)) + provide(Key, shallowRef(1)) }, render() { return h('div', []) diff --git a/packages/.vitepress/theme/components/DemoContainer.vue b/packages/.vitepress/theme/components/DemoContainer.vue index 37bec84439d..b6c4b635ead 100644 --- a/packages/.vitepress/theme/components/DemoContainer.vue +++ b/packages/.vitepress/theme/components/DemoContainer.vue @@ -1,7 +1,7 @@ diff --git a/packages/core/computedInject/demoReceiver.vue b/packages/core/computedInject/demoReceiver.vue index d9467ec170d..33bb338fc07 100644 --- a/packages/core/computedInject/demoReceiver.vue +++ b/packages/core/computedInject/demoReceiver.vue @@ -1,6 +1,6 @@ diff --git a/packages/core/useFocusWithin/index.test.ts b/packages/core/useFocusWithin/index.test.ts index d561697302b..59426e5eef6 100644 --- a/packages/core/useFocusWithin/index.test.ts +++ b/packages/core/useFocusWithin/index.test.ts @@ -1,6 +1,6 @@ import type { Ref } from 'vue' import { beforeEach, describe, expect, it } from 'vitest' -import { ref } from 'vue' +import { shallowRef } from 'vue' import { useFocusWithin } from './index' describe('useFocusWithin', () => { @@ -10,19 +10,19 @@ describe('useFocusWithin', () => { let grandchild: Ref beforeEach(() => { - parent = ref(document.createElement('form')) + parent = shallowRef(document.createElement('form')) parent.value.tabIndex = 0 document.body.appendChild(parent.value) - child = ref(document.createElement('div')) + child = shallowRef(document.createElement('div')) child.value.tabIndex = 0 parent.value.appendChild(child.value) - child2 = ref(document.createElement('div')) + child2 = shallowRef(document.createElement('div')) child2.value.tabIndex = 0 parent.value.appendChild(child2.value) - grandchild = ref(document.createElement('input')) + grandchild = shallowRef(document.createElement('input')) grandchild.value.tabIndex = 0 child.value.appendChild(grandchild.value) }) diff --git a/packages/core/useFocusWithin/index.ts b/packages/core/useFocusWithin/index.ts index 020a5c63892..84db0fabfbd 100644 --- a/packages/core/useFocusWithin/index.ts +++ b/packages/core/useFocusWithin/index.ts @@ -1,7 +1,7 @@ import type { ComputedRef } from 'vue' import type { ConfigurableWindow } from '../_configurable' import type { MaybeElementRef } from '../unrefElement' -import { computed, ref } from 'vue' +import { computed, shallowRef } from 'vue' import { defaultWindow } from '../_configurable' import { unrefElement } from '../unrefElement' import { useActiveElement } from '../useActiveElement' @@ -28,7 +28,7 @@ const PSEUDO_CLASS_FOCUS_WITHIN = ':focus-within' export function useFocusWithin(target: MaybeElementRef, options: ConfigurableWindow = {}): UseFocusWithinReturn { const { window = defaultWindow } = options const targetElement = computed(() => unrefElement(target)) - const _focused = ref(false) + const _focused = shallowRef(false) const focused = computed(() => _focused.value) const activeElement = useActiveElement(options) diff --git a/packages/core/useFps/index.ts b/packages/core/useFps/index.ts index 5170ff28d83..2657e7cf243 100644 --- a/packages/core/useFps/index.ts +++ b/packages/core/useFps/index.ts @@ -1,5 +1,5 @@ import type { Ref } from 'vue' -import { ref } from 'vue' +import { shallowRef } from 'vue' import { useRafFn } from '../useRafFn' export interface UseFpsOptions { @@ -11,7 +11,7 @@ export interface UseFpsOptions { } export function useFps(options?: UseFpsOptions): Ref { - const fps = ref(0) + const fps = shallowRef(0) if (typeof performance === 'undefined') return fps const every = options?.every ?? 10 diff --git a/packages/core/useFullscreen/component.ts b/packages/core/useFullscreen/component.ts index d975e4ec5ae..6201e50a7bc 100644 --- a/packages/core/useFullscreen/component.ts +++ b/packages/core/useFullscreen/component.ts @@ -1,12 +1,12 @@ import type { RenderableComponent } from '../types' import { useFullscreen } from '@vueuse/core' -import { defineComponent, h, reactive, ref } from 'vue' +import { ref as deepRef, defineComponent, h, reactive } from 'vue' export const UseFullscreen = /* #__PURE__ */ defineComponent({ name: 'UseFullscreen', props: ['as'] as unknown as undefined, setup(props, { slots }) { - const target = ref() + const target = deepRef() const data = reactive(useFullscreen(target)) return () => { diff --git a/packages/core/useFullscreen/index.ts b/packages/core/useFullscreen/index.ts index 9af4974cebc..3e31b1e6043 100644 --- a/packages/core/useFullscreen/index.ts +++ b/packages/core/useFullscreen/index.ts @@ -1,7 +1,7 @@ import type { ConfigurableDocument } from '../_configurable' import type { MaybeElementRef } from '../unrefElement' import { tryOnScopeDispose } from '@vueuse/shared' -import { computed, ref } from 'vue' +import { computed, shallowRef } from 'vue' import { defaultDocument } from '../_configurable' import { unrefElement } from '../unrefElement' import { useEventListener } from '../useEventListener' @@ -41,7 +41,7 @@ export function useFullscreen( } = options const targetRef = computed(() => unrefElement(target) ?? document?.documentElement) - const isFullscreen = ref(false) + const isFullscreen = shallowRef(false) const requestMethod = computed<'requestFullscreen' | undefined>(() => { return [ diff --git a/packages/core/useGamepad/index.ts b/packages/core/useGamepad/index.ts index 655f3aeac50..d16c8707966 100644 --- a/packages/core/useGamepad/index.ts +++ b/packages/core/useGamepad/index.ts @@ -1,7 +1,7 @@ import type { Ref } from 'vue' import type { ConfigurableNavigator, ConfigurableWindow } from '../_configurable' import { createEventHook, tryOnMounted } from '@vueuse/shared' -import { computed, ref } from 'vue' +import { computed, ref as deepRef } from 'vue' import { defaultNavigator } from '../_configurable' import { useEventListener } from '../useEventListener' import { useRafFn } from '../useRafFn' @@ -64,7 +64,7 @@ export function useGamepad(options: UseGamepadOptions = {}) { navigator = defaultNavigator, } = options const isSupported = useSupported(() => navigator && 'getGamepads' in navigator) - const gamepads = ref([]) + const gamepads = deepRef([]) const onConnectedHook = createEventHook() const onDisconnectedHook = createEventHook() diff --git a/packages/core/useGeolocation/index.ts b/packages/core/useGeolocation/index.ts index fb14b6b89e1..a1e7daab097 100644 --- a/packages/core/useGeolocation/index.ts +++ b/packages/core/useGeolocation/index.ts @@ -3,7 +3,7 @@ import type { Ref } from 'vue' import type { ConfigurableNavigator } from '../_configurable' import { tryOnScopeDispose } from '@vueuse/shared' -import { ref, shallowRef } from 'vue' +import { ref as deepRef, shallowRef } from 'vue' import { defaultNavigator } from '../_configurable' import { useSupported } from '../useSupported' @@ -28,9 +28,9 @@ export function useGeolocation(options: UseGeolocationOptions = {}) { const isSupported = useSupported(() => navigator && 'geolocation' in navigator) - const locatedAt: Ref = ref(null) + const locatedAt: Ref = deepRef(null) const error = shallowRef(null) - const coords: Ref> = ref({ + const coords: Ref> = deepRef({ accuracy: 0, latitude: Number.POSITIVE_INFINITY, longitude: Number.POSITIVE_INFINITY, diff --git a/packages/core/useIdle/index.ts b/packages/core/useIdle/index.ts index 78da19366f1..bd050619594 100644 --- a/packages/core/useIdle/index.ts +++ b/packages/core/useIdle/index.ts @@ -3,7 +3,7 @@ import type { Ref } from 'vue' import type { ConfigurableWindow } from '../_configurable' import type { WindowEventName } from '../useEventListener' import { createFilterWrapper, throttleFilter, timestamp } from '@vueuse/shared' -import { ref } from 'vue' +import { shallowRef } from 'vue' import { defaultWindow } from '../_configurable' import { useEventListener } from '../useEventListener' @@ -55,8 +55,8 @@ export function useIdle( window = defaultWindow, eventFilter = throttleFilter(50), } = options - const idle = ref(initialState) - const lastActive = ref(timestamp()) + const idle = shallowRef(initialState) + const lastActive = shallowRef(timestamp()) let timer: any diff --git a/packages/core/useImage/demo.vue b/packages/core/useImage/demo.vue index d36492f50cd..0b044d2f04b 100644 --- a/packages/core/useImage/demo.vue +++ b/packages/core/useImage/demo.vue @@ -1,8 +1,8 @@ diff --git a/packages/core/useMouseInElement/index.ts b/packages/core/useMouseInElement/index.ts index 7e056062e4f..94d9f104ca9 100644 --- a/packages/core/useMouseInElement/index.ts +++ b/packages/core/useMouseInElement/index.ts @@ -1,6 +1,6 @@ import type { MaybeElementRef } from '../unrefElement' import type { UseMouseOptions } from '../useMouse' -import { ref, watch } from 'vue' +import { ref as deepRef, shallowRef, watch } from 'vue' import { defaultWindow } from '../_configurable' import { unrefElement } from '../unrefElement' import { useEventListener } from '../useEventListener' @@ -29,14 +29,14 @@ export function useMouseInElement( const { x, y, sourceType } = useMouse(options) - const targetRef = ref(target ?? window?.document.body) - const elementX = ref(0) - const elementY = ref(0) - const elementPositionX = ref(0) - const elementPositionY = ref(0) - const elementHeight = ref(0) - const elementWidth = ref(0) - const isOutside = ref(true) + const targetRef = deepRef(target ?? window?.document.body) + const elementX = shallowRef(0) + const elementY = shallowRef(0) + const elementPositionX = shallowRef(0) + const elementPositionY = shallowRef(0) + const elementHeight = shallowRef(0) + const elementWidth = shallowRef(0) + const isOutside = shallowRef(true) let stop = () => {} diff --git a/packages/core/useMousePressed/component.ts b/packages/core/useMousePressed/component.ts index ff145340dfe..8cb58ae5189 100644 --- a/packages/core/useMousePressed/component.ts +++ b/packages/core/useMousePressed/component.ts @@ -1,13 +1,13 @@ import type { MousePressedOptions } from '@vueuse/core' import type { RenderableComponent } from '../types' import { useMousePressed } from '@vueuse/core' -import { defineComponent, h, reactive, ref } from 'vue' +import { ref as deepRef, defineComponent, h, reactive } from 'vue' export const UseMousePressed = /* #__PURE__ */ defineComponent & RenderableComponent>({ name: 'UseMousePressed', props: ['touch', 'initialValue', 'as'] as unknown as undefined, setup(props, { slots }) { - const target = ref() + const target = deepRef() const data = reactive(useMousePressed({ ...props, target })) return () => { diff --git a/packages/core/useMousePressed/index.ts b/packages/core/useMousePressed/index.ts index 2a6ca1cbd2a..05eec3f7518 100644 --- a/packages/core/useMousePressed/index.ts +++ b/packages/core/useMousePressed/index.ts @@ -1,7 +1,7 @@ import type { ConfigurableWindow } from '../_configurable' import type { MaybeComputedElementRef } from '../unrefElement' import type { UseMouseSourceType } from '../useMouse' -import { computed, ref } from 'vue' +import { computed, ref as deepRef } from 'vue' import { defaultWindow } from '../_configurable' import { unrefElement } from '../unrefElement' import { useEventListener } from '../useEventListener' @@ -71,8 +71,8 @@ export function useMousePressed(options: MousePressedOptions = {}) { window = defaultWindow, } = options - const pressed = ref(initialValue) - const sourceType = ref(null) + const pressed = deepRef(initialValue) + const sourceType = deepRef(null) if (!window) { return { diff --git a/packages/core/useMutationObserver/demo.vue b/packages/core/useMutationObserver/demo.vue index 8d28b2ff453..0cef2be3310 100644 --- a/packages/core/useMutationObserver/demo.vue +++ b/packages/core/useMutationObserver/demo.vue @@ -1,11 +1,11 @@ diff --git a/packages/core/useSpeechRecognition/index.ts b/packages/core/useSpeechRecognition/index.ts index 33ddf529b70..8089c0c94e6 100644 --- a/packages/core/useSpeechRecognition/index.ts +++ b/packages/core/useSpeechRecognition/index.ts @@ -6,7 +6,7 @@ import type { Ref } from 'vue' import type { ConfigurableWindow } from '../_configurable' import type { SpeechRecognition, SpeechRecognitionErrorEvent } from './types' import { toRef, tryOnScopeDispose } from '@vueuse/shared' -import { ref, shallowRef, toValue, watch } from 'vue' +import { shallowRef, toValue, watch } from 'vue' import { defaultWindow } from '../_configurable' import { useSupported } from '../useSupported' @@ -54,9 +54,9 @@ export function useSpeechRecognition(options: UseSpeechRecognitionOptions = {}) } = options const lang = toRef(options.lang || 'en-US') - const isListening = ref(false) - const isFinal = ref(false) - const result = ref('') + const isListening = shallowRef(false) + const isFinal = shallowRef(false) + const result = shallowRef('') const error = shallowRef(undefined) as Ref let recognition: SpeechRecognition | undefined diff --git a/packages/core/useSpeechSynthesis/demo.vue b/packages/core/useSpeechSynthesis/demo.vue index 347663bc0ef..8ac466bc3fe 100644 --- a/packages/core/useSpeechSynthesis/demo.vue +++ b/packages/core/useSpeechSynthesis/demo.vue @@ -1,11 +1,11 @@