Created
November 21, 2024 16:21
-
-
Save developit/d635c2ff6159f6857facdd86331c5421 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 { | |
signal, | |
effect, | |
useSignal, | |
type useSignalEffect, | |
type Signal, | |
type ReadonlySignal, | |
} from '@preact/signals'; | |
import { VNode, type ComponentChildren } from 'preact'; | |
import { | |
useLayoutEffect, | |
useMemo, | |
useRef, | |
useId, | |
useEffect, | |
} from 'preact/hooks'; | |
type Items<T> = T extends ReadonlyArray<infer U> ? U : never; | |
const Item = ({ v, k, f }: { v: any; k?: any; f: any }) => f(v, k); | |
/** | |
* Like signal.value.map(fn), but doesn't re-render. | |
*/ | |
export function map<T extends ReadonlyArray<any> = any[]>( | |
signal: Signal<T>, | |
fn: (item: Items<T>, key: keyof T) => ComponentChildren, | |
) { | |
return <For each={signal} children={fn} />; | |
} | |
/** | |
* Like signal.value.map(fn), but doesn't re-render. | |
*/ | |
export function For<T extends ReadonlyArray<any> = any[]>({ | |
each, | |
children: f, | |
fallback, | |
}: { | |
each: Signal<T>; | |
children: (item: Items<T>, key: keyof T) => preact.ComponentChildren; | |
fallback?: ComponentChildren; | |
}) { | |
let c = useMemo(() => new Map(), []); | |
return (each.value?.map((v, k) => { | |
let item = c.get(v); | |
if (!item) c.set(v, (item = <Item v={v} k={k} f={f} />)); | |
return item; | |
}) ?? fallback) as unknown as VNode; | |
} | |
/** | |
* Renders it's children when the given Signal is truthy. | |
* Note: children can also be a function that is passed the signal value. | |
*/ | |
export function Show<T>({ | |
when, | |
fallback, | |
children: f, | |
}: { | |
when: Signal<T>; | |
children: preact.ComponentChildren | ((value: T) => preact.ComponentChildren); | |
fallback?: preact.ComponentChildren; | |
}) { | |
const v = when.value; | |
return ( | |
v ? typeof f === 'function' ? <Item v={v} f={f} /> : f : fallback | |
) as VNode; | |
} | |
/** | |
* useSignal, but re-rendering with a different value arg updates the signal. | |
* Useful for: const a = useLiveSignal(props.a) | |
*/ | |
export const useLiveSignal: typeof useSignal = (value) => { | |
const s = useSignal(value); | |
if (s.peek() !== value) s.value = value; | |
return s; | |
}; | |
/** | |
* useSignal, but works as a ref on DOM elements. | |
*/ | |
export function useSignalRef<T>(value: T) { | |
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(this: Signal) { | |
return this.value; | |
}, | |
set(this: Signal, v: any) { | |
this.value = v; | |
}, | |
}; | |
/** | |
* The `useLayoutEffect()` version of `useSignalEffect()` | |
*/ | |
export const useSignalLayoutEffect: typeof useSignalEffect = (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