import {
untrack,
createSignal,
createResource,
createMemo,
devComponent,
$PROXY,
$DEVCOMP
} from "../reactive/signal.js";
import { sharedConfig, nextHydrateContext, setHydrateContext } from "./hydration.js";
import type { JSX } from "../jsx.js";
let hydrationEnabled = false;
export function enableHydration() {
hydrationEnabled = true;
}
/**
* A general `Component` has no implicit `children` prop. If desired, you can
* specify one as in `Component<{name: String, children: JSX.Element>}`.
*/
export type Component
= (props: P) => JSX.Element;
/**
* Extend props to forbid the `children` prop.
* Use this to prevent accidentally passing `children` to components that
* would silently throw them away.
*/
export type VoidProps
= P & { children?: never };
/**
* `VoidComponent` forbids the `children` prop.
* Use this to prevent accidentally passing `children` to components that
* would silently throw them away.
*/
export type VoidComponent
= Component>;
/**
* Extend props to allow an optional `children` prop with the usual
* type in JSX, `JSX.Element` (which allows elements, arrays, functions, etc.).
* Use this for components that you want to accept children.
*/
export type ParentProps = P & { children?: JSX.Element };
/**
* `ParentComponent` allows an optional `children` prop with the usual
* type in JSX, `JSX.Element` (which allows elements, arrays, functions, etc.).
* Use this for components that you want to accept children.
*/
export type ParentComponent
= Component>;
/**
* Extend props to require a `children` prop with the specified type.
* Use this for components where you need a specific child type,
* typically a function that receives specific argument types.
* Note that all JSX are of the type `JSX.Element`.
*/
export type FlowProps = P & { children: C };
/**
* `FlowComponent` requires a `children` prop with the specified type.
* Use this for components where you need a specific child type,
* typically a function that receives specific argument types.
* Note that all JSX are of the type `JSX.Element`.
*/
export type FlowComponent = Component>;
/** @deprecated: use `ParentProps` instead */
export type PropsWithChildren = ParentProps
;
export type ValidComponent =
| keyof JSX.IntrinsicElements
| Component
| (string & {});
/**
* Takes the props of the passed component and returns its type
*
* @example
* ComponentProps // { mount?: Node; useShadow?: boolean; children: JSX.Element }
* ComponentProps<'div'> // JSX.HTMLAttributes
*/
export type ComponentProps =
T extends Component
? P
:
T extends keyof JSX.IntrinsicElements
? JSX.IntrinsicElements[T]
: Record;
/**
* Type of `props.ref`, for use in `Component` or `props` typing.
*
* @example Component<{ref: Ref}>
*/
export type Ref = T | ((val: T) => void);
export function createComponent(Comp: Component, props: T): JSX.Element {
if (hydrationEnabled) {
if (sharedConfig.context) {
const c = sharedConfig.context;
setHydrateContext(nextHydrateContext());
const r = "_SOLID_DEV_"
? devComponent(Comp, props || ({} as T))
: untrack(() => Comp(props || ({} as T)));
setHydrateContext(c);
return r;
}
}
if ("_SOLID_DEV_") return devComponent(Comp, props || ({} as T));
return untrack(() => Comp(props || ({} as T)));
}
function trueFn() {
return true;
}
const propTraps: ProxyHandler<{
get: (k: string | number | symbol) => any;
has: (k: string | number | symbol) => boolean;
keys: () => string[];
}> = {
get(_, property, receiver) {
if (property === $PROXY) return receiver;
return _.get(property);
},
has(_, property) {
return _.has(property);
},
set: trueFn,
deleteProperty: trueFn,
getOwnPropertyDescriptor(_, property) {
return {
configurable: true,
enumerable: true,
get() {
return _.get(property);
},
set: trueFn,
deleteProperty: trueFn
};
},
ownKeys(_) {
return _.keys();
}
};
type DistributeOverride = T extends undefined ? F : T;
type Override = T extends any
? U extends any
? {
[K in keyof T]: K extends keyof U ? DistributeOverride : T[K];
} & {
[K in keyof U]: K extends keyof T ? DistributeOverride : U[K];
}
: T & U
: T & U;
type OverrideSpread = T extends any
? {
[K in keyof ({ [K in keyof T]: any } & { [K in keyof U]?: any } & {
[K in U extends any ? keyof U : keyof U]?: any;
})]: K extends keyof T
? Exclude | T[K]
: U extends any
? U[K & keyof U]
: never;
}
: T & U;
type Simplify = T extends any ? { [K in keyof T]: T[K] } : T;
type _MergeProps = T extends [
infer Next | (() => infer Next),
...infer Rest
]
? _MergeProps>
: T extends [...infer Rest, infer Next | (() => infer Next)]
? Override<_MergeProps, Next>
: T extends []
? Curr
: T extends (infer I | (() => infer I))[]
? OverrideSpread
: Curr;
export type MergeProps = Simplify<_MergeProps>;
function resolveSource(s: any) {
return (s = typeof s === "function" ? s() : s) == null ? {} : s;
}
export function mergeProps(...sources: T): MergeProps {
return new Proxy(
{
get(property: string | number | symbol) {
for (let i = sources.length - 1; i >= 0; i--) {
const v = resolveSource(sources[i])[property];
if (v !== undefined) return v;
}
},
has(property: string | number | symbol) {
for (let i = sources.length - 1; i >= 0; i--) {
if (property in resolveSource(sources[i])) return true;
}
return false;
},
keys() {
const keys = [];
for (let i = 0; i < sources.length; i++)
keys.push(...Object.keys(resolveSource(sources[i])));
return [...new Set(keys)];
}
},
propTraps
) as unknown as MergeProps;
}
export type SplitProps = [
...{
[P in keyof K]: P extends `${number}`
? Pick[number]>
: never;
},
Omit
];
export function splitProps(
props: T,
...keys: K
): SplitProps {
const blocked = new Set(keys.flat());
const descriptors = Object.getOwnPropertyDescriptors(props);
const res = keys.map(k => {
const clone = {};
for (let i = 0; i < k.length; i++) {
const key = k[i];
Object.defineProperty(
clone,
key,
descriptors[key]
? descriptors[key]
: {
get() {
return props[key];
},
set() {
return true;
}
}
);
}
return clone;
});
res.push(
new Proxy(
{
get(property: string | number | symbol) {
return blocked.has(property as keyof T) ? undefined : props[property as keyof T];
},
has(property: string | number | symbol) {
return blocked.has(property as keyof T) ? false : property in props;
},
keys() {
return Object.keys(props).filter(k => !blocked.has(k as keyof T));
}
},
propTraps
)
);
return res as SplitProps;
}
// lazy load a function component asynchronously
export function lazy>(
fn: () => Promise<{ default: T }>
): T & { preload: () => Promise<{ default: T }> } {
let comp: () => T | undefined;
let p: Promise<{ default: T }> | undefined;
const wrap: T & { preload?: () => void } = ((props: any) => {
const ctx = sharedConfig.context;
if (ctx) {
const [s, set] = createSignal();
(p || (p = fn())).then(mod => {
setHydrateContext(ctx);
set(() => mod.default);
setHydrateContext();
});
comp = s;
} else if (!comp) {
const [s] = createResource(() => (p || (p = fn())).then(mod => mod.default));
comp = s;
} else {
const c = comp();
if (c) return c(props);
}
let Comp: T | undefined;
return createMemo(
() =>
(Comp = comp()) &&
untrack(() => {
if ("_SOLID_DEV_") Object.assign(Comp!, { [$DEVCOMP]: true });
if (!ctx) return Comp!(props);
const c = sharedConfig.context;
setHydrateContext(ctx);
const r = Comp!(props);
setHydrateContext(c);
return r;
})
);
}) as T;
wrap.preload = () => p || ((p = fn()).then(mod => (comp = () => mod.default)), p);
return wrap as T & { preload: () => Promise<{ default: T }> };
}
let counter = 0;
export function createUniqueId(): string {
const ctx = sharedConfig.context;
return ctx ? `${ctx.id}${ctx.count++}` : `cl-${counter++}`;
}