JezK
Edit File: Mega_Menu.php
<?php namespace Essential_Addons_Elementor\Elements; // If this file is called directly, abort. if ( ! defined( 'ABSPATH' ) ) { exit; } use Elementor\Modules\NestedElements\Base\Widget_Nested_Base; use Essential_Addons_Elementor\Classes\Helper; use Essential_Addons_Elementor\MegaMenu\Controls\Content_Controls; use Essential_Addons_Elementor\MegaMenu\Controls\Style_Controls; use Essential_Addons_Elementor\MegaMenu\Manager; use Essential_Addons_Elementor\MegaMenu\Renderers\Editor_Renderer; use Essential_Addons_Elementor\MegaMenu\Renderers\Frontend_Renderer; use Essential_Addons_Elementor\MegaMenu\Traits\Menu_Items; /** * Mega Menu. * * A nested Elementor widget: every menu item behaves like a tab and owns its own * container, so any widget can be dropped inside a submenu panel. * * The class is only ever instantiated when * \Essential_Addons_Elementor\MegaMenu\Conditions::is_supported() passes — see the * `condition` entry for `mega-menu` in config.php — because `Widget_Nested_Base` * only ships with Elementor 3.8+. * * @since 6.3.0 */ class Mega_Menu extends Widget_Nested_Base { use Menu_Items; /** * @var Frontend_Renderer|null */ protected $renderer = null; /** * @inheritDoc */ public function get_name() { return Manager::WIDGET_NAME; } /** * @inheritDoc */ public function get_title() { return esc_html__( 'Mega Menu', 'essential-addons-for-elementor-lite' ); } /** * @inheritDoc */ public function get_icon() { return 'eaicon-mega-menu'; } /** * @inheritDoc */ public function get_categories() { return [ 'essential-addons-elementor' ]; } /** * @inheritDoc */ public function get_keywords() { return [ 'mega menu', 'menu', 'nav', 'navigation', 'header', 'dropdown', 'nested', 'ea mega menu', 'ea', 'essential addons', ]; } /** * Documentation link shown as "Need Help" at the bottom of the widget panel. * * @inheritDoc */ public function get_custom_help_url() { return 'https://essential-addons.com/elementor/docs/ea-mega-menu/'; } /** * Hide the widget while Elementor's nested elements API is switched off. * * @return bool */ public function show_in_panel(): bool { return Manager::instance()->is_available_in_panel(); } /** * @inheritDoc */ public function has_widget_inner_wrapper(): bool { return ! Helper::eael_e_optimized_markup(); } /** * One container per default menu item. * * @inheritDoc */ protected function get_default_children_elements() { return Manager::instance()->get_default_children_elements(); } /** * @inheritDoc */ protected function get_default_repeater_title_setting_key() { return 'eael_mega_menu_item_label'; } /** * @inheritDoc */ protected function get_default_children_title() { /* translators: %d: Menu item index. */ return esc_html__( 'Menu Item #%d', 'essential-addons-for-elementor-lite' ); } /** * Where the editor mounts the nested container views. * * @inheritDoc */ protected function get_default_children_placeholder_selector() { return '.eael-mega-menu__panels'; } /** * @inheritDoc */ protected function register_controls() { Content_Controls::register( $this ); Style_Controls::register( $this ); } /** * Per row settings the frontend handler actually reads. * * Mirrors getItemType() / getItemSetting() in src/js/view/mega-menu.js. The * legacy switcher is kept because getItemType() still falls back to it for * rows saved before the type control existed. */ const FRONTEND_ITEM_KEYS = [ 'eael_mega_menu_item_type', 'eael_mega_menu_item_has_submenu', 'eael_mega_menu_item_submenu_width', 'eael_mega_menu_item_submenu_custom_width', 'eael_mega_menu_item_panel_align', 'eael_mega_menu_item_panel_offset_x', 'eael_mega_menu_item_panel_offset_y', ]; /** * Strip the menu item rows down to what the handler needs. * * The repeater is `frontend_available` because the handler resolves each * panel's width and placement from it — but the default serialisation ships * *every* field, labels, links, icons, template ids, CSS classes and all, * into a `data-settings` attribute. On a widget built for site headers that * is dead weight on every page of the site: a six item menu measured 7.2 KB, * roughly half of it never read. * * Only the front end is affected. Inside the editor the handler reads the * live settings model rather than this attribute, so the template preview and * every other editor-only lookup keep seeing the full row. * * @inheritDoc */ public function get_frontend_settings() { $settings = parent::get_frontend_settings(); if ( empty( $settings['eael_mega_menu_items'] ) || ! is_array( $settings['eael_mega_menu_items'] ) ) { return $settings; } $keep = array_flip( self::FRONTEND_ITEM_KEYS ); foreach ( $settings['eael_mega_menu_items'] as $index => $item ) { $settings['eael_mega_menu_items'][ $index ] = array_intersect_key( (array) $item, $keep ); } return $settings; } /** * Frontend renderer, lazily built. * * @return Frontend_Renderer */ protected function get_renderer() { if ( null === $this->renderer ) { $this->renderer = new Frontend_Renderer( $this ); } return $this->renderer; } /** * @inheritDoc */ protected function render() { $this->get_renderer()->render(); } /** * @inheritDoc */ protected function content_template() { Editor_Renderer::content_template(); } /** * Print a nested container as a submenu panel. * * Mirrors Elementor's own nested tabs approach — the container's wrapper * attributes are injected through a short lived filter so the panel keeps its * id, ARIA wiring and ordering without altering the container element itself. * * @param int $index Child index. * @param array $prepared Prepared repeater row. */ public function print_child( $index, $prepared = [] ) { $children = $this->get_children(); if ( empty( $children[ $index ] ) ) { return; } if ( empty( $prepared ) ) { $children[ $index ]->print_element(); return; } $child_id = $children[ $index ]->get_id(); $renderer = $this->get_renderer(); $decorate_container = function ( $should_render, $container ) use ( $child_id, $prepared, $renderer ) { if ( is_object( $container ) && $container->get_id() === $child_id ) { $renderer->add_panel_attributes( $container, $prepared ); } return $should_render; }; add_filter( 'elementor/frontend/container/should_render', $decorate_container, 10, 2 ); $children[ $index ]->print_element(); remove_filter( 'elementor/frontend/container/should_render', $decorate_container, 10 ); } }