All files / utils/tick-time tick-time.js

81.37% Statements 83/102
66.66% Branches 4/6
57.14% Functions 4/7
81.37% Lines 83/102

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 91 92 93 94 95 96 97 98 99 100 101 102 1031x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x                         1x 1x 1x 1x 1x 1x 1x       1x 1x 1x 1x 1x 1x     1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x     1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x  
import { IterableWeakSet } from '../algorithms/iterable-weak-struct'
import { timeNowFrame } from '../algorithms/time.utils'
 
/**
 * A margin that improves the reliability of the next time value to change
 * due to possible protections to fingerprinting on the client side
 *
 * {@see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/now}
 */
const MARGIN_MILLIS = 100
 
let timeTickInstances
/**
 * @param {TimeTickInstance} newInstance - target time tick instance
 */
export function checkTick (newInstance) {
  const { callbacks, tickingElements, timeoutNumber } = newInstance
  if (timeoutNumber === undefined && tickingElements.size > 0 && callbacks.size > 0) {
    const nextSecond = 1000 - timeNowFrame() % 1000
    newInstance.timeoutNumber = setTimeout(() => {
      for (const tickCallback of callbacks) {
        tickCallback({
          targets: [...tickingElements],
          untick: (el) => tickingElements.delete(el),
        })
      }
      newInstance.timeoutNumber = undefined
      checkTick(newInstance)
    }, nextSecond + MARGIN_MILLIS)
  }
}
 
/**
 * @this {TimeTickInstance}
 * @param {Element} element - target element
 */
export function tickElement (element) {
  this.tickingElements.add(element)
  checkTick(this)
}
 
/**
 * @this {TimeTickInstance}
 * @param {Element} element - target element
 */
export function untickElement (element) {
  this.tickingElements.add(element)
}
 
/**
 * @this {TimeTickInstance}
 * @param {TimeTickCallback} callback - callback to trigger each time tick
 */
export function addCallback (callback) {
  this.callbacks.add(callback)
  checkTick(this)
}
 
/**
 * @this {TimeTickInstance}
 * @param {TimeTickCallback} callback - callback added on {@link addCallback}
 */
export function removeCallback (callback) {
  this.callbacks.delete(callback)
}
 
/**
 * @class
 * Builds a time tick instance
 */
function TimeTickInstance () {
  /** @type {IterableWeakSet<Element>} */
  this.tickingElements = new IterableWeakSet()
  /** @type {unknown} */
  this.timeoutNumber = undefined
  /** @type {Set<TimeTickCallback>} */
  this.callbacks = new Set()
}
 
TimeTickInstance.prototype.tickElement = tickElement
TimeTickInstance.prototype.untickElement = untickElement
TimeTickInstance.prototype.addCallback = addCallback
TimeTickInstance.prototype.removeCallback = removeCallback
 
/**
 * @returns {TimeTickInstance} singleton time tick instance
 */
export function timeTick () {
  timeTickInstances ??= new TimeTickInstance()
  return timeTickInstances
}
 
/**
 * @typedef {object} TimeTickCallbackParams
 * @property {Element[]} targets - target ticking elements
 * @property {(el: Element) => void} untick - stop ticking an element, does nothing if already not ticking
 */
 
/**
 * @callback TimeTickCallback
 * @param {TimeTickCallbackParams} param
 */