accounts
Version:
Tempo Accounts SDK
137 lines • 4.8 kB
JavaScript
import { useCallback, useEffect, useMemo, useRef, useState as react_useState } from 'react';
import { useStore } from 'zustand';
import * as IO from '../core/IntersectionObserver.js';
import * as TrustedHosts from '../core/TrustedHosts.js';
/** Monitors element visibility using IntersectionObserver v2. */
export function useEnsureVisibility(remote, options = {}) {
const { enabled = true } = options;
const origin = useState(remote, (s) => s.origin);
const trusted = useMemo(() => {
if (!origin)
return false;
try {
const hostname = new URL(origin).hostname.replace(/^www\./, '');
return TrustedHosts.match(remote.trustedHosts, hostname, window.location.hostname);
}
catch {
return false;
}
}, [origin, remote.trustedHosts]);
const active = enabled && !trusted;
const ref = useRef(null);
const [visible, setVisible] = react_useState(true);
useEffect(() => {
if (!active)
return;
if (!ref.current)
return;
if (!IO.supported()) {
setVisible(false);
return;
}
const observer = new IntersectionObserver((entries) => {
if (document.visibilityState === 'hidden')
return;
const entry = entries[0];
if (!entry)
return;
const isVisible = entry.isVisible || false;
setVisible(isVisible);
}, {
delay: 100,
threshold: [0.99],
trackVisibility: true,
});
observer.observe(ref.current);
return () => observer.disconnect();
}, [active]);
const invokePopup = useCallback(() => remote.messenger.send('switch-mode', { mode: 'popup' }), [remote]);
return { invokePopup, ref, visible };
}
export function useState(remote, selector) {
return useStore(remote.store, selector);
}
/** Applies theme overrides from URL search params and live messenger updates. */
export function useTheme(remote) {
const snapshot = useRef(undefined);
useEffect(() => {
if (typeof window === 'undefined')
return;
snapshot.current = captureTheme();
const params = new URLSearchParams(window.location.search);
restoreTheme(snapshot.current);
applyTheme({
accent: params.get('accent') ?? undefined,
radius: params.get('radius') ?? undefined,
scheme: params.get('scheme') ?? undefined,
});
return () => {
if (snapshot.current)
restoreTheme(snapshot.current);
snapshot.current = undefined;
};
}, []);
useEffect(() => {
if (!remote)
return;
return remote.messenger.on('theme', (payload) => {
if (snapshot.current)
restoreTheme(snapshot.current);
applyTheme(payload);
});
}, [remote]);
}
/** Applies theme values to the document root. */
function applyTheme(theme) {
const root = document.documentElement;
const { accent, radius, scheme } = theme;
if (accent) {
root.style.removeProperty('--theme-accent');
root.setAttribute('data-theme-accent', getAccentName(accent));
if (!isAccentPreset(accent))
root.style.setProperty('--theme-accent', accent);
}
if (scheme)
root.style.colorScheme = scheme;
if (radius)
root.setAttribute('data-theme-radius', radius);
}
function captureTheme() {
const root = document.documentElement;
return {
accentPreset: root.getAttribute('data-theme-accent'),
accentValue: root.style.getPropertyValue('--theme-accent'),
colorScheme: root.style.colorScheme,
radius: root.getAttribute('data-theme-radius'),
};
}
function restoreTheme(snapshot) {
const root = document.documentElement;
restoreAttribute(root, 'data-theme-accent', snapshot.accentPreset);
restoreAttribute(root, 'data-theme-radius', snapshot.radius);
if (snapshot.accentValue)
root.style.setProperty('--theme-accent', snapshot.accentValue);
else
root.style.removeProperty('--theme-accent');
root.style.colorScheme = snapshot.colorScheme;
}
function restoreAttribute(element, name, value) {
if (value === null)
element.removeAttribute(name);
else
element.setAttribute(name, value);
}
function getAccentName(accent) {
if (isAccentPreset(accent))
return accent;
return 'custom';
}
function isAccentPreset(accent) {
return (accent === 'neutral' ||
accent === 'blue' ||
accent === 'red' ||
accent === 'amber' ||
accent === 'green' ||
accent === 'purple');
}
//# sourceMappingURL=Remote.js.map