Plugin Directory

Changeset 3458971


Ignore:
Timestamp:
02/11/2026 01:02:27 PM (7 weeks ago)
Author:
appwavedev
Message:

Release 1.0.22: Enable Widget checkbox fix, Elementor shortcode detection, shortcode-only script loading, Test Connection removed

Location:
bettercx-widget/trunk
Files:
8 added
14 edited

Legend:

Unmodified
Added
Removed
  • bettercx-widget/trunk/assets/admin.css

    r3370223 r3458971  
    8888}
    8989
    90 #test-connection {
    91   margin-left: 10px;
    92 }
    93 
    94 #test-connection:disabled {
    95   opacity: 0.6;
    96   cursor: not-allowed;
    97 }
  • bettercx-widget/trunk/assets/admin.js

    r3370223 r3458971  
    11jQuery(document).ready(function ($) {
    2   // Test connection functionality
    3   $('#test-connection').on('click', function (e) {
    4     e.preventDefault();
    5 
    6     var button = $(this);
    7     var originalText = button.text();
    8 
    9     button.prop('disabled', true).text('Testing...');
    10 
    11     $.ajax({
    12       url: bettercxWidgetAdmin.ajaxUrl,
    13       type: 'POST',
    14       data: {
    15         action: 'bettercx_test_connection',
    16         nonce: bettercxWidgetAdmin.nonce,
    17       },
    18       success: function (response) {
    19         if (response.success) {
    20           alert('Connection test successful!');
    21         } else {
    22           alert('Connection test failed: ' + response.data);
    23         }
    24       },
    25       error: function () {
    26         alert('Connection test failed: Network error');
    27       },
    28       complete: function () {
    29         button.prop('disabled', false).text(originalText);
    30       },
    31     });
    32   });
    33 
    342  // Form validation
    353  $('#bettercx-widget-settings-form').on('submit', function (e) {
  • bettercx-widget/trunk/assets/bettercx-widget.esm.js

    r3435673 r3458971  
    1 import{p as e,g as a,b as t}from"./p-BnsX22WT.js";export{s as setNonce}from"./p-BnsX22WT.js";(()=>{const a=import.meta.url,s={};return""!==a&&(s.resourcesUrl=new URL(".",a).href),e(s)})().then((async e=>(await a(),t([["p-c0e0975b",[[257,"bcx-chat-list",{apiService:[16,"api-service"],language:[1],theme:[1],chats:[32],selectedChatId:[32],messages:[32],isLoading:[32],isLoadingMore:[32],hasMore:[32],currentPage:[32],error:[32]},null,{language:["onLanguageChange"]}],[257,"bcx-message-composer",{disabled:[4],loading:[4],placeholder:[1],maxLength:[2,"max-length"],theme:[1],isAttachmentsDisabled:[4,"is-attachments-disabled"],message:[32],images:[32]}],[257,"bcx-product-slider",{products:[16],language:[1],showAfterStreaming:[4,"show-after-streaming"],currentIndex:[32],isVisible:[32]},null,{products:["onProductsChange"],showAfterStreaming:["onShowAfterStreamingChange"]}]]],["p-66ae8630",[[257,"bettercx-widget",{publicKey:[1,"public-key"],theme:[1],debug:[4],baseUrl:[1,"base-url"],aiServiceUrl:[1,"ai-service-url"],autoInit:[4,"auto-init"],position:[1],language:[1],embedded:[4],embeddedSize:[1,"embedded-size"],embeddedPlacement:[1,"embedded-placement"],isAttachmentsDisabled:[4,"is-attachments-disabled"],state:[32],timeUpdateTrigger:[32],isDropdownOpen:[32],isFullscreen:[32],showChatList:[32],showDelayMessage:[32],delayMessageType:[32],open:[64],close:[64],toggle:[64],sendMessage:[64]},null,{publicKey:["onPublicKeyChange"],theme:["onThemeChange"],language:["onLanguageChange"],"state.messages":["onMessagesChange"],embedded:["onEmbeddedChange"],embeddedSize:["onEmbeddedSizeChange"],embeddedPlacement:["onEmbeddedPlacementChange"]}]]]],e))));
     1import{p as e,g as a,b as t}from"./p-BnsX22WT.js";export{s as setNonce}from"./p-BnsX22WT.js";(()=>{const a=import.meta.url,s={};return""!==a&&(s.resourcesUrl=new URL(".",a).href),e(s)})().then((async e=>(await a(),t([["p-51b4eee3",[[257,"bcx-chat-list",{apiService:[16,"api-service"],language:[1],theme:[1],chats:[32],selectedChatId:[32],messages:[32],isLoading:[32],isLoadingMore:[32],hasMore:[32],currentPage:[32],error:[32]},null,{language:["onLanguageChange"]}],[257,"bcx-message-composer",{disabled:[4],loading:[4],placeholder:[1],maxLength:[2,"max-length"],theme:[1],isAttachmentsDisabled:[4,"is-attachments-disabled"],message:[32],images:[32]}],[257,"bcx-product-slider",{products:[16],language:[1],showAfterStreaming:[4,"show-after-streaming"],currentIndex:[32],isVisible:[32]},null,{products:["onProductsChange"],showAfterStreaming:["onShowAfterStreamingChange"]}]]],["p-798e9c30",[[257,"bettercx-widget",{publicKey:[1,"public-key"],theme:[1],debug:[4],baseUrl:[1,"base-url"],aiServiceUrl:[1,"ai-service-url"],autoInit:[4,"auto-init"],position:[1],language:[1],embedded:[4],embeddedSize:[1,"embedded-size"],embeddedPlacement:[1,"embedded-placement"],isAttachmentsDisabled:[4,"is-attachments-disabled"],state:[32],timeUpdateTrigger:[32],isDropdownOpen:[32],isFullscreen:[32],showChatList:[32],showDelayMessage:[32],delayMessageType:[32],open:[64],close:[64],toggle:[64],sendMessage:[64]},null,{publicKey:["onPublicKeyChange"],theme:["onThemeChange"],language:["onLanguageChange"],"state.messages":["onMessagesChange"],embedded:["onEmbeddedChange"],embeddedSize:["onEmbeddedSizeChange"],embeddedPlacement:["onEmbeddedPlacementChange"]}]]]],e))));
  • bettercx-widget/trunk/assets/index.esm.js

    r3435673 r3458971  
    1 export{a as ApiService,A as AuthService,B as BetterCXWidget,T as ThemeService}from"./p-DC9T6BNW.js";import"./p-BnsX22WT.js";import"./p-BU1bGO0l.js";function e(e,r,t){return(e||"")+(r?` ${r}`:"")+(t?` ${t}`:"")}export{e as format}
     1export{a as ApiService,A as AuthService,B as BetterCXWidget,T as ThemeService}from"./p-BTyCVrZy.js";import"./p-BnsX22WT.js";import"./p-BU1bGO0l.js";function e(e,r,t){return(e||"")+(r?` ${r}`:"")+(t?` ${t}`:"")}export{e as format}
  • bettercx-widget/trunk/assets/p-V8up-zPo.system.js

    r3435673 r3458971  
    1 var __awaiter=this&&this.__awaiter||function(e,t,n,a){function i(e){return e instanceof n?e:new n((function(t){t(e)}))}return new(n||(n=Promise))((function(n,r){function s(e){try{c(a.next(e))}catch(e){r(e)}}function o(e){try{c(a["throw"](e))}catch(e){r(e)}}function c(e){e.done?n(e.value):i(e.value).then(s,o)}c((a=a.apply(e,t||[])).next())}))};var __generator=this&&this.__generator||function(e,t){var n={label:0,sent:function(){if(r[0]&1)throw r[1];return r[1]},trys:[],ops:[]},a,i,r,s;return s={next:o(0),throw:o(1),return:o(2)},typeof Symbol==="function"&&(s[Symbol.iterator]=function(){return this}),s;function o(e){return function(t){return c([e,t])}}function c(o){if(a)throw new TypeError("Generator is already executing.");while(s&&(s=0,o[0]&&(n=0)),n)try{if(a=1,i&&(r=o[0]&2?i["return"]:o[0]?i["throw"]||((r=i["return"])&&r.call(i),0):i.next)&&!(r=r.call(i,o[1])).done)return r;if(i=0,r)o=[o[0]&2,r.value];switch(o[0]){case 0:case 1:r=o;break;case 4:n.label++;return{value:o[1],done:false};case 5:n.label++;i=o[1];o=[0];continue;case 7:o=n.ops.pop();n.trys.pop();continue;default:if(!(r=n.trys,r=r.length>0&&r[r.length-1])&&(o[0]===6||o[0]===2)){n=0;continue}if(o[0]===3&&(!r||o[1]>r[0]&&o[1]<r[3])){n.label=o[1];break}if(o[0]===6&&n.label<r[1]){n.label=r[1];r=o;break}if(r&&n.label<r[2]){n.label=r[2];n.ops.push(o);break}if(r[2])n.ops.pop();n.trys.pop();continue}o=t.call(e,n)}catch(e){o=[6,e];i=0}finally{a=r=0}if(o[0]&5)throw o[1];return{value:o[0]?o[1]:void 0,done:true}}};System.register(["./p-Cbgoi924.system.js"],(function(e,t){"use strict";var n,a,i;return{setters:[function(t){n=t.p;a=t.g;i=t.b;e("setNonce",t.s)}],execute:function(){var e=this;var r=function(){var e=t.meta.url;var a={};if(e!==""){a.resourcesUrl=new URL(".",e).href}return n(a)};r().then((function(t){return __awaiter(e,void 0,void 0,(function(){return __generator(this,(function(e){switch(e.label){case 0:return[4,a()];case 1:e.sent();return[2,i([["p-5d3a139e.system",[[257,"bcx-chat-list",{apiService:[16,"api-service"],language:[1],theme:[1],chats:[32],selectedChatId:[32],messages:[32],isLoading:[32],isLoadingMore:[32],hasMore:[32],currentPage:[32],error:[32]},null,{language:["onLanguageChange"]}],[257,"bcx-message-composer",{disabled:[4],loading:[4],placeholder:[1],maxLength:[2,"max-length"],theme:[1],isAttachmentsDisabled:[4,"is-attachments-disabled"],message:[32],images:[32]}],[257,"bcx-product-slider",{products:[16],language:[1],showAfterStreaming:[4,"show-after-streaming"],currentIndex:[32],isVisible:[32]},null,{products:["onProductsChange"],showAfterStreaming:["onShowAfterStreamingChange"]}]]],["p-05e57c9d.system",[[257,"bettercx-widget",{publicKey:[1,"public-key"],theme:[1],debug:[4],baseUrl:[1,"base-url"],aiServiceUrl:[1,"ai-service-url"],autoInit:[4,"auto-init"],position:[1],language:[1],embedded:[4],embeddedSize:[1,"embedded-size"],embeddedPlacement:[1,"embedded-placement"],isAttachmentsDisabled:[4,"is-attachments-disabled"],state:[32],timeUpdateTrigger:[32],isDropdownOpen:[32],isFullscreen:[32],showChatList:[32],showDelayMessage:[32],delayMessageType:[32],open:[64],close:[64],toggle:[64],sendMessage:[64]},null,{publicKey:["onPublicKeyChange"],theme:["onThemeChange"],language:["onLanguageChange"],"state.messages":["onMessagesChange"],embedded:["onEmbeddedChange"],embeddedSize:["onEmbeddedSizeChange"],embeddedPlacement:["onEmbeddedPlacementChange"]}]]]],t)]}}))}))}))}}}));
     1var __awaiter=this&&this.__awaiter||function(e,t,n,a){function i(e){return e instanceof n?e:new n((function(t){t(e)}))}return new(n||(n=Promise))((function(n,r){function s(e){try{c(a.next(e))}catch(e){r(e)}}function o(e){try{c(a["throw"](e))}catch(e){r(e)}}function c(e){e.done?n(e.value):i(e.value).then(s,o)}c((a=a.apply(e,t||[])).next())}))};var __generator=this&&this.__generator||function(e,t){var n={label:0,sent:function(){if(r[0]&1)throw r[1];return r[1]},trys:[],ops:[]},a,i,r,s;return s={next:o(0),throw:o(1),return:o(2)},typeof Symbol==="function"&&(s[Symbol.iterator]=function(){return this}),s;function o(e){return function(t){return c([e,t])}}function c(o){if(a)throw new TypeError("Generator is already executing.");while(s&&(s=0,o[0]&&(n=0)),n)try{if(a=1,i&&(r=o[0]&2?i["return"]:o[0]?i["throw"]||((r=i["return"])&&r.call(i),0):i.next)&&!(r=r.call(i,o[1])).done)return r;if(i=0,r)o=[o[0]&2,r.value];switch(o[0]){case 0:case 1:r=o;break;case 4:n.label++;return{value:o[1],done:false};case 5:n.label++;i=o[1];o=[0];continue;case 7:o=n.ops.pop();n.trys.pop();continue;default:if(!(r=n.trys,r=r.length>0&&r[r.length-1])&&(o[0]===6||o[0]===2)){n=0;continue}if(o[0]===3&&(!r||o[1]>r[0]&&o[1]<r[3])){n.label=o[1];break}if(o[0]===6&&n.label<r[1]){n.label=r[1];r=o;break}if(r&&n.label<r[2]){n.label=r[2];n.ops.push(o);break}if(r[2])n.ops.pop();n.trys.pop();continue}o=t.call(e,n)}catch(e){o=[6,e];i=0}finally{a=r=0}if(o[0]&5)throw o[1];return{value:o[0]?o[1]:void 0,done:true}}};System.register(["./p-Cbgoi924.system.js"],(function(e,t){"use strict";var n,a,i;return{setters:[function(t){n=t.p;a=t.g;i=t.b;e("setNonce",t.s)}],execute:function(){var e=this;var r=function(){var e=t.meta.url;var a={};if(e!==""){a.resourcesUrl=new URL(".",e).href}return n(a)};r().then((function(t){return __awaiter(e,void 0,void 0,(function(){return __generator(this,(function(e){switch(e.label){case 0:return[4,a()];case 1:e.sent();return[2,i([["p-7b9255d8.system",[[257,"bcx-chat-list",{apiService:[16,"api-service"],language:[1],theme:[1],chats:[32],selectedChatId:[32],messages:[32],isLoading:[32],isLoadingMore:[32],hasMore:[32],currentPage:[32],error:[32]},null,{language:["onLanguageChange"]}],[257,"bcx-message-composer",{disabled:[4],loading:[4],placeholder:[1],maxLength:[2,"max-length"],theme:[1],isAttachmentsDisabled:[4,"is-attachments-disabled"],message:[32],images:[32]}],[257,"bcx-product-slider",{products:[16],language:[1],showAfterStreaming:[4,"show-after-streaming"],currentIndex:[32],isVisible:[32]},null,{products:["onProductsChange"],showAfterStreaming:["onShowAfterStreamingChange"]}]]],["p-2877dab7.system",[[257,"bettercx-widget",{publicKey:[1,"public-key"],theme:[1],debug:[4],baseUrl:[1,"base-url"],aiServiceUrl:[1,"ai-service-url"],autoInit:[4,"auto-init"],position:[1],language:[1],embedded:[4],embeddedSize:[1,"embedded-size"],embeddedPlacement:[1,"embedded-placement"],isAttachmentsDisabled:[4,"is-attachments-disabled"],state:[32],timeUpdateTrigger:[32],isDropdownOpen:[32],isFullscreen:[32],showChatList:[32],showDelayMessage:[32],delayMessageType:[32],open:[64],close:[64],toggle:[64],sendMessage:[64]},null,{publicKey:["onPublicKeyChange"],theme:["onThemeChange"],language:["onLanguageChange"],"state.messages":["onMessagesChange"],embedded:["onEmbeddedChange"],embeddedSize:["onEmbeddedSizeChange"],embeddedPlacement:["onEmbeddedPlacementChange"]}]]]],t)]}}))}))}))}}}));
  • bettercx-widget/trunk/bettercx-widget.php

    r3445478 r3458971  
    44 * Plugin URI: https://wordpress.org/plugins/bettercx-widget/
    55 * Description: Professional AI-powered chat widget for BetterCX platform. Seamlessly integrate intelligent customer support into any website with full WordPress compatibility. Fully functional out of the box with no trial limitations.
    6  * Version: 1.0.21
     6 * Version: 1.0.22
    77 * Author: BetterCX
    88 * Author URI: https://bettercx.ai
     
    1616 *
    1717 * @package BetterCX_Widget
    18  * @version 1.0.21
     18 * @version 1.0.22
    1919 * @author BetterCX
    2020 * @license GPLv2+
     
    3737
    3838// Define plugin constants
    39 define('BETTERCX_WIDGET_VERSION', '1.0.21');
     39define('BETTERCX_WIDGET_VERSION', '1.0.22');
    4040define('BETTERCX_WIDGET_PLUGIN_FILE', __FILE__);
    4141define('BETTERCX_WIDGET_PLUGIN_DIR', plugin_dir_path(__FILE__));
     
    7474
    7575    /**
     76     * Request-level cache for page_has_bettercx_shortcode() to avoid repeated lookups.
     77     *
     78     * @since 1.0.0
     79     * @var bool|null
     80     */
     81    private static $page_has_shortcode_cache = null;
     82
     83    /**
    7684     * Get plugin instance
    7785     *
     
    122130        // AJAX handlers
    123131        add_action('wp_ajax_bettercx_save_settings', array($this, 'ajax_save_settings'));
    124         add_action('wp_ajax_bettercx_test_connection', array($this, 'ajax_test_connection'));
    125 
    126132        // Allow custom elements in content
    127133        add_filter('wp_kses_allowed_html', array($this, 'allow_custom_elements'), 10, 2);
     
    214220     */
    215221    public function enqueue_scripts() {
    216         // Only load if widget is enabled and we have a public key
    217         if (!$this->settings['widget_enabled'] || empty($this->settings['public_key'])) {
     222        $has_public_key = !empty($this->settings['public_key']);
     223        $show_globally = !empty($this->settings['widget_enabled']);
     224        $show_via_shortcode = $this->page_has_bettercx_shortcode();
     225
     226        // Load assets when widget is shown globally and/or this page contains the shortcode.
     227        if (!$has_public_key || (!$show_globally && !$show_via_shortcode)) {
    218228            return;
    219229        }
     
    322332            'nonce' => wp_create_nonce('bettercx_widget_admin_nonce'),
    323333            'strings' => array(
    324                 'testConnectionSuccess' => __('Connection test successful!', 'bettercx-widget'),
    325                 'testConnectionError' => __('Connection test failed. Please check your settings.', 'bettercx-widget'),
    326334                'settingsSaved' => __('Settings saved successfully!', 'bettercx-widget'),
    327335                'settingsError' => __('Error saving settings. Please try again.', 'bettercx-widget'),
     
    553561        $sanitized = array();
    554562
     563        // Defensive: ensure we have an array (WordPress sends array; avoid overwriting with partial data)
     564        if (!is_array($input)) {
     565            return get_option('bettercx_widget_settings', $this->get_default_settings());
     566        }
     567
    555568        if (isset($input['public_key'])) {
    556569            $sanitized['public_key'] = sanitize_text_field($input['public_key']);
     
    589602        $sanitized['enable_analytics'] = isset($input['enable_analytics']) ? (bool) $input['enable_analytics'] : false;
    590603        $sanitized['privacy_consent'] = isset($input['privacy_consent']) ? (bool) $input['privacy_consent'] : false;
    591         $sanitized['widget_enabled'] = isset($input['widget_enabled']) ? (bool) $input['widget_enabled'] : true;
     604        // Unchecked checkboxes are not sent in POST; treat missing as false so "Enable Widget" can be turned off.
     605        $sanitized['widget_enabled'] = !empty($input['widget_enabled']);
    592606        $sanitized['logged_in_only'] = isset($input['logged_in_only']) ? (bool) $input['logged_in_only'] : false;
    593607        $sanitized['is_attachments_disabled'] = isset($input['is_attachments_disabled']) ? (bool) $input['is_attachments_disabled'] : false;
     
    605619        }
    606620
    607         return $sanitized;
     621        // Merge with existing options so we never drop keys (e.g. after plugin update or partial form submit).
     622        $existing = get_option('bettercx_widget_settings', array());
     623        if (!is_array($existing)) {
     624            $existing = array();
     625        }
     626        return array_merge($existing, $sanitized);
    608627    }
    609628
     
    687706
    688707    /**
     708     * Check if the current singular page contains the BetterCX shortcode.
     709     * Supports classic post_content and Elementor (_elementor_data) for page builders.
     710     * Result is cached for the current request to avoid repeated lookups.
     711     *
     712     * @since 1.0.0
     713     * @return bool True if the shortcode is present, false otherwise.
     714     */
     715    private function page_has_bettercx_shortcode() {
     716        if (self::$page_has_shortcode_cache !== null) {
     717            return self::$page_has_shortcode_cache;
     718        }
     719
     720        self::$page_has_shortcode_cache = false;
     721
     722        if (!is_singular()) {
     723            return self::$page_has_shortcode_cache;
     724        }
     725
     726        $post_id = get_the_ID();
     727        if (!$post_id || !is_numeric($post_id)) {
     728            return self::$page_has_shortcode_cache;
     729        }
     730
     731        $post = get_post($post_id);
     732        if (!$post || !isset($post->post_content) || !is_string($post->post_content)) {
     733            // Fallback: check Elementor data only (e.g. Elementor-built page with no classic content).
     734            $elementor_data = get_post_meta($post_id, '_elementor_data', true);
     735            if (is_string($elementor_data) && $elementor_data !== '' && strpos($elementor_data, 'bettercx_widget') !== false) {
     736                self::$page_has_shortcode_cache = true;
     737            }
     738            return self::$page_has_shortcode_cache;
     739        }
     740
     741        if (has_shortcode($post->post_content, 'bettercx_widget')) {
     742            self::$page_has_shortcode_cache = true;
     743            return self::$page_has_shortcode_cache;
     744        }
     745
     746        $elementor_data = get_post_meta($post_id, '_elementor_data', true);
     747        if (is_string($elementor_data) && $elementor_data !== '' && strpos($elementor_data, 'bettercx_widget') !== false) {
     748            self::$page_has_shortcode_cache = true;
     749        }
     750
     751        return self::$page_has_shortcode_cache;
     752    }
     753
     754    /**
    689755     * Render widget in footer
    690756     *
     
    702768        }
    703769
    704         // Check if widget should be rendered (not already rendered via shortcode)
    705         if (is_singular() && has_shortcode(get_post()->post_content, 'bettercx_widget')) {
     770        // Do not render global widget on pages that already output the shortcode (avoids duplicate widget).
     771        if ($this->page_has_bettercx_shortcode()) {
    706772            return;
    707773        }
     
    863929
    864930    /**
    865      * AJAX handler for testing connection
    866      *
    867      * @since 1.0.0
    868      */
    869     public function ajax_test_connection() {
    870         check_ajax_referer('bettercx_widget_admin_nonce', 'nonce');
    871 
    872         if (!current_user_can('manage_options')) {
    873             wp_die(esc_html__('Insufficient permissions.', 'bettercx-widget'));
    874         }
    875 
    876         $public_key = isset($_POST['public_key']) ? sanitize_text_field(wp_unslash($_POST['public_key'])) : '';
    877 
    878         // Test connection logic here
    879         $response = wp_remote_get('https://api.bettercx.ai/api/widgets/session/create/', array(
    880             'timeout' => 10,
    881             'headers' => array(
    882                 'Content-Type' => 'application/json',
    883             ),
    884             'body' => json_encode(array(
    885                 'widget_key' => $public_key,
    886                 'origin' => home_url(),
    887             )),
    888         ));
    889 
    890         if (is_wp_error($response)) {
    891             wp_send_json_error(__('Connection test failed: ', 'bettercx-widget') . $response->get_error_message());
    892         }
    893 
    894         $status_code = wp_remote_retrieve_response_code($response);
    895         if ($status_code === 200) {
    896             wp_send_json_success(__('Connection test successful!', 'bettercx-widget'));
    897         } else {
    898             wp_send_json_error(__('Connection test failed. Please check your public key.', 'bettercx-widget'));
    899         }
    900     }
    901 
    902     /**
    903931     * Add privacy policy content
    904932     *
     
    11251153                    <h2><?php esc_html_e('BetterCX Widget', 'bettercx-widget'); ?></h2>
    11261154                    <p><?php esc_html_e('Professional AI-powered chat widget for your WordPress site.', 'bettercx-widget'); ?></p>
    1127                 </div>
    1128                 <div class="bettercx-widget-admin-actions">
    1129                     <button type="button" id="test-connection" class="button button-secondary">
    1130                         <?php esc_html_e('Test Connection', 'bettercx-widget'); ?>
    1131                     </button>
    11321155                </div>
    11331156            </div>
  • bettercx-widget/trunk/readme.txt

    r3445478 r3458971  
    55Tested up to: 6.8
    66Requires PHP: 7.4
    7 Stable tag: 1.0.21
     7Stable tag: 1.0.22
    88License: GPLv2 or later
    99License URI: https://www.gnu.org/licenses/gpl-2.0.html
     
    248248== Changelog ==
    249249
     250= 1.0.22 =
     251* Fixed "Enable Widget" checkbox: unchecked state now correctly saves and disables the global widget (previously reverted to checked)
     252* Shortcode detection now works on Elementor-built pages: widget no longer appears twice when shortcode is used in Elementor (global footer widget is skipped on shortcode pages)
     253* Scripts and styles now load on pages that use only the shortcode (no global widget), so embedded/shortcode-only usage works reliably
     254* Settings save now merges with existing options to avoid losing any setting on form submit; improved backward compatibility
     255* Removed "Test Connection" button from admin (request sent from admin URL; connection is validated on frontend)
     256* Internal: added request-cached helper for shortcode detection; defensive sanitization and option merge for stability
     257
    250258= 1.0.21 =
    251259* Optimized animations for improved performance and smoother user experience
     
    399407== Upgrade Notice ==
    400408
     409= 1.0.22 =
     410Update: "Enable Widget" checkbox now saves correctly when unchecked. Shortcode-only and Elementor pages work properly (no duplicate widget, scripts load). Test Connection button removed from admin.
     411
    401412= 1.0.21 =
    402413Update: Optimized animations for improved performance and smoother user experience. Enhanced animation rendering with better frame rates and reduced CPU usage. Improved widget responsiveness during animations across all devices.
  • bettercx-widget/trunk/src/components/bcx-chat-list/bcx-chat-list.scss

    r3417078 r3458971  
    165165  background: var(--bcx-bg-secondary, #f9f9f9);
    166166  cursor: pointer;
    167   transition: all 0.3s cubic-bezier(0.16, 1, 0.3, 1);
     167  transition: transform 0.3s cubic-bezier(0.16, 1, 0.3, 1), box-shadow 0.3s cubic-bezier(0.16, 1, 0.3, 1), border-color 0.3s cubic-bezier(0.16, 1, 0.3, 1), background 0.3s cubic-bezier(0.16, 1, 0.3, 1);
    168168  text-align: left;
    169169  font-family: inherit;
     
    173173  flex-shrink: 0; /* Prevent items from shrinking */
    174174  min-height: fit-content; /* Ensure items maintain their natural height */
     175  /* AUDYT: chat list items - usunięto will-change i contain (zbyt wiele elementów) */
     176  /* AUDYT: usunięto backface-visibility - animacja 2D (slide) */
    175177
    176178  &::before {
     
    193195    background: var(--bcx-bg-tertiary, #f3f4f6);
    194196    border-color: color-mix(in srgb, var(--bcx-primary, #007bff) 30%, transparent);
    195     transform: translateY(-2px);
     197    transform: translate3d(0, -2px, 0); /* OPTYMALIZACJA: translate3d zamiast translateY */
    196198    box-shadow:
    197199      0 8px 16px color-mix(in srgb, var(--bcx-text-primary, #1f2937) 8%, transparent),
     
    209211
    210212  &:active {
    211     transform: translateY(0);
     213    transform: translate3d(0, 0, 0); /* OPTYMALIZACJA: translate3d zamiast translateY */
    212214    box-shadow:
    213215      0 4px 8px color-mix(in srgb, var(--bcx-text-primary, #1f2937) 6%, transparent),
     
    393395  max-width: 85%;
    394396  animation: bcx-message-slide-in 0.25s cubic-bezier(0.25, 0.46, 0.45, 0.94);
     397  /* AUDYT: usunięto will-change, contain, backface-visibility - pojedyncze wiadomości */
    395398
    396399  &--user {
     
    499502  from {
    500503    opacity: 0;
    501     transform: translateY(10px);
     504    transform: translate3d(0, 10px, 0); /* OPTYMALIZACJA: translate3d zamiast translateY */
    502505  }
    503506  to {
    504507    opacity: 1;
    505     transform: translateY(0);
     508    transform: translate3d(0, 0, 0); /* OPTYMALIZACJA: translate3d zamiast translateY */
    506509  }
    507510}
     
    509512@keyframes bcx-spin {
    510513  0% {
    511     transform: rotate(0deg);
     514    transform: translate3d(0, 0, 0) rotate(0deg); /* AUDYT: translate3d zamiast translateZ(0) dla spójności */
    512515  }
    513516  100% {
    514     transform: rotate(360deg);
     517    transform: translate3d(0, 0, 0) rotate(360deg); /* AUDYT: translate3d zamiast translateZ(0) dla spójności */
    515518  }
    516519}
  • bettercx-widget/trunk/src/components/bcx-chat-list/bcx-chat-list.tsx

    r3417078 r3458971  
    5454    // Setup infinite scroll after messages are loaded
    5555    if (this.selectedChatId) {
    56       setTimeout(() => this.setupInfiniteScroll(), 100);
     56      // OPTYMALIZACJA: Użycie requestAnimationFrame zamiast setTimeout
     57      requestAnimationFrame(() => {
     58        requestAnimationFrame(() => {
     59          this.setupInfiniteScroll();
     60        });
     61      });
    5762    }
    5863  }
     
    6166    // Re-setup infinite scroll when selected chat changes
    6267    if (this.selectedChatId && this.messages.length > 0) {
    63       setTimeout(() => this.setupInfiniteScroll(), 100);
     68      // OPTYMALIZACJA: Użycie requestAnimationFrame zamiast setTimeout
     69      requestAnimationFrame(() => {
     70        requestAnimationFrame(() => {
     71          this.setupInfiniteScroll();
     72        });
     73      });
    6474    }
    6575  }
     
    94104    await this.loadMessages(chatId, 1);
    95105
    96     // Setup infinite scroll after messages are loaded
    97     setTimeout(() => this.setupInfiniteScroll(), 100);
     106    // OPTYMALIZACJA: Użycie requestAnimationFrame zamiast setTimeout
     107    requestAnimationFrame(() => {
     108      requestAnimationFrame(() => {
     109        this.setupInfiniteScroll();
     110      });
     111    });
    98112  }
    99113
  • bettercx-widget/trunk/src/components/bcx-message-composer/bcx-message-composer.scss

    r3435658 r3458971  
    267267@keyframes bcx-composer-spin {
    268268  from {
    269     transform: rotate(0deg);
     269    transform: translate3d(0, 0, 0) rotate(0deg); /* AUDYT: translate3d zamiast translateZ(0) dla spójności */
    270270  }
    271271  to {
    272     transform: rotate(360deg);
     272    transform: translate3d(0, 0, 0) rotate(360deg); /* AUDYT: translate3d zamiast translateZ(0) dla spójności */
    273273  }
    274274}
  • bettercx-widget/trunk/src/components/bcx-product-slider/bcx-product-slider.scss

    r3417078 r3458971  
    6161  display: flex;
    6262  transition: transform 0.3s cubic-bezier(0.16, 1, 0.3, 1);
    63   will-change: transform;
     63  will-change: transform; /* AUDYT: slider track - główny element animacji, zostawiamy */
    6464  cursor: grab;
    65   contain: layout style paint;
     65  contain: layout paint; /* AUDYT: usunięto nieistniejące 'style', zostawiamy contain dla izolacji */
    6666  -webkit-overflow-scrolling: touch;
    6767  -webkit-user-select: none;
     
    7272  margin: 0;
    7373  padding: 0;
     74  transform: translateZ(0);
     75  /* AUDYT: usunięto backface-visibility - animacja 2D (slide) */
    7476
    7577  &:active {
  • bettercx-widget/trunk/src/components/bcx-product-slider/bcx-product-slider.tsx

    r3417078 r3458971  
    2121    if (this.products.length > 0) {
    2222      this.currentIndex = 0;
    23       // Use setTimeout to ensure DOM is updated
    24       setTimeout(() => {
     23      // OPTYMALIZACJA: Użycie requestAnimationFrame zamiast setTimeout dla lepszej synchronizacji z renderowaniem
     24      requestAnimationFrame(() => {
    2525        this.animateToSlide(0);
    26       }, 0);
     26      });
    2727    }
    2828  }
     
    4646  private currentX: number = 0;
    4747  private initialTranslate: number = 0;
     48  // AUDYT: Cleanup dla requestAnimationFrame - zapobieganie memory leaks
     49  private animationRafId: number | null = null;
    4850
    4951  componentDidLoad() {
     
    6466  disconnectedCallback() {
    6567    this.cleanupTouchHandlers();
     68    // AUDYT: Cleanup requestAnimationFrame - zapobieganie memory leaks
     69    if (this.animationRafId !== null) {
     70      cancelAnimationFrame(this.animationRafId);
     71      this.animationRafId = null;
     72    }
    6673  }
    6774
     
    173180  private updateSliderPosition(translate: number) {
    174181    if (!this.sliderRef) return;
    175     this.sliderRef.style.transform = `translateX(${translate}%)`;
     182    // OPTYMALIZACJA: Użycie translate3d zamiast translateX dla GPU acceleration
     183    this.sliderRef.style.transform = `translate3d(${translate}%, 0, 0)`;
    176184  }
    177185
     
    191199    if (!this.sliderRef || this.products.length <= 1) return;
    192200
     201    // AUDYT: Cleanup poprzedniej animacji jeśli istnieje
     202    if (this.animationRafId !== null) {
     203      cancelAnimationFrame(this.animationRafId);
     204      this.animationRafId = null;
     205    }
     206
    193207    const translatePercentage = -index * (100 / this.products.length);
    194208
     209    // OPTYMALIZACJA: Użycie translate3d zamiast translateX dla GPU acceleration
    195210    this.sliderRef.style.transition = 'transform 0.3s cubic-bezier(0.16, 1, 0.3, 1)';
    196     this.sliderRef.style.transform = `translateX(${translatePercentage}%)`;
    197 
    198     setTimeout(() => {
    199       if (this.sliderRef) {
    200         this.sliderRef.style.transition = '';
     211    this.sliderRef.style.transform = `translate3d(${translatePercentage}%, 0, 0)`;
     212
     213    // OPTYMALIZACJA: Użycie requestAnimationFrame zamiast setTimeout
     214    const startTime = performance.now();
     215    const checkComplete = (currentTime: number) => {
     216      if (currentTime - startTime >= 300) {
     217        if (this.sliderRef) {
     218          this.sliderRef.style.transition = '';
     219        }
     220        this.animationRafId = null; // AUDYT: Cleanup po zakończeniu
     221      } else {
     222        this.animationRafId = requestAnimationFrame(checkComplete);
    201223      }
    202     }, 300);
     224    };
     225    this.animationRafId = requestAnimationFrame(checkComplete);
    203226  }
    204227
     
    249272            style={{
    250273              width: `${this.products.length * 100}%`,
    251               transform: this.products.length > 1 ? `translateX(-${this.currentIndex * (100 / this.products.length)}%)` : 'none',
     274              transform: this.products.length > 1 ? `translate3d(-${this.currentIndex * (100 / this.products.length)}%, 0, 0)` : 'none',
    252275            }}
    253276          >
  • bettercx-widget/trunk/src/components/bettercx-widget/bettercx-widget.scss

    r3435673 r3458971  
    201201    0 6px 12px rgba(0, 0, 0, 0.08),
    202202    0 0 0 1px rgba(255, 255, 255, 0.12);
    203   transition: all var(--bcx-transition-normal);
     203  transition: transform var(--bcx-transition-normal), box-shadow var(--bcx-transition-normal), background var(--bcx-transition-normal);
    204204  position: relative;
    205205  z-index: 1;
    206206  backdrop-filter: blur(24px); /* Increased blur for more glassmorphism */
    207207  -webkit-backdrop-filter: blur(24px);
     208  /* AUDYT: toggle button - główny element interakcji, zostawiamy will-change i contain */
     209  transform: translateZ(0);
     210  will-change: transform;
     211  contain: layout paint; /* AUDYT: usunięto nieistniejące 'style' */
    208212
    209213  :host(.dark) & {
     
    227231
    228232  &:hover {
    229     transform: translateY(-2px) scale(1.04); /* Reduced lift effect to prevent overlap */
     233    transform: translate3d(0, -2px, 0) scale(1.04); /* OPTYMALIZACJA: translate3d zamiast translateY */
    230234    box-shadow:
    231235      0 28px 56px rgba(0, 0, 0, 0.2),
     
    252256
    253257  &:active {
    254     transform: translateY(-1px) scale(1.02); /* Reduced active state to prevent overlap */
     258    transform: translate3d(0, -1px, 0) scale(1.02); /* OPTYMALIZACJA: translate3d zamiast translateY */
    255259    box-shadow:
    256260      0 16px 32px rgba(0, 0, 0, 0.15),
     
    415419  -webkit-backdrop-filter: blur(32px);
    416420  transform-origin: bottom right;
     421  /* OPTYMALIZACJA: GPU acceleration i izolacja renderingu - tylko na głównym kontenerze */
     422  transform: translateZ(0);
     423  will-change: transform, opacity;
     424  contain: layout paint; /* AUDYT: usunięto nieistniejące 'style' z contain */
    417425
    418426  :host(.dark) & {
     
    498506    min-width: 0; // Allows flex items to shrink below their content size
    499507    transform: translateX(0);
    500     backface-visibility: hidden;
     508    /* AUDYT: usunięto backface-visibility - animacja 2D (flex-direction change) */
    501509    -webkit-font-smoothing: antialiased;
    502510    position: relative;
     
    513521      width 0.5s cubic-bezier(0.25, 0.46, 0.45, 0.94);
    514522    flex-shrink: 0;
    515     transform: translateX(0) translateZ(0);
     523    transform: translate3d(0, 0, 0); /* AUDYT: translate3d zamiast translateX + translateZ */
    516524    will-change: transform, justify-content, width;
    517     backface-visibility: hidden;
     525    /* AUDYT: usunięto backface-visibility - animacja 2D */
    518526    position: relative;
    519527  }
     
    526534    max-height: 0;
    527535    opacity: 0;
    528     transform: translateY(-8px) translateZ(0);
     536    transform: translate3d(0, -8px, 0); /* AUDYT: translate3d zamiast translateY + translateZ */
    529537    visibility: hidden;
    530538    pointer-events: none;
    531539    margin-top: 0;
    532540    will-change: max-height, opacity, transform, visibility;
    533     backface-visibility: hidden;
    534     contain: layout style paint;
     541    /* AUDYT: usunięto backface-visibility - animacja 2D (fade/slide) */
     542    contain: layout paint; /* AUDYT: usunięto nieistniejące 'style' */
    535543    transition:
    536544      opacity 0.15s cubic-bezier(0.4, 0, 1, 1) 0s,
     
    557565    white-space: nowrap;
    558566    flex-shrink: 0;
    559     transform: translateX(0) translateZ(0);
     567    transform: translate3d(0, 0, 0); /* AUDYT: translate3d zamiast translateX + translateZ */
    560568    will-change: transform, font-size;
    561     backface-visibility: hidden;
     569    /* AUDYT: usunięto backface-visibility - animacja 2D */
    562570    -webkit-font-smoothing: antialiased;
    563571    text-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
     
    580588    transform: translateX(0) translateZ(0);
    581589    will-change: transform;
    582     backface-visibility: hidden;
     590    /* AUDYT: usunięto backface-visibility - animacja 2D (scale) */
    583591    box-shadow:
    584592      0 2px 4px rgba(0, 0, 0, 0.1),
     
    604612    /* Subtle hover effect */
    605613    &:hover {
    606       transform: translateX(0) translateZ(0) scale(1.02);
     614      transform: translate3d(0, 0, 0) scale(1.02); /* AUDYT: translate3d zamiast translateX + translateZ */
    607615      box-shadow:
    608616        0 3px 6px rgba(0, 0, 0, 0.12),
     
    647655    opacity: 0.95;
    648656    transition: opacity 0.2s cubic-bezier(0.4, 0, 1, 1);
    649     will-change: opacity;
     657    /* AUDYT: usunięto will-change: opacity - zbyt drobny element, przeglądarka zoptymalizuje */
    650658    text-shadow: 0 1px 2px rgba(0, 0, 0, 0.08);
    651659    letter-spacing: -0.01em;
     
    659667    opacity: 0.8;
    660668    transition: opacity 0.2s cubic-bezier(0.4, 0, 1, 1);
    661     will-change: opacity;
     669    /* AUDYT: usunięto will-change: opacity - zbyt drobny element */
    662670    text-shadow: 0 1px 1px rgba(0, 0, 0, 0.06);
    663671    letter-spacing: 0.01em;
     
    703711      max-height: 200px;
    704712      opacity: 1;
    705       transform: translateY(0) translateZ(0);
     713      transform: translate3d(0, 0, 0); /* AUDYT: translate3d zamiast translateY + translateZ */
    706714      visibility: visible;
    707715      pointer-events: auto;
     
    758766      max-height: 0;
    759767      opacity: 0;
    760       transform: translateY(-8px) translateZ(0);
     768      transform: translate3d(0, -8px, 0); /* AUDYT: translate3d zamiast translateY + translateZ */
    761769      visibility: hidden;
    762770      pointer-events: none;
     
    915923  backdrop-filter: blur(12px);
    916924  -webkit-backdrop-filter: blur(12px);
     925  /* AUDYT: dropdown - główny element UI, zostawiamy will-change */
    917926  will-change: transform, opacity;
    918927
     
    14761485  max-width: 85%;
    14771486  animation: bcx-message-appear 0.25s cubic-bezier(0.25, 0.46, 0.45, 0.94); /* Premium message appearance */
     1487  /* AUDYT: usunięto will-change i contain z pojedynczych wiadomości - przeglądarka zoptymalizuje sama */
     1488  /* AUDYT: usunięto backface-visibility - animacja 2D (slide/fade) nie potrzebuje */
    14781489
    14791490  &--user {
    14801491    align-self: flex-end;
    14811492    animation: bcx-message-slide-in-right 0.25s cubic-bezier(0.25, 0.46, 0.45, 0.94);
     1493    /* AUDYT: usunięto will-change - pojedyncze wiadomości nie potrzebują */
    14821494
    14831495    .bcx-widget__message-content {
     
    17141726    gap: 0;
    17151727    animation: bcx-message-slide-in-left 0.25s cubic-bezier(0.25, 0.46, 0.45, 0.94);
     1728    /* AUDYT: usunięto will-change - pojedyncze wiadomości nie potrzebują */
    17161729
    17171730    .bcx-widget__message-content {
     
    20612074  margin: 0;
    20622075  cursor: pointer;
    2063   transition: all 0.3s cubic-bezier(0.16, 1, 0.3, 1);
     2076  transition: transform 0.3s cubic-bezier(0.16, 1, 0.3, 1), box-shadow 0.3s cubic-bezier(0.16, 1, 0.3, 1), border-color 0.3s cubic-bezier(0.16, 1, 0.3, 1), background 0.3s cubic-bezier(0.16, 1, 0.3, 1);
    20642077  text-align: left;
    20652078  font-family: inherit;
    20662079  position: relative;
    20672080  overflow: hidden;
    2068   will-change: transform, box-shadow, border-color;
     2081  /* AUDYT: FAQ items - usunięto will-change i contain z pojedynczych elementów */
     2082  /* AUDYT: usunięto backface-visibility - animacja 2D (slide) */
    20692083
    20702084  // Animation only when parent has --animate class
     
    20912105    background: var(--bcx-bg-tertiary);
    20922106    border-color: color-mix(in srgb, var(--bcx-primary) 30%, transparent);
    2093     transform: translateY(-2px);
     2107    transform: translate3d(0, -2px, 0); /* OPTYMALIZACJA: translate3d zamiast translateY */
    20942108    box-shadow:
    20952109      0 8px 16px color-mix(in srgb, var(--bcx-text-primary) 8%, transparent),
     
    21112125
    21122126  &:active {
    2113     transform: translateY(0);
     2127    transform: translate3d(0, 0, 0); /* OPTYMALIZACJA: translate3d zamiast translateY */
    21142128    box-shadow:
    21152129      0 4px 8px color-mix(in srgb, var(--bcx-text-primary) 6%, transparent),
     
    22642278    box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
    22652279    flex-shrink: 0; /* Prevent dots from shrinking */
     2280    /* AUDYT: usunięto will-change i backface-visibility - zbyt drobne elementy */
     2281    transform: translateZ(0);
    22662282
    22672283    &:nth-child(1) {
     
    22952311  width: 100%;
    22962312  box-sizing: border-box;
     2313  /* AUDYT: usunięto will-change - zbyt drobny element */
    22972314}
    22982315
     
    24622479  animation: bcx-spin 1s linear infinite;
    24632480  margin-bottom: var(--bcx-space-4);
     2481  /* AUDYT: spinner - mały element, usunięto will-change i backface-visibility */
     2482  transform: translateZ(0);
    24642483
    24652484  &--small {
     
    24742493  0% {
    24752494    opacity: 0;
    2476     transform: translateY(32px) scale(0.92);
    2477     filter: blur(6px) brightness(0.8);
     2495    transform: translate3d(0, 32px, 0) scale(0.92);
    24782496  }
    24792497  20% {
    24802498    opacity: 0.3;
    2481     transform: translateY(20px) scale(0.95);
    2482     filter: blur(4px) brightness(0.9);
     2499    transform: translate3d(0, 20px, 0) scale(0.95);
    24832500  }
    24842501  40% {
    24852502    opacity: 0.6;
    2486     transform: translateY(12px) scale(0.97);
    2487     filter: blur(2px) brightness(0.95);
     2503    transform: translate3d(0, 12px, 0) scale(0.97);
    24882504  }
    24892505  60% {
    24902506    opacity: 0.8;
    2491     transform: translateY(6px) scale(0.99);
    2492     filter: blur(1px) brightness(0.98);
     2507    transform: translate3d(0, 6px, 0) scale(0.99);
    24932508  }
    24942509  80% {
    24952510    opacity: 0.95;
    2496     transform: translateY(2px) scale(1.005);
    2497     filter: blur(0.5px) brightness(1);
     2511    transform: translate3d(0, 2px, 0) scale(1.005);
    24982512  }
    24992513  100% {
    25002514    opacity: 1;
    2501     transform: translateY(0) scale(1);
    2502     filter: blur(0) brightness(1);
     2515    transform: translate3d(0, 0, 0) scale(1);
    25032516  }
    25042517}
     
    25072520  0% {
    25082521    opacity: 0;
    2509     transform: translateY(24px) scale(0.94);
    2510     filter: blur(4px) brightness(0.9);
     2522    transform: translate3d(0, 24px, 0) scale(0.94);
    25112523  }
    25122524  15% {
    25132525    opacity: 0.2;
    2514     transform: translateY(18px) scale(0.96);
    2515     filter: blur(3px) brightness(0.92);
     2526    transform: translate3d(0, 18px, 0) scale(0.96);
    25162527  }
    25172528  30% {
    25182529    opacity: 0.4;
    2519     transform: translateY(12px) scale(0.97);
    2520     filter: blur(2px) brightness(0.95);
     2530    transform: translate3d(0, 12px, 0) scale(0.97);
    25212531  }
    25222532  50% {
    25232533    opacity: 0.7;
    2524     transform: translateY(6px) scale(0.98);
    2525     filter: blur(1px) brightness(0.98);
     2534    transform: translate3d(0, 6px, 0) scale(0.98);
    25262535  }
    25272536  70% {
    25282537    opacity: 0.85;
    2529     transform: translateY(2px) scale(0.99);
    2530     filter: blur(0.5px) brightness(0.99);
     2538    transform: translate3d(0, 2px, 0) scale(0.99);
    25312539  }
    25322540  85% {
    25332541    opacity: 0.95;
    2534     transform: translateY(1px) scale(1.005);
    2535     filter: blur(0.2px) brightness(1);
     2542    transform: translate3d(0, 1px, 0) scale(1.005);
    25362543  }
    25372544  100% {
    25382545    opacity: 1;
    2539     transform: translateY(0) scale(1);
    2540     filter: blur(0) brightness(1);
     2546    transform: translate3d(0, 0, 0) scale(1);
    25412547  }
    25422548}
     
    25452551  0% {
    25462552    opacity: 0;
    2547     transform: translateY(16px) scale(0.92);
    2548     filter: blur(3px);
     2553    transform: translate3d(0, 16px, 0) scale(0.92);
    25492554  }
    25502555  30% {
    25512556    opacity: 0.4;
    2552     transform: translateY(8px) scale(0.96);
    2553     filter: blur(1px);
     2557    transform: translate3d(0, 8px, 0) scale(0.96);
    25542558  }
    25552559  60% {
    25562560    opacity: 0.8;
    2557     transform: translateY(2px) scale(0.99);
    2558     filter: blur(0.5px);
     2561    transform: translate3d(0, 2px, 0) scale(0.99);
    25592562  }
    25602563  100% {
    25612564    opacity: 1;
    2562     transform: translateY(0) scale(1);
    2563     filter: blur(0);
     2565    transform: translate3d(0, 0, 0) scale(1);
    25642566  }
    25652567}
     
    25692571  80%,
    25702572  100% {
    2571     transform: scale(0.85);
     2573    transform: translate3d(0, 0, 0) scale(0.85); /* AUDYT: translate3d zamiast translateZ(0) dla spójności */
    25722574    opacity: 0.5;
    25732575  }
    25742576  40% {
    2575     transform: scale(1.15);
     2577    transform: translate3d(0, 0, 0) scale(1.15); /* AUDYT: translate3d zamiast translateZ(0) dla spójności */
    25762578    opacity: 1;
    25772579  }
     
    25922594  0%,
    25932595  100% {
    2594     transform: scale(1);
     2596    transform: translate3d(0, 0, 0) scale(1); /* AUDYT: translate3d zamiast translateZ(0) dla spójności */
    25952597    box-shadow:
    25962598      0 20px 40px rgba(0, 0, 0, 0.15),
     
    26002602  }
    26012603  50% {
    2602     transform: scale(1.02); /* Reduced scale to prevent overlap */
     2604    transform: translate3d(0, 0, 0) scale(1.02); /* AUDYT: translate3d zamiast translateZ(0) dla spójności */
    26032605    box-shadow:
    26042606      0 24px 48px rgba(0, 0, 0, 0.18),
     
    26132615  0% {
    26142616    opacity: 0;
    2615     transform: scale(0.92) translateY(24px);
    2616     filter: blur(4px) brightness(0.8);
     2617    transform: translate3d(0, 24px, 0) scale(0.92);
    26172618  }
    26182619  30% {
    26192620    opacity: 0.4;
    2620     transform: scale(0.96) translateY(12px);
    2621     filter: blur(2px) brightness(0.9);
     2621    transform: translate3d(0, 12px, 0) scale(0.96);
    26222622  }
    26232623  60% {
    26242624    opacity: 0.8;
    2625     transform: scale(0.99) translateY(4px);
    2626     filter: blur(1px) brightness(0.95);
     2625    transform: translate3d(0, 4px, 0) scale(0.99);
    26272626  }
    26282627  100% {
    26292628    opacity: 1;
    2630     transform: scale(1) translateY(0);
    2631     filter: blur(0) brightness(1);
     2629    transform: translate3d(0, 0, 0) scale(1);
    26322630  }
    26332631}
     
    26362634  0% {
    26372635    opacity: 1;
    2638     transform: translateY(0) scale(1);
    2639     filter: blur(0) brightness(1);
     2636    transform: translate3d(0, 0, 0) scale(1);
    26402637  }
    26412638  20% {
    26422639    opacity: 0.8;
    2643     transform: translateY(4px) scale(0.99);
    2644     filter: blur(1px) brightness(0.95);
     2640    transform: translate3d(0, 4px, 0) scale(0.99);
    26452641  }
    26462642  40% {
    26472643    opacity: 0.6;
    2648     transform: translateY(12px) scale(0.97);
    2649     filter: blur(2px) brightness(0.9);
     2644    transform: translate3d(0, 12px, 0) scale(0.97);
    26502645  }
    26512646  60% {
    26522647    opacity: 0.4;
    2653     transform: translateY(20px) scale(0.95);
    2654     filter: blur(3px) brightness(0.85);
     2648    transform: translate3d(0, 20px, 0) scale(0.95);
    26552649  }
    26562650  80% {
    26572651    opacity: 0.2;
    2658     transform: translateY(28px) scale(0.93);
    2659     filter: blur(4px) brightness(0.8);
     2652    transform: translate3d(0, 28px, 0) scale(0.93);
    26602653  }
    26612654  100% {
    26622655    opacity: 0;
    2663     transform: translateY(32px) scale(0.92);
    2664     filter: blur(6px) brightness(0.7);
     2656    transform: translate3d(0, 32px, 0) scale(0.92);
    26652657  }
    26662658}
     
    26682660@keyframes bcx-question-click {
    26692661  0% {
    2670     transform: scale(1);
     2662    transform: translate3d(0, 0, 0) scale(1); /* AUDYT: translate3d zamiast translateZ(0) dla spójności */
    26712663  }
    26722664  50% {
    2673     transform: scale(0.95);
     2665    transform: translate3d(0, 0, 0) scale(0.95); /* AUDYT: translate3d zamiast translateZ(0) dla spójności */
    26742666  }
    26752667  100% {
    2676     transform: scale(1);
     2668    transform: translate3d(0, 0, 0) scale(1); /* AUDYT: translate3d zamiast translateZ(0) dla spójności */
    26772669  }
    26782670}
     
    26812673  0%,
    26822674  100% {
    2683     transform: scale(1);
    2684     filter: brightness(1);
     2675    transform: translate3d(0, 0, 0) scale(1); /* AUDYT: translate3d zamiast translateZ(0) dla spójności */
     2676    opacity: 1;
    26852677  }
    26862678  50% {
    2687     transform: scale(1.005);
    2688     filter: brightness(1.02);
     2679    transform: translate3d(0, 0, 0) scale(1.005); /* AUDYT: translate3d zamiast translateZ(0) dla spójności */
     2680    opacity: 1.02;
    26892681  }
    26902682}
     
    26932685  0% {
    26942686    opacity: 0;
    2695     transform: translateY(24px) scale(0.92);
    2696     filter: blur(4px);
     2687    transform: translate3d(0, 24px, 0) scale(0.92);
    26972688  }
    26982689  30% {
    26992690    opacity: 0.6;
    2700     transform: translateY(12px) scale(0.96);
    2701     filter: blur(2px);
     2691    transform: translate3d(0, 12px, 0) scale(0.96);
    27022692  }
    27032693  60% {
    27042694    opacity: 0.8;
    2705     transform: translateY(4px) scale(0.99);
    2706     filter: blur(1px);
     2695    transform: translate3d(0, 4px, 0) scale(0.99);
    27072696  }
    27082697  100% {
    27092698    opacity: 1;
    2710     transform: translateY(0) scale(1);
    2711     filter: blur(0);
     2699    transform: translate3d(0, 0, 0) scale(1);
    27122700  }
    27132701}
     
    27342722  0% {
    27352723    opacity: 0;
    2736     transform: translateX(40px) translateY(16px) scale(0.92);
    2737     filter: blur(3px) brightness(0.8);
     2724    transform: translate3d(40px, 16px, 0) scale(0.92);
    27382725  }
    27392726  20% {
    27402727    opacity: 0.3;
    2741     transform: translateX(24px) translateY(12px) scale(0.95);
    2742     filter: blur(2px) brightness(0.85);
     2728    transform: translate3d(24px, 12px, 0) scale(0.95);
    27432729  }
    27442730  40% {
    27452731    opacity: 0.6;
    2746     transform: translateX(12px) translateY(8px) scale(0.97);
    2747     filter: blur(1px) brightness(0.9);
     2732    transform: translate3d(12px, 8px, 0) scale(0.97);
    27482733  }
    27492734  60% {
    27502735    opacity: 0.8;
    2751     transform: translateX(4px) translateY(4px) scale(0.99);
    2752     filter: blur(0.5px) brightness(0.95);
     2736    transform: translate3d(4px, 4px, 0) scale(0.99);
    27532737  }
    27542738  80% {
    27552739    opacity: 0.95;
    2756     transform: translateX(1px) translateY(1px) scale(1.005);
    2757     filter: blur(0.2px) brightness(0.98);
     2740    transform: translate3d(1px, 1px, 0) scale(1.005);
    27582741  }
    27592742  100% {
    27602743    opacity: 1;
    2761     transform: translateX(0) translateY(0) scale(1);
    2762     filter: blur(0) brightness(1);
     2744    transform: translate3d(0, 0, 0) scale(1);
    27632745  }
    27642746}
     
    27672749  0% {
    27682750    opacity: 0;
    2769     transform: translateX(-40px) translateY(16px) scale(0.92);
    2770     filter: blur(3px) brightness(0.8);
     2751    transform: translate3d(-40px, 16px, 0) scale(0.92);
    27712752  }
    27722753  20% {
    27732754    opacity: 0.3;
    2774     transform: translateX(-24px) translateY(12px) scale(0.95);
    2775     filter: blur(2px) brightness(0.85);
     2755    transform: translate3d(-24px, 12px, 0) scale(0.95);
    27762756  }
    27772757  40% {
    27782758    opacity: 0.6;
    2779     transform: translateX(-12px) translateY(8px) scale(0.97);
    2780     filter: blur(1px) brightness(0.9);
     2759    transform: translate3d(-12px, 8px, 0) scale(0.97);
    27812760  }
    27822761  60% {
    27832762    opacity: 0.8;
    2784     transform: translateX(-4px) translateY(4px) scale(0.99);
    2785     filter: blur(0.5px) brightness(0.95);
     2763    transform: translate3d(-4px, 4px, 0) scale(0.99);
    27862764  }
    27872765  80% {
    27882766    opacity: 0.95;
    2789     transform: translateX(-1px) translateY(1px) scale(1.005);
    2790     filter: blur(0.2px) brightness(0.98);
     2767    transform: translate3d(-1px, 1px, 0) scale(1.005);
    27912768  }
    27922769  100% {
    27932770    opacity: 1;
    2794     transform: translateX(0) translateY(0) scale(1);
    2795     filter: blur(0) brightness(1);
     2771    transform: translate3d(0, 0, 0) scale(1);
    27962772  }
    27972773}
     
    27992775@keyframes bcx-spin {
    28002776  0% {
    2801     transform: rotate(0deg);
     2777    transform: translate3d(0, 0, 0) rotate(0deg); /* AUDYT: translate3d zamiast translateZ(0) dla spójności */
    28022778  }
    28032779  100% {
    2804     transform: rotate(360deg);
     2780    transform: translate3d(0, 0, 0) rotate(360deg); /* AUDYT: translate3d zamiast translateZ(0) dla spójności */
    28052781  }
    28062782}
     
    31003076  padding: var(--bcx-space-4);
    31013077  overflow: hidden;
     3078  /* AUDYT: ping message - główny element UI, zostawiamy will-change */
    31023079  will-change: transform, opacity;
    31033080
  • bettercx-widget/trunk/src/components/bettercx-widget/bettercx-widget.tsx

    r3435658 r3458971  
    8989  // Delay message handling
    9090  private delayMessageTimeout: ReturnType<typeof setTimeout> | null = null;
     91
     92  // AUDYT: Cleanup dla requestAnimationFrame - zapobieganie memory leaks
     93  private typingRafId: number | null = null;
     94  private faqAnimationRafId: number | null = null;
    9195  @State() private showDelayMessage: boolean = false;
    9296  @State() private delayMessageType: 'selecting' | 'thinking' = null; // 'selecting' = no AI messages, 'thinking' = has AI messages
     
    172176          this.isProgrammaticScroll = true;
    173177          this.messagesEndRef.scrollIntoView({ behavior: 'auto', block: 'end', inline: 'nearest' });
    174           setTimeout(() => {
    175             this.isProgrammaticScroll = false;
    176           }, 50);
     178          // OPTYMALIZACJA: Użycie requestAnimationFrame zamiast setTimeout
     179          requestAnimationFrame(() => {
     180            requestAnimationFrame(() => {
     181              this.isProgrammaticScroll = false;
     182            });
     183          });
    177184        }
    178185      });
     
    194201      clearTimeout(this.delayMessageTimeout);
    195202      this.delayMessageTimeout = null;
     203    }
     204
     205    // AUDYT: Cleanup requestAnimationFrame - zapobieganie memory leaks
     206    if (this.typingRafId !== null) {
     207      cancelAnimationFrame(this.typingRafId);
     208      this.typingRafId = null;
     209    }
     210    if (this.faqAnimationRafId !== null) {
     211      cancelAnimationFrame(this.faqAnimationRafId);
     212      this.faqAnimationRafId = null;
    196213    }
    197214  }
     
    9891006        inline: 'nearest',
    9901007      });
    991       // Reset flag after scroll completes (smooth scroll takes ~300-500ms)
    992       setTimeout(
    993         () => {
     1008      // OPTYMALIZACJA: Użycie requestAnimationFrame zamiast setTimeout dla instant scroll
     1009      // Dla smooth scroll używamy krótszego opóźnienia z RAF
     1010      if (smooth) {
     1011        // Smooth scroll trwa ~300-500ms, więc używamy multiple RAF cycles
     1012        requestAnimationFrame(() => {
     1013          requestAnimationFrame(() => {
     1014            requestAnimationFrame(() => {
     1015              requestAnimationFrame(() => {
     1016                this.isProgrammaticScroll = false;
     1017              });
     1018            });
     1019          });
     1020        });
     1021      } else {
     1022        requestAnimationFrame(() => {
    9941023          this.isProgrammaticScroll = false;
    995         },
    996         smooth ? 600 : 50,
    997       );
     1024        });
     1025      }
    9981026    }
    9991027  }
     
    10321060        });
    10331061      }
    1034       // Reset flag after instant scroll
    1035       setTimeout(() => {
    1036         this.isProgrammaticScroll = false;
    1037       }, 100);
     1062      // OPTYMALIZACJA: Użycie requestAnimationFrame zamiast setTimeout
     1063      requestAnimationFrame(() => {
     1064        requestAnimationFrame(() => {
     1065          this.isProgrammaticScroll = false;
     1066        });
     1067      });
    10381068    });
    10391069  }
     
    10891119
    10901120  private applyColorsToMessageComposer() {
    1091     setTimeout(() => {
    1092       const messageComposer = this.el.querySelector('bcx-message-composer');
    1093       if (messageComposer) {
    1094         const primaryColor = this.el.style.getPropertyValue('--bcx-primary') || '#007bff';
    1095         const secondaryColor = this.el.style.getPropertyValue('--bcx-secondary') || '#6c757d';
    1096         const backgroundColor = this.el.style.getPropertyValue('--bcx-background') || '#ffffff';
    1097         const textColor = this.el.style.getPropertyValue('--bcx-text') || '#212529';
    1098 
    1099         messageComposer.style.setProperty('--bcx-primary', primaryColor);
    1100         messageComposer.style.setProperty('--bcx-secondary', secondaryColor);
    1101         messageComposer.style.setProperty('--bcx-background', backgroundColor);
    1102         messageComposer.style.setProperty('--bcx-text', textColor);
    1103       }
    1104     }, 100);
     1121    // OPTYMALIZACJA: Użycie requestAnimationFrame zamiast setTimeout dla lepszej synchronizacji z renderowaniem
     1122    requestAnimationFrame(() => {
     1123      requestAnimationFrame(() => {
     1124        const messageComposer = this.el.querySelector('bcx-message-composer');
     1125        if (messageComposer) {
     1126          const primaryColor = this.el.style.getPropertyValue('--bcx-primary') || '#007bff';
     1127          const secondaryColor = this.el.style.getPropertyValue('--bcx-secondary') || '#6c757d';
     1128          const backgroundColor = this.el.style.getPropertyValue('--bcx-background') || '#ffffff';
     1129          const textColor = this.el.style.getPropertyValue('--bcx-text') || '#212529';
     1130
     1131          messageComposer.style.setProperty('--bcx-primary', primaryColor);
     1132          messageComposer.style.setProperty('--bcx-secondary', secondaryColor);
     1133          messageComposer.style.setProperty('--bcx-background', backgroundColor);
     1134          messageComposer.style.setProperty('--bcx-text', textColor);
     1135        }
     1136      });
     1137    });
    11051138  }
    11061139
     
    11351168    let currentText = '';
    11361169    let index = 0;
    1137     const typingSpeed = 25;
    1138 
    1139     const typeNextCharacter = () => {
     1170    const typingSpeed = 25; // milliseconds per character
     1171    let lastTime = 0;
     1172
     1173    // AUDYT: Cleanup poprzedniej animacji jeśli istnieje
     1174    if (this.typingRafId !== null) {
     1175      cancelAnimationFrame(this.typingRafId);
     1176      this.typingRafId = null;
     1177    }
     1178
     1179    // OPTYMALIZACJA: Użycie requestAnimationFrame zamiast setTimeout dla płynniejszych animacji
     1180    const typeNextCharacter = (currentTime: number) => {
    11401181      if (index < fullText.length) {
    1141         currentText += fullText[index];
    1142         index++;
    1143 
    1144         const updatedMessage = { ...assistantMessage, content: currentText };
    1145         const updatedMessages = [...this.state.messages];
    1146         updatedMessages[updatedMessages.length - 1] = updatedMessage;
    1147 
    1148         this.setState({
    1149           messages: updatedMessages,
    1150         });
    1151 
    1152         setTimeout(typeNextCharacter, typingSpeed);
     1182        // Throttle updates to match typingSpeed using RAF
     1183        if (currentTime - lastTime >= typingSpeed) {
     1184          currentText += fullText[index];
     1185          index++;
     1186          lastTime = currentTime;
     1187
     1188          const updatedMessage = { ...assistantMessage, content: currentText };
     1189          const updatedMessages = [...this.state.messages];
     1190          updatedMessages[updatedMessages.length - 1] = updatedMessage;
     1191
     1192          this.setState({
     1193            messages: updatedMessages,
     1194          });
     1195        }
     1196
     1197        this.typingRafId = requestAnimationFrame(typeNextCharacter);
    11531198      } else {
     1199        // AUDYT: Cleanup po zakończeniu animacji
     1200        if (this.typingRafId !== null) {
     1201          cancelAnimationFrame(this.typingRafId);
     1202          this.typingRafId = null;
     1203        }
    11541204        this.emitEvent('message-received', assistantMessage as unknown as Record<string, unknown>);
    11551205      }
    11561206    };
    11571207
    1158     setTimeout(typeNextCharacter, 200);
     1208    // Start animation after initial delay using RAF
     1209    requestAnimationFrame(() => {
     1210      requestAnimationFrame(() => {
     1211        lastTime = performance.now();
     1212        this.typingRafId = requestAnimationFrame(typeNextCharacter);
     1213      });
     1214    });
    11591215  }
    11601216
     
    15161572                    ref={el => {
    15171573                      if (el && !this.faqsHaveBeenShown) {
    1518                         // Set flag after animation completes to prevent re-animation on rerenders
     1574                        // AUDYT: Cleanup poprzedniej animacji jeśli istnieje
     1575                        if (this.faqAnimationRafId !== null) {
     1576                          cancelAnimationFrame(this.faqAnimationRafId);
     1577                          this.faqAnimationRafId = null;
     1578                        }
     1579
     1580                        // OPTYMALIZACJA: Użycie requestAnimationFrame zamiast setTimeout
    15191581                        // Animation duration: 0.4s (container) + 0.5s (items) = ~600ms total
    1520                         setTimeout(() => {
    1521                           this.faqsHaveBeenShown = true;
    1522                         }, 600);
     1582                        // Używamy multiple RAF cycles aby zsynchronizować z animacją
     1583                        const startTime = performance.now();
     1584                        const checkComplete = (currentTime: number) => {
     1585                          if (currentTime - startTime >= 600) {
     1586                            this.faqsHaveBeenShown = true;
     1587                            this.faqAnimationRafId = null; // AUDYT: Cleanup po zakończeniu
     1588                          } else {
     1589                            this.faqAnimationRafId = requestAnimationFrame(checkComplete);
     1590                          }
     1591                        };
     1592                        this.faqAnimationRafId = requestAnimationFrame(checkComplete);
    15231593                      }
    15241594                    }}
Note: See TracChangeset for help on using the changeset viewer.