import React, { createContext, forwardRef, Fragment, lazy, useContext, useMemo, useRef, type ComponentType, type FC, type ReactNode, } from 'react'; import type { MaybePromise } from '@wener/utils'; export interface DefineComponentOptions
{
name: string;
title?: string;
props?: P;
schema?: any;
metadata?: Record ;
load?: () => Promise }>;
}
export interface ComponentDef {
name: string;
title: string;
props?: Record ({
Component: _Component,
load,
...opts
}: DefineComponentOptions ): ContextComponentType {
{
const Component = createComponent({
Component: _Component as any,
load,
});
// if (Component === Fragment) {
// console.warn(`Component ${opts.name} not resolved`);
// }
const def: ComponentDef = {
title: opts.title || opts.name,
...opts,
Component,
metadata: {},
};
let last = _components.find((v) => v.name === def.name);
if (last) {
console.error(`Component ${def.name} already defined`);
// last.schema = def.schema;
// last.props = def.props;
// last.metadata = def.metadata;
}
_components.unshift(def);
}
const name = opts.name;
let component = Object.assign(
forwardRef ;
return component;
}
export function getComponents() {
return _components;
}
export const ConsumeComponent = forwardRef = ComponentType & { [ComponentNamePropKey]: string };
export function createContextComponent (name: string): ContextComponentType {
let component = Object.assign(
forwardRef ;
component.displayName = `${ComponentNamePropKey}(${name})`;
return component;
}
type LoadableComponent = () => MaybePromise }>;
type ProvidedComponent = {
provide: NameLike ;
Component?: ComponentType ;
load?: LoadableComponent ;
};
export type ComponentProviderProps = {
components: Array = string | ContextComponentType | ComponentType ;
type ComponentContextObject = {
parent?: ComponentContextObject;
components: ProvidedComponent[];
useComponent: (comp: NameLike ) => UseComponentResult ;
};
type ComponentProviderState = {};
type UseComponentResult = [ComponentType , { found: boolean }];
const RootValue: ComponentContextObject = {
get components() {
return _components.map((v) => {
return {
provide: v.name,
Component: v.Component,
};
});
},
useComponent: (name) => resolveComponent(name, RootValue),
};
const ComponentContext = createContext (def: NameLike ) {
let name: string;
if (typeof def === 'string') {
name = def;
} else {
name =
(def as ContextComponentType)[ComponentNamePropKey] ||
(def as ComponentType).displayName ||
// (def as ComponentType).name || // this is not reliable
'';
}
return name;
}
export function useComponent (comp: NameLike , def?: ComponentType ): UseComponentResult {
const { useComponent } = useContext(ComponentContext);
const [o, r] = useComponent (comp);
return [r.found ? o : def || o, r];
}
export const ComponentProvider: FC (comp: NameLike , obj: ComponentContextObject): UseComponentResult {
let cur: ComponentContextObject | undefined = obj;
let Component = Fragment as ComponentType ;
let found = false;
const name = resolveName(comp);
const isMatch = (provide: NameLike