coupdoeil
Version:
Javascript for Ruby on Rails Coupdoeil gem
143 lines (122 loc) • 4.6 kB
JavaScript
import {getOptions} from "./attributes";
export const OPTIONS = {
animation: { getter: getAnimation },
cache: { getter: getCache },
loading: { getter: getLoading },
offset: { getter: getOffset },
openingDelay: { getter: getOpeningDelay },
placement: { getter: getPlacement },
trigger: { getter: getTrigger },
}
const ORDERED_OPTIONS = [
"trigger", // bit size: 1 shift: 0
"loading", // bit size: 2 shift: 1
"cache", // bit size: 1 shift: 3
"openingDelay", // bit size: 1 shift: 4
"animation", // bit size: 3 shift: 5
"placement", // bit size: 16 shift: 8
"offset" // bit size: 20 shift: 24
]
const TRIGGERS = ["hover", "click"]
const ANIMATIONS = [false, "slide-in", "fade-in", "slide-out", "custom"]
const PLACEMENTS = [
'auto',
'top', 'top-start', 'top-end',
'right', 'right-start', 'right-end',
'bottom', 'bottom-start', 'bottom-end',
'left', 'left-start', 'left-end'
]
const LOADINGS = ["async", "preload", "lazy"]
function parseCSSSize(value) {
if (typeof value === 'number') {
return value
} else if ((/^(-?\d+\.?\d+)px$/).test(value)) {
return parseFloat(value)
} else if ((/^(-?\d*\.?\d+)rem$/).test(value)) {
return parseFloat(value) * parseFloat(getComputedStyle(document.documentElement).fontSize)
}
return 0
}
// Offset option is a three part number.
// integer (8 bits) + decimals (10 bits) + config (2 bits)
// config bits are for sign (positive (0) or negative (1)) and offset unit (px (0) or rem (1))
// Examples:
// - "1.25rem" is: 00000001 . 0000011001 . 10
// - "-16px" is: 00010000 . 00000000 . 01
// 10 bits are reserved for decimals so values like 0.875rem are possible.
function getOffset(optionsInt) {
// shift is BigInt(16 + 3 + 1 + 1 + 2 + 1)
const offsetBits = Number(BigInt(optionsInt) >> BigInt(24))
if (offsetBits === 0)
return 0
const isNegative = (offsetBits & 1) === 1
const isREM = (offsetBits & 2) === 2
const decimals = (offsetBits >> 2 /* config bits */) & 1023 // (2 ** 10) - 1
const integer = (offsetBits >> (12 /* config (2) + decimals (10) bits */))
const CSSSize = `${isNegative ? '-' : ''}${integer}.${decimals}${isREM ? 'rem' : 'px'}`
return parseCSSSize(CSSSize)
}
// Placement option can have up to 4 placement values: a main one and 3 fallbacks.
// There are 13 possible values for one placement, stored as an array in the PLACEMENTS const.
// The max index of this array is 12, so a placement value can be stored as an index of this array,
// and this index value fits in 4 bits.
// Option for placements then consists of 4 times (main and fallbacks) this index value:
// So from 0.0.0.0 up to 15.15.15.15, or from 0 up to 65535 (0b1111111111111111)
function getPlacement(optionsInt) {
// shift is 3 + 1 + 1 + 2 + 1, mask is 2 ** 16 - 1 or 0b1111111111111111
const placementBits = (optionsInt >> 8) & 65535
let shift = 0
let lastPlacement = null
const placements = []
while (lastPlacement !== "auto" && shift < 16) {
// mask is 2 ** 4 - 1
lastPlacement = PLACEMENTS[(placementBits >> shift) & 15]
placements.push(lastPlacement)
shift += 4
}
return placements
}
function getAnimation(optionsInt) {
// return ANIMATIONS[(optionsInt & 56) >> 5]
return ANIMATIONS[(optionsInt >> 5) & 7]
}
function getOpeningDelay(optionsInt) {
return ((optionsInt >> 4) & 1) === 1
}
function getCache(optionsInt) {
return (optionsInt & 8) === 8
}
function getLoading(optionsInt) {
// Shift right 1 time to remove trigger bit, mask with 3 (0b11).
return LOADINGS[(optionsInt >> 1) & 3]
}
function getTrigger(optionsInt) {
return TRIGGERS[optionsInt & 1]
}
const popoverOptions = {
animation: undefined,
cache: undefined,
loading: undefined,
offset: undefined,
openingDelay: undefined,
placement: undefined,
trigger: undefined,
}
export function extractOptionsFromElement(coupdoeilElement) {
const controller = coupdoeilElement.popoverController
const optionsInt = controller.optionsInt ||= parseOptionsInt((controller))
const options = Object.create(popoverOptions)
for (const option of ORDERED_OPTIONS) {
options[option] = OPTIONS[option].getter(optionsInt)
}
return options
}
function parseOptionsInt(controller) {
const optionsString = getOptions(controller)
return parseInt(optionsString, 36)
}
export function extractOptionFromElement(coupdoeilElement, optionName) {
const controller = coupdoeilElement.popoverController
const optionsInt = controller.optionsInt ||= parseOptionsInt((controller))
return OPTIONS[optionName].getter(optionsInt)
}