import {useBindable} from 'app/common/gutil'; import {BindableValue, Computed, dom, IDisposableOwner, Observable, UseCB} from 'grainjs'; /** * Version of makeTestId that can be appended conditionally. */ export function makeTestId(prefix: string) { return (id: BindableValue<string>, obs?: BindableValue<boolean>) => { return dom.cls(use => { if (obs !== undefined && !useBindable(use, obs)) { return ''; } return `${useBindable(use, prefix)}${useBindable(use, id)}`; }); }; } export function autoFocus() { return (el: HTMLElement) => void setTimeout(() => el.focus(), 10); } export function autoSelect() { return (el: HTMLElement) => void setTimeout(() => (el as any).select?.(), 10); } /** * Async computed version of Computed. */ export const AsyncComputed = { create<T>(owner: IDisposableOwner, cb: (use: UseCB) => Promise<T>): AsyncComputed<T> { const backend: Observable<T|undefined> = Observable.create(owner, undefined); const dirty = Observable.create(owner, true); const computed: Computed<Promise<T>> = Computed.create(owner, cb as any); let ticket = 0; const listener = (prom: Promise<T>): void => { dirty.set(true); const myTicket = ++ticket; prom.then(v => { if (ticket !== myTicket) { return; } if (backend.isDisposed()) { return; } dirty.set(false); backend.set(v); }).catch(reportError); }; owner?.autoDispose(computed.addListener(listener)); listener(computed.get()); return Object.assign(backend, { dirty }); } }; export interface AsyncComputed<T> extends Observable<T|undefined> { /** * Whether computed wasn't updated yet. */ dirty: Observable<boolean>; } /** * Stops propagation of the event, and prevents default action. */ export function stopEvent(ev: Event) { ev.stopPropagation(); ev.preventDefault(); ev.stopImmediatePropagation(); }