Skip to content

Instantly share code, notes, and snippets.

@jonathantneal
Created May 10, 2021 02:26
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jonathantneal/a5fb0d54233d50c05b068fade140d62b to your computer and use it in GitHub Desktop.
Save jonathantneal/a5fb0d54233d50c05b068fade140d62b to your computer and use it in GitHub Desktop.
Some, like, useful functions when building cqfill. This gist may be deleted later.
/** Observes stylesheet lists and runs a callback whenever it encounteres added or removed stylesheets. */
export const createStyleSheetObserver = (/** @type {(addedSheets?: CSSStyleSheet[], removedSheets?: CSSStyleSheet[]) => void} */ callback) => {
/** @type {Set<StyleSheetList>} Observed style sheet lists. */
const sheetLists = new Set
/** @type {Set<CSSStyleSheet>} Previously observed style sheets. */
let lastSheets = new Set
/** Observes style sheet on each animation frame. */
const onAnimationFrame = () => {
requestAnimationFrame(onAnimationFrame)
/** @type {CSSStyleSheet[]} Observed StyleSheets after this frame. */
const nextSheets = []
/** @type {CSSStyleSheet[]} New StyleSheets added during this frame. */
const addedSheets = new Set
// check each StyleSheetList
for (const sheets of sheetLists) {
// check each StyleSheet
for (const sheet of sheets) {
nextSheets.push(sheet)
// add stylesheets that have not yet been observed
if (!lastSheets.delete(sheet)) {
addedSheets.add(sheet)
}
}
}
if (addedSheets.size || lastSheets.size) {
callback(addedSheets, lastSheets)
}
lastSheets = new Set(nextSheets)
}
onAnimationFrame()
return sheetLists
}
const { getComputedStyle: __getComputedStyle = () => ({}) } = globalThis
const __getComputedStyleMap = new WeakMap
/** @type {(element: Element) => CSSStyleDeclaration} Returns the computed CSS values of an element. */
export const getComputedStyle = element => __getComputedStyleMap.get(element) || __getComputedStyleMap.set(element, __getComputedStyle(element)).get(element),
const matchContainerQuery = /(?:--css-container)? and \((min|max)-(width|height): ([-+]?(?:\d*\.)?(?:\d+))(em|px|rem)\)/gy
/** Returns a container query from a media list that represents a container query. */
export const getContainerQueryFromMediaList = (/** @type {MediaList} */ mediaList) => {
/** @type {CQFContainerQuery} */
const query = {}
/** @type {[boolean, 'max' | 'min', CQFAxis, string, CQFUnit] | void} */
let match
while (match = matchContainerQuery.exec(mediaList[0])) {
let [ isLogical, edge, prop, value, unit ] = match
isLogical = mediaList[1] === '--is-logical'
prop = (
isLogical
? prop === 'width' ? 'inline-size' : 'block-size'
: prop
)
query[prop] = query[prop] || { min: [0, 'px'], max: [Infinity, 'px'] }
query[prop][edge] = [ Number(value), unit ]
}
return query
}
/** @typedef {"em" | "px" | "rem"} CQFUnit */
/** @typedef {[number, CQFUnit]} CQFLimit */
/** @typedef {{ max: CQFLimit, min: CQFLimit }} CQFRange */
/** @typedef {'block-size' | 'height' | 'inline-size' | 'width'} CQFAxis */
/** @typedef {{ [key in CQFAxis]?: CQFRange }} CQFContainerQuery */
/** @type {(style: CSSStyleDeclaration) => { block: boolean, inline: boolean, width: boolean, height: boolean } | null} Returns a list of `contain` values in a style. */
export const getContainFromCSSStyleDeclaration = (style) => {
/** Raw contain style. */
const contain = style ? style.getPropertyValue('--css-contain').trim().toLowerCase().split(/\s+/) : []
if (!contain.includes('layout')) return null
const size = contain.includes('size')
const block = size && contain.includes('block-size')
const inline = size && contain.includes('inline-size')
const width = contain.includes('width')
const height = contain.includes('height')
return block || inline || width || height ? { block, inline, width, height } : null
}
import { getComputedStyle } from './getComputedStyle.js'
/** @type {(entry: ResizeObserverEntry) => ContentBoxSize} Returns useful sizing information from a resize observer entry. */
export const getContentBoxSizeFromResizeObserverEntry = ({ contentRect, target }) => {
/** Computed style of the target element. */
const computedStyle = getComputedStyle(target)
/** Whether the target element is in a horizontal writing mode. */
const isHorizontal = computedStyle.writingMode.charCodeAt(0) === 104
/** @type {ContentBoxSize} */
const contentBoxSize = {
width: contentRect.width,
height: contentRect.height,
blockSize: isHorizontal ? contentRect.height : contentRect.width,
inlineSize: isHorizontal ? contentRect.width: contentRect.height,
fontSize: parseFloat(computedStyle.fontSize),
}
return contentBoxSize
}
/** @typedef {{ blockSize: number, fontSize: number, height: number, inlineSize: number, width: number }} ContentBoxSize */
import { getComputedStyle } from './getComputedStyle.js'
/** @type {(element: Element) => boolean} Returns whether the element has an inline outer-display. */
export const hasInlineOuterDisplay = (element) => /^inline/.test(getComputedStyle(element).display),
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment