/** * The React seams the components share: two subscriptions this plugin needs * beyond the ones the slot owner already provides, plus the seat rule for * translation. * * @module @dsh-plugin/session-notify/client/ui/hooks */ import * as React from 'react' import { windowIsAway } from '../platform.js' /** Subscribe to this plugin's own store from inside a component. */ export function useOwnStore(source) { return React.useSyncExternalStore(source.subscribe, source.getSnapshot, source.getSnapshot) } /** * Subscribe to every browser signal that says whether the app is visible: * the window's focus events and the document's visibility change. The value * itself is a boolean, so the observer re-renders once per transition. */ export function useWindowState() { const subscribe = React.useCallback((listener) => { try { window.addEventListener('focus', listener) window.addEventListener('blur', listener) document.addEventListener('visibilitychange', listener) } catch { return () => {} } return () => { window.removeEventListener('focus', listener) window.removeEventListener('blur', listener) document.removeEventListener('visibilitychange', listener) } }, []) return React.useSyncExternalStore(subscribe, () => !windowIsAway(), () => true) } /** * Every component translates through this: the slot's own seat when the owner * supplies one, else this package's own translator. * @param props - the slot entry's props, which carry the owner's `t` when it has one. * @param fallback - the plugin's own translator, used when the seat has none. * @returns the translate function this render should use. */ export function seatTranslator(props, fallback) { return typeof props.t === 'function' ? props.t : fallback }