From ae69fb8af4f06eeda5372a9782b92740fc28af12 Mon Sep 17 00:00:00 2001 From: Sergey Danilchenko Date: Tue, 17 Jan 2023 09:48:19 +0200 Subject: [PATCH] feat(usePointerLock): new function (#2590) * feat(usePointerLock): new function * feat(useMouse): support for movement detection * fix(usePointerLock): update demo * fix(usePointerLock): use event.currentTarget instead of event.target * fix(usePointerLock): update demo * fix(usePointerLock): fix pending element tracking when usePointerLock used in multiple components * fix(usePointerLock): unlock fix * feat(usePointerLock): simplify demo * fix(usePointerLock): reset targetElement on pointer lock release even if release is not caused by the user * feat(usePointerLock): keep track of element which triggered pointer lock (for cases where pointer lock is acquired on a different element) * feat(usePointerLock): support unadjusted mouse movement tracking (experimental) * feat(usePointerLock): migrate demo to unocss Co-authored-by: Sergey Danilchenko Co-authored-by: Jelf <353742991@qq.com> Co-authored-by: wheat --- packages/components/index.ts | 1 + packages/core/index.ts | 1 + packages/core/usePointerLock/component.ts | 17 ++++ packages/core/usePointerLock/demo.vue | 63 +++++++++++++++ packages/core/usePointerLock/index.md | 26 +++++++ packages/core/usePointerLock/index.ts | 95 +++++++++++++++++++++++ 6 files changed, 203 insertions(+) create mode 100644 packages/core/usePointerLock/component.ts create mode 100644 packages/core/usePointerLock/demo.vue create mode 100644 packages/core/usePointerLock/index.md create mode 100644 packages/core/usePointerLock/index.ts diff --git a/packages/components/index.ts b/packages/components/index.ts index d59f53a77bf..fab5a19fae5 100644 --- a/packages/components/index.ts +++ b/packages/components/index.ts @@ -37,6 +37,7 @@ export * from '../core/useOffsetPagination/component' export * from '../core/useOnline/component' export * from '../core/usePageLeave/component' export * from '../core/usePointer/component' +export * from '../core/usePointerLock/component' export * from '../core/usePreferredColorScheme/component' export * from '../core/usePreferredContrast/component' export * from '../core/usePreferredDark/component' diff --git a/packages/core/index.ts b/packages/core/index.ts index 872b92d91c8..65e32e837be 100644 --- a/packages/core/index.ts +++ b/packages/core/index.ts @@ -80,6 +80,7 @@ export * from './usePageLeave' export * from './useParallax' export * from './usePermission' export * from './usePointer' +export * from './usePointerLock' export * from './usePointerSwipe' export * from './usePreferredColorScheme' export * from './usePreferredContrast' diff --git a/packages/core/usePointerLock/component.ts b/packages/core/usePointerLock/component.ts new file mode 100644 index 00000000000..c476c030470 --- /dev/null +++ b/packages/core/usePointerLock/component.ts @@ -0,0 +1,17 @@ +import { defineComponent, h, reactive, ref } from 'vue-demi' +import { usePointerLock } from '@vueuse/core' +import type { RenderableComponent } from '../types' + +export const UsePointerLock = defineComponent({ + name: 'UsePointerLock', + props: ['as'] as unknown as undefined, + setup(props, { slots }) { + const target = ref() + const data = reactive(usePointerLock(target)) + + return () => { + if (slots.default) + return h(props.as || 'div', { ref: target }, slots.default(data)) + } + }, +}) diff --git a/packages/core/usePointerLock/demo.vue b/packages/core/usePointerLock/demo.vue new file mode 100644 index 00000000000..c6edf2c9eb7 --- /dev/null +++ b/packages/core/usePointerLock/demo.vue @@ -0,0 +1,63 @@ + + + + + diff --git a/packages/core/usePointerLock/index.md b/packages/core/usePointerLock/index.md new file mode 100644 index 00000000000..fff0bde65f0 --- /dev/null +++ b/packages/core/usePointerLock/index.md @@ -0,0 +1,26 @@ +--- +category: Sensors +--- + +# usePointerLock + +Reactive [pointer lock](https://developer.mozilla.org/en-US/docs/Web/API/Pointer_Lock_API). + +## Basic Usage + +```js +import { usePointerLock } from '@vueuse/core' + +const { isSupported, lock, unlock, element, triggerElement } = usePointerLock() +``` + +## Component Usage + +```html + + + + +``` diff --git a/packages/core/usePointerLock/index.ts b/packages/core/usePointerLock/index.ts new file mode 100644 index 00000000000..ec5d3538408 --- /dev/null +++ b/packages/core/usePointerLock/index.ts @@ -0,0 +1,95 @@ +import { ref } from 'vue-demi' +import { until } from '@vueuse/shared' +import { useEventListener } from '../useEventListener' +import { useSupported } from '../useSupported' +import { unrefElement } from '../unrefElement' +import type { MaybeElementRef } from '../unrefElement' +import type { ConfigurableDocument } from '../_configurable' +import { defaultDocument } from '../_configurable' + +declare global { + interface PointerLockOptions { + unadjustedMovement?: boolean + } + + interface Element { + requestPointerLock(options?: PointerLockOptions): Promise | void + } +} + +type MaybeHTMLElement = HTMLElement | undefined | null + +export interface UsePointerLockOptions extends ConfigurableDocument { + pointerLockOptions?: PointerLockOptions +} + +/** + * Reactive pointer lock. + * + * @see https://vueuse.org/usePointerLock + * @param target + * @param options + */ +export function usePointerLock(target?: MaybeElementRef, options: UsePointerLockOptions = {}) { + const { document = defaultDocument, pointerLockOptions } = options + + const isSupported = useSupported(() => document && 'pointerLockElement' in document) + + const element = ref() + + const triggerElement = ref() + + let targetElement: MaybeHTMLElement + + if (isSupported.value) { + useEventListener(document, 'pointerlockchange', () => { + const currentElement = document!.pointerLockElement ?? element.value + if (targetElement && currentElement === targetElement) { + element.value = document!.pointerLockElement as MaybeHTMLElement + if (!element.value) + targetElement = triggerElement.value = null + } + }) + + useEventListener(document, 'pointerlockerror', () => { + const currentElement = document!.pointerLockElement ?? element.value + if (targetElement && currentElement === targetElement) { + const action = document!.pointerLockElement ? 'release' : 'acquire' + throw new Error(`Failed to ${action} pointer lock.`) + } + }) + } + + async function lock(e: MaybeElementRef | Event, options?: PointerLockOptions) { + if (!isSupported.value) + throw new Error('Pointer Lock API is not supported by your browser.') + + triggerElement.value = e instanceof Event ? e.currentTarget : null + targetElement = e instanceof Event ? unrefElement(target) ?? triggerElement.value : unrefElement(e) + if (!targetElement) + throw new Error('Target element undefined.') + targetElement.requestPointerLock(options ?? pointerLockOptions) + + return await until(element).toBe(targetElement) + } + + async function unlock() { + if (!element.value) + return false + + document!.exitPointerLock() + + await until(element).toBeNull() + return true + } + + return { + isSupported, + element, + triggerElement, + lock, + unlock, + } +} + +export type UsePointerLockReturn = ReturnType