/* * This file belongs to Hoist, an application development toolkit * developed by Extremely Heavy Industries (www.xh.io | info@xh.io) * * Copyright © 2026 Extremely Heavy Industries Inc. */ import {TEST_ID} from '@xh/hoist/utils/js'; import {castArray, isFunction, isNil, isPlainObject} from 'lodash'; import { ComponentType, createElement as reactCreateElement, isValidElement, JSX, Key, ReactElement, ReactNode } from 'react'; import {PlainObject, Thunkable} from './types/Types'; /** * Alternative format for specifying React Elements in render functions. This type is designed to * provide a well-formatted, declarative, native javascript approach to configuring Elements and * their children. It serves as an alternative to JSX and is especially useful for code-heavy * element trees. (For element trees with a significant amount of hypertext, JSX could be a better * choice.) * * The core enhancement to this format over jsx is it expects child elements and props to be * specified in a single bundle, with children placed within an `item` or `items` key. This allows * developers to write declarative, multi-level element trees in a concise yet highly-readable * style. An additional feature is the minor-but-useful support for an `omit` property, which * allows element subtrees to be declaratively excluded from rendering if a given condition is met. * This avoids the need for the common, but clunky React practice of wrapping elements in ternary * statements to accomplish conditional rendering. * * @see {@link createElement} - That function is a thin-wrapper over `React.createChildren` that * consumes this format. * * Finally, note that if a React Component has its own props of `item`, `items`, or `omit`, the * props may be specified in this object with a `$` prefix (e.g. `$item`) to avoid conflicting * with this API. The '$' will be stripped from the prop name before passing it along to the * underlying component. */ export type ElementSpec
= Omit
& {
//---------------------------------------------
// Enhanced attributes to support element factory
//---------------------------------------------
/** Child Element(s). Equivalent provided as Rest Arguments to React.createElement.*/
items?: ReactNode;
/** Equivalent to `items`, offered for code clarity when only one child is needed. */
item?: ReactNode;
/** True to exclude the Element. */
omit?: Thunkable = ((arg: ElementSpec ) => ReactElement ) &
((...args: ReactNode[]) => ReactElement );
/**
* Create a React Element from a Component type and an ElementSpec.
*
* This function is a thin-wrapper over `React.createChildren` that
* consumes the ElementSpec format.
*
* @param component - React Component or string representing an HTML element.
* @param spec - element spec.
*/
export function createElement (component: ReactComponent): ElementFactory ;
export function elementFactory(component: ReactComponent): ElementFactory {
const ret = function (...args) {
return createElement(component, normalizeArgs(args));
};
ret.isElementFactory = true;
return ret;
}
//------------------------
// Implementation
//------------------------
function normalizeArgs(args: any[]) {
const len = args.length;
if (len === 0) return {};
if (len === 1) {
const arg = args[0];
if (isPlainObject(arg) && !isValidElement(arg)) return arg;
return {items: arg};
}
// Assume > 1 args are children.
return {items: args};
}
type PropType