diff options
Diffstat (limited to 'assets/lib')
| -rw-r--r-- | assets/lib/.gitignore | 34 | ||||
| -rw-r--r-- | assets/lib/build.js | 1 | ||||
| -rw-r--r-- | assets/lib/bun.lock | 15 | ||||
| -rw-r--r-- | assets/lib/package.json | 8 | ||||
| -rw-r--r-- | assets/lib/shared.js | 130 |
5 files changed, 188 insertions, 0 deletions
diff --git a/assets/lib/.gitignore b/assets/lib/.gitignore new file mode 100644 index 0000000..a14702c --- /dev/null +++ b/assets/lib/.gitignore @@ -0,0 +1,34 @@ +# dependencies (bun install) +node_modules + +# output +out +dist +*.tgz + +# code coverage +coverage +*.lcov + +# logs +logs +_.log +report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json + +# dotenv environment variable files +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# caches +.eslintcache +.cache +*.tsbuildinfo + +# IntelliJ based IDEs +.idea + +# Finder (MacOS) folder config +.DS_Store diff --git a/assets/lib/build.js b/assets/lib/build.js new file mode 100644 index 0000000..173fbe3 --- /dev/null +++ b/assets/lib/build.js @@ -0,0 +1 @@ +export { thumbHashToDataURL } from 'thumbhash' diff --git a/assets/lib/bun.lock b/assets/lib/bun.lock new file mode 100644 index 0000000..0ba7ea2 --- /dev/null +++ b/assets/lib/bun.lock @@ -0,0 +1,15 @@ +{ + "lockfileVersion": 1, + "configVersion": 1, + "workspaces": { + "": { + "name": "lib", + "dependencies": { + "thumbhash": "^0.1.1", + }, + }, + }, + "packages": { + "thumbhash": ["thumbhash@0.1.1", "", {}, "sha512-kH5pKeIIBPQXAOni2AiY/Cu/NKdkFREdpH+TLdM0g6WA7RriCv0kPLgP731ady67MhTAqrVG/4mnEeibVuCJcg=="], + } +} diff --git a/assets/lib/package.json b/assets/lib/package.json new file mode 100644 index 0000000..806efa7 --- /dev/null +++ b/assets/lib/package.json @@ -0,0 +1,8 @@ +{ + "scripts": { + "build": "bun build build.js --outdir=dist --format=esm --minify" + }, + "dependencies": { + "thumbhash": "^0.1.1" + } +} diff --git a/assets/lib/shared.js b/assets/lib/shared.js new file mode 100644 index 0000000..940b608 --- /dev/null +++ b/assets/lib/shared.js @@ -0,0 +1,130 @@ +/** + * Base class for web components. + * Calls render() on connect and on any observed attribute change. + */ +export class BaseElement extends HTMLElement { + connectedCallback() { + this.render() + } + + attributeChangedCallback() { + this.render() + } + + render() { } + + /** + * Read an attribute with type coercion based on the fallback type. + * - number fallback → parseFloat + * - boolean fallback → true if attribute present and not "false" + * - otherwise → string or fallback + */ + attr(name, fallback = null) { + const val = this.getAttribute(name) + if (val === null) return fallback + if (typeof fallback === 'number') return parseFloat(val) + if (typeof fallback === 'boolean') return val !== 'false' + return val + } + + /** + * Dispatch a CustomEvent from this element. + */ + dispatch(type, detail = {}, options = {}) { + this.dispatchEvent(new CustomEvent(type, { bubbles: true, composed: true, detail, ...options })) + } +} + +/** + * Register a custom element and return the class. + * Lets you export default define('my-el', class extends BaseElement { ... }) + */ +export function define(tag, Cls) { + customElements.define(tag, Cls) + return Cls +} + +/** + * Tagged template for inline HTML strings. + */ +export function html(strings, ...values) { + return strings.reduce((acc, str, i) => acc + str + (values[i] ?? ''), '').trim() +} + +/** + * Tagged template for inline CSS strings. + */ +export function css(strings, ...values) { + return strings.reduce((acc, str, i) => acc + str + (values[i] ?? ''), '').trim() +} + +/** + * Create a DOM element with optional attributes and children. + * + * @param {string} tag + * @param {Record<string, string|boolean>} [attrs] + * @param {...(Node|string)} children + * @returns {HTMLElement} + * + * @example + * el('button', { type: 'button', class: 'btn' }, 'Click me') + */ +export function el(tag, attrs = {}, ...children) { + const node = document.createElement(tag) + for (const [k, v] of Object.entries(attrs)) { + if (v === false || v == null) continue + if (v === true) node.setAttribute(k, '') + else node.setAttribute(k, v) + } + node.append(...children) + return node +} + +/** + * Shorthand for querySelector. Scoped to `root` (default: document). + * @template {Element} T + * @param {string} selector + * @param {ParentNode} [root] + * @returns {T|null} + */ +export function qs(selector, root = document) { + return root.querySelector(selector) +} + +/** + * Shorthand for querySelectorAll. Returns a plain Array. + * @template {Element} T + * @param {string} selector + * @param {ParentNode} [root] + * @returns {T[]} + */ +export function qsa(selector, root = document) { + return Array.from(root.querySelectorAll(selector)) +} + +/** + * Add an event listener and return a cleanup function. + * @param {EventTarget} target + * @param {string} event + * @param {EventListener} handler + * @param {AddEventListenerOptions} [options] + * @returns {() => void} unlisten + */ +export function on(target, event, handler, options) { + target.addEventListener(event, handler, options) + return () => target.removeEventListener(event, handler, options) +} + +/** + * Wrap a plain object in a Proxy that calls onChange on any top-level property set. + * Shallow only — nested mutations do not trigger onChange. + */ +export function reactive(init, onChange) { + return new Proxy(init, { + set(target, key, value) { + target[key] = value + onChange() + return true + }, + }) +} |
