UNPKG

viewport-extra

Version:

Enable setting minimum and maximum viewport width

183 lines (165 loc) 9.94 kB
/*! Viewport Extra v3.0.0 | (c) dsktschy | MIT License */ var ViewportExtra = (function (exports) { 'use strict'; const arrayFrom = /* eslint-disable-next-line compat/compat */ Array.from ; const ensureViewportMetaElement = (doc) => { const viewportMetaElement = doc.querySelector('meta[name="viewport"]'); if (viewportMetaElement) return viewportMetaElement; const htmlMetaElement = doc.createElement("meta"); htmlMetaElement.setAttribute("name", "viewport"); doc.head.appendChild(htmlMetaElement); return htmlMetaElement; }; const getMetaElementList = (doc) => arrayFrom(doc.querySelectorAll('meta[name="viewport"],meta[name="viewport-extra"]')); const getDocumentClientWidth = (doc) => doc.documentElement.clientWidth; const camelizeKebabCaseString = (str) => str .replace(/\s+/g, "") .toLowerCase() .replace(/-./g, (s) => s[1].toUpperCase()); const kebabizeCamelCaseString = (str) => str .replace(/\s+/g, "") .replace(/[A-Z]+/g, (s) => `-${s[0]}`) .toLowerCase(); const mergeNullableContentAttributes = (precedingNullableContentAttribute, followingNullableContentAttribute) => precedingNullableContentAttribute ? followingNullableContentAttribute ? [ precedingNullableContentAttribute, followingNullableContentAttribute, ].join(",") : precedingNullableContentAttribute : followingNullableContentAttribute; const createOptionalPartialContent = (nullableContentAttribute) => nullableContentAttribute ? nullableContentAttribute .split(",") .reduce((partialContent, equalSeparatedContent) => { const [key, value] = equalSeparatedContent .split("=") .map((keyOrValue) => keyOrValue.trim()); if (key && value) { const numberValue = +value; // biome-ignore lint/suspicious/noGlobalIsNan: isNaN is safe to use here partialContent[camelizeKebabCaseString(key)] = isNaN(numberValue) ? value : numberValue; } return partialContent; }, {}) : undefined; const mergeNullableMediaAttribute = (precedingNullableMediaAttribute, followingNullableMediaAttribute) => followingNullableMediaAttribute ?? precedingNullableMediaAttribute; const createOptionalMedia = (nullableMediaAttribute) => nullableMediaAttribute ?? undefined; const mathTrunc = /* eslint-disable-next-line compat/compat */ Math.trunc ; const truncateDecimalNumber = (num, decimalPlaces) => // biome-ignore lint/suspicious/noGlobalIsFinite: isFinite is safe to use here isFinite(decimalPlaces) ? mathTrunc(num * 10 ** decimalPlaces) / 10 ** decimalPlaces : num; const defaultContent = { width: "device-width", initialScale: 1, minimumWidth: 0, maximumWidth: Infinity, }; const createContent = (partialContent = {}) => ({ ...defaultContent, ...partialContent, }); const mergeOptionalPartialContent = (precedingOptionalPartialContent, followingOptionalPartialContent) => precedingOptionalPartialContent ? { ...precedingOptionalPartialContent, ...(followingOptionalPartialContent ?? {}), } : followingOptionalPartialContent; const createContentAttribute$1 = (content = { ...defaultContent }, // biome-ignore lint/style/noInferrableTypes: number type cannot be inferred from initialization documentClientWidth = 0, decimalPlaces = 0) => { const { width, initialScale } = content; const { minimumWidth: _minimumWidth, maximumWidth: _maximumWidth, minWidth, maxWidth, ...contentWithoutExtraProperties } = content; const minimumWidth = minWidth ?? _minimumWidth; const maximumWidth = maxWidth ?? _maximumWidth; if (minimumWidth <= maximumWidth && width === "device-width") { if (documentClientWidth < minimumWidth) { contentWithoutExtraProperties.width = minimumWidth; contentWithoutExtraProperties.initialScale = (documentClientWidth / minimumWidth) * initialScale; } else if (documentClientWidth > maximumWidth) { contentWithoutExtraProperties.width = maximumWidth; contentWithoutExtraProperties.initialScale = (documentClientWidth / maximumWidth) * initialScale; } } return Object.keys(contentWithoutExtraProperties) .map((key) => `${kebabizeCamelCaseString(key)}=${typeof contentWithoutExtraProperties[key] === "number" ? truncateDecimalNumber(contentWithoutExtraProperties[key], decimalPlaces) : contentWithoutExtraProperties[key]}`) .sort() .join(","); }; const defaultMedia = ""; const createMedia = (optionalMedia) => optionalMedia ?? defaultMedia; const mergeOptionalMedia = (precedingOptionalMedia, followingOptionalMedia) => followingOptionalMedia ?? precedingOptionalMedia; const createMediaSpecificParameters = (partialMediaSpecificParameters = {}) => ({ content: createContent(partialMediaSpecificParameters.content), media: createMedia(partialMediaSpecificParameters.media), }); const mergePartialMediaSpecificParameters = (precedingPartialMediaSpecificParameters, followingPartialMediaSpecificParameters) => { const partialMediaSpecificParameters = {}; const optionalPartialContent = mergeOptionalPartialContent(precedingPartialMediaSpecificParameters.content, followingPartialMediaSpecificParameters.content); const optionalMedia = mergeOptionalMedia(precedingPartialMediaSpecificParameters.media, followingPartialMediaSpecificParameters.media); if (optionalPartialContent) partialMediaSpecificParameters.content = optionalPartialContent; if (typeof optionalMedia !== "undefined") partialMediaSpecificParameters.media = optionalMedia; return partialMediaSpecificParameters; }; const createContentAttribute = (optionalMediaSpecificParameters, optionalDocumentClientWidth, optionalDecimalPlaces) => optionalMediaSpecificParameters && typeof optionalDocumentClientWidth !== "undefined" && typeof optionalDecimalPlaces !== "undefined" ? createContentAttribute$1(optionalMediaSpecificParameters.content, optionalDocumentClientWidth, optionalDecimalPlaces) : createContentAttribute$1(); const assignOptionalPartialContent = (optionalPartialMediaSpecificParameters, optionalPartialContent) => optionalPartialContent ? { ...(optionalPartialMediaSpecificParameters ?? {}), content: optionalPartialContent, } : (optionalPartialMediaSpecificParameters ?? {}); const assignOptionalMedia = (optionalPartialMediaSpecificParameters, optionalMedia) => typeof optionalMedia !== "undefined" ? { ...(optionalPartialMediaSpecificParameters ?? {}), media: optionalMedia, } : (optionalPartialMediaSpecificParameters ?? {}); const createPartialMediaSpecificParametersMerger = (isMatchingCurrentViewport) => (precedingPartialMediaSpecificParameters, followingPartialMediaSpecificParameters) => isMatchingCurrentViewport(followingPartialMediaSpecificParameters.media) ? mergePartialMediaSpecificParameters(precedingPartialMediaSpecificParameters, followingPartialMediaSpecificParameters) : precedingPartialMediaSpecificParameters; const getNullableContentAttribute = (htmlMetaElement) => mergeNullableContentAttributes(htmlMetaElement.getAttribute("content"), htmlMetaElement.getAttribute("data-extra-content")); const getNullableMediaAttribute = (htmlMetaElement) => mergeNullableMediaAttribute(htmlMetaElement.getAttribute("data-media"), htmlMetaElement.getAttribute("data-extra-media")); const createPartialMediaSpecificParameters = (htmlMetaElement) => assignOptionalMedia(assignOptionalPartialContent(undefined, createOptionalPartialContent(getNullableContentAttribute(htmlMetaElement))), createOptionalMedia(getNullableMediaAttribute(htmlMetaElement))); const setContentAttribute = (htmlMetaElement, contentAttribute) => htmlMetaElement.setAttribute("content", contentAttribute); const applyMediaSpecificParameters = (htmlMetaElement, getDocumentClientWidth, getMediaSpecificParameters) => { setContentAttribute(htmlMetaElement, createContentAttribute()); setContentAttribute(htmlMetaElement, createContentAttribute(getMediaSpecificParameters(), getDocumentClientWidth(), Infinity)); }; const createMatchMediaPredicate = (mm) => (media) => typeof media !== "undefined" ? mm(media).matches : true; const setMediaSpecificParametersList = (partialMediaSpecificParametersList) => { if (typeof window === "undefined") return; applyMediaSpecificParameters(ensureViewportMetaElement(document), () => getDocumentClientWidth(document), () => createMediaSpecificParameters(partialMediaSpecificParametersList.reduce(createPartialMediaSpecificParametersMerger(createMatchMediaPredicate(matchMedia)), // Value that does not need to check matching current viewport createMediaSpecificParameters()))); }; const activateMediaSpecificAttributes = () => { if (typeof window === "undefined") return; setMediaSpecificParametersList(getMetaElementList(document).map(createPartialMediaSpecificParameters)); }; activateMediaSpecificAttributes(); exports.apply = setMediaSpecificParametersList; return exports; })({}); //# sourceMappingURL=viewport-extra.js.map