-
-
Save nichoth/1939344dde60d853e29fff45d73e6fa5 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { useSignal, signal, effect } from '@preact/signals'; | |
import { useLayoutEffect, useMemo, useRef } from 'preact/hooks'; | |
/** @template T @typedef {T extends (infer U)[] ? U : never} Items */ | |
/** @param {{ v, k?, f }} props */ | |
const Item = ({ v, k, f }) => f(v, k); | |
/** | |
* Like signal.value.map(fn), but doesn't re-render. | |
* @template {any[]} T | |
* @param {Signal<T>} signal | |
* @param {(item: Items<T>) => import('preact').ComponentChildren} fn | |
*/ | |
export function map(signal, fn) { | |
return <For each={signal} children={fn} />; | |
} | |
/** | |
* Like signal.value.map(fn), but doesn't re-render. | |
* @template {any[]} T | |
* @param {{ each: Signal<T>, children: (item: Items<T>) => preact.ComponentChildren, fallback?: preact.ComponentChildren }} props | |
* @returns {any} - our types are too strict and don't allow arrays | |
*/ | |
export function For({ each, children: f, fallback }) { | |
let c = useMemo(() => new Map(), []); | |
return ( | |
each.value?.map( | |
(v, k, x) => c.get(v) || (c.set(v, (x = <Item {...{ key: v, v, k, f }} />)), x) | |
) ?? fallback | |
); | |
} | |
/** | |
* Renders it's children when the given Signal is truthy. | |
* Note: children can also be a function that is passed the signal value. | |
* @template T | |
* @param {{ when: Signal<T>, children: preact.ComponentChildren | ((value: T) => preact.ComponentChildren), fallback?: preact.ComponentChildren }} props | |
* @returns {any} - our types are too strict and don't allow arrays | |
*/ | |
export function Show({ when, fallback, children: f }) { | |
const v = when.value; | |
return v ? typeof f === 'function' ? <Item {...{ v, f }} /> : f : fallback; | |
} | |
/** | |
* useSignal, but re-rendering with a different value arg updates the signal. | |
* Useful for: const a = useLiveSignal(props.a) | |
* @type {typeof useSignal} | |
*/ | |
export function useLiveSignal(value) { | |
const s = useSignal(value); | |
if (s.peek() !== value) s.value = value; | |
return s; | |
} | |
/** | |
* useSignal, but works as a ref on DOM elements. | |
* @template T | |
* @param {T} value | |
*/ | |
export function useSignalRef(value) { | |
const ref = /** @type {Signal<T> & { current: T }} */ (useSignal(value)); | |
if (!('current' in ref)) Object.defineProperty(ref, 'current', refSignalProto); | |
return ref; | |
} | |
const refSignalProto = { | |
configurable: true, | |
get() { | |
return this.value; | |
}, | |
set(v) { | |
this.value = v; | |
}, | |
}; | |
/** | |
* The `useLayoutEffect()` version of `useSignalEffect()` | |
* @type {typeof import('@preact/signals').useSignalEffect} | |
*/ | |
export function useSignalLayoutEffect(cb) { | |
const callback = useRef(cb); | |
callback.current = cb; | |
useLayoutEffect(() => effect(() => callback.current()), []); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment