UNPKG

sprae

Version:

DOM microhydration

662 lines (642 loc) 24 kB
(function (g, f) {if ("object" == typeof exports && "object" == typeof module) {module.exports = f();} else if ("function" == typeof define && define.amd) {define("sprae", [], f);} else if ("object" == typeof exports) {exports["sprae"] = f();} else {g["sprae"] = f();}}(typeof self !== 'undefined' ? self : typeof globalThis !== 'undefined' ? globalThis : this, () => {var exports = {};var module = { exports }; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __hasOwnProp = Object.prototype.hasOwnProperty; var __esm = (fn, res) => function __init() { return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res; }; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // signal.js var current, signal, effect, computed, batch, untracked, use; var init_signal = __esm({ "signal.js"() { signal = (v, s, obs = /* @__PURE__ */ new Set()) => (s = { get value() { current?.deps.push(obs.add(current)); return v; }, set value(val) { if (val === v) return; v = val; for (let sub of obs) sub(); }, peek() { return v; } }, s.toJSON = s.then = s.toString = s.valueOf = () => s.value, s); effect = (fn, teardown, fx, deps) => (fx = (prev) => { teardown?.call?.(); prev = current, current = fx; try { teardown = fn(); } finally { current = prev; } }, deps = fx.deps = [], fx(), (dep) => { teardown?.call?.(); while (dep = deps.pop()) dep.delete(fx); }); computed = (fn, s = signal(), c, e) => (c = { get value() { e || (e = effect(() => s.value = fn())); return s.value; }, peek: s.peek }, c.toJSON = c.then = c.toString = c.valueOf = () => c.value, c); batch = (fn) => fn(); untracked = batch; use = (s) => (signal = s.signal, effect = s.effect, computed = s.computed, batch = s.batch || batch, untracked = s.untracked || untracked); } }); // store.js var store_exports = {}; __export(store_exports, { _change: () => _change, _signals: () => _signals, _stash: () => _stash, default: () => store_default, list: () => list, setter: () => setter, store: () => store }); var _signals, _change, _stash, store, list, mut, set, setter, store_default; var init_store = __esm({ "store.js"() { init_signal(); init_core(); _signals = Symbol("signals"); _change = Symbol("change"); _stash = "__"; store = (values, parent) => { if (!values) return values; if (values[_signals] || values[Symbol.toStringTag]) return values; if (values.constructor !== Object) return Array.isArray(values) ? list(values) : values; let signals = Object.create(parent?.[_signals] || {}), _len = signal(Object.keys(values).length), stash; let state = new Proxy(signals, { get: (_, k) => k === _change ? _len : k === _signals ? signals : k === _stash ? stash : k in signals ? signals[k]?.valueOf() : globalThis[k], set: (_, k, v, s) => k === _stash ? (stash = v, 1) : (s = k in signals, set(signals, k, v), s || ++_len.value), // bump length for new signal deleteProperty: (_, k) => (signals[k] && (signals[k][Symbol.dispose]?.(), delete signals[k], _len.value--), 1), // subscribe to length when object is spread ownKeys: () => (_len.value, Reflect.ownKeys(signals)), has: (_) => true // sandbox prevents writing to global }), descs = Object.getOwnPropertyDescriptors(values); for (let k in values) { if (descs[k]?.get) (signals[k] = computed(descs[k].get.bind(state)))._set = descs[k].set?.bind(state); else signals[k] = null, set(signals, k, values[k]); } return state; }; list = (values) => { let lastProp, _len = signal(values.length), signals = Array(values.length).fill(), state = new Proxy(signals, { get(_, k) { if (typeof k === "symbol") return k === _change ? _len : k === _signals ? signals : signals[k]; if (k === "length") return mut.includes(lastProp) ? _len.peek() : _len.value; lastProp = k; return (signals[k] ?? (signals[k] = signal(store(values[k])))).valueOf(); }, set(_, k, v) { if (k === "length") { for (let i = v; i < signals.length; i++) delete state[i]; _len.value = signals.length = v; } else { set(signals, k, v); if (k >= _len.peek()) _len.value = signals.length = +k + 1; } return 1; }, deleteProperty: (_, k) => (signals[k]?.[Symbol.dispose]?.(), delete signals[k], 1) }); return state; }; mut = ["push", "pop", "shift", "unshift", "splice"]; set = (signals, k, v) => { let s = signals[k], cur; if (k[0] === "_") signals[k] = v; else if (!s) signals[k] = s = v?.peek ? v : signal(store(v)); else if (v === (cur = s.peek())) ; else if (s._set) s._set(v); else if (Array.isArray(v) && Array.isArray(cur)) { if (cur[_change]) batch(() => { for (let i = 0; i < v.length; i++) cur[i] = v[i]; cur.length = v.length; }); else s.value = v; } else s.value = store(v); }; setter = (expr, set2 = parse(`${expr}=${_stash}`)) => (state, value) => (state[_stash] = value, // save value to stash set2(state)); store_default = store; } }); // core.js var _dispose, _state, _on, _off, directive, dir, sprae, parse, memo, err, compile, prefix, frag, core_default; var init_core = __esm({ "core.js"() { init_signal(); init_store(); _dispose = Symbol.dispose || (Symbol.dispose = Symbol("dispose")); _state = Symbol("state"); _on = Symbol("on"); _off = Symbol("off"); directive = {}; dir = (name, create, p = parse) => directive[name] = (el, expr, state, name2, update, evaluate) => (update = create(el, state, expr, name2), evaluate = p(expr, ":" + name2), () => update(evaluate(state))); sprae = (el = document.body, values) => { if (el[_state]) return Object.assign(el[_state], values); let state = store(values || {}), offs = [], fx = []; let init = (el2, attrs = el2.attributes) => { if (attrs) for (let i = 0; i < attrs.length; ) { let { name, value } = attrs[i], update, dir2; if (name.startsWith(prefix)) { el2.removeAttribute(name); for (dir2 of name.slice(prefix.length).split(":")) { update = (directive[dir2] || directive.default)(el2, value, state, dir2); fx.push(update); offs.push(effect(update)); if (el2[_state] === null) return; } } else i++; } for (let child of el2.childNodes) child.nodeType == 1 && init(child); }; init(el); if (!(_state in el)) { el[_state] = state; el[_off] = () => (offs.map((off) => off()), offs = []); el[_on] = () => offs = fx.map((f) => effect(f)); el[_dispose] = () => (el[_off](), el[_off] = el[_on] = el[_dispose] = el[_state] = null); } return state; }; sprae.use = (s) => (s.signal && use(s), s.compile && (compile = s.compile), s.prefix && (prefix = s.prefix)); parse = (expr, dir2, fn) => { if (fn = memo[expr = expr.trim()]) return fn; try { fn = compile(expr); } catch (e) { err(e, dir2, expr); } return memo[expr] = (s) => { try { return fn(s); } catch (e) { err(e, dir2, expr); } }; }; memo = {}; err = (e, dir2 = "", expr = "") => { throw Object.assign(e, { message: `\u2234 ${e.message} ${dir2}${expr ? `="${expr}" ` : ""}`, expr }); }; prefix = ":"; frag = (tpl) => { if (!tpl.nodeType) return tpl; let content = tpl.content.cloneNode(true), attributes = [...tpl.attributes], ref = document.createTextNode(""), childNodes = (content.append(ref), [...content.childNodes]); return { // get parentNode() { return childNodes[0].parentNode }, childNodes, content, remove: () => content.append(...childNodes), replaceWith(el) { if (el === ref) return; ref.before(el); content.append(...childNodes); }, attributes, removeAttribute(name) { attributes.splice(attributes.findIndex((a) => a.name === name), 1); } // setAttributeNode() { } }; }; core_default = sprae; } }); // directive/if.js var _prevIf; var init_if = __esm({ "directive/if.js"() { init_core(); _prevIf = Symbol("if"); dir("if", (el, state) => { let holder = document.createTextNode(""); let nextEl = el.nextElementSibling, curEl, ifEl, elseEl; el.replaceWith(holder); ifEl = el.content ? frag(el) : el; ifEl[_state] = null; if (nextEl?.hasAttribute(":else")) { nextEl.removeAttribute(":else"); if (!nextEl.hasAttribute(":if")) nextEl.remove(), elseEl = nextEl.content ? frag(nextEl) : nextEl, elseEl[_state] = null; } else nextEl = null; return (value, newEl = el[_prevIf] ? null : value ? ifEl : elseEl) => { if (nextEl) nextEl[_prevIf] = el[_prevIf] || newEl == ifEl; if (curEl != newEl) { if (curEl) curEl.remove(), curEl[_off]?.(); if (curEl = newEl) { holder.before(curEl.content || curEl); curEl[_state] === null ? (delete curEl[_state], core_default(curEl, state)) : curEl[_on](); } } }; }); } }); // directive/each.js var init_each = __esm({ "directive/each.js"() { init_core(); init_store(); init_signal(); dir( "each", (tpl, state, expr) => { let [itemVar, idxVar = "$"] = expr.split(/\bin\b/)[0].trim().split(/\s*,\s*/); let holder = document.createTextNode(""); let cur, keys2, items, prevl = 0; let update = () => { var _a, _b; let i = 0, newItems = items, newl = newItems.length; if (cur && !cur[_change]) { for (let s of cur[_signals] || []) s[Symbol.dispose](); cur = null, prevl = 0; } if (newl < prevl) cur.length = newl; else { if (!cur) cur = newItems; else while (i < prevl) cur[i] = newItems[i++]; for (; i < newl; i++) { cur[i] = newItems[i]; let idx = i, scope = store_default({ [itemVar]: cur[_signals]?.[idx] || cur[idx], [idxVar]: keys2 ? keys2[idx] : idx }, state), el = tpl.content ? frag(tpl) : tpl.cloneNode(true); holder.before(el.content || el); core_default(el, scope); ((_b = cur[_a = _signals] || (cur[_a] = []))[i] || (_b[i] = {}))[Symbol.dispose] = () => { el[Symbol.dispose]?.(), el.remove(); }; } } prevl = newl; }; tpl.replaceWith(holder); tpl[_state] = null; return (value) => { keys2 = null; if (typeof value === "number") items = Array.from({ length: value }, (_, i) => i + 1); else if (value?.constructor === Object) keys2 = Object.keys(value), items = Object.values(value); else items = value || []; let planned = 0; return effect(() => { items[_change]?.value; if (!planned++) update(), queueMicrotask(() => (planned > 1 && update(), planned = 0)); }); }; }, // redefine evaluator to take second part of expression (expr) => parse(expr.split(/\bin\b/)[1]) ); } }); // directive/ref.js var init_ref = __esm({ "directive/ref.js"() { init_core(); init_signal(); init_store(); dir("ref", (el, state, expr) => typeof parse(expr)(state) == "function" ? (v) => v.call(null, el) : (setter(expr)(state, el), (_) => _)); } }); // directive/with.js var init_with = __esm({ "directive/with.js"() { init_core(); init_signal(); init_store(); dir("with", (el, rootState, state) => (state = null, (values) => !state ? ( // NOTE: we force untracked because internal directives can eval outside of effects (like ref etc) that would cause unwanted subscribe // FIXME: since this can be async effect, we should create & sprae it in advance. untracked(() => core_default(el, state = store_default(values, rootState))) ) : core_default(el, values))); } }); // directive/text.js var init_text = __esm({ "directive/text.js"() { init_core(); dir("text", (el) => ( // <template :text="a"/> or previously initialized template (el.content && el.replaceWith(el = frag(el).childNodes[0]), (value) => el.textContent = value == null ? "" : value) )); } }); // directive/class.js var init_class = __esm({ "directive/class.js"() { init_core(); dir( "class", (el, cur) => (cur = /* @__PURE__ */ new Set(), (v) => { let clsx = /* @__PURE__ */ new Set(); if (v) { if (typeof v === "string") v.split(" ").map((cls) => clsx.add(cls)); else if (Array.isArray(v)) v.map((v2) => v2 && clsx.add(v2)); else Object.entries(v).map(([k, v2]) => v2 && clsx.add(k)); } for (let cls of cur) if (clsx.has(cls)) clsx.delete(cls); else el.classList.remove(cls); for (let cls of cur = clsx) el.classList.add(cls); }) ); } }); // directive/style.js var init_style = __esm({ "directive/style.js"() { init_core(); dir( "style", (el, initStyle) => (initStyle = el.getAttribute("style"), (v) => { if (typeof v === "string") el.setAttribute("style", initStyle + (initStyle.endsWith(";") ? "" : "; ") + v); else { if (initStyle) el.setAttribute("style", initStyle); for (let k in v) k[0] == "-" ? el.style.setProperty(k, v[k]) : el.style[k] = v[k]; } }) ); } }); // directive/default.js var mods, keys, throttle, debounce, attr, dashcase; var init_default = __esm({ "directive/default.js"() { init_core(); dir("default", (target, state, expr, name) => { if (!name.startsWith("on")) return name ? (value) => attr(target, name, value) : (value) => { for (let key in value) attr(target, dashcase(key), value[key]); }; let ctxs = name.split("..").map((e) => { let ctx = { evt: "", target, test: () => true }; ctx.evt = (e.startsWith("on") ? e.slice(2) : e).replace( /\.(\w+)?-?([-\w]+)?/g, (_, mod, param = "") => (ctx.test = mods[mod]?.(ctx, ...param.split("-")) || ctx.test, "") ); return ctx; }); let addListener = (fn, { evt, target: target2, test, defer, stop, prevent, immediate, ...opts }, cb) => { if (defer) fn = defer(fn); cb = (e) => { try { test(e) && (stop && (immediate ? e.stopImmediatePropagation() : e.stopPropagation()), prevent && e.preventDefault(), fn?.call(state, e)); } catch (error) { err(error, `:on${evt}`, fn); } }; target2.addEventListener(evt, cb, opts); return () => target2.removeEventListener(evt, cb, opts); }; if (ctxs.length == 1) return (v) => addListener(v, ctxs[0]); let startFn, nextFn, off, idx = 0; let nextListener = (fn) => { off = addListener((e) => (off(), nextFn = fn?.(e), (idx = ++idx % ctxs.length) ? nextListener(nextFn) : startFn && nextListener(startFn)), ctxs[idx]); }; return (value) => (startFn = value, !off && nextListener(startFn), () => startFn = null); }); mods = { // actions prevent(ctx) { ctx.prevent = true; }, stop(ctx) { ctx.stop = true; }, immediate(ctx) { ctx.immediate = true; }, // options once(ctx) { ctx.once = true; }, passive(ctx) { ctx.passive = true; }, capture(ctx) { ctx.capture = true; }, // target window(ctx) { ctx.target = window; }, document(ctx) { ctx.target = document; }, parent(ctx) { ctx.target = ctx.target.parentNode; }, throttle(ctx, limit = 108) { ctx.defer = (fn) => throttle(fn, limit); }, debounce(ctx, wait = 108) { ctx.defer = (fn) => debounce(fn, wait); }, // test outside: (ctx) => (e) => { let target = ctx.target; if (target.contains(e.target)) return false; if (e.target.isConnected === false) return false; if (target.offsetWidth < 1 && target.offsetHeight < 1) return false; return true; }, self: (ctx) => (e) => e.target === ctx.target, // keyboard ctrl: (_, ...param) => (e) => keys.ctrl(e) && param.every((p) => keys[p] ? keys[p](e) : e.key === p), shift: (_, ...param) => (e) => keys.shift(e) && param.every((p) => keys[p] ? keys[p](e) : e.key === p), alt: (_, ...param) => (e) => keys.alt(e) && param.every((p) => keys[p] ? keys[p](e) : e.key === p), meta: (_, ...param) => (e) => keys.meta(e) && param.every((p) => keys[p] ? keys[p](e) : e.key === p), // NOTE: we don't expose up/left/right/down as too verbose: can and better be handled/differentiated at once arrow: () => keys.arrow, enter: () => keys.enter, esc: () => keys.esc, tab: () => keys.tab, space: () => keys.space, delete: () => keys.delete, digit: () => keys.digit, letter: () => keys.letter, char: () => keys.char }; keys = { ctrl: (e) => e.ctrlKey || e.key === "Control" || e.key === "Ctrl", shift: (e) => e.shiftKey || e.key === "Shift", alt: (e) => e.altKey || e.key === "Alt", meta: (e) => e.metaKey || e.key === "Meta" || e.key === "Command", arrow: (e) => e.key.startsWith("Arrow"), enter: (e) => e.key === "Enter", esc: (e) => e.key.startsWith("Esc"), tab: (e) => e.key === "Tab", space: (e) => e.key === "\xA0" || e.key === "Space" || e.key === " ", delete: (e) => e.key === "Delete" || e.key === "Backspace", digit: (e) => /^\d$/.test(e.key), letter: (e) => /^\p{L}$/gu.test(e.key), char: (e) => /^\S$/.test(e.key) }; throttle = (fn, limit) => { let pause, planned, block = (e) => { pause = true; setTimeout(() => { pause = false; if (planned) return planned = false, block(e), fn(e); }, limit); }; return (e) => { if (pause) return planned = true; block(e); return fn(e); }; }; debounce = (fn, wait) => { let timeout; return (e) => { clearTimeout(timeout); timeout = setTimeout(() => { timeout = null; fn(e); }, wait); }; }; attr = (el, name, v) => { if (v == null || v === false) el.removeAttribute(name); else el.setAttribute(name, v === true ? "" : typeof v === "number" || typeof v === "string" ? v : ""); }; dashcase = (str) => { return str.replace(/[A-Z\u00C0-\u00D6\u00D8-\u00DE]/g, (match, i) => (i ? "-" : "") + match.toLowerCase()); }; } }); // directive/value.js var init_value = __esm({ "directive/value.js"() { init_core(); init_core(); init_signal(); init_store(); init_default(); dir("value", (el, state, expr) => { const update = el.type === "text" || el.type === "" ? (value) => el.setAttribute("value", el.value = value == null ? "" : value) : el.tagName === "TEXTAREA" || el.type === "text" || el.type === "" ? (value, from, to) => ( // we retain selection in input (from = el.selectionStart, to = el.selectionEnd, el.setAttribute("value", el.value = value == null ? "" : value), from && el.setSelectionRange(from, to)) ) : el.type === "checkbox" ? (value) => (el.checked = value, attr(el, "checked", value)) : el.type === "select-one" ? (value) => { for (let o of el.options) o.value == value ? o.setAttribute("selected", "") : o.removeAttribute("selected"); el.value = value; } : el.type === "select-multiple" ? (value) => { for (let o of el.options) o.removeAttribute("selected"); for (let v of value) el.querySelector(`[value="${v}"]`).setAttribute("selected", ""); } : (value) => el.value = value; try { const set2 = setter(expr); const handleChange = el.type === "checkbox" ? () => set2(state, el.checked) : el.type === "select-multiple" ? () => set2(state, [...el.selectedOptions].map((o) => o.value)) : () => set2(state, el.selectedIndex < 0 ? null : el.value); el.oninput = el.onchange = handleChange; if (el.type?.startsWith("select")) { new MutationObserver(handleChange).observe(el, { childList: true, subtree: true, attributes: true }); core_default(el, state); } parse(expr)(state) ?? handleChange(); } catch { } return update; }); } }); // directive/fx.js var init_fx = __esm({ "directive/fx.js"() { init_core(); dir("fx", (_) => (_2) => _2); } }); // directive/aria.js var init_aria = __esm({ "directive/aria.js"() { init_core(); init_default(); dir("aria", (el) => (value) => { for (let key in value) attr(el, "aria-" + dashcase(key), value[key] == null ? null : value[key] + ""); }); } }); // directive/data.js var init_data = __esm({ "directive/data.js"() { init_core(); dir("data", (el) => (value) => { for (let key in value) el.dataset[key] = value[key]; }); } }); // sprae.js var sprae_exports = {}; __export(sprae_exports, { default: () => sprae_default }); var sprae_default; var init_sprae = __esm({ "sprae.js"() { init_core(); init_if(); init_each(); init_ref(); init_with(); init_text(); init_class(); init_style(); init_value(); init_fx(); init_default(); init_aria(); init_data(); core_default.use({ compile: (expr) => core_default.constructor(`with (arguments[0]) { return ${expr} };`) }); sprae_default = core_default; } }); // <stdin> var sprae2 = (init_sprae(), __toCommonJS(sprae_exports)).default; sprae2.store = (init_store(), __toCommonJS(store_exports)).default; sprae2.use({ prefix: document.currentScript.getAttribute("prefix") }); document.readyState === "loading" ? document.addEventListener("DOMContentLoaded", () => sprae2()) : sprae2(); module.exports = sprae2; ;if (typeof module.exports == "object" && typeof exports == "object") { var __cp = (to, from, except, desc) => { if ((from && typeof from === "object") || typeof from === "function") { for (let key of Object.getOwnPropertyNames(from)) { if (!Object.prototype.hasOwnProperty.call(to, key) && key !== except) Object.defineProperty(to, key, { get: () => from[key], enumerable: !(desc = Object.getOwnPropertyDescriptor(from, key)) || desc.enumerable, }); } } return to; }; module.exports = __cp(module.exports, exports); } return module.exports; }))