Current section
Files
Jump to
Current section
Files
priv/static/off-topic.mjs
// build/dev/javascript/prelude.mjs
var List$NonEmpty$first = (value) => value.head;
var List$NonEmpty$rest = (value) => value.tail;
// build/dev/javascript/gleam_stdlib/dict.mjs
var bits = 5;
var mask = (1 << bits) - 1;
var noElementMarker = Symbol();
var generationKey = Symbol();
// build/dev/javascript/gleam_stdlib/gleam_stdlib.mjs
var unicode_whitespaces = [
" ",
// Space
" ",
// Horizontal tab
"\n",
// Line feed
"\v",
// Vertical tab
"\f",
// Form feed
"\r",
// Carriage return
"\x85",
// Next line
"\u2028",
// Line separator
"\u2029"
// Paragraph separator
].join("");
var trim_start_regex = /* @__PURE__ */ new RegExp(
`^[${unicode_whitespaces}]*`
);
var trim_end_regex = /* @__PURE__ */ new RegExp(`[${unicode_whitespaces}]*$`);
var MIN_I32 = -(2 ** 31);
var MAX_I32 = 2 ** 31 - 1;
var U32 = 2 ** 32;
var MAX_SAFE = Number.MAX_SAFE_INTEGER;
var MIN_SAFE = Number.MIN_SAFE_INTEGER;
// build/dev/javascript/off_topic/off_topic/internal/utils_ffi.mjs
function iterate(list2, callback) {
if (Array.isArray(list2)) {
for (let i = 0; i < list2.length; i++) {
callback(list2[i]);
}
} else if (list2) {
for (list2; List$NonEmpty$rest(list2); list2 = List$NonEmpty$rest(list2)) {
callback(List$NonEmpty$first(list2));
}
}
}
function resolveElement(target, root) {
if (target instanceof EventTarget) return target;
if (target === "root" || target === ":scope") return root;
if (target === "host") return root?.host;
if (target === "document") return document;
if (target === "window") return window;
if (!root) root = document;
return root instanceof Element && root.matches(target) ? root : root.querySelector(target);
}
// build/dev/javascript/off_topic/off_topic/internal/subscriptions_ffi.mjs
function on(target, eventName, dispatch, root) {
const element = resolveElement(target, root);
if (!element) throw new Error("invalid target");
element.addEventListener(eventName, dispatch, { passive: true });
return () => element.removeEventListener(eventName, dispatch);
}
function on_page_state(dispatch) {
const getState = () => {
if (document.visibilityState === "hidden") return "hidden";
if (document.hasFocus()) return "active";
return "passive";
};
const handler = (newState) => {
if (newState !== lastState) {
dispatch(newState);
lastState = newState;
}
};
let lastState = getState();
if (lastState !== "active") {
dispatch(lastState);
}
const abort = new AbortController();
const opts = { capture: true, signal: abort.signal };
["pageshow", "focus", "blur", "visibilitychange", "resume"].forEach((type) => {
window.addEventListener(type, () => handler(getState()), opts);
});
window.addEventListener(
"freeze",
() => {
handler("frozen");
},
opts
);
window.addEventListener(
"pagehide",
(event) => {
handler(event.persisted ? "frozen" : "terminated");
},
opts
);
return () => abort.abort();
}
function here(dispatch) {
const getOffset = () => -(/* @__PURE__ */ new Date()).getTimezoneOffset();
let lastOffset = getOffset();
dispatch(lastOffset);
const handle = setInterval(() => {
const offset = getOffset();
if (offset !== lastOffset) {
lastOffset = offset;
dispatch(offset);
}
}, 6e4);
return () => clearInterval(handle);
}
var online = enter_exit(
window,
"online",
"offline",
() => navigator.onLine
);
var window_hover = enter_exit(
document.documentElement,
"pointerenter",
"pointerleave",
() => document.documentElement.matches(":hover")
);
function prevent_unload(callback) {
const handler = (event) => {
event.preventDefault();
callback();
};
window.addEventListener("beforeunload", handler);
return () => window.removeEventListener("beforeunload", handler);
}
function on_idle(timeout_ms, dispatch) {
let idle = false;
let timer2 = null;
const reset = () => {
if (idle) {
idle = false;
dispatch(false);
}
clearTimeout(timer2);
timer2 = setTimeout(() => {
idle = true;
dispatch(true);
}, timeout_ms);
};
const onVisibilityChange = () => {
if (!document.hidden) reset();
};
const windowEvents = [
"pointermove",
"pointerdown",
"keydown",
"scroll",
"wheel",
"resize"
];
for (const event of windowEvents) {
window.addEventListener(event, reset, { passive: true });
}
document.addEventListener("visibilitychange", onVisibilityChange);
reset();
return () => {
clearTimeout(timer2);
for (const event of windowEvents) {
window.removeEventListener(event, reset);
}
document.removeEventListener("visibilitychange", onVisibilityChange);
};
}
function window_size(dispatch) {
return trigger(
null,
window,
"resize",
() => dispatch(window.innerWidth, window.innerHeight)
);
}
function window_scroll(dispatch) {
return trigger(
null,
window,
"scroll",
() => dispatch(window.scrollX, window.scrollY)
);
}
function screen_orientation(dispatch) {
return trigger(
null,
screen.orientation,
"change",
() => dispatch(screen.orientation.type)
);
}
function language(dispatch) {
return trigger(
null,
window,
"languagechange",
() => dispatch(navigator.language)
);
}
function media_query(fallback, pairs, dispatch) {
const matchers = [];
iterate(pairs, ([query, value]) => {
matchers.push([window.matchMedia(query), value]);
});
const current = () => matchers.find(([mq]) => mq.matches)?.[1] ?? fallback;
const handler = () => dispatch(current());
handler();
for (const [mq] of matchers) mq.addEventListener("change", handler);
return () => {
for (const [mq] of matchers) mq.removeEventListener("change", handler);
};
}
var local_storage = storage(localStorage);
var session_storage = storage(sessionStorage);
function storage(storage2) {
return (key, dispatch) => {
const value = storage2.getItem(key);
dispatch(value !== null, value ?? "");
const handler = (event) => {
if (event.storageArea === storage2 && event.key === key) {
const v = event.newValue;
dispatch(v !== null, v ?? "");
}
};
return on(window, "storage", handler);
};
}
function element_size(selector, dispatch, root) {
const el = resolveElement(selector, root);
if (!el) return noop;
const { width, height } = el.getBoundingClientRect();
dispatch(Math.round(width), Math.round(height));
const observer = new ResizeObserver((entries) => {
const { width: width2, height: height2 } = entries[0].contentRect;
dispatch(Math.round(width2), Math.round(height2));
});
observer.observe(el);
return () => observer.disconnect();
}
function element_visible(selector, dispatch, root) {
const el = resolveElement(selector, root);
if (!el) return noop;
const rect = el.getBoundingClientRect();
dispatch(
rect.bottom > 0 && rect.right > 0 && rect.top < window.innerHeight && rect.left < window.innerWidth
);
const observer = new IntersectionObserver((entries) => {
dispatch(entries[0].isIntersecting);
});
observer.observe(el);
return () => observer.disconnect();
}
function element_scroll(selector, dispatch, root) {
const el = resolveElement(selector, root);
if (!el) return noop;
const handler = () => dispatch(Math.round(el.scrollLeft), Math.round(el.scrollTop));
handler();
el.addEventListener("scroll", handler, { passive: true });
return () => el.removeEventListener("scroll", handler);
}
function title(value) {
document.title = value;
return noop;
}
function meta(name, content) {
let el = document.head.querySelector(`meta[name="${CSS.escape(name)}"]`);
if (!el) {
el = document.createElement("meta");
el.setAttribute("name", name);
document.head.appendChild(el);
}
el.setAttribute("content", content);
return noop;
}
function body_class(className) {
const classes = className.split(" ");
document.body.classList.add(...classes);
return () => document.body.classList.remove(...classes);
}
function aria(name, value, _handler, root) {
const host = root instanceof ShadowRoot ? root.host : root;
const internals = host?.internals;
if (internals) {
const prop = name === "role" ? "role" : "aria" + name.slice(5).replace(/-([a-z])/g, (_, c) => c.toUpperCase()).replace(/^./, (c) => c.toUpperCase());
internals[prop] = value;
}
return noop;
}
function pseudo_state(name, checked, _handler, root) {
const host = root instanceof ShadowRoot ? root.host : root;
const internals = host?.internals;
if (internals) {
if (checked) {
internals.states.add(name);
} else {
internals.states.delete(name);
}
}
return noop;
}
function form_value(exists, value, _handler, root) {
const host = root instanceof ShadowRoot ? root.host : root;
host?.internals?.setFormValue(exists ? value : void 0);
return noop;
}
function trigger(root, selector, event, trigger2) {
const el = resolveElement(selector, root);
if (!el) noop;
trigger2();
return on(el, event, trigger2);
}
function enter_exit(target, enterEvent, exitEvent, getValue) {
return (dispatch) => {
dispatch(getValue());
const enter = () => dispatch(true);
const leave = () => dispatch(false);
target.addEventListener(enterEvent, enter);
target.addEventListener(exitEvent, leave);
return () => {
target.removeEventListener(enterEvent, enter);
target.removeEventListener(exitEvent, leave);
};
};
}
function noop() {
}
// build/dev/javascript/off_topic/off_topic/internal/commands_ffi.mjs
function dispatchStorageEvent(storage2, key, oldValue, newValue) {
window.dispatchEvent(new StorageEvent("storage", {
key,
oldValue,
newValue,
storageArea: storage2,
url: window.location.href
}));
}
function set_local_storage(key, value, emit) {
const oldValue = emit ? localStorage.getItem(key) : null;
localStorage.setItem(key, value);
if (emit) dispatchStorageEvent(localStorage, key, oldValue, value);
}
function set_session_storage(key, value, emit) {
const oldValue = emit ? sessionStorage.getItem(key) : null;
sessionStorage.setItem(key, value);
if (emit) dispatchStorageEvent(sessionStorage, key, oldValue, value);
}
function scroll_to(selector, x, y, behavior, root) {
resolveElement(selector, root)?.scrollTo({ left: x, top: y, behavior });
}
function scroll_into_view(selector, block, inline, behavior, root) {
resolveElement(selector, root)?.scrollIntoView({ block, inline, behavior });
}
function focus(selector, root) {
resolveElement(selector, root)?.focus();
}
function blur() {
document.activeElement?.blur();
}
// build/dev/javascript/off_topic/off_topic/internal/component.mjs
var OffTopicClientRuntime = class extends HTMLElement {
#root = null;
#subscriptions = /* @__PURE__ */ new Map();
connectedCallback() {
this.#root = findRoot(this);
if (this.#root) {
this.#root.addEventListener("off-topic:subscribe", this.#onSubscribe);
this.#root.addEventListener("off-topic:unsubscribe", this.#onUnsubscribe);
this.#root.addEventListener("off-topic:command", this.#onCommand);
}
this.dispatchEvent(new CustomEvent("off-topic:mount"));
}
connectedMoveCallback() {
}
disconnectedCallback() {
if (this.#root) {
this.#root.removeEventListener("off-topic:subscribe", this.#onSubscribe);
this.#root.removeEventListener(
"off-topic:unsubscribe",
this.#onUnsubscribe
);
this.#root = null;
}
queueMicrotask(() => {
if (this.isConnected) return;
for (const subscription of this.#subscriptions.values()) {
clearTimeout(subscription.delayTimeout);
subscription.cleanup?.();
}
this.#subscriptions.clear();
});
}
#onSubscribe = (event) => {
const {
detail: { tick, path, name, params, include, throttle = 0, delay = 0 }
} = event;
const key = `${tick}:${path.join(":")}`;
if (this.#subscriptions.has(key)) return;
const start = window.OffTopic.subscriptions[name];
if (!start) {
console.warn(
`off-topic: subscription '${name}' requested, but not registered`
);
return;
}
const state = {
tick,
path,
params,
cleanup: void 0,
lastFired: 0,
lastThrottledData: void 0,
delayTimeout: void 0
};
const dispatch = (data2) => {
const message = Array.isArray(data2) ? data2.map((arg) => pickPaths(arg, include)) : pickPaths(data2, include);
const ev = new CustomEvent("off-topic:dispatch", {
detail: { tick, path, message }
});
this.dispatchEvent(ev);
};
const handler = (...args) => {
const data2 = args.length === 1 ? args[0] : args;
if (throttle > 0) {
const now = Date.now();
if (now >= state.lastFired + throttle) {
state.lastFired = now;
state.lastThrottledData = data2;
dispatch(data2);
}
}
if (delay > 0) {
clearTimeout(state.delayTimeout);
state.delayTimeout = setTimeout(() => {
if (data2 === state.lastThrottledData) return;
dispatch(data2);
}, delay);
}
if (!throttle && !delay) {
dispatch(data2);
}
};
state.cleanup = start(...params, handler, this.#root);
this.#subscriptions.set(key, state);
};
#onUnsubscribe = (event) => {
const {
detail: { tick, path }
} = event;
const key = `${tick}:${path.join(":")}`;
const subscription = this.#subscriptions.get(key);
if (!subscription) return;
this.#subscriptions.delete(key);
clearTimeout(subscription.delayTimeout);
subscription.cleanup?.();
};
#onCommand = (event) => {
const {
detail: { name, params }
} = event;
window.OffTopic.commands[name]?.(...params, this.#root);
};
};
function findRoot(node) {
while (node != null) {
if (node instanceof ShadowRoot && node.host.tagName.toLowerCase() === "lustre-server-component") {
return node.host;
}
node = node.parentNode;
}
console.error(
"<ot-client-runtime> is not mounted inside a Lustre server component and will not work!\nIf you are using lustre.start, you can use off_topic's effects directly and can remove this component."
);
return null;
}
function pickPaths(event, include) {
if (typeof event !== "object") {
return event;
}
const data2 = {};
for (const property of include) {
const path = property.split(".");
for (let i = 0, input = event, output = data2; i < path.length; i++) {
if (i === path.length - 1) {
output[path[i]] = input[path[i]];
} else {
output = output[path[i]] ??= {};
input = input[path[i]];
}
}
}
return data2;
}
window.OffTopic ??= {};
window.OffTopic.subscriptions ??= {};
window.OffTopic.commands ??= {};
customElements.define("ot-client-runtime", OffTopicClientRuntime);
Object.assign(window.OffTopic.subscriptions, {
"off-topic/event": on,
"off-topic/page-state": on_page_state,
"off-topic/prevent-unload": prevent_unload,
"off-topic/media-query": media_query,
"off-topic/window-hover": window_hover,
"off-topic/here": here,
"off-topic/window-size": window_size,
"off-topic/window-scroll": window_scroll,
"off-topic/screen-orientation": screen_orientation,
"off-topic/language": language,
"off-topic/local-storage": local_storage,
"off-topic/session-storage": session_storage,
"off-topic/title": title,
"off-topic/meta": meta,
"off-topic/body-class": body_class,
"off-topic/aria": aria,
"off-topic/pseudo-state": pseudo_state,
"off-topic/form-value": form_value,
"off-topic/element-size": element_size,
"off-topic/element-scroll": element_scroll,
"off-topic/element-visible": element_visible,
"off-topic/on-idle": on_idle
});
Object.assign(window.OffTopic.commands, {
"off-topic/set-local-storage": set_local_storage,
"off-topic/set-session-storage": set_session_storage,
"off-topic/scroll-to": scroll_to,
"off-topic/scroll-into-view": scroll_into_view,
"off-topic/focus": focus,
"off-topic/blur": blur
});