Skip to content

Instantly share code, notes, and snippets.

@doeixd
Last active May 9, 2024 11:48
Show Gist options
  • Save doeixd/8960a4ef6aab93c84d7e68b359228ffd to your computer and use it in GitHub Desktop.
Save doeixd/8960a4ef6aab93c84d7e68b359228ffd to your computer and use it in GitHub Desktop.
lib.js
function ensureRunAfterDOM (fn) {
let handleDOMLoaded = Fn(fn)
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', handleDOMLoaded);
} else {
handleDOMLoaded();
}
}
function Fn (fn) {
return function (...args) {
return fn(...args)
}
}
let isString = value => typeof value === 'string' || value instanceof String;
function createObserver(fn, options) {
const observer = new MutationObserver(fn)
return function (element) {
if (isString(element)) element = document.querySelector(element)
observer.observe(element, options)
}
}
function addEventListenerTo(selector, event, callback, parent = 'body') {
const handler = Fn(callback)
const addListener = element => {
element.addEventListener(event, handler)
}
function addListenerToMatches() {
const parentElement = document.querySelector(parent);
const matchingChildren = parentElement.querySelectorAll(selector)
addListenerToElements(matchingChildren)
}
function addListenerToElements(elements) {
for (let element of [...elements]) {
addListener(element)
}
}
const getAddedElements = mutations => {
let addedElements = []
for (let mutation of mutations) {
for (let element of mutation.addedNodes) {
addedElements.push(element)
if (!(element instanceof HTMLElement)) continue
const descendants = element.querySelectorAll('*')
addedElements.push(...descendants)
}
}
return addedElements
}
ensureRunAfterDOM(() => {
addListenerToMatches()
createObserver((mutations) => {
const addedElements = getAddedElements(mutations)
const matchingElements = addedElements.filter(addedElement => addedElement?.matches?.(selector))
addListenerToElements(matchingElements)
}, {childList: true, subtree: true})
(parent);
})
}
function JS (obj) {
return JSON.stringify(obj, null, 2)
}
function clone (obj) {
return JSON.parse(JSON.stringify(obj))
}
function entries (obj, one = false) {
return Object.entries(obj).map((o, i) => {
return one ? [i + 1, o[1]] : o
})
}
}
function isType (type, value) {
const isIt = val => {
if (type == null || value == null) return value === type
return Object.getPrototypeOf(value ?? {}).constructor === type
}
if (!value) return function (value) {
return isIt(value)
}
return isIt(value)
}
function applyInlineStylesToElement(element) {
return function(styles) {
const oldStyles = JSON.parse(JSON.stringify(element.style))
const applyStyles = obj => {
Object.entries(obj).forEach(([key, value]) => {
element.style[key] = value
})
}
return {
apply: (additionalStyles) => {
applyStyles({...styles, ...additionalStyles})
},
revert: () => {
applyStyles(oldStyles)
}
}
}
}
function randomIndex(arr, opts) {
const idx = Math.floor(Math.random() * arr.length)
return idx
}
function randomItem(arr, opts) {
const idx = Math.floor(Math.random() * arr.length)
return arr[idx]
}
function dedup(arr) {
return [...new Set(arr)]
}
function dedup_words(str = '') {
str = String(str)
return dedup(str.split(' ')).join(' ')
}
function structurally_deduplicate_array_of_objects(array) {
let map = new Map()
for (let object of array) {
let _hash = JSON.stringify(object)
if (!map.has(_hash)) map.set(_hash, object)
}
return Array.from(map.values())
}
function html(html) {
var template = document.createElement('template');
html = html.trim();
template.innerHTML = html;
return template.content.firstChild;
}
const identity = (strings, ...values) => String.raw({ raw: strings }, ...values);
function* range(start, end, step) {
let current = start;
while (current <= end) {
yield current;
current += step;
}
}
// --------
const $ = (...args) => document.querySelector(...args)
const $$ = (...args) => Array.from(document.querySelectorAll(...args))
(window || {}).$ = $
(window || {}).$$ = $$
function convertColor(color, toSpace) {
let div = document.createElement('div')
div.style.color = `color-mix(in ${toSpace}, ${color} 100%, transparent)`
div.style.display = 'none'
document.body.appendChild(div)
let result = window.getComputedStyle(div).color
div.remove()
return result
}
export function setQueryParam(key, value, type = 'soft') {
const url = new URL(window.location)
url.searchParams.set(key, value)
if (type == 'hard') window.location.search = url.href
if (type == 'soft') history.pushState(urlParams, '', url.href)
}
function getQueryParam(key) {
const urlParams = new URLSearchParams(window.location.search)
urlParams.get(key)
window.location.search = urlParams
}
function getAllQueryParam(key) {
const urlParams = new URLSearchParams(window.location.search)
urlParams.getAll(key)
window.location.search = urlParams
}
const Params = {
get: getQueryParam,
set: setQueryParam,
getAll: getAllQueryParam
}
(window || {}).Params = Params
// ----
function pick(fields, obj) {
let res = {}
if (!fields || fields.length == 0) return res
fields = [fields].flat()
let map = new Set(fields)
for (let field of fields) {
if (map.has(field)) res[field] = obj?.[field]
}
return res
}
function is_object_empty(obj) {
return Object.keys(obj).length == 0
}
function delete_fields(obj, ...fields) {
let _obj = clone(obj)
for (let field of fields) {
if (Object.hasOwn(_obj, field)) delete _obj[field]
}
return _obj
}
function rename_field (obj, from_field, to_field) {
let _obj = clone(obj)
if (Object.hasOwn(_obj, from_field)) {
let from_val = _obj[from_field]
delete _obj[from_field]
_obj[to_field] = from_val
}
return _obj
}
let bound = (obj, val) => {
return new Proxy(obj, {
get: async (target, key) => {
return await target[key]?.(val)
}
})
}
const destructure = (obj, list, defaults = {}) => {
if (typeof list === 'object') {
if (Array.isArray(list)) {
list = list.map((l) => l.trim());
} else {
list = Object.keys(list);
}
}
let acc = {};
for (let key of list) {
if (typeof obj[key] === 'object' && obj[key] !== null) {
acc[key] = destructure(obj[key], list[key], defaults[key] || {});
} else {
acc[key] = obj?.[key] !== undefined ? obj[key] : defaults[key];
}
}
return acc;
}
const d = (destructure_obj, fn) => {
return (args) => {
return fn(destructure(args, destructure_obj))
}
}
const invariant = (_case, msg) => {
if (!_case) throw new Error(msg)
}
const fallback = (subject, fallbacks = [{}], _case = (arg) => Boolean(arg), base = undefined) => {
return new Proxy(subject, {
get: (target, key) => {
let target_val = Reflect.get(target, key)
if (_case(target_val)) return target_val
fallbacks.reverse()
for (let fallback of fallbacks) {
let fallback_val = Reflect.get(fallback, key)
if (typeof fallback_val == 'function') fallback_val = fallback_val.bind(subject)
if (_case(fallback_val)) return fallback_val
}
return base
},
})
}
// ------
function where(field, op, value) {
op = op == 'includes' ? (pred) => pred.includes(value) : op
op = op == 'is' ? (pred) => pred == value : op
op = op == 'startsWith' ? (pred) => pred.startsWith(value) : op
op = op == 'endsWith' ? (pred) => pred.endsWith(value) : op
op = op == 'matches' ? (pred) => value.test(pred) : op
return function (subject) {
subject = (field && subject?.[field]) ? subject?.[field] : subject
return op(subject)
}
}
function not(fn) {
return (...args) => !fn(...args)
}
function exists(field) {
return function (subject) {
if (field) return subject?.[field]
return subject
}
}
function sort(direction = 'asc', on) {
let command;
if (direction == 'asc') {
command = (a, b) => a - b
if (on) command = (a, b) => a?.[on] - b?.[on]
}
if (direction == 'desc') {
command = (a, b) => b - a
if (on) command = (a, b) => b?.[on] - a?.[on]
}
return command
}
function copy_shape (obj) {
return Object.fromEntries(Object.keys(obj).map(key => [key, undefined]))
}
// -----------
function curry(fn, arity = fn.length, ...args) {
return arity <= args.length ? fn(...args) : curry.bind(null, fn, arity, ...args);
}
function* zip(...arrays) {
let item_idx = 0
let item = arrays?.[0]?.[0]
// let result = []
while (item !== undefined) {
for (let array of arrays) {
item = array?.[item_idx]
if (item === undefined) return
// if (item === undefined) return result
// result.push(item)
yield item
}
item_idx += 1
}
}
function s (fn) {
return function (a, b) {
return fn(b, a)
}
}
function r (...args) {
return args.toReversed()
}
function g (outside) { return inside => (...args) => outside(inside(...args)) }
function is_false (value) { return value == false || value === undefined || isNaN(value) || value }
function or (fallback, determiner = is_false) {
return (fn) => (...args) => {
let value = fn(...args)
if (determiner(value)) return fallback
return value
}
}
function compose (fns, og) { return (...args) => fns.reduce((acc, fn) => fn(acc), og(...args)) }
var trait = curry(compose)
function last (fn, ...first_args) { return (...last_args) => {
return fn(...[...first_args, ...last_args])
} }
function first (fn, ...last_args) { return (...first_args) => {
return fn(...[...first_args, ...last_args])
} }
// ------------
function eq (val) { return x => x == val }
function till (cond_fn, val_fn) { return (...args) => {
const value = val_fn(...args)
const cond_val = cond_fn(value)
return [cond_val, value]
} }
function count (start = 0, inc = 1) { return count + inc }
function range_ (start, stop, inc) { return stream(till(eq(stop), (prev = start) => count(prev, inc)))() }
function* stream (fn) {
let result = fn()
let should_stop = false
while (!should_stop) {
[should_stop, result] = fn(result)
if (should_stop) break;
yield result
}
return result
}
// ---------
function match(regex, value) {
return String(value).match(regex)?.[0]
}
// ---------
function wait(ms, fn) {
return new Promise((resolve) => {
setTimeout(() => resolve(fn().then((l) => l)), ms)
})
}
@doeixd
Copy link
Author

doeixd commented May 8, 2024

function Fn (fn) {
  return function (...args) {
    return fn(...args)
  }
}

function addSelectorListener (selector, listener, el = document) {
  if (!selector) throw new Error('[addSelectorListener]: No selector provided')
  if (!listener || !(listener instanceof Function)) throw new Error('[addSelectorListener]: Invalid listener provided')

 
  
  function setup() {  
    const hasAlreadyBeenSetup = document.adoptedStyleSheets?.find(sheet => Object.hasOwn(sheet, Symbol.for('selector-listener-sheet')))
    if (hasAlreadyBeenSetup) return hasAlreadyBeenSetup

    const sheet = new CSSStyleSheet()
    sheet[Symbol.for('selector-listener-sheet')] = true

    document.adoptedStyleSheets = [...(document?.adoptedStyleSheets || []), sheet]

    sheet.insertRule(`
      @keyframes selector-watch {
        to {
          outline-color: rgba(0,0,0,0)
        }
      }
    `)

    return sheet
  }

  const styles = setup()


  selector = `${selector}`.trim()

  if (!el) el = this

  el.selectorListeners ||= new Map()
  if (!el.selectorListeners.has(selector)) el.selectorListeners.set(selector, [])

  let ruleIndex = styles.insertRule(`
    ${selector}::after {
        animation: selector-watch 1ms;
        content: ' ';
    }
  `)

  el.selectorListeners.get(selector).push([listener, ruleIndex])

  const runEvent = function (e) {
    if (e?.animationName !== 'selector-watch') return
    if (!e.target.matches(selector)) return

    e.preventDefault()
    e.stopPropagation()

    e.selector = selector
      console.log(el.selectorListeners, el.selectorListeners.get(selector))
      console.log('fns?', el.selectorListeners?.get(selector))

      el.selectorListeners?.get(selector).forEach((op) => Fn(op[0]).call(this, e))
  }

  el.addEventListener('animationstart', runEvent, { capture: true })

}

function removeSelectorListener (selector, listener, el = document) {
  if (!selector) throw new Error('[addSelectorListener]: No selector provided')
  if (!listener || !(listener instanceof Function)) throw new Error('[addSelectorListener]: Invalid listener provided')

  const listeners = el?.selectorListeners?.get?.(selector)
  if (!listeners) return

  const listenerIndex = listeners?.indexOf?.(listener)
  const [_, ruleIndex] = listeners?.[listenerIndex] ?? [ undefined, undefined ]
  const hasListener = listenerIndex > -1

  if (!hasListener) return

  listeners.splice(listenerIndex, 1)
  const sheet = document.adoptedStyleSheets?.find(sheet => Object.hasOwn(sheet, Symbol.for('selector-listener-sheet')))

  if (!sheet) return
  sheet.deleteRule(ruleIndex)
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment