PettyUI/packages/core/src/primitives/create-controllable-signal.ts
2026-03-29 07:23:19 +07:00

39 lines
1.3 KiB
TypeScript

import { type Accessor, createSignal } from "solid-js";
export interface CreateControllableSignalOptions<T> {
/** Returns the controlled value, or undefined if uncontrolled. */
value: Accessor<T | undefined>;
/** Default value used when uncontrolled. Only read once at creation time — not reactive after mount. */
defaultValue: Accessor<T>;
/** Called whenever the value changes (both modes). */
onChange?: ((value: T) => void) | undefined;
}
/**
* Handles controlled vs uncontrolled state for any stateful component.
* When `value()` is not undefined, the component is controlled — the external
* value is the source of truth. Otherwise, an internal signal manages state.
*/
export function createControllableSignal<T>(
options: CreateControllableSignalOptions<T>,
): [Accessor<T>, (value: T) => void] {
const [internalValue, setInternalValue] = createSignal<T>(options.defaultValue());
const get: Accessor<T> = () => {
const controlled = options.value();
if (controlled !== undefined) return controlled;
return internalValue();
};
const set = (value: T) => {
const isControlled = options.value() !== undefined;
if (!isControlled) {
// Use functional form so SolidJS does not interpret T-as-function as an updater.
setInternalValue(() => value);
}
options.onChange?.(value);
};
return [get, set];
}