All files / utils mobile-detection.js

85.55% Statements 77/90
84.61% Branches 11/13
88.88% Functions 8/9
85.55% Lines 77/90

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 911x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x                           1x 1x 5x 5x 5x 5x 1x 1x 1x 1x 1x 1x 1x 5x 5x 5x 5x 5x 5x 5x 5x 9x 9x 9x 9x 9x 9x 9x 9x 9x 9x 5x 5x 5x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x  
import { IterableWeakMap, IterableWeakSet } from './iterable-weak-struct.js'
 
const portraitMobileMatchMedia = window.matchMedia('(max-width: 576px) and (orientation: portrait)')
const landscapeMobileMatchMedia = window.matchMedia('(max-width: 768px) and (orientation: landscape)')
 
export const hasTouchScreen = () => 'ontouchstart' in window || navigator.maxTouchPoints > 0
 
export const isPortraitMobile = () => portraitMobileMatchMedia.matches && hasTouchScreen()
export const isLandscapeMobile = () => landscapeMobileMatchMedia.matches && hasTouchScreen()
 
export const isMobile = () => isPortraitMobile() || isLandscapeMobile()
 
/**
 * Map of created observers with its data
 * @type {IterableWeakMap<MobileDetectionObserverCallback, MobileDetectionObserverData>}
 */
const observerData = new IterableWeakMap()
 
/**
 *
 */
function updateData () {
  const isNowMobile = isMobile()
  for (const [callback, data] of observerData.entries()) {
    const { currentIsMobile, observingNodes } = data
    if (currentIsMobile === isNowMobile) { continue }
    data.currentIsMobile = isNowMobile
    const events = Iterator.from(observingNodes).map(element => ({
      target: element,
      isMobile: isNowMobile,
    })).toArray()
    callback(events)
  }
}
 
let observeMatchMedia = () => {
  portraitMobileMatchMedia.addEventListener('change', updateData)
  landscapeMobileMatchMedia.addEventListener('change', updateData)
  observeMatchMedia = () => {}
}
 
 
/**
 * @param {MobileDetectionObserverCallback} callback - mutation callback
 * @returns {MobileDetectionObserver} - observer
 */
export function MobileDetectionObserver (callback) {
  const data = observerData.getOrInsertComputed(callback, () => ({
    observingNodes: new IterableWeakSet(),
    currentIsMobile: undefined,
  }))
 
  return Object.freeze({
 
    observe (node) {
      observeMatchMedia()
      data.currentIsMobile ??= isMobile()
      const { currentIsMobile, observingNodes } = data
      if (observingNodes.has(node)) { return }
      observingNodes.add(node)
      callback([{
        target: node,
        isMobile: currentIsMobile,
      }])
    },
 
  })
}
 
/**
 * @typedef {object} MobileDetectionObserver
 * @property {(node: Node) => void} observe - observer callback
 */
 
/**
 * @typedef {object} MobileDetectionObserverData
 * @property {boolean} [currentIsMobile] - flag saved on observer to determine if the mode changed
 * @property {IterableWeakSet<Node>} observingNodes - observer callback
 */
 
/**
 * @callback MobileDetectionObserverCallback
 * @param {MobileStateMutationRecord[]} mutations - mutations happened for each observing element
 */
 
/**
 * @typedef {object} MobileStateMutationRecord
 * @property {Node} target - observing element
 * @property {boolean} isMobile - flag to determine if it's on mobile mode
 */