@ffsm/factory
Version:
A powerful component factory for React that simplifies creation of reusable components with built-in support for composition patterns, conditional rendering, prop management, and TypeScript integration.
129 lines (128 loc) • 5.76 kB
JavaScript
var __assign = (this && this.__assign) || function () {
__assign = Object.assign || function(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};
var __rest = (this && this.__rest) || function (s, e) {
var t = {};
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
t[p] = s[p];
if (s != null && typeof Object.getOwnPropertySymbols === "function")
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
t[p[i]] = s[p[i]];
}
return t;
};
import { jsx as _jsx } from "react/jsx-runtime";
import { factory as factoryBase } from './factory';
import { cloneProps, getAndDeleteProp } from './utils';
import { Empty } from '@ffsm/compositor/empty';
import { Condition } from '@ffsm/compositor/condition';
import { AsSlot } from '@ffsm/compositor/as-slot';
import { AsNode } from '@ffsm/compositor/as-node';
function getCompositorOptions(options) {
var clonedOptions = cloneProps(options);
var asSlot = getAndDeleteProp(clonedOptions, 'asSlot');
var asNode = getAndDeleteProp(clonedOptions, 'asNode', false);
var asNodeFalsy = getAndDeleteProp(clonedOptions, 'asNodeFalsy', false);
var emptyFallback = getAndDeleteProp(clonedOptions, 'emptyFallback', null);
var condition = getAndDeleteProp(clonedOptions, 'condition', undefined);
var conditionFallback = getAndDeleteProp(clonedOptions, 'conditionFallback', null);
var conditionFalsy = getAndDeleteProp(clonedOptions, 'conditionFalsy', false);
return {
asSlot: asSlot,
asNode: asNode,
asNodeFalsy: asNodeFalsy,
emptyFallback: emptyFallback,
condition: condition,
conditionFallback: conditionFallback,
conditionFalsy: conditionFalsy,
};
}
/**
* Creates a factory component with integrated Compositor features
*
* This factory function combines the standard factory component creation with
* automatic integration of @ffsm/compositor components. While the basic factory
* focuses on creating reusable UI components with dynamic props and forwarding,
* this compositor-enabled version adds support for advanced UI patterns:
*
* - Conditional rendering via Condition component
* - Empty state handling via Empty component
* - Children-based conditional rendering via AsNode component
* - Content projection via AsSlot component
*
* @template AdditionalProps - Additional props type that the factory component accepts
* @template Element - The base element type, defaults to 'div'
* @param {string} displayName - Display name for the component in React DevTools
* @param {InitialProps<Element, AdditionalProps>} [init] - Initial props or props factory function
* @param {FactoryOptions<Element, AdditionalProps>} [options={}] - Factory options including compositor features
* @returns {ForwardRefExoticComponent<PropsWithoutRef<FactoryProps<Element, AdditionalProps>> & RefAttributes<Factory<Element>>>}
* A forward ref component with compositor integration
*
* @example
* // Create a card with empty state
* const Card = factory('Card',
* { className: 'card p-4' },
* { emptyFallback: <p>No content available</p> }
* );
*
* @example
* // Create a component that only renders when authenticated
* const ProtectedArea = factory('ProtectedArea',
* { className: 'protected-area' },
* {
* condition: (props) => props.isAuthenticated,
* conditionFallback: <LoginPrompt />
* }
* );
*
* @example
* // Create a component with slot-based composition
* const Dialog = factory('Dialog',
* { className: 'dialog' },
* {
* asSlot: true,
* outlet: <div className="dialog-content" />
* }
* );
*
* @example
* // Create a component that only renders when it has children
* const Section = factory('Section',
* { className: 'section' },
* { asNode: true }
* );
*/
export function factory(displayName, init, options) {
if (options === void 0) { options = {}; }
var _a = getCompositorOptions(options), emptyFallback = _a.emptyFallback, condition = _a.condition, conditionFalsy = _a.conditionFalsy, conditionFallback = _a.conditionFallback, asSlot = _a.asSlot, asNode = _a.asNode, asNodeFalsy = _a.asNodeFalsy;
function template(Component, props, initProps) {
var children = props.children, rest = __rest(props, ["children"]);
var content = children;
if (emptyFallback) {
content = _jsx(Empty, { fallback: emptyFallback, children: content });
}
if (condition !== undefined) {
content = (_jsx(Condition, { when: condition, falsy: conditionFalsy, fallback: conditionFallback, children: content }));
}
var outlet = getAndDeleteProp(initProps, 'outlet', null);
if (asSlot) {
content = (_jsx(AsSlot, { outlet: outlet, outletProps: initProps, children: content }));
}
var templateOption = (options === null || options === void 0 ? void 0 : options.template) || (function (Comp, props) { return _jsx(Comp, __assign({}, props)); });
var render = templateOption(Component, __assign(__assign({}, rest), { children: content }), initProps);
if (asNode) {
return (_jsx(AsNode, { of: content, falsy: asNodeFalsy, children: render }));
}
return render;
}
return factoryBase(displayName, init, __assign(__assign({}, options), { template: template }));
}