UNPKG

@meonode/ui

Version:

A structured approach to component composition with built-in theming, prop separation, and dynamic children handling.

94 lines 9.61 kB
"use strict";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}}