@tempots/dom
Version:
Fully-typed frontend framework alternative to React and Angular
236 lines (235 loc) • 7.54 kB
TypeScript
import { DOMContext } from '../dom/dom-context';
import { ProviderMark, Renderable, TNode } from '../types/domain';
/**
* Converts an array of `Provider` types `T` into an array of their corresponding types.
* @public
*/
export type ToProviderTypes<T extends unknown[]> = T extends [] ? [] : T extends [Provider<infer K>] ? [K] : T extends [Provider<infer K>, ...infer R] ? [K, ...ToProviderTypes<R>] : never;
/**
* Represents a provider for a specific type `T`.
* @public
*/
export type Provider<T, O = any> = {
/** The provider mark. */
mark: ProviderMark<T>;
/** The function to create the provider. */
create: (options: O | undefined, ctx: DOMContext) => {
value: T;
dispose: () => void;
onUse?: () => void;
};
};
/**
* Represents an object with provider options.
* @public
*/
export type ProviderOptions = {
/** The function to use a provider. */
use: <T, O = any>(provider: Provider<T, O>) => T;
/** The function to set a provider. */
set: <T, O = any>(provider: Provider<T, O>, options?: O) => void;
};
/**
* Returns a renderable function that executes the given function with the
* current DOMContext as argument.
* The given function can return a TNode or void. If you need to perform some
* actions when the Renderable is disposed, you can use `OnDispose` as the
* return value.
*
* @param fn - The function to be executed with the DOMContext argument.
* @returns A Clear function that can be used to clean up any resources associated with the execution.
* @public
*/
export declare const WithProvider: (fn: (opts: ProviderOptions) => TNode | void) => Renderable;
/**
* Makes a provider available to all child components in the component tree.
*
* This function creates a provider context that child components can access using `Use`.
* The provider is automatically disposed when the component is unmounted.
*
* @example
* ```typescript
* // Create a theme provider
* const ThemeProvider: Provider<Signal<string>> = {
* mark: makeProviderMark<Signal<string>>('Theme'),
* create: () => {
* const theme = prop('light')
* return {
* value: theme,
* dispose: () => theme.dispose()
* }
* }
* }
*
* // Provide the theme to child components
* const App = () =>
* Provide(
* ThemeProvider,
* {}, // options
* () => html.div(
* html.h1('My App'),
* ThemeToggle(), // Can access the theme
* MainContent() // Can also access the theme
* )
* )
* ```
*
* @example
* ```typescript
* // Provider with options
* const ConfigProvider: Provider<Config, { apiUrl: string }> = {
* mark: makeProviderMark<Config>('Config'),
* create: (options) => {
* const config = new Config(options.apiUrl)
* return {
* value: config,
* dispose: () => config.cleanup()
* }
* }
* }
*
* // Provide with specific options
* Provide(
* ConfigProvider,
* { apiUrl: 'https://api.example.com' },
* () => AppContent()
* )
* ```
*
* @template T - The type of value provided by the provider
* @template O - The type of options passed to the provider
* @param provider - The provider definition containing mark and create function
* @param options - Options to pass to the provider's create function
* @param child - Function that returns the child components that can access the provider
* @returns A renderable that provides the value to its children
* @public
*/
export declare const Provide: <T, O>(provider: Provider<T, O>, options: O, child: () => TNode) => Renderable;
/**
* Consumes a provider value and makes it available to a child component.
*
* This function looks up a provider in the component tree and passes its value
* to the child function. The provider must have been made available by a parent
* component using `Provide`.
*
* @example
* ```typescript
* // Use a theme provider
* const ThemeToggle = () =>
* Use(
* ThemeProvider,
* (theme) => html.button(
* on.click(() => {
* theme.value = theme.value === 'light' ? 'dark' : 'light'
* }),
* 'Current theme: ', theme
* )
* )
* ```
*
* @example
* ```typescript
* // Use multiple properties from a provider
* const UserProfile = () =>
* Use(
* UserProvider,
* (user) => html.div(
* html.h2('Welcome, ', user.map(u => u.name)),
* html.p('Email: ', user.map(u => u.email)),
* html.p('Role: ', user.map(u => u.role))
* )
* )
* ```
*
* @example
* ```typescript
* // Use provider in conditional rendering
* const ConditionalContent = () =>
* Use(
* AuthProvider,
* (auth) => auth.map(a => a.isLoggedIn)
* ? html.div('Welcome back!')
* : html.div('Please log in')
* )
* ```
*
* @template T - The type of value provided by the provider
* @param provider - The provider to consume (must be available in parent components)
* @param child - Function that receives the provider value and returns content to render
* @returns A renderable that consumes the provider and renders the child content
* @throws {ProviderNotFoundError} When the provider is not found in the component tree
* @public
*/
export declare const Use: <T>(provider: Provider<T>, child: (provider: T) => TNode) => Renderable;
/**
* Consumes multiple providers and makes their values available to a child component.
*
* This function is a convenience wrapper for consuming multiple providers at once.
* It's equivalent to nesting multiple `Use` calls but provides a cleaner API when
* you need access to several providers simultaneously.
*
* @example
* ```typescript
* // Use multiple providers together
* const Dashboard = () =>
* UseMany(
* UserProvider,
* ThemeProvider,
* ConfigProvider
* )(
* (user, theme, config) => html.div(
* attr.class(theme.map(t => `theme-${t}`)),
* html.h1('Dashboard for ', user.map(u => u.name)),
* html.p('API URL: ', config.map(c => c.apiUrl)),
* html.p('Current theme: ', theme)
* )
* )
* ```
*
* @example
* ```typescript
* // Equivalent to nested Use calls
* const Dashboard = () =>
* Use(UserProvider, user =>
* Use(ThemeProvider, theme =>
* Use(ConfigProvider, config =>
* html.div(
* // Same content as above
* )
* )
* )
* )
* ```
*
* @example
* ```typescript
* // Type-safe access to multiple providers
* const SettingsPanel = () =>
* UseMany(
* PreferencesProvider,
* UserProvider
* )(
* (preferences, user) => html.div(
* html.h2('Settings for ', user.map(u => u.name)),
* html.label(
* 'Theme: ',
* html.select(
* attr.value(preferences.map(p => p.theme)),
* on.change(emitValue(value => {
* preferences.update(p => ({ ...p, theme: value }))
* })),
* html.option(attr.value('light'), 'Light'),
* html.option(attr.value('dark'), 'Dark')
* )
* )
* )
* )
* ```
*
* @template T - Tuple type representing the types of all providers
* @param providers - Variable number of providers to consume
* @returns Function that takes a child function and returns a renderable
* @throws {ProviderNotFoundError} When any of the providers is not found in the component tree
* @public
*/
export declare const UseMany: <T extends Provider<unknown>[]>(...providers: T) => (child: (...values: ToProviderTypes<T>) => TNode) => Renderable;