vue3-mq
Version:
Build responsive design into your Vue 3 app
324 lines (323 loc) • 12.9 kB
JavaScript
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 };