UNPKG

vue-screen

Version:

Reactive screen size and media query states for Vue. Integrates with most UI frameworks out of the box.

321 lines (306 loc) 10.5 kB
import { reactive, getCurrentInstance, onUnmounted } from 'vue'; var inBrowser = typeof window !== 'undefined'; var debounce = function (callback, wait) { var timeout; // eslint-disable-next-line func-names return function () { // eslint-disable-next-line @typescript-eslint/no-this-alias var context = this; // eslint-disable-next-line prefer-rest-params var args = arguments; // eslint-disable-next-line func-names var later = function () { timeout = null; callback.apply(context, args); }; clearTimeout(timeout); timeout = setTimeout(later, wait); }; }; var remToPx = function (rem) { var fontSize; if (inBrowser) { fontSize = parseFloat(getComputedStyle(document.documentElement).fontSize); } else { fontSize = 16; } return parseFloat(rem.toString()) * fontSize; }; // GoogleBot default screen properties var DEFAULT_WIDTH = 410; var DEFAULT_HEIGHT = 730; var DEFAULT_ORIENTATION = 'portrait'; var DEFAULT_TOUCH_SUPPORT = true; var DEFAULT_DEBOUNCE_DELAY = 100; var useScreen = function (config, debounceDelay) { if (config === void 0) { config = {}; } if (debounceDelay === void 0) { debounceDelay = DEFAULT_DEBOUNCE_DELAY; } var width = config.width || DEFAULT_WIDTH; var height = config.height || DEFAULT_HEIGHT; var orientation = config.orientation || DEFAULT_ORIENTATION; var touch = config.touch === undefined ? DEFAULT_TOUCH_SUPPORT : config.touch; var screen = reactive({ resolution: "".concat(width, "x").concat(height), width: width, height: height, orientation: orientation, portrait: orientation === 'portrait', landscape: orientation !== 'portrait', touch: touch, }); /* istanbul ignore next */ var updateWindowProperties = function () { screen.width = window.innerWidth; screen.height = window.innerHeight; screen.resolution = "".concat(screen.width, "x").concat(screen.height); }; /* istanbul ignore next */ var updateOrientationPropperties = function (e) { screen.portrait = e.matches; screen.landscape = !e.matches; screen.orientation = e.matches ? 'portrait' : 'landscape'; }; /* istanbul ignore if */ if (inBrowser) { var resizeListener_1 = debounce(updateWindowProperties, debounceDelay); window.addEventListener('resize', resizeListener_1); updateWindowProperties(); // This does not react to resize events. // You always need to reload the browser to add/remove touch support, // even when using DevTools device simulation screen.touch = 'ontouchstart' in window; var query_1 = 'matchMedia' in window && window.matchMedia('(orientation: portrait)'); if (query_1) { if ('addEventListener' in query_1) { query_1.addEventListener('change', updateOrientationPropperties); } else { // https://github.com/reegodev/vue-screen/issues/30 // query.addListener is not deprecated for iOS 12 // eslint-disable-next-line @typescript-eslint/no-explicit-any query_1.addListener(updateOrientationPropperties); } updateOrientationPropperties(query_1); } // Do not leak memory by keeping event listeners active. // This appears to work as expected, using useScreen() inside components // triggers this hook when they are destroyed. if (getCurrentInstance()) { onUnmounted(function () { window.removeEventListener('resize', resizeListener_1); if (query_1) { if ('removeEventListener' in query_1) { query_1.removeEventListener('change', updateOrientationPropperties); } else { // https://github.com/reegodev/vue-screen/issues/30 // query.removeListener is not deprecated for iOS 12 // eslint-disable-next-line @typescript-eslint/no-explicit-any query_1.removeListener(updateOrientationPropperties); } } }); } } return screen; }; var tailwind = { sm: '640px', md: '768px', lg: '1024px', xl: '1280px', '2xl': '1536px', }; var bootstrap = { xs: '0px', sm: '768px', md: '992px', lg: '1200px', }; var bootstrap4 = { xs: '0px', sm: '576px', md: '768px', lg: '992px', xl: '1200px', }; var bootstrap5 = { xs: '0px', sm: '576px', md: '768px', lg: '992px', xl: '1200px', xxl: '1400px', }; var bulma = { mobile: 0, tablet: 769, desktop: 1024, widescreen: 1216, fullhd: 1408, }; var materialize = { s: 0, m: 601, l: 993, xl: 1201, }; var foundation = { small: 0, medium: 640, large: 1024, }; var semanticUi = { mobile: 0, tablet: 768, computer: 992, large: 1201, }; var grids = { tailwind: tailwind, bootstrap: bootstrap, bootstrap3: bootstrap, bootstrap4: bootstrap4, bootstrap5: bootstrap5, bulma: bulma, materialize: materialize, foundation: foundation, semanticUi: semanticUi, }; var DEFAULT_GRID_FRAMEWORK = 'tailwind'; var createGridObject = function (config) { return Object.keys(config).reduce(function (accumulator, key) { accumulator[key] = false; return accumulator; }, { breakpoint: '', }); }; var createConfigFromLiteral = function (literal) { if (!grids[literal]) { throw new Error("Invalid grid type \"".concat(literal, "\"")); } return grids[literal]; }; var getCurrentBreakpoint = function (config, object) { var current = Object.keys(config) .filter(function (key) { return typeof config[key] !== "function"; }) .sort(function (a, b) { var valueA = remToPx(config[a]); var valueB = remToPx(config[b]); return valueA - valueB; }) .reverse() .find(function (key) { return object[key]; }); return current || ""; }; /* istanbul ignore next */ var updateComputedProperties = function (config, object) { Object.keys(config) .filter(function (breakpoint) { return typeof config[breakpoint] === 'function'; }) .forEach(function (breakpoint) { var fn = config[breakpoint]; object[breakpoint] = fn.call(null, object); }); object.breakpoint = getCurrentBreakpoint(config, object); }; /* istanbul ignore next */ var createMediaQueries = function (config, object) { var debouncedUpdateComputedProperties = debounce(updateComputedProperties, 100); Object.keys(config) .filter(function (breakpoint) { return typeof config[breakpoint] !== 'function'; }) .forEach(function (breakpoint) { var width = config[breakpoint]; if (typeof width === 'number') { width = "".concat(width, "px"); } else { width = width.toString(); } var onChange = function (event) { object[breakpoint] = event.matches; debouncedUpdateComputedProperties(config, object); }; var query = 'matchMedia' in window && window.matchMedia("(min-width: ".concat(width, ")")); if (query) { if ('addEventListener' in query) { query.addEventListener('change', onChange); } else { // https://github.com/reegodev/vue-screen/issues/30 // query.addListener is not deprecated for iOS 12 // eslint-disable-next-line @typescript-eslint/no-explicit-any query.addListener(onChange); } object[breakpoint] = query.matches; } // Do not leak memory by keeping event listeners active. // This appears to work as expected, using useGrid() inside components // triggers this hook when they are destroyed. if (getCurrentInstance()) { onUnmounted(function () { if (query) { if ('removeEventListener' in query) { query.removeEventListener('change', onChange); } else { // https://github.com/reegodev/vue-screen/issues/30 // query.removeListener is not deprecated for iOS 12 // eslint-disable-next-line @typescript-eslint/no-explicit-any query.removeListener(onChange); } } }); } }); updateComputedProperties(config, object); }; function useGrid(gridConfig) { if (gridConfig === void 0) { gridConfig = DEFAULT_GRID_FRAMEWORK; } var config; if (typeof gridConfig === 'string') { config = createConfigFromLiteral(gridConfig); } else { config = Object.assign(gridConfig); } var gridObject = reactive(createGridObject(config)); /* istanbul ignore if */ if (inBrowser) { createMediaQueries(config, gridObject); } return gridObject; } var extendGrid = function (literalConfig, extraProperties) { return Object.assign({}, createConfigFromLiteral(literalConfig), extraProperties); }; var install = function (app, options) { var screen; var grid; if (typeof options === 'string') { screen = useScreen(); grid = useGrid(options); } else { options = options || { grid: undefined, ssr: undefined, debounceDelay: undefined }; screen = useScreen(options.ssr, options.debounceDelay); // ts cant figure out the type of arguments when union types are // passed to an overloaded function, so we need to use "any" or do a typeof check // on the arguments, which would ship 5 lines of js instead of one. // eslint-disable-next-line @typescript-eslint/no-explicit-any grid = useGrid(options.grid); } app.config.globalProperties.$screen = screen; app.config.globalProperties.$grid = grid; app.provide('screen', screen); app.provide('grid', grid); }; var plugin = { install: install }; export { plugin as default, extendGrid, grids, useGrid, useScreen };