Files
tools/vue/toolkit/src/composables/elements/useWindowSize/index.ts
T
robonen ab6d8f6ce0
Publish to NPM / Check version changes and publish (push) Failing after 10m34s
build: bump new versions
2026-06-18 02:57:03 +07:00

136 lines
4.1 KiB
TypeScript

import { shallowRef, watch } from 'vue';
import type { ShallowRef } from 'vue';
import { defaultWindow } from '@/types';
import type { ConfigurableWindow } from '@/types';
import { useEventListener } from '@/composables/browser/useEventListener';
import { useMediaQuery } from '@/composables/browser/useMediaQuery';
import { tryOnMounted } from '@/composables/lifecycle/tryOnMounted';
/**
* Which window dimensions to track.
*
* - `'inner'` — `window.innerWidth/innerHeight` (or `documentElement.clientWidth/clientHeight`
* when `includeScrollbar` is `false`). The viewport size.
* - `'outer'` — `window.outerWidth/outerHeight`. The whole browser window, including chrome.
* - `'visual'` — `window.visualViewport` size, accounting for pinch-zoom scale. Useful on
* mobile where the visual viewport differs from the layout viewport.
*/
export type WindowSizeType = 'inner' | 'outer' | 'visual';
export interface UseWindowSizeOptions extends ConfigurableWindow {
/**
* The initial width, used before the window is available (e.g. during SSR).
*
* @default Number.POSITIVE_INFINITY
*/
initialWidth?: number;
/**
* The initial height, used before the window is available (e.g. during SSR).
*
* @default Number.POSITIVE_INFINITY
*/
initialHeight?: number;
/**
* Listen to orientation changes via a `(orientation: portrait)` media query.
*
* @default true
*/
listenOrientation?: boolean;
/**
* Use `window.innerWidth/innerHeight` (includes scrollbar) instead of
* `documentElement.clientWidth/clientHeight`. Only affects the `'inner'` type.
*
* @default true
*/
includeScrollbar?: boolean;
/**
* Which window dimensions to track.
*
* @default 'inner'
*/
type?: WindowSizeType;
}
export interface UseWindowSizeReturn {
width: ShallowRef<number>;
height: ShallowRef<number>;
}
/**
* @name useWindowSize
* @category Elements
* @description Reactive window size. Tracks the inner viewport, the outer window, or the
* visual viewport (pinch-zoom aware), and reacts to resize and orientation changes.
*
* @param {UseWindowSizeOptions} [options={}] Options
* @returns {UseWindowSizeReturn} Reactive `width` and `height`
*
* @example
* const { width, height } = useWindowSize();
*
* @example
* // Track the pinch-zoom aware visual viewport on mobile
* const { width, height } = useWindowSize({ type: 'visual' });
*
* @since 0.0.14
*/
export function useWindowSize(options: UseWindowSizeOptions = {}): UseWindowSizeReturn {
const {
window = defaultWindow,
initialWidth = Number.POSITIVE_INFINITY,
initialHeight = Number.POSITIVE_INFINITY,
listenOrientation = true,
includeScrollbar = true,
type = 'inner',
} = options;
const width = shallowRef(initialWidth);
const height = shallowRef(initialHeight);
const update = (): void => {
if (!window)
return;
if (type === 'outer') {
width.value = window.outerWidth;
height.value = window.outerHeight;
}
else if (type === 'visual' && window.visualViewport) {
const { width: visualWidth, height: visualHeight, scale } = window.visualViewport;
width.value = Math.round(visualWidth * scale);
height.value = Math.round(visualHeight * scale);
}
else if (includeScrollbar) {
width.value = window.innerWidth;
height.value = window.innerHeight;
}
else {
width.value = window.document.documentElement.clientWidth;
height.value = window.document.documentElement.clientHeight;
}
};
update();
tryOnMounted(update);
const listenerOptions = { passive: true } as const;
useEventListener('resize', update, listenerOptions);
// Reactive getter target: auto-binds when `visualViewport` becomes available and
// is a no-op otherwise (SSR / unsupported), without recreating listeners.
if (type === 'visual')
useEventListener(() => window?.visualViewport, 'resize', update, listenerOptions);
if (listenOrientation) {
const orientation = useMediaQuery('(orientation: portrait)', { window });
watch(orientation, update);
}
return { width, height };
}