start_controls_section('mxchat_placement', array( 'label' => __('Placement', 'mxchat'), 'tab' => \Elementor\Controls_Manager::TAB_CONTENT, )); $this->add_control('display_mode', array( 'label' => __('Display mode', 'mxchat'), 'type' => \Elementor\Controls_Manager::SELECT, 'default' => 'inline', 'options' => array( 'inline' => __('Inline (in the page)', 'mxchat'), 'floating' => __('Floating bubble', 'mxchat'), ), 'description' => __('Inline renders the chat box where the widget sits. Floating renders the familiar corner launcher; if the chatbot also displays automatically on this page, the usual single-widget rules apply.', 'mxchat'), )); // Bot picker only while Multi-Bot is active — same rule as the block: // no free-text bot id anywhere (a typo would mean a silently wrong // bot). Elementor SELECT keys can't be the empty string, so 'default' // is the sentinel and maps to an omitted bot_id at render, making an // untouched widget follow the page-level bot setting like a bare // shortcode. $bots = MxChat_Block::get_bot_choices(); if (!empty($bots)) { $options = array(); foreach ($bots as $bot) { $key = ($bot['value'] === '') ? 'default' : $bot['value']; $options[$key] = $bot['label']; } $this->add_control('bot_id', array( 'label' => __('Bot', 'mxchat'), 'type' => \Elementor\Controls_Manager::SELECT, 'default' => 'default', 'options' => $options, 'description' => __('Default Bot follows this page’s chatbot settings.', 'mxchat'), )); } $this->end_controls_section(); } protected function render() { $settings = $this->get_settings_for_display(); $display_mode = (isset($settings['display_mode']) && $settings['display_mode'] === 'floating') ? 'floating' : 'inline'; $bot_id = isset($settings['bot_id']) ? sanitize_key($settings['bot_id']) : ''; if ($bot_id === 'default') { $bot_id = ''; } // Editor canvas / preview iframe: static placeholder, never the live // widget — the edit-mode trap this plan is built around. $elementor = \Elementor\Plugin::$instance; $in_editor = ($elementor->editor && $elementor->editor->is_edit_mode()) || ($elementor->preview && $elementor->preview->is_preview_mode()); if ($in_editor) { $this->render_placeholder($display_mode, $bot_id); return; } echo MxChat_Block::render(array( 'displayMode' => $display_mode, 'botId' => $bot_id, )); } /** * Static canvas placeholder — same look as the Gutenberg block's * (assets/block/chatbot-block-editor.css), self-contained here because * that stylesheet never loads in the Elementor canvas. The style block is * emitted once per request; repeats are harmless dedupe misses across the * editor's separate AJAX renders. */ private function render_placeholder($display_mode, $bot_id) { static $css_emitted = false; $mode_text = ($display_mode === 'floating') ? __('Floating bubble', 'mxchat') : __('Inline (in the page)', 'mxchat'); $bots = MxChat_Block::get_bot_choices(); $meta_text = $mode_text; if (!empty($bots)) { $bot_label = __('Default Bot', 'mxchat'); foreach ($bots as $bot) { if ($bot['value'] === $bot_id && $bot_id !== '') { $bot_label = $bot['label']; break; } } $meta_text = $mode_text . ' · ' . $bot_label; } if (!$css_emitted) { $css_emitted = true; echo ''; } // Same brand icon as the Gutenberg block (chatbot-block.js). echo '
' . '' . '' . '' . '' . '' . esc_html__('MxChat Chatbot', 'mxchat') . '' . '' . esc_html($meta_text) . '' . '' . '
'; } }