UNPKG

svarog-ui-core

Version:

Core components of Svarog UI without themes

581 lines (492 loc) 17.3 kB
// src/components/MuchandyHero/MuchandyHero.js import { createElement, validateProps, createComponent, } from '../../utils/componentFactory.js'; import { createBaseComponent } from '../../utils/baseComponent.js'; import { withThemeAwareness } from '../../utils/composition.js'; import Tabs from '../Tabs/Tabs.js'; import { createStyleInjector } from '../../utils/styleInjection.js'; import { muchandyHeroStyles } from './MuchandyHero.styles.js'; // Create style injector for MuchandyHero component const injectMuchandyHeroStyles = createStyleInjector('MuchandyHero'); /** * ALGORITHMIC OPTIMIZATION: Mathematical Tab Index Calculation * Uses mathematical mapping instead of string comparisons * @private */ const calculateTabIndex = (defaultTab) => { // Mathematical mapping: O(1) instead of O(n) string comparison const tabMap = { repair: 0, sell: 1 }; return tabMap[defaultTab] || 0; }; /** * ALGORITHMIC OPTIMIZATION: Efficient Tab Configuration * Pre-computes tab configuration to avoid runtime calculations * @private */ const createTabConfiguration = (state) => { const tabConfig = [ { id: 'repair', label: 'Reparatur', contentElement: state.repairForm.getElement(), }, { id: 'sell', label: 'Verkaufen', contentElement: state.buybackForm.getElement(), }, ]; // Optimize tab creation with pre-wrapped content return tabConfig.map((tab) => { const wrapper = createElement('div', { classes: ['muchandy-hero__form-wrapper'], }); wrapper.appendChild(tab.contentElement); return { id: tab.id, label: tab.label, content: wrapper, }; }); }; /** * ALGORITHMIC OPTIMIZATION: Efficient Style Application * Applies styles using optimized DOM operations and CSS custom properties * @private */ const applyContainerStyles = (container, backgroundImageUrl, blurIntensity) => { // Use CSS custom property for background image if (backgroundImageUrl) { container.style.setProperty( '--muchandy-hero-bg-image', `url(${backgroundImageUrl})` ); } else { // Clear background image when empty string is passed container.style.removeProperty('--muchandy-hero-bg-image'); } // Set blur intensity if provided if (blurIntensity !== undefined) { container.style.setProperty('--muchandy-hero-blur', `${blurIntensity}px`); } }; /** * Normalize props to handle legacy properties * @private */ const normalizeProps = (props) => { const normalized = { ...props }; // Handle legacy 'backgroundImage' prop if ( normalized.backgroundImage !== undefined && normalized.backgroundImageUrl === undefined ) { console.warn( 'MuchandyHero: "backgroundImage" prop is deprecated, use "backgroundImageUrl" instead' ); normalized.backgroundImageUrl = normalized.backgroundImage; delete normalized.backgroundImage; } return normalized; }; /** * Creates muchandy hero DOM element with algorithmic optimizations * @param {Object} state - MuchandyHero state * @returns {HTMLElement} - MuchandyHero element */ const renderMuchandyHero = (state) => { // ADD: Inject styles on render injectMuchandyHeroStyles(muchandyHeroStyles); // Create main container with efficient class handling const classes = ['muchandy-hero']; // Add blur modifier class if specified if (state.blurIntensity === 0) { classes.push('muchandy-hero--no-blur'); } else if (state.blurIntensity && state.blurIntensity <= 4) { classes.push('muchandy-hero--blur-light'); } else if ( state.blurIntensity && state.blurIntensity >= 12 && state.blurIntensity < 20 ) { classes.push('muchandy-hero--blur-heavy'); } else if (state.blurIntensity && state.blurIntensity >= 20) { classes.push('muchandy-hero--blur-extreme'); } if (state.className) { classes.push(state.className); } const container = createElement('div', { classes: classes.filter(Boolean), }); // Apply background styles efficiently with blur support applyContainerStyles( container, state.backgroundImageUrl, state.blurIntensity ); // Apply overlay opacity if provided if (state.overlayOpacity !== undefined) { container.style.setProperty( '--muchandy-hero-overlay', `rgba(0, 0, 0, ${state.overlayOpacity})` ); } // Create grid structure efficiently const gridContainer = createElement('div', { classes: ['muchandy-hero__grid'], }); const contentColumn = createElement('div', { classes: ['muchandy-hero__content-column'], }); // Add title with optimized HTML handling if (state.title) { const title = createElement('h1', { classes: ['muchandy-hero__title'], html: state.title, // Supports HTML for line breaks }); contentColumn.appendChild(title); } // Add subtitle with efficient text handling if (state.subtitle) { const subtitle = createElement('h2', { classes: ['muchandy-hero__subtitle'], text: state.subtitle, }); contentColumn.appendChild(subtitle); } // Create form container const formContainer = createElement('div', { classes: ['muchandy-hero__form-container'], }); // Create tabs with algorithmic optimization const tabsData = createTabConfiguration(state); const defaultActiveTab = calculateTabIndex(state.defaultTab); // Create tabs component with optimized configuration const tabsComponent = Tabs({ tabs: tabsData, defaultValue: defaultActiveTab, // <- use defaultValue instead className: 'muchandy-hero__tabs', variant: 'border', align: 'center', }); // Store components for efficient updates and cleanup const components = { tabs: tabsComponent, repairForm: state.repairForm, buybackForm: state.buybackForm, }; formContainer.appendChild(tabsComponent.getElement()); contentColumn.appendChild(formContainer); gridContainer.appendChild(contentColumn); container.appendChild(gridContainer); // Store components and state for updates container._components = components; container._currentState = { ...state }; return container; }; /** * ALGORITHMIC OPTIMIZATION: Efficient Props Validation * Validates all required props using optimized checks * @private */ const validateMuchandyHeroProps = (props) => { // Use bitwise operations for fast validation const hasRepairForm = props.repairForm ? 1 : 0; const hasBuybackForm = props.buybackForm ? 1 : 0; const hasValidForms = hasRepairForm & hasBuybackForm; if (!hasValidForms) { const missing = []; if (!hasRepairForm) { missing.push('repairForm'); } if (!hasBuybackForm) { missing.push('buybackForm'); } throw new Error( `MuchandyHero: Missing required props: ${missing.join(', ')}` ); } // Validate form objects have required methods const requiredMethods = ['getElement', 'destroy']; [props.repairForm, props.buybackForm].forEach((form, index) => { const formName = index === 0 ? 'repairForm' : 'buybackForm'; requiredMethods.forEach((method) => { if (typeof form[method] !== 'function') { throw new Error( `MuchandyHero: ${formName}.${method} must be a function` ); } }); }); }; /** * Create default configuration with optimized defaults * @private */ const createDefaultConfig = () => ({ backgroundImageUrl: '', title: 'Finden Sie<br>Ihren Preis', subtitle: 'Jetzt Preis berechnen.', defaultTab: 'repair', className: '', blurIntensity: 4, // Default blur intensity in pixels overlayOpacity: 0.3, // Default overlay opacity }); /** * Create a MuchandyHero component with algorithmic optimizations * @param {Object} props - MuchandyHero properties * @returns {Object} MuchandyHero component API */ const createMuchandyHero = (props) => { // Normalize props for backward compatibility const normalizedProps = normalizeProps(props); // Validate props efficiently validateProps(normalizedProps, createMuchandyHero.requiredProps); validateMuchandyHeroProps(normalizedProps); // Create optimized initial state const defaultConfig = createDefaultConfig(); const initialState = { ...defaultConfig, ...normalizedProps, // Ensure forms are properly stored repairForm: normalizedProps.repairForm, buybackForm: normalizedProps.buybackForm, }; // Create the base component const muchandyHero = createBaseComponent(renderMuchandyHero)(initialState); // Store reference to the original base component update method const originalUpdate = muchandyHero.update; // Maintain component state reference let currentState = { ...initialState }; // Enhanced state update method muchandyHero.setState = function (newState) { // Normalize new state for backward compatibility const normalizedNewState = normalizeProps(newState); // Update state efficiently currentState = { ...currentState, ...normalizedNewState }; // Store state on element for updates const element = this.getElement(); if (element) { element._currentState = currentState; } // Efficient partial updates this.partialUpdate(element, normalizedNewState); return this; }; // Define when we need a full re-render with algorithmic efficiency muchandyHero.shouldRerender = (newProps) => { // Normalize new props for backward compatibility const normalizedNewProps = normalizeProps(newProps); // Only these props require full re-render (structural changes) const rerenderProps = ['repairForm', 'buybackForm', 'defaultTab']; // Use some() for efficient early termination return rerenderProps.some( (prop) => normalizedNewProps[prop] !== undefined && normalizedNewProps[prop] !== currentState[prop] ); }; // Efficient partial update method muchandyHero.partialUpdate = (element, newProps) => { if (!element?._components) { console.warn( 'MuchandyHero partialUpdate: No element or components found' ); return; } console.log('MuchandyHero partialUpdate called with:', newProps); // Update background image and blur efficiently if ( newProps.backgroundImageUrl !== undefined || newProps.blurIntensity !== undefined ) { console.log('Updating background image to:', newProps.backgroundImageUrl); console.log('Updating blur intensity to:', newProps.blurIntensity); const bgUrl = newProps.backgroundImageUrl !== undefined ? newProps.backgroundImageUrl : element._currentState?.backgroundImageUrl; const blur = newProps.blurIntensity !== undefined ? newProps.blurIntensity : element._currentState?.blurIntensity; applyContainerStyles(element, bgUrl, blur); } // Update overlay opacity if provided if (newProps.overlayOpacity !== undefined) { element.style.setProperty( '--muchandy-hero-overlay', `rgba(0, 0, 0, ${newProps.overlayOpacity})` ); } // Update title efficiently if (newProps.title !== undefined) { const titleElement = element.querySelector('.muchandy-hero__title'); if (titleElement) { titleElement.innerHTML = newProps.title; console.log('Updated title to:', newProps.title); } } // Update subtitle efficiently if (newProps.subtitle !== undefined) { const subtitleElement = element.querySelector('.muchandy-hero__subtitle'); if (subtitleElement) { subtitleElement.textContent = newProps.subtitle; console.log('Updated subtitle to:', newProps.subtitle); } } // Update className efficiently with blur modifiers if ( newProps.className !== undefined || newProps.blurIntensity !== undefined ) { const classes = ['muchandy-hero']; // Add blur modifier class const blur = newProps.blurIntensity !== undefined ? newProps.blurIntensity : element._currentState?.blurIntensity; if (blur === 0) { classes.push('muchandy-hero--no-blur'); } else if (blur && blur <= 4) { classes.push('muchandy-hero--blur-light'); } else if (blur && blur >= 12 && blur < 20) { classes.push('muchandy-hero--blur-heavy'); } else if (blur && blur >= 20) { classes.push('muchandy-hero--blur-extreme'); } const customClass = newProps.className !== undefined ? newProps.className : element._currentState?.className; if (customClass) { classes.push(customClass); } element.className = classes.filter(Boolean).join(' '); console.log('Updated className to:', element.className); } }; // Enhanced destroy method with proper cleanup muchandyHero.destroy = function () { const element = this.getElement(); if (element && element._components) { const { tabs } = element._components; // Clean up tabs component if (tabs && typeof tabs.destroy === 'function') { try { tabs.destroy(); } catch (error) { console.warn('MuchandyHero: Error destroying tabs component:', error); } } // Note: We don't destroy the forms as they might be used elsewhere // The parent/creator of this component should handle form cleanup // Clear component references element._components = null; element._currentState = null; } // Call original destroy method from base component if it exists if (originalUpdate && typeof originalUpdate.destroy === 'function') { try { originalUpdate.destroy.call(this); } catch (error) { console.warn('MuchandyHero: Error calling base destroy:', error); } } }; // Convenience methods for easier state management muchandyHero.setBackgroundImageUrl = function (backgroundImageUrl) { return this.setState({ backgroundImageUrl }); }; // Legacy method for backward compatibility muchandyHero.setBackgroundImage = function (backgroundImage) { console.warn( 'MuchandyHero: "setBackgroundImage" method is deprecated, use "setBackgroundImageUrl" instead' ); return this.setBackgroundImageUrl(backgroundImage); }; muchandyHero.setTitle = function (title) { return this.setState({ title }); }; muchandyHero.setSubtitle = function (subtitle) { return this.setState({ subtitle }); }; muchandyHero.setBlurIntensity = function (blurIntensity) { return this.setState({ blurIntensity }); }; muchandyHero.setOverlayOpacity = function (overlayOpacity) { return this.setState({ overlayOpacity }); }; muchandyHero.getCurrentState = function () { return { ...currentState }; }; // Enhanced update method with validation muchandyHero.update = function (newProps) { try { // Normalize new props for backward compatibility const normalizedNewProps = normalizeProps(newProps); // Only validate form props if they're being updated if (normalizedNewProps.repairForm || normalizedNewProps.buybackForm) { validateMuchandyHeroProps({ repairForm: normalizedNewProps.repairForm || currentState.repairForm, buybackForm: normalizedNewProps.buybackForm || currentState.buybackForm, }); } if (this.shouldRerender(normalizedNewProps)) { console.log( 'MuchandyHero: Full rerender needed for props:', Object.keys(normalizedNewProps) ); // Update current state currentState = { ...currentState, ...normalizedNewProps }; // Get current element and its parent const oldElement = this.getElement(); const parent = oldElement?.parentNode; if (parent) { // Create new element with updated state const newElement = renderMuchandyHero(currentState); // Replace the old element with the new one parent.replaceChild(newElement, oldElement); // Update the component's element reference muchandyHero.getElement = () => newElement; console.log('MuchandyHero: Full rerender completed'); } return this; } else { console.log( 'MuchandyHero: Using partial update for props:', Object.keys(normalizedNewProps) ); // Use partial updates for efficiency return this.setState(normalizedNewProps); } } catch (error) { console.error('MuchandyHero update error:', error); return this; } }; // Add theme change handler muchandyHero.onThemeChange = (newTheme, previousTheme) => { console.log(`MuchandyHero theme changed: ${previousTheme} -> ${newTheme}`); // Apply theme-specific adjustments if needed }; // Initialize element state reference const element = muchandyHero.getElement(); if (element) { element._currentState = currentState; } return muchandyHero; }; // Required props for validation createMuchandyHero.requiredProps = ['repairForm', 'buybackForm']; // Create the component with theme awareness and optimizations const MuchandyHero = withThemeAwareness( createComponent('MuchandyHero', createMuchandyHero) ); // Export as a factory function export default MuchandyHero;