@meonode/ui
Version:
A structured approach to component composition with built-in theming, prop separation, and dynamic children handling.
94 lines • 9.61 kB
JavaScript
;var _excluded=["nodetheme"],_excluded2=["provider","nodetheme"];function _typeof(a){"@babel/helpers - typeof";return _typeof="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(a){return typeof a}:function(a){return a&&"function"==typeof Symbol&&a.constructor===Symbol&&a!==Symbol.prototype?"symbol":typeof a},_typeof(a)}function ownKeys(a,b){var c=Object.keys(a);if(Object.getOwnPropertySymbols){var d=Object.getOwnPropertySymbols(a);b&&(d=d.filter(function(b){return Object.getOwnPropertyDescriptor(a,b).enumerable})),c.push.apply(c,d)}return c}function _objectSpread(a){for(var b,c=1;c<arguments.length;c++)b=null==arguments[c]?{}:arguments[c],c%2?ownKeys(Object(b),!0).forEach(function(c){_defineProperty(a,c,b[c])}):Object.getOwnPropertyDescriptors?Object.defineProperties(a,Object.getOwnPropertyDescriptors(b)):ownKeys(Object(b)).forEach(function(c){Object.defineProperty(a,c,Object.getOwnPropertyDescriptor(b,c))});return a}function _defineProperty(a,b,c){return(b=_toPropertyKey(b))in a?Object.defineProperty(a,b,{value:c,enumerable:!0,configurable:!0,writable:!0}):a[b]=c,a}function _toPropertyKey(a){var b=_toPrimitive(a,"string");return"symbol"==_typeof(b)?b:b+""}function _toPrimitive(a,b){if("object"!=_typeof(a)||!a)return a;var c=a[Symbol.toPrimitive];if(void 0!==c){var d=c.call(a,b||"default");if("object"!=_typeof(d))return d;throw new TypeError("@@toPrimitive must return a primitive value.")}return("string"===b?String:Number)(a)}function _objectWithoutProperties(a,b){if(null==a)return{};var c,d,e=_objectWithoutPropertiesLoose(a,b);if(Object.getOwnPropertySymbols){var f=Object.getOwnPropertySymbols(a);for(d=0;d<f.length;d++)c=f[d],-1===b.indexOf(c)&&{}.propertyIsEnumerable.call(a,c)&&(e[c]=a[c])}return e}function _objectWithoutPropertiesLoose(a,b){if(null==a)return{};var c={};for(var d in a)if({}.hasOwnProperty.call(a,d)){if(-1!==b.indexOf(d))continue;c[d]=a[d]}return c}import{BaseNode,Node}from"../core.node";/**
* Creates a portal component with a fixed set of providers by passing an array of `NodeInstance`s.
* The content component will be rendered within these providers. This method is considered deprecated.
* @deprecated Use `Portal(provider: NodeInstance<any>, component: ...)` instead for fixed providers.
* Passing an array of providers for fixed setup is deprecated and will trigger a console warning.
* @param provider An array of `NodeInstance`s that will wrap the portal content. Each NodeInstance
* should represent a React context provider (e.g., `ThemeProvider({ theme })`).
* @param component The React component function that defines the portal's content. It receives
* props of type `PortalProps<P>` and should return a `ComponentNode`.
* @returns A launcher function that, when called, creates and controls the portal instance.
* @example
* ```ts
* // Example of a deprecated usage with an array of fixed providers:
* const DeprecatedThemedModal = Portal(
* [ThemeProvider({ theme: 'dark' }), LanguageProvider({ lang: 'en' })],
* (props) => Div({ children: props.children, style: { background: props.nodetheme?.background } })
* );
*
* const deprecatedModalInstance = DeprecatedThemedModal({ children: "Deprecated content" });
* // A console warning will be logged when DeprecatedThemedModal is created.
* deprecatedModalInstance.unmount();
* ```
*//**
* Creates a portal component with a single fixed provider.
* The content component will be rendered within this provider. This is the preferred method
* for providing fixed context to your portal content.
* @param provider A single `NodeInstance` that will wrap the portal content. This should typically
* be a React context provider (e.g., `ThemeProvider({ theme })`).
* @param component The React component function that defines the portal's content. It receives
* props of type `PortalProps<P>` and should return a `ComponentNode`.
* @returns A launcher function that, when called, creates and controls the portal instance.
* @example
* ```ts
* // Example of preferred usage with a single fixed provider:
* const ThemedModal = Portal(
* ThemeProvider({ theme: 'light' }),
* (props) => Div({ children: props.children, style: { background: props.nodetheme?.background } })
* );
*
* const modalInstance = ThemedModal({ children: "Preferred content" });
* modalInstance.unmount();
* ```
*//**
* Creates a basic portal component without any fixed providers.
* Dynamic providers can still be passed as props when launching the portal instance.
* @param component The React component function that defines the portal's content. It receives
* props of type `PortalProps<P>` and should return a `ComponentNode`.
* @returns A launcher function that, when called, creates and controls the portal instance.
* @example
* ```ts
* // Example of basic usage without fixed providers:
* const BasicModal = Portal(
* (props) => Div({ children: props.children, style: { padding: '20px', border: '1px solid black' } })
* );
*
* const basicModalInstance = BasicModal({ children: "Hello from a basic portal!" });
* basicModalInstance.unmount();
*
* // Example with dynamic providers when launching:
* const DynamicThemedModal = Portal(
* (props) => Div({ children: props.children, style: { background: props.nodetheme?.background || 'white' } })
* );
*
* const dynamicModalInstance = DynamicThemedModal({
* providers: ThemeProvider({ theme: 'blue' }), // Dynamic provider
* children: "Content with dynamic theme"
* });
* dynamicModalInstance.unmount();
* ```
*/// --- Implementation ---
export function Portal(a,b){// --- Initialization ---
var c,d=void 0,e=null;// --- Argument Parsing and Overload Handling ---
// Determines which Portal overload was called (e.g., with fixed provider or just component).
if("function"==typeof b&&(a instanceof BaseNode||Array.isArray(a)&&a.every(function(a){return a instanceof BaseNode})))Array.isArray(a)?(console.warn("Portal: Passing an array of providers as the first argument is deprecated. Please pass a single NodeInstance instead."),d=a):d=[a],c=b;else if("function"==typeof a&&b===void 0)// Handles the case where only the component function is passed.
c=a;else throw new Error("Invalid arguments for Portal HOC. Use Portal(component), Portal(providerNodeInstance, component), or (deprecated) Portal(providerArray, component).");// --- Core Content Renderer Function ---
// This function is the actual React component that will be rendered inside the portal.
// It receives props and handles theme application and portal control.
var f=function Renderer(){var a=0<arguments.length&&arguments[0]!==void 0?arguments[0]:{},b=a.nodetheme,d=_objectWithoutProperties(a,_excluded),f=c(_objectSpread(_objectSpread({},d),{},{portal:e// Passes the portal control object to the content component
}));// Ensures that the theme is correctly applied if the result is a BaseNode.
if(f instanceof BaseNode){var g,h;return Node(f.element,_objectSpread(_objectSpread({},f.rawProps),{},{nodetheme:(null===(g=f.rawProps)||void 0===g?void 0:g.nodetheme)||(null===(h=f.rawProps)||void 0===h?void 0:h.theme)||a.nodetheme})).render()}return f};// --- Portal Launcher Function (Returned to User) ---
// This is the function that developers call to actually create and manage a portal instance.
return function Func(a){// --- Helper for Deep Content Injection ---
// Recursively injects content into the deepest child of a provider chain.
function injectContentDeeply(a,b,c){var d,e,f,g=null===(d=a.rawProps)||void 0===d?void 0:d.children;// If no children, or children is not a NodeInstance, inject directly
if(!g||!(g instanceof BaseNode)){var h,i;return Node(a.element,_objectSpread(_objectSpread({},a.rawProps),{},{children:b,nodetheme:(null===(h=a.rawProps)||void 0===h?void 0:h.nodetheme)||(null===(i=a.rawProps)||void 0===i?void 0:i.theme)||c}))}// Recursively inject into the deepest node
var j=injectContentDeeply(g,b);return Node(a.element,_objectSpread(_objectSpread({},a.rawProps),{},{children:j,nodetheme:(null===(e=a.rawProps)||void 0===e?void 0:e.nodetheme)||(null===(f=a.rawProps)||void 0===f?void 0:f.theme)||c}))}// --- Provider Wrapping Logic ---
// Iterates through the combined providers (fixed + dynamic) to wrap the content.
// Providers are applied in reverse order to ensure the innermost content is wrapped by the outermost provider.
var b,c=[];// Combine fixed and dynamic providers
Array.isArray(a.provider)?(console.warn("Portal: Passing an array of providers as the `provider` prop is deprecated. Please pass a single NodeInstance instead."),c.push(...a.provider)):a.provider&&c.push(a.provider);var g=[...(null!==d&&void 0!==d?d:[]),...c],h=a.provider,i=a.nodetheme,j=_objectWithoutProperties(a,_excluded2),k=_objectSpread(_objectSpread({},j),{},{nodetheme:i}),l=Node(f,k);// Separates props for the portal's content from internal props like 'provider' or 'nodetheme'.
// Creates the base node for the portal's content.
return b=0<g.length?g.reduceRight(function(a,b){var c,d,e;if(!(b instanceof BaseNode))return console.warn("Portal: Item in provider is not a valid NodeInstance. Skipping.",b),a;var f=(null===(c=b.rawProps)||void 0===c?void 0:c.children)instanceof BaseNode;// If the provider already has nested children, inject content deeply.
// Otherwise, simply set currentWrappedContent as its direct child.
return f?injectContentDeeply(b,a,i):Node(b.element,_objectSpread(_objectSpread({},b.rawProps),{},{children:a,nodetheme:(null===(d=b.rawProps)||void 0===d?void 0:d.nodetheme)||(null===(e=b.rawProps)||void 0===e?void 0:e.theme)||i}))},l):l,e=b.toPortal(),e}}