> */ public static function all() { $items = array_merge( self::free_definitions(), self::pro_definitions() ); /** * Filter Atomic element catalog definitions. * * @param list> $items Definitions. */ $filtered = apply_filters( 'reactwoo/atomic/elements_catalog', $items ); return is_array( $filtered ) ? array_values( $filtered ) : $items; } /** * @param string $id Element id. * @return array|null */ public static function get( $id ) { $id = sanitize_key( (string) $id ); foreach ( self::all() as $item ) { if ( isset( $item['id'] ) && $id === (string) $item['id'] ) { return $item; } } return null; } /** * Whether an item may register with Elementor. * * @param array $item Catalog row. * @return bool */ public static function is_registrable( array $item ) { $status = isset( $item['status'] ) ? (string) $item['status'] : 'prototype'; if ( in_array( $status, array( 'hidden', 'prototype' ), true ) ) { return false; } if ( 'stable' === $status ) { return true; } if ( 'beta' === $status ) { /** * Allow beta Atomic elements (default: Content Switcher Phase 1). * * @param bool $allow Default true. * @param array $item Catalog row. */ return (bool) apply_filters( 'reactwoo/atomic/allow_beta', true, $item ); } if ( 'alpha' === $status ) { return (bool) apply_filters( 'reactwoo/atomic/allow_alpha', false, $item ); } return false; } /** * Structural child types must register whenever their parent is registrable. * * @return list */ public static function registrable_element_classes() { $classes = array(); foreach ( self::all() as $item ) { if ( empty( $item['widget_class'] ) ) { continue; } if ( empty( $item['atomic_type'] ) || 'element' !== $item['atomic_type'] ) { continue; } if ( ! empty( $item['structural'] ) ) { continue; } if ( ! self::is_registrable( $item ) ) { continue; } $classes[] = (string) $item['widget_class']; if ( ! empty( $item['structural_classes'] ) && is_array( $item['structural_classes'] ) ) { foreach ( $item['structural_classes'] as $sc ) { $classes[] = (string) $sc; } } } return array_values( array_unique( $classes ) ); } /** * @return list */ public static function registrable_widget_classes() { $classes = array(); foreach ( self::all() as $item ) { if ( empty( $item['widget_class'] ) ) { continue; } if ( ! self::is_registrable( $item ) ) { continue; } if ( ! empty( $item['structural_widget_classes'] ) && is_array( $item['structural_widget_classes'] ) ) { foreach ( $item['structural_widget_classes'] as $sc ) { $classes[] = (string) $sc; } } if ( empty( $item['atomic_type'] ) || 'widget' !== $item['atomic_type'] ) { continue; } $classes[] = (string) $item['widget_class']; } return array_values( array_unique( $classes ) ); } /** * Map Elementor widget class => catalog id. * * @return array */ public static function class_to_id_map() { $map = array(); foreach ( self::all() as $item ) { if ( ! empty( $item['widget_class'] ) && ! empty( $item['id'] ) ) { $map[ (string) $item['widget_class'] ] = (string) $item['id']; } if ( ! empty( $item['structural_classes'] ) && is_array( $item['structural_classes'] ) ) { foreach ( $item['structural_classes'] as $sc ) { $map[ (string) $sc ] = (string) $item['id']; } } if ( ! empty( $item['structural_widget_classes'] ) && is_array( $item['structural_widget_classes'] ) ) { foreach ( $item['structural_widget_classes'] as $sc ) { $map[ (string) $sc ] = (string) $item['id']; } } } return $map; } /** * Asset folder key under assets/widgets/. * * @param string $id Element id. * @return string */ public static function asset_folder( $id ) { $item = self::get( $id ); return $item && ! empty( $item['asset_folder'] ) ? (string) $item['asset_folder'] : ''; } /** * @return list */ public static function categories() { return array( 'layout', 'interaction', 'navigation', 'dynamic', 'woocommerce', 'ai' ); } /** * @return list> */ private static function free_definitions() { $ns = 'ReactWoo\\Atomic\\Widgets\\Free\\'; $cs = $ns . 'ContentSwitcher\\'; $cr = $ns . 'Carousel\\'; $oc = $ns . 'Offcanvas\\'; $md = $ns . 'Modal\\'; $ba = $ns . 'BeforeAfter\\'; $pr = $ns . 'Progress\\'; $hs = $ns . 'Hotspots\\'; $sm = $ns . 'SmartMenu\\'; $ct = $ns . 'ComparisonTable\\'; $tl = $ns . 'Timeline\\'; $mq = $ns . 'Marquee\\'; $sn = $ns . 'ScrollNav\\'; $mf = $ns . 'MultistepForm\\'; return array( self::def( 'reactwoo-content-switcher', 'Content Switcher', 'Accessible tabbed content sections.', 'interaction', 'free', true, true, 3, 'content-switcher', $cs . 'Content_Switcher', 'beta', 'element', 'managed-composite', array( $cs . 'Switcher_Menu', $cs . 'Switcher_Trigger', $cs . 'Switcher_Content_Area', $cs . 'Switcher_Panel', ) ), self::def( 'reactwoo-atomic-carousel', 'Atomic Carousel', 'Accessible multi-slide carousel with arrows and dots.', 'interaction', 'free', true, true, 3, 'carousel', $cr . 'Carousel', 'beta', 'element', 'open-composite', array( $cr . 'Carousel_Viewport', $cr . 'Carousel_Track', $cr . 'Carousel_Slide', $cr . 'Carousel_Prev', $cr . 'Carousel_Next', $cr . 'Carousel_Pagination', $cr . 'Carousel_Dot', ) ), self::def( 'reactwoo-smart-menu', 'Smart Menu', 'Responsive mega menu with hover/click desktop panels and mobile drilldown.', 'navigation', 'free', true, true, 3, 'smart-menu', $sm . 'Smart_Menu', 'beta', 'element', 'open-composite', array( $sm . 'Smart_Menu_Toggle', $sm . 'Smart_Menu_Desktop', $sm . 'Smart_Menu_Mobile', $sm . 'Smart_Menu_Item', $sm . 'Smart_Menu_Link', $sm . 'Smart_Menu_Panel', ) ), self::def( 'reactwoo-offcanvas', 'Off-Canvas Drawer', 'Sliding drawer panel triggered by a button, with header/body/footer regions.', 'interaction', 'free', true, true, 3, 'offcanvas', $oc . 'Offcanvas', 'beta', 'element', 'open-composite', array( $oc . 'Offcanvas_Trigger', $oc . 'Offcanvas_Overlay', $oc . 'Offcanvas_Panel', $oc . 'Offcanvas_Header', $oc . 'Offcanvas_Title', $oc . 'Offcanvas_Close', $oc . 'Offcanvas_Body', $oc . 'Offcanvas_Footer', ) ), self::def( 'reactwoo-modal', 'Modal', 'Centered dialog overlay triggered by a button, with header/body/footer regions.', 'interaction', 'free', true, true, 3, 'modal', $md . 'Modal', 'beta', 'element', 'open-composite', array( $md . 'Modal_Trigger', $md . 'Modal_Overlay', $md . 'Modal_Dialog', $md . 'Modal_Header', $md . 'Modal_Title', $md . 'Modal_Close', $md . 'Modal_Body', $md . 'Modal_Footer', ) ), self::def( 'reactwoo-before-after', 'Before & After', 'Draggable before/after image comparison slider with keyboard support.', 'interaction', 'free', true, true, 3, 'before-after', $ba . 'Before_After', 'beta', 'widget', 'leaf' ), self::def( 'reactwoo-hotspots', 'Hotspots', 'Managed image hotspots: markers with click/hover popovers over a background image.', 'interaction', 'free', true, true, 3, 'hotspots', $hs . 'Hotspots', 'beta', 'element', 'managed-composite', array( $hs . 'Hotspot_Item', $hs . 'Hotspot_Marker', $hs . 'Hotspot_Popover', ) ), self::def( 'reactwoo-comparison-table', 'Comparison Table', 'Pricing plan cards (default) or a features x plans comparison table.', 'layout', 'free', true, true, 3, 'comparison-table', $ct . 'Comparison_Table', 'beta', 'element', 'managed-composite', array( $ct . 'Comparison_Table_Plan', $ct . 'Comparison_Table_Row', ) ), self::def( 'reactwoo-timeline', 'Timeline', 'Ordered timeline of dated events with optional scroll reveal.', 'layout', 'free', true, true, 3, 'timeline', $tl . 'Timeline', 'beta', 'element', 'managed-composite', array( $tl . 'Timeline_Item', $tl . 'Timeline_Content', ) ), self::def( 'reactwoo-marquee', 'Marquee', 'Accessible scrolling ticker built from editable content segments.', 'layout', 'free', true, true, 3, 'marquee', $mq . 'Marquee', 'beta', 'element', 'managed-composite', array( $mq . 'Marquee_Viewport', $mq . 'Marquee_Track', $mq . 'Marquee_Segment', ) ), self::def( 'reactwoo-scroll-nav', 'Scroll Nav', 'Sticky section navigator with smooth scroll and scroll-spy highlighting.', 'navigation', 'free', true, true, 3, 'scroll-nav', $sn . 'Scroll_Nav', 'beta', 'widget', 'leaf' ), self::def( 'reactwoo-progress', 'Progress', 'Scroll-tracking progress indicator: bar, ring, or section markers, fixed or inline.', 'layout', 'free', true, true, 3, 'progress', $pr . 'Progress', 'beta', 'widget', 'leaf' ), self::def( 'reactwoo-multistep-form', 'Multi-step Form', 'Multi-step lead form: option cards, fields, progress, validation, email delivery and redirect.', 'interaction', 'free', true, true, 3, 'multistep-form', $mf . 'Multistep_Form', 'beta', 'element', 'leaf' ), ); } /** * @return list> */ private static function pro_definitions() { $ns = 'ReactWoo\\Atomic\\Widgets\\Pro\\'; $dl = $ns . 'DynamicLoop\\'; $rl = $ns . 'RelationshipLoop\\'; $rpl = $ns . 'RepeaterLoop\\'; $dc = $ns . 'DynamicCarousel\\'; $dg = $ns . 'DynamicGallery\\'; $dt = $ns . 'DynamicTable\\'; $pg = $ns . 'ProductGrid\\'; $rp = $ns . 'RelatedProducts\\'; $pt = $ns . 'ProductTable\\'; $rv = $ns . 'RecentlyViewed\\'; $dmm = $ns . 'DynamicMegaMenu\\'; $qv = $ns . 'QuickView\\'; $cd = $ns . 'CartDrawer\\'; $ai = $ns . 'AiConcierge\\'; return array( self::def( 'reactwoo-dynamic-loop', 'Dynamic Loop', 'Query-driven post loop with shared item template.', 'dynamic', 'pro', true, true, 4, 'dynamic-loop', $dl . 'Dynamic_Loop', 'beta', 'element', 'data-template', array( $dl . 'Dynamic_Loop_List', $dl . 'Dynamic_Loop_Item' ), array( $dl . 'Loop_Field', $dl . 'Dynamic_Loop_Empty', $dl . 'Dynamic_Loop_Filter' ) ), self::def( 'reactwoo-relationship-loop', 'Relationship Loop', 'Related content loop with shared item template.', 'dynamic', 'pro', true, true, 4, 'relationship-loop', $rl . 'Relationship_Loop', 'beta', 'element', 'data-template', array( $rl . 'Relationship_Loop_List', $rl . 'Relationship_Loop_Item' ) ), self::def( 'reactwoo-repeater-loop', 'Repeater Loop', 'Manual post selection loop with shared item template.', 'dynamic', 'pro', true, true, 4, 'repeater-loop', $rpl . 'Repeater_Loop', 'beta', 'element', 'data-template', array( $rpl . 'Repeater_Loop_List', $rpl . 'Repeater_Loop_Item' ) ), self::def( 'reactwoo-dynamic-carousel', 'Dynamic Carousel', 'Query-driven carousel with shared item template.', 'dynamic', 'pro', true, true, 4, 'dynamic-carousel', $dc . 'Dynamic_Carousel', 'beta', 'element', 'data-template', array( $dc . 'Dynamic_Carousel_List', $dc . 'Dynamic_Carousel_Item' ) ), self::def( 'reactwoo-dynamic-gallery', 'Dynamic Gallery', 'Query-driven gallery with shared item template.', 'dynamic', 'pro', true, true, 4, 'dynamic-gallery', $dg . 'Dynamic_Gallery', 'beta', 'element', 'data-template', array( $dg . 'Dynamic_Gallery_List', $dg . 'Dynamic_Gallery_Item' ) ), self::def( 'reactwoo-smart-filters', 'Smart Filters', 'URL-driven product filters with applied chips, result count, clear all and a mobile drawer.', 'dynamic', 'pro', true, false, 3, 'smart-filters', $ns . 'SmartFilters\\Smart_Filters', 'beta', 'element', 'open-composite', array(), array( $ns . 'SmartFilters\\Smart_Filters_Chips', $ns . 'SmartFilters\\Smart_Filters_Panel', $ns . 'SmartFilters\\Smart_Filters_Results', ) ), self::def( 'reactwoo-live-search', 'Live Search', 'Accessible search box with live, keyboard-friendly suggestions.', 'dynamic', 'pro', true, false, 3, 'live-search', $ns . 'LiveSearch\\Live_Search', 'beta', 'element', 'open-composite', array(), array( $ns . 'LiveSearch\\Live_Search_Label', $ns . 'LiveSearch\\Live_Search_Field', $ns . 'LiveSearch\\Live_Search_Results', ) ), self::def( 'reactwoo-directory-map', 'Directory + Map', 'Location directory: accessible list with a synchronised map.', 'dynamic', 'pro', true, false, 4, 'directory-map', $ns . 'DirectoryMap\\Directory_Map', 'beta', 'element', 'open-composite', array( $ns . 'DirectoryMap\\Directory_Map_Results', $ns . 'DirectoryMap\\Directory_Map_Location', ), array( $ns . 'DirectoryMap\\Directory_Map_Canvas', ) ), self::def( 'reactwoo-dynamic-table', 'Dynamic Table', 'Query-driven table with shared item template.', 'dynamic', 'pro', true, true, 4, 'dynamic-table', $dt . 'Dynamic_Table', 'beta', 'element', 'data-template', array( $dt . 'Dynamic_Table_List', $dt . 'Dynamic_Table_Item' ) ), self::def( 'reactwoo-dynamic-mega-menu', 'Dynamic Mega Menu', 'Query-aware mega menu composite.', 'navigation', 'pro', true, true, 4, 'dynamic-mega-menu', $dmm . 'Dynamic_Mega_Menu', 'beta', 'element', 'open-composite', array( $dmm . 'Dynamic_Mega_Menu_Trigger', $dmm . 'Dynamic_Mega_Menu_Panel', $dmm . 'Dynamic_Mega_Menu_Body', ) ), self::def( 'reactwoo-product-grid', 'Product Grid', 'WooCommerce product grid with shared item template.', 'woocommerce', 'pro', true, true, 4, 'product-grid', $pg . 'Product_Grid', 'beta', 'element', 'data-template', array( $pg . 'Product_Grid_List', $pg . 'Product_Grid_Item' ) ), self::def( 'reactwoo-related-products', 'Related Products', 'Related WooCommerce products with shared item template.', 'woocommerce', 'pro', true, true, 4, 'related-products', $rp . 'Related_Products', 'beta', 'element', 'data-template', array( $rp . 'Related_Products_List', $rp . 'Related_Products_Item' ) ), self::def( 'reactwoo-ajax-product-search', 'AJAX Product Search', 'Live WooCommerce product search with image, name and price suggestions.', 'woocommerce', 'pro', true, false, 3, 'ajax-product-search', $ns . 'AjaxProductSearch\\Ajax_Product_Search', 'beta', 'element', 'open-composite', array(), array( $ns . 'AjaxProductSearch\\Ajax_Product_Search_Label', $ns . 'AjaxProductSearch\\Ajax_Product_Search_Field', $ns . 'AjaxProductSearch\\Ajax_Product_Search_Results', ) ), self::def( 'reactwoo-quick-view', 'Quick View', 'Product quick-view dialog composite.', 'woocommerce', 'pro', true, true, 4, 'quick-view', $qv . 'Quick_View', 'beta', 'element', 'open-composite', array( $qv . 'Quick_View_Trigger', $qv . 'Quick_View_Panel', $qv . 'Quick_View_Body', ) ), self::def( 'reactwoo-cart-drawer', 'Cart Drawer', 'Off-canvas WooCommerce cart drawer.', 'woocommerce', 'pro', true, true, 4, 'cart-drawer', $cd . 'Cart_Drawer', 'beta', 'element', 'open-composite', array( $cd . 'Cart_Drawer_Trigger', $cd . 'Cart_Drawer_Panel', $cd . 'Cart_Drawer_Body', ) ), self::def( 'reactwoo-sticky-add-to-cart', 'Sticky Add to Cart', 'Purchase bar that appears once the product add-to-cart button leaves view.', 'woocommerce', 'pro', true, false, 3, 'sticky-add-to-cart', $ns . 'StickyAddToCart\\Sticky_Add_To_Cart', 'beta', 'element', 'open-composite', array(), array( $ns . 'StickyAddToCart\\Sticky_Add_To_Cart_Thumb', $ns . 'StickyAddToCart\\Sticky_Add_To_Cart_Action', ) ), self::def( 'reactwoo-bought-together', 'Bought Together', 'Frequently bought together upsells.', 'woocommerce', 'pro', true, false, 3, 'bought-together', $ns . 'BoughtTogether\\Bought_Together', 'beta', 'element', 'open-composite', array(), array( $ns . 'BoughtTogether\\Bought_Together_Heading', $ns . 'BoughtTogether\\Bought_Together_Items', $ns . 'BoughtTogether\\Bought_Together_Total', $ns . 'BoughtTogether\\Bought_Together_Action', ) ), self::def( 'reactwoo-product-compare', 'Product Compare', 'Comparison matrix with frozen labels, difference emphasis and a mobile scroll cue.', 'woocommerce', 'pro', true, false, 3, 'product-compare', $ns . 'ProductCompare\\Product_Compare', 'beta', 'element', 'open-composite', array(), array( $ns . 'ProductCompare\\Product_Compare_Products', $ns . 'ProductCompare\\Product_Compare_Attributes', ) ), self::def( 'reactwoo-wishlist', 'Wishlist', 'Saved-for-later list with move to basket, undo and signed-out storage.', 'woocommerce', 'pro', true, false, 3, 'wishlist', $ns . 'Wishlist\\Wishlist', 'beta', 'element', 'open-composite', array(), array( $ns . 'Wishlist\\Wishlist_Header', $ns . 'Wishlist\\Wishlist_Items', $ns . 'Wishlist\\Wishlist_Note', ) ), self::def( 'reactwoo-variation-swatches', 'Variation Swatches', 'Accessible colour and size swatches for variable products.', 'woocommerce', 'pro', true, false, 3, 'variation-swatches', $ns . 'VariationSwatches\\Variation_Swatches', 'beta', 'element', 'open-composite', array(), array( $ns . 'VariationSwatches\\Variation_Swatches_Group', ) ), self::def( 'reactwoo-product-table', 'Product Table', 'WooCommerce product table with shared item template.', 'woocommerce', 'pro', true, true, 4, 'product-table', $pt . 'Product_Table', 'beta', 'element', 'data-template', array( $pt . 'Product_Table_List', $pt . 'Product_Table_Item' ) ), self::def( 'reactwoo-recently-viewed', 'Recently Viewed', 'Recently viewed products with shared item template.', 'woocommerce', 'pro', true, false, 3, 'recently-viewed', $rv . 'Recently_Viewed', 'beta', 'element', 'data-template', array( $rv . 'Recently_Viewed_List', $rv . 'Recently_Viewed_Item' ) ), self::def( 'reactwoo-form-summary', 'Form Answer Summary', 'Thank-you page summary of a Multi-step Form submission; cache-safe, loads the answers over REST.', 'interaction', 'pro', true, true, 2, 'form-summary', $ns . 'FormSummary\\Form_Summary', 'beta', 'element', 'leaf' ), self::def( 'reactwoo-shipping-progress', 'Shipping Progress', 'Live free-delivery threshold with progress track and complete state.', 'woocommerce', 'pro', true, false, 3, 'shipping-progress', $ns . 'ShippingProgress\\Shipping_Progress', 'beta', 'element', 'open-composite', array( $ns . 'ShippingProgress\\Shipping_Progress_Card', ), array( $ns . 'ShippingProgress\\Shipping_Progress_Track', ) ), self::def( 'reactwoo-ai-concierge', 'AI Concierge', 'Ability-gated AI assistant composite.', 'ai', 'pro', true, true, 4, 'ai-concierge', $ai . 'Ai_Concierge', 'beta', 'element', 'managed-composite', array( $ai . 'Ai_Concierge_Messages', $ai . 'Ai_Concierge_Form', ) ), ); } /** * Prototype / hidden catalogue row. * * @param string $id ID. * @param string $name Name. * @param string $category Category. * @param string $edition Edition. * @param string $folder Asset folder. * @param string $class Class. * @param string $architecture Architecture. * @return array */ private static function proto( $id, $name, $category, $edition, $folder, $class, $architecture ) { return self::def( $id, $name, __( 'Prototype — hidden until rebuilt to Atomic contract.', 'reactwoo-atomic' ), $category, $edition, false, false, 1, $folder, $class, 'prototype', ( false !== strpos( $architecture, 'composite' ) || 'data-template' === $architecture ) ? 'element' : 'widget', $architecture, array() ); } /** * @param string $id ID. * @param string $name Name. * @param string $description Description. * @param string $category Category. * @param string $edition free|pro. * @param bool $enabled_by_default Default on. * @param bool $recommended Recommended. * @param int $asset_size KB estimate. * @param string $asset_folder Asset folder. * @param string $widget_class Widget class. * @param string $status Status. * @param string $atomic_type widget|element. * @param string $architecture Architecture. * @param list $structural_classes Structural child classes. * @param list $structural_widget_classes Leaf widget atoms of an element family. * @return array */ private static function def( $id, $name, $description, $category, $edition, $enabled_by_default, $recommended, $asset_size, $asset_folder, $widget_class, $status = 'prototype', $atomic_type = 'widget', $architecture = 'leaf', array $structural_classes = array(), array $structural_widget_classes = array() ) { return array( 'id' => $id, 'name' => $name, 'description' => $description, 'category' => $category, 'edition' => $edition, 'enabled_by_default' => (bool) $enabled_by_default, 'recommended' => (bool) $recommended, 'asset_size' => (int) $asset_size, 'icon' => strtoupper( substr( preg_replace( '/[^a-z]/i', '', $name ), 0, 1 ) ), 'dependencies' => array(), 'status' => $status, 'atomic_type' => $atomic_type, 'architecture' => $architecture, 'asset_folder' => $asset_folder, 'widget_class' => $widget_class, 'structural_classes' => $structural_classes, // Leaf atoms that belong to an element family (e.g. Dynamic Loop field atoms): // registered as widgets whenever the family is. 'structural_widget_classes' => $structural_widget_classes, ); } }