UNPKG

vue3-mq

Version:

Build responsive design into your Vue 3 app

324 lines (323 loc) 12.9 kB
import { Transition, TransitionGroup, computed, h, inject, reactive, readonly, ref } from "vue"; var __defProp = Object.defineProperty, __export = (f, V) => { let H = {}; for (var U in f) __defProp(H, U, { get: f[U], enumerable: !0 }); return V && __defProp(H, Symbol.toStringTag, { value: "Module" }), H; }, _availableBreakpoints = ref([]), _defaultBreakpoint = ref(null), _defaultOrientation = ref(null), _defaultTheme = ref(null), _defaultMotion = ref(null), _mqState = reactive({ current: "" }); const _listeners = [], _isMounted = ref(!1), availableBreakpoints = readonly(_availableBreakpoints), defaultBreakpoint = readonly(_defaultBreakpoint), defaultOrientation = readonly(_defaultOrientation), defaultTheme = readonly(_defaultTheme), defaultMotion = readonly(_defaultMotion), mqState = readonly(_mqState), setAvailableBreakpoints = (f) => { _availableBreakpoints.value = f; }, setDefaultBreakpoint = (f) => { _defaultBreakpoint.value = f; }, setDefaultOrientation = (f) => { _defaultOrientation.value = f; }, setDefaultTheme = (f) => { _defaultTheme.value = f; }, setDefaultMotion = (f) => { _defaultMotion.value = f; }, updateState = (f = defaultBreakpoint.value) => { _mqState.current = f; let V = availableBreakpoints.value.findIndex((V) => V.name === f), H = availableBreakpoints.value.map((f) => f.name); for (let U = 0; U < H.length; U++) { if (U > 0 && U < H.length - 1) { let f = H[U] + "Minus", W = H[U] + "Plus"; _mqState[f] = V <= U, _mqState[W] = V >= U; } _mqState[H[U]] = H[U] === f; } }, resetState = () => { let f = Object.keys(_mqState); for (let V of f) delete _mqState[V]; updateState(), updateOrientationState(), updateThemeState(), updateMotionState(); }, updateOrientationState = (f = defaultOrientation.value) => { _mqState.orientation = f, _mqState.isLandscape = f === "landscape", _mqState.isPortrait = f === "portrait"; }, updateThemeState = (f = defaultTheme.value || "light") => { _mqState.theme = f, _mqState.isDark = f === "dark", _mqState.isLight = f === "light"; }, updateMotionState = (f = defaultMotion.value || "no-preference") => { _mqState.motionPreference = f, _mqState.isMotion = f === "no-preference", _mqState.isInert = f === "reduce"; }; function removeListeners() { for (; _listeners.length > 0;) { let f = _listeners.shift(); if (f && typeof f == "object") { let { mql: V, cb: H } = f; V.addEventListener && typeof V.addEventListener == "function" ? V.removeEventListener("change", H) : V.removeListener(H); } } } function createMediaQueries() { return availableBreakpoints.value.reduce((f, V, H, U) => { let W = `(min-width: ${V.min}px)`, G = H < U.length - 1 ? `(max-width: ${U[H + 1].min - 1}px)` : null, K = W + (G ? " and " + G : ""); return Object.assign(f, { [V.name]: K }); }, {}); } function subscribeToMediaQuery(f, V) { if (typeof window > "u" || !window.matchMedia) return !1; if (typeof window < "u" && !window.matchMedia) return console.error("Vue3 Mq: No MatchMedia support detected in this browser. Responsive breakpoints not available."), !1; { _isMounted.value = !0; let H = window.matchMedia(f), U = ({ matches: f }) => { f && V(); }; _listeners.push({ mql: H, cb: U }), H.addEventListener && typeof H.addEventListener == "function" ? H.addEventListener("change", U) : H.addListener(U), U(H); } } var validateBreakpoint = (f) => availableBreakpoints.value.some((V) => V.name === f); const calculateBreakpointsToRender = (f, V) => { let H = V.value.map((f) => f.name); if (f) { if (Array.isArray(f)) return f.filter((f) => validateBreakpoint(f)); if (typeof f == "string" && /\w+\+$/.test(f)) { if (f = f.replace(/\+$/, ""), validateBreakpoint(f) === !1) return console.error(`Vue3 Mq: ${f} is not a valid breakpoint key. Invalid range.`), H; let U = V.value.findIndex((V) => V.name === f); return V.value.slice(U).map((f) => f.name); } else if (typeof f == "string" && /\w+-$/.test(f)) { if (f = f.replace(/-$/, ""), validateBreakpoint(f) === !1) return console.error(`Vue3 Mq: ${f} is not a valid breakpoint key. Invalid range.`), H; let U = V.value.findIndex((V) => V.name === f); return V.value.slice(0, U + 1).map((f) => f.name); } else if (typeof f == "string" && /^\w+-\w+$/.test(f)) { let [U, W] = f.split("-"); if (validateBreakpoint(U) === !1) return console.error(`Vue3 Mq: ${U} is not a valid breakpoint key. Invalid range.`), H; if (validateBreakpoint(W) === !1) return console.error(`Vue3 Mq: ${W} is not a valid breakpoint key. Invalid range.`), H; let G = V.value.findIndex((f) => f.name === U), K = V.value.findIndex((f) => f.name === W); return V.value.slice(G, K + 1).map((f) => f.name); } else if (typeof f == "string" && validateBreakpoint(f) === !0) return [f]; else return H; } else return H; }, calculateOrientationsToRender = (f, V) => { let H = []; return !f && !V ? ["landscape", "portrait"] : (f && H.push("landscape"), V && H.push("portrait"), H); }, calculateThemesToRender = (f, V) => { let H = []; return !V && !f ? ["light", "dark"] : (V && H.push("light"), f && H.push("dark"), H); }, calculateMotionToRender = (f, V) => { let H = []; return !f && !V ? ["reduce", "no-preference"] : (f && H.push("reduce"), V && H.push("no-preference"), H); }; var presets_exports = /* @__PURE__ */ __export({ bootstrap3: () => bootstrap3, bootstrap4: () => bootstrap4, bootstrap5: () => bootstrap5, bulma: () => bulma, devices: () => devices, mui: () => mui, tailwind: () => tailwind, vuetify: () => vuetify, vuetify3: () => vuetify3, vuetify4: () => vuetify4, wordpress: () => wordpress }, 1); const bootstrap5 = { xs: 0, sm: 576, md: 768, lg: 992, xl: 1200, xxl: 1400 }, bootstrap4 = { xs: 0, sm: 576, md: 768, lg: 992, xl: 1200 }, bootstrap3 = { xs: 0, sm: 768, md: 992, lg: 1200 }, vuetify4 = { xs: 0, sm: 600, md: 840, lg: 1145, xl: 1545, xxl: 2138 }, vuetify3 = { xs: 0, sm: 600, md: 960, lg: 1280, xl: 1920, xxl: 2560 }, vuetify = { xs: 0, sm: 600, md: 960, lg: 1264, xl: 1904 }, tailwind = { xs: 0, sm: 640, md: 768, lg: 1024, xl: 1280, xxl: 1536 }, devices = { phone: 0, tablet: 768, laptop: 1370, desktop: 1906 }, wordpress = { mobile: 0, small: 600, medium: 782, large: 960, xlarge: 1080, wide: 1280, huge: 1440 }, mui = { xs: 0, sm: 600, md: 900, lg: 1200, xl: 1536 }, bulma = { mobile: 0, tablet: 768, desktop: 1024, widescreen: 1216, fullhd: 1408 }, validatePreset = (f) => { if (typeof f == "string" && presets_exports[f]) return presets_exports[f]; { let V = Object.keys(presets_exports); return console.error(`Vue3 Mq: "${f}" is not a valid preset. Available options are: ${V.join(", ")}`), !1; } }, validateOrientation = (f) => ["landscape", "portrait"].includes(f) === !1 ? (console.error(`Vue3 Mq: "${f}" is not a valid default orientation. Reverting to unset value.`), null) : f, validateTheme = (f = null) => ["dark", "light"].includes(f) === !1 && f !== null ? (console.error(`Vue3 Mq: "${f}" is not a valid default theme. Reverting to unset value.`), null) : f, validateMotion = (f = null) => ["no-preference", "reduce"].includes(f) === !1 && f !== null ? (console.error(`Vue3 Mq: "${f}" is not a valid default motion preference. Reverting to unset value.`), null) : f, sanitiseBreakpoints = (f) => { if (!f || typeof f != "object") return !1; let V = []; for (let H in f) { let U = parseFloat(f[H]); if (!H || typeof H != "string") { console.warn(`Vue3 Mq: Invalid or missing breakpoint key (${JSON.stringify(H)}). Skipping.`); continue; } else if (/^[^a-z]/i.test(H) || /[^a-zA-Z0-9_]/.test(H)) { console.warn(`Vue3 Mq: "${H}" is an invalid breakpoint key. Breakpoint keys must start with a letter and contain only alphanumeric characters and underscores. Skipping.`); continue; } else if (!U && U !== 0 || isNaN(U) || U < 0) { console.warn(`Vue3 Mq: "${H}: ${f[H]}" is not a valid breakpoint. Breakpoints should be a number of zero or above. Skipping.`); continue; } V.push({ name: H, min: U }); } return V.some((f) => f.min === 0) || console.warn("Vue3 Mq: You have not declared a breakpoint with a minimum value of 0. There may be screen sizes to which Vue3Mq does not respond."), new Set(V.map((f) => f.min)).size < V.length && console.warn("Vue3 Mq: Your breakpoint configuration contains duplicate values. Behaviour may be unpredictable."), V.length === 0 ? !1 : V.sort((f, V) => f.min - V.min); }, extractSlotNameProperties = (f) => { let V = f.split(":"), H = {}; for (let f of V) if (/\D/.test(f) === !1) continue; else ["landscape", "portrait"].includes(f) ? H.slotOrientation = f : ["light", "dark"].includes(f) ? H.slotTheme = f : ["inert", "motion"].includes(f) ? H.slotMotion = f : H.slotBp = f; return H; }; var defaultTransitionOptions = { name: "fade", mode: "out-in" }, component_default = { name: "MqResponsive", props: { target: [String, Array], landscape: { type: Boolean, default: !1 }, portrait: { type: Boolean, default: !1 }, dark: { type: Boolean, default: !1 }, light: { type: Boolean, default: !1 }, inert: { type: Boolean, default: !1 }, motion: { type: Boolean, default: !1 }, tag: { type: String, default: "div" }, listTag: { type: String, default: "div" }, group: { type: Boolean, default: !1 } }, setup(W, { attrs: G, emit: K, slots: q }) { let J = computed(() => calculateBreakpointsToRender(W.target, availableBreakpoints)), Y = computed(() => calculateOrientationsToRender(W.landscape, W.portrait)), X = computed(() => calculateThemesToRender(W.dark, W.light)), Z = computed(() => calculateMotionToRender(W.inert, W.motion)), Q = computed(() => J.value.includes(mqState.current) && Y.value.includes(mqState.orientation) && X.value.includes(mqState.theme) && Z.value.includes(mqState.motionPreference)), $ = (f) => { if (!W.group && q.length > 0) return q; let V = []; for (let W in q) { let { slotBp: G, slotOrientation: K, slotTheme: J, slotMotion: Y } = extractSlotNameProperties(W), X = computed(() => calculateBreakpointsToRender(G, availableBreakpoints)), Z = computed(() => calculateOrientationsToRender(K === "landscape", K === "portrait")), Q = computed(() => calculateThemesToRender(J === "dark", J === "light")), $ = computed(() => calculateMotionToRender(Y === "inert", Y === "motion")); computed(() => X.value.includes(mqState.current) && Z.value.includes(mqState.orientation) && Q.value.includes(mqState.theme) && $.value.includes(mqState.motionPreference)).value === !0 && V.push(h(f || q[W], { key: W }, f ? q[W]() : void 0)); } return V.length > 0 ? V : void 0; }; return q.default ? () => Q.value ? h(W.tag, { ...G }, q.default()) : void 0 : () => { let H = Object.assign({}, defaultTransitionOptions, G, { tag: W.tag }); return h(W.group ? TransitionGroup : Transition, H, () => $(W.listTag)); }; } }; function updateBreakpoints({ breakpoints: f, mergeBreakpoints: V = {}, preset: H }) { let U = H ? validatePreset(H) : !1, W = f ? sanitiseBreakpoints(f) : !1; if (U === !1 && !W) throw TypeError("Vue3 Mq: You must provide a valid preset, or valid breakpoint settings."); setAvailableBreakpoints(W || sanitiseBreakpoints({ ...U, ...V })), removeListeners(), resetState(); let G = createMediaQueries(); for (let f in G) { let V = G[f]; subscribeToMediaQuery(V, () => { updateState(f); }); } ["portrait", "landscape"].forEach((f) => { subscribeToMediaQuery(`(orientation: ${f})`, () => { updateOrientationState(f); }); }), ["light", "dark"].forEach((f) => { subscribeToMediaQuery(`(prefers-color-scheme: ${f})`, () => { updateThemeState(f); }); }), ["reduce", "no-preference"].forEach((f) => { subscribeToMediaQuery(`(prefers-reduced-motion: ${f})`, () => { updateMotionState(f); }); }); } function useMq() { let f = inject("mq"); if (f) return f; throw Error("Vue3Mq is not installed in this app. Please follow the installation instructions and try again."); } var plugin_default = { install: (f, { preset: V = "bootstrap5", breakpoints: H, mergeBreakpoints: U, defaultBreakpoint: W, defaultOrientation: G = "landscape", defaultMotion: K = "no-preference", defaultTheme: q, global: J = !1 } = {}) => { try { let Y = validateOrientation(G), X = validateTheme(q), Z = validateMotion(K); setDefaultBreakpoint(W), setDefaultOrientation(Y), setDefaultTheme(X), setDefaultMotion(Z), f.provide("mq", mqState), f.provide("updateBreakpoints", updateBreakpoints), J === !0 && (f.component("MqResponsive", component_default), f.config.globalProperties.$mq = mqState), updateBreakpoints({ breakpoints: H, mergeBreakpoints: U, preset: V }); } catch (f) { console.error(f); } } }; export { component_default as MqResponsive, plugin_default as Vue3Mq, availableBreakpoints, updateBreakpoints, useMq };