@empathyco/x-components
Version:
Empathy X Components
161 lines (158 loc) • 5.71 kB
JavaScript
import { reduce } from '@empathyco/x-utils';
import { useMemoize, useWindowSize, useScreenOrientation, useEventListener } from '@vueuse/core';
import { effectScope, computed, ref } from 'vue';
import { capitalize } from '../utils/string.js';
import '../utils/storage.js';
/**
* Factory function that creates a composable for device detection using the devices parameter
* to configure breakpoints.
*
* @param devices - An object containing the breakpoints, where the key is the name of the device
* and the value is the screen width.
* @returns A composable which provides multiple reactive flags and values for detecting the
* current device. The flags names depends on the names passed in the `devices` parameter.
* @remarks The `orientation` only works for orientation-sensor devices (mobile, tablet, etc). If
* in a desktop, the height of the window is larger than the width, the orientation will be
* `landscape`.
*
* @example
* ´´´typescript
* const useDevice = createUseDevice(\{ mobile: 0, tablet: 744, desktop: 1024 \});
* const \{
* isMobile,
* isMobileOrLess,
* isMobileOrGreater,
* isTablet,
* isTabletOrLess,
* isTabletOrGreater,
* isDesktop,
* isDesktopOrLess,
* isDesktopOrGreater,
* deviceName,
* orientation,
* istTouchable
* \} = useDevice();
*
* @public
*/
function createUseDevice(devices) {
let devicesFlags;
let orientation;
let isTouchable;
let deviceName;
// The `effectScope` group all the changes in one to avoid multiple re-renderings.
const scope = effectScope();
scope.run(() => {
devicesFlags = getDeviceFlags(devices);
orientation = getOrientation();
isTouchable = getIsTouchable();
deviceName = getDeviceName(devices, devicesFlags);
});
return () => ({
...devicesFlags,
orientation,
isTouchable,
deviceName,
});
}
/**
* A function that returns reactive flags to detect the current device based on provided
* breakpoints.
*
* @param devices - An object containing the breakpoints, where the key is the name of the device
* and the value is the screen width.
* @returns A object containing the multiple reactive flags.
*
* @internal
*/
function getDeviceFlags(devices) {
const { width: windowSize } = useWindowSize();
return reduce(devices, (accumulator, device, deviceWidth) => {
const isDevice = computed(() => isCurrentDevice(device, devices, windowSize.value));
accumulator[`is${capitalize(device)}`] = isDevice;
accumulator[`is${capitalize(device)}OrLess`] = computed(() => deviceWidth >= windowSize.value || isDevice.value);
accumulator[`is${capitalize(device)}OrGreater`] = computed(() => deviceWidth <= windowSize.value);
return accumulator;
}, {});
}
/**
* To get the devices sorted by size and not run this calculation on every check.
*/
const getSortedByWidthDevices = useMemoize((devices) => Object.entries(devices).sort(([, aWidth], [, bWidth]) => bWidth - aWidth));
/**
* Checks if the current device satisfies the criteria of being a valid device.
*
* @param device - The name of the current device.
* @param devices - An object containing device names and their
* respective widths.
* @param windowSize - The width of the window.
* @returns A boolean value indicating whether the current device satisfies the
* criteria of being a valid device.
*
* @internal
*/
function isCurrentDevice(device, devices, windowSize) {
const deviceWidth = devices[device];
return (deviceWidth <= windowSize &&
!getSortedByWidthDevices(devices).some(([, otherDeviceWidth]) => otherDeviceWidth <= windowSize && otherDeviceWidth > deviceWidth));
}
/**
* A function that returns the current device orientation as a reactive value.
*
* @returns A reactive value indicating the current device
* orientation.
*
* @internal
*/
function getOrientation() {
const { orientation } = useScreenOrientation();
return computed(() => orientation.value?.includes('landscape')
? 'landscape'
: orientation.value?.includes('portrait')
? 'portrait'
: undefined);
}
/**
* A function that returns a reactive boolean indicating whether the current device is
* touch-enabled.
*
* @returns A reactive boolean indicating whether the current device is touch-enabled.
*
* @internal
*/
function getIsTouchable() {
const isTouchableRef = ref(detectTouchable());
if (window) {
useEventListener(window, 'resize', () => (isTouchableRef.value = detectTouchable()), {
passive: true,
});
}
return isTouchableRef;
}
/**
* A function that returns a reactive string indicating the name of the currently detected device
* based on the provided devices and device flags.
*
* @param devices - An object containing the breakpoints, where the key is the name of the device
* and the value is the screen width.
* @param devicesFlags - An object containing multiple reactive flags and values for detecting
* the current device.
* @returns A reactive string indicating the name of the currently detected device.
*
* @internal
*/
function getDeviceName(devices, devicesFlags) {
return computed(() => Object.keys(devices).find(device => devicesFlags[`is${capitalize(device)}`]?.value) ?? '');
}
/**
* A utility function that detects whether the current device is touch-enabled.
*
* @returns A boolean indicating whether the current device is touch-enabled.
*
* @internal
*/
function detectTouchable() {
return 'ontouchstart' in window || navigator.maxTouchPoints > 0;
}
export { createUseDevice };
//# sourceMappingURL=create-use-device.js.map