# blockenberg/2.0.7/blocks/timeline/index.js

Blockenberg — 600+ Advanced Gutenberg Blocks &amp; AI Agent for WordPress Block Editor, version 2.0.7. 807 lines.

- Page: https://pluginprobe.com/plugins/blockenberg/2.0.7/code/blocks/timeline/index.js
- Raw: https://pluginprobe.com/plugins/blockenberg/2.0.7/raw/blocks/timeline/index.js
- Modified: 2026-03-06T12:32:40+00:00

Line numbers below start at 1. Link to a line or a range by appending a fragment to the
page URL, for example `https://pluginprobe.com/plugins/blockenberg/2.0.7/code/blocks/timeline/index.js#L10-L20`.

```javascript
( function () {
    var el = wp.element.createElement;
    var Fragment = wp.element.Fragment;
    var __ = wp.i18n.__;
    var useState = wp.element.useState;
    var useMemo = wp.element.useMemo;
    var registerBlockType = wp.blocks.registerBlockType;
    var InspectorControls = wp.blockEditor.InspectorControls;
    var useBlockProps = wp.blockEditor.useBlockProps;
    var RichText = wp.blockEditor.RichText;
    var PanelBody = wp.components.PanelBody;
    var PanelColorSettings = wp.blockEditor.PanelColorSettings;
    var ToggleControl = wp.components.ToggleControl;
    var RangeControl = wp.components.RangeControl;
    var SelectControl = wp.components.SelectControl;
    var Button = wp.components.Button;
    var BlockControls = wp.blockEditor.BlockControls;
    var ToolbarGroup = wp.components.ToolbarGroup;
    var ToolbarButton = wp.components.ToolbarButton;
    var Popover = wp.components.Popover;
    var TabPanel = wp.components.TabPanel;
    var TextControl = wp.components.TextControl;

    var _tc; function getTypoControl() { return _tc || (_tc = window.bkbgTypographyControl); }
    var _tv; function getTypoCssVars() { return _tv || (_tv = window.bkbgTypoCssVars); }
    var _tvf = function (typo, prefix) { var fn = getTypoCssVars(); return fn ? fn(typo, prefix) : {}; };

    // Full Dashicons list (300+ icons)
    var dashicons = [
        // Admin Menu
        'menu', 'admin-site', 'admin-site-alt', 'admin-site-alt2', 'admin-site-alt3', 'dashboard',
        'admin-post', 'admin-media', 'admin-links', 'admin-page', 'admin-comments',
        'admin-appearance', 'admin-plugins', 'admin-users', 'admin-tools', 'admin-settings',
        'admin-network', 'admin-home', 'admin-generic', 'admin-collapse', 'filter',
        'admin-customizer', 'admin-multisite',
        // Welcome Screen
        'welcome-write-blog', 'welcome-add-page', 'welcome-view-site', 'welcome-widgets-menus',
        'welcome-comments', 'welcome-learn-more',
        // Post Formats
        'format-aside', 'format-image', 'format-gallery', 'format-video', 'format-status',
        'format-quote', 'format-chat', 'format-audio',
        // Media
        'camera', 'camera-alt', 'images-alt', 'images-alt2', 'video-alt', 'video-alt2',
        'video-alt3', 'media-archive', 'media-audio', 'media-code', 'media-default',
        'media-document', 'media-interactive', 'media-spreadsheet', 'media-text',
        'media-video', 'playlist-audio', 'playlist-video', 'controls-play',
        'controls-pause', 'controls-forward', 'controls-skipforward', 'controls-back',
        'controls-skipback', 'controls-repeat', 'controls-volumeon', 'controls-volumeoff',
        // Image Editing
        'image-crop', 'image-rotate', 'image-rotate-left', 'image-rotate-right',
        'image-flip-vertical', 'image-flip-horizontal', 'image-filter',
        // Sorting & Misc UI
        'align-left', 'align-right', 'align-center', 'align-none', 'align-pull-left', 'align-pull-right',
        'align-full-width', 'align-wide',
        'lock', 'unlock', 'calendar', 'calendar-alt', 'visibility', 'hidden',
        'post-status', 'edit', 'trash', 'sticky',
        // Arrows & Navigation
        'arrow-up', 'arrow-down', 'arrow-right', 'arrow-left',
        'arrow-up-alt', 'arrow-down-alt', 'arrow-right-alt', 'arrow-left-alt',
        'arrow-up-alt2', 'arrow-down-alt2', 'arrow-right-alt2', 'arrow-left-alt2',
        'sort', 'leftright', 'randomize', 'list-view', 'excerpt-view', 'grid-view',
        'move', 'undo', 'redo',
        // Social & Sharing
        'share', 'share-alt', 'share-alt2', 'rss', 'email', 'email-alt', 'email-alt2',
        'networking', 'amazon', 'facebook', 'facebook-alt', 'google',
        'twitter', 'twitter-alt', 'instagram', 'linkedin', 'pinterest', 'podio',
        'reddit', 'spotify', 'twitch', 'whatsapp', 'xing', 'youtube',
        // WordPress.org Specific
        'hammer', 'art', 'migrate', 'performance', 'universal-access',
        'universal-access-alt', 'tickets', 'nametag', 'clipboard', 'heart', 'megaphone',
        'schedule', 'tide', 'rest-api', 'code-standards',
        // BuddyPress
        'buddicons-activity', 'buddicons-bbpress-logo', 'buddicons-buddypress-logo',
        'buddicons-community', 'buddicons-forums', 'buddicons-friends',
        'buddicons-groups', 'buddicons-pm', 'buddicons-replies',
        'buddicons-topics', 'buddicons-tracking',
        // WordPress Core
        'wordpress', 'wordpress-alt', 'pressthis', 'update', 'update-alt',
        'screenoptions', 'info', 'cart', 'feedback', 'cloud', 'cloud-saved',
        'cloud-upload', 'translation', 'tag', 'category', 'archive',
        // Status & Alerts
        'yes', 'yes-alt', 'no', 'no-alt', 'plus', 'plus-alt', 'plus-alt2',
        'minus', 'dismiss', 'marker', 'star-filled', 'star-half', 'star-empty',
        'flag', 'warning', 'thumbs-up', 'thumbs-down', 'smiley',
        // Location & Places
        'location', 'location-alt', 'vault', 'shield', 'shield-alt',
        'sos', 'search', 'slides', 'text-page', 'analytics', 'chart-pie',
        'chart-bar', 'chart-line', 'chart-area', 'groups', 'businessman', 'businesswoman',
        'businessperson', 'id', 'id-alt', 'products', 'awards', 'forms', 'testimonial',
        'portfolio', 'book', 'book-alt', 'download', 'upload', 'backup',
        'clock', 'lightbulb', 'microphone', 'desktop', 'laptop', 'tablet',
        'smartphone', 'phone', 'index-card', 'carrot', 'building', 'store',
        'album', 'palmtree', 'tickets-alt', 'money', 'money-alt', 'bank',
        // Misc Objects
        'layout', 'paperclip', 'color-picker', 'edit-large', 'edit-page',
        'airplane', 'beer', 'bell', 'bitcoin', 'calculator', 'car', 'coffee',
        'database', 'database-add', 'database-export', 'database-import',
        'database-remove', 'database-view', 'drumstick', 'ellipsis', 'exit',
        'food', 'fullscreen-alt', 'fullscreen-exit-alt', 'games', 'hourglass',
        'html', 'insert', 'insert-after', 'insert-before', 'open-folder',
        'pdf', 'pets', 'plugins-checked', 'printer', 'privacy', 'remove',
        'saved', 'shortcode', 'superhero', 'superhero-alt', 'text', 'external',
        // Table Operations
        'table-col-after', 'table-col-before', 'table-col-delete',
        'table-row-after', 'table-row-before', 'table-row-delete',
        // Editor Controls
        'editor-aligncenter', 'editor-alignleft', 'editor-alignright', 'editor-bold',
        'editor-break', 'editor-code', 'editor-contract', 'editor-customchar',
        'editor-expand', 'editor-help', 'editor-indent', 'editor-insertmore',
        'editor-italic', 'editor-justify', 'editor-kitchensink', 'editor-ltr',
        'editor-ol', 'editor-ol-rtl', 'editor-outdent', 'editor-paragraph',
        'editor-paste-text', 'editor-paste-word', 'editor-quote', 'editor-removeformatting',
        'editor-rtl', 'editor-spellcheck', 'editor-strikethrough', 'editor-table',
        'editor-textcolor', 'editor-ul', 'editor-underline', 'editor-unlink', 'editor-video'
    ];

    // Helper: parse icon value (supports legacy string or new object format)
    function parseIcon(iconValue) {
        if (!iconValue) return { type: 'dashicon', value: 'flag' };
        if (typeof iconValue === 'string') {
            // Legacy format - just the icon name
            return { type: 'dashicon', value: iconValue };
        }
        return iconValue;
    }

    registerBlockType('blockenberg/timeline', {
        title: __('Timeline', 'blockenberg'),
        icon: 'backup',
        category: 'bkbg-business',
        description: __('Display events, milestones, or steps in a beautiful timeline layout.', 'blockenberg'),

        edit: function (props) {
            var attributes = props.attributes;
            var setAttributes = props.setAttributes;
            var isSelected = props.isSelected;
            var a = attributes;

            var hoverState = useState(null);
            var hoveredItem = hoverState[0];
            var setHoveredItem = hoverState[1];

            // State for icon picker popover
            var iconPickerState = useState(null);
            var openIconPicker = iconPickerState[0];
            var setOpenIconPicker = iconPickerState[1];

            // Search state for dashicons
            var searchState = useState('');
            var iconSearch = searchState[0];
            var setIconSearch = searchState[1];

            // Filter dashicons based on search
            var filteredDashicons = useMemo(function() {
                if (!iconSearch) return dashicons;
                var search = iconSearch.toLowerCase();
                return dashicons.filter(function(icon) {
                    return icon.toLowerCase().includes(search);
                });
            }, [iconSearch]);

            // Update item field
            function updateItem(index, field, value) {
                var newItems = a.items.map(function (item, i) {
                    if (i === index) {
                        var updated = {};
                        for (var k in item) { updated[k] = item[k]; }
                        updated[field] = value;
                        return updated;
                    }
                    return item;
                });
                setAttributes({ items: newItems });
            }

            // Add new item
            function addItem() {
                var newItem = {
                    date: 'New Date',
                    title: 'New Event',
                    content: 'Describe this milestone or event.',
                    icon: { type: 'dashicon', value: 'flag' }
                };
                setAttributes({ items: a.items.concat([newItem]) });
            }

            // Insert item after specific index
            function insertItemAfter(index) {
                var newItem = {
                    date: 'New Date',
                    title: 'New Event',
                    content: 'Describe this milestone or event.',
                    icon: { type: 'dashicon', value: 'flag' }
                };
                var newItems = a.items.slice();
                newItems.splice(index + 1, 0, newItem);
                setAttributes({ items: newItems });
            }

            // Remove item
            function removeItem(index) {
                if (a.items.length <= 1) return;
                setAttributes({ items: a.items.filter(function (_, i) { return i !== index; }) });
            }

            // Move item
            function moveItem(index, direction) {
                var newIndex = index + direction;
                if (newIndex < 0 || newIndex >= a.items.length) return;
                var newItems = a.items.slice();
                var temp = newItems[index];
                newItems[index] = newItems[newIndex];
                newItems[newIndex] = temp;
                setAttributes({ items: newItems });
            }

            // Duplicate item
            function duplicateItem(index) {
                var newItems = a.items.slice();
                var copy = Object.assign({}, a.items[index]);
                newItems.splice(index + 1, 0, copy);
                setAttributes({ items: newItems });
            }

            // Inspector controls
            var inspector = el(InspectorControls, {},
                // Layout Panel
                el(PanelBody, { title: __('Layout', 'blockenberg'), initialOpen: true },
                    el(SelectControl, {
                        label: __('Layout Style', 'blockenberg'),
                        value: a.layoutStyle,
                        options: [
                            { label: __('Vertical', 'blockenberg'), value: 'vertical' },
                            { label: __('Horizontal', 'blockenberg'), value: 'horizontal' }
                        ],
                        onChange: function (v) { setAttributes({ layoutStyle: v }); }
                    }),
                    a.layoutStyle === 'vertical' && el(ToggleControl, {
                        label: __('Alternating Layout', 'blockenberg'),
                        checked: a.alternating,
                        __nextHasNoMarginBottom: true,
                        onChange: function (v) { setAttributes({ alternating: v }); }
                    }),
                    el(RangeControl, {
                        label: __('Item Spacing', 'blockenberg'),
                        value: a.itemSpacing,
                        onChange: function (v) { setAttributes({ itemSpacing: v }); },
                        min: 10,
                        max: 100
                    })
                ),

                // Connector Panel
                el(PanelBody, { title: __('Connector Line', 'blockenberg'), initialOpen: false },
                    el(ToggleControl, {
                        label: __('Show Connector', 'blockenberg'),
                        checked: a.showConnector,
                        __nextHasNoMarginBottom: true,
                        onChange: function (v) { setAttributes({ showConnector: v }); }
                    }),
                    a.showConnector && el(SelectControl, {
                        label: __('Connector Style', 'blockenberg'),
                        value: a.connectorStyle,
                        options: [
                            { label: __('Solid', 'blockenberg'), value: 'solid' },
                            { label: __('Dashed', 'blockenberg'), value: 'dashed' },
                            { label: __('Dotted', 'blockenberg'), value: 'dotted' }
                        ],
                        onChange: function (v) { setAttributes({ connectorStyle: v }); }
                    }),
                    a.showConnector && el(RangeControl, {
                        label: __('Line Width', 'blockenberg'),
                        value: a.lineWidth,
                        onChange: function (v) { setAttributes({ lineWidth: v }); },
                        min: 1,
                        max: 8
                    })
                ),

                // Marker Panel
                el(PanelBody, { title: __('Marker', 'blockenberg'), initialOpen: false },
                    el(SelectControl, {
                        label: __('Marker Style', 'blockenberg'),
                        value: a.markerStyle,
                        options: [
                            { label: __('Icon', 'blockenberg'), value: 'icon' },
                            { label: __('Number', 'blockenberg'), value: 'number' },
                            { label: __('Dot', 'blockenberg'), value: 'dot' }
                        ],
                        onChange: function (v) { setAttributes({ markerStyle: v }); }
                    }),
                    el(RangeControl, {
                        label: __('Marker Size', 'blockenberg'),
                        value: a.markerSize,
                        onChange: function (v) { setAttributes({ markerSize: v }); },
                        min: 24,
                        max: 80
                    })
                ),

                // Card Panel
                el(PanelBody, { title: __('Card Style', 'blockenberg'), initialOpen: false },
                    el(ToggleControl, {
                        label: __('Card Shadow', 'blockenberg'),
                        checked: a.cardShadow,
                        __nextHasNoMarginBottom: true,
                        onChange: function (v) { setAttributes({ cardShadow: v }); }
                    }),
                    el(RangeControl, {
                        label: __('Card Padding', 'blockenberg'),
                        value: a.cardPadding,
                        onChange: function (v) { setAttributes({ cardPadding: v }); },
                        min: 12,
                        max: 48
                    }),
                    el(RangeControl, {
                        label: __('Border Radius', 'blockenberg'),
                        value: a.cardBorderRadius,
                        onChange: function (v) { setAttributes({ cardBorderRadius: v }); },
                        min: 0,
                        max: 24
                    }),
                    el(RangeControl, {
                        label: __('Border Width', 'blockenberg'),
                        value: a.cardBorderWidth,
                        onChange: function (v) { setAttributes({ cardBorderWidth: v }); },
                        min: 0,
                        max: 4
                    })
                ),

                // Animation Panel
                el(PanelBody, { title: __('Animation', 'blockenberg'), initialOpen: false },
                    el(ToggleControl, {
                        label: __('Animate on Scroll', 'blockenberg'),
                        checked: a.animateOnScroll,
                        __nextHasNoMarginBottom: true,
                        onChange: function (v) { setAttributes({ animateOnScroll: v }); }
                    }),
                    a.animateOnScroll && el(SelectControl, {
                        label: __('Animation Type', 'blockenberg'),
                        value: a.animationType,
                        options: [
                            { label: __('Fade Up', 'blockenberg'), value: 'fade-up' },
                            { label: __('Fade In', 'blockenberg'), value: 'fade-in' },
                            { label: __('Slide In', 'blockenberg'), value: 'slide-in' },
                            { label: __('Scale Up', 'blockenberg'), value: 'scale-up' }
                        ],
                        onChange: function (v) { setAttributes({ animationType: v }); }
                    })
                ),

                // Typography Panel
                el(PanelBody, { title: __('Typography', 'blockenberg'), initialOpen: false },
                    getTypoControl() && el(getTypoControl(), {
                        label: __('Date Typography', 'blockenberg'),
                        value: a.dateTypo || {},
                        onChange: function (v) { setAttributes({ dateTypo: v }); }
                    }),
                    getTypoControl() && el(getTypoControl(), {
                        label: __('Title Typography', 'blockenberg'),
                        value: a.titleTypo || {},
                        onChange: function (v) { setAttributes({ titleTypo: v }); }
                    }),
                    getTypoControl() && el(getTypoControl(), {
                        label: __('Content Typography', 'blockenberg'),
                        value: a.contentTypo || {},
                        onChange: function (v) { setAttributes({ contentTypo: v }); }
                    })
                ),

                // Colors Panel
                el(PanelColorSettings, {
                    title: __('Colors', 'blockenberg'),
                    initialOpen: false,
                    colorSettings: [
                        {
                            value: a.lineColor,
                            onChange: function (v) { setAttributes({ lineColor: v }); },
                            label: __('Line Color', 'blockenberg')
                        },
                        {
                            value: a.markerBg,
                            onChange: function (v) { setAttributes({ markerBg: v }); },
                            label: __('Marker Background', 'blockenberg')
                        },
                        {
                            value: a.markerColor,
                            onChange: function (v) { setAttributes({ markerColor: v }); },
                            label: __('Marker Icon Color', 'blockenberg')
                        },
                        {
                            value: a.cardBg,
                            onChange: function (v) { setAttributes({ cardBg: v }); },
                            label: __('Card Background', 'blockenberg')
                        },
                        {
                            value: a.cardBorderColor,
                            onChange: function (v) { setAttributes({ cardBorderColor: v }); },
                            label: __('Card Border Color', 'blockenberg')
                        },
                        {
                            value: a.dateColor,
                            onChange: function (v) { setAttributes({ dateColor: v }); },
                            label: __('Date Color', 'blockenberg')
                        },
                        {
                            value: a.titleColor,
                            onChange: function (v) { setAttributes({ titleColor: v }); },
                            label: __('Title Color', 'blockenberg')
                        },
                        {
                            value: a.contentColor,
                            onChange: function (v) { setAttributes({ contentColor: v }); },
                            label: __('Content Color', 'blockenberg')
                        }
                    ]
                })
            );

            // CSS Variables
            var wrapStyle = {
                '--bkbg-tl-line-color': a.lineColor,
                '--bkbg-tl-line-width': a.lineWidth + 'px',
                '--bkbg-tl-marker-bg': a.markerBg,
                '--bkbg-tl-marker-color': a.markerColor,
                '--bkbg-tl-marker-size': a.markerSize + 'px',
                '--bkbg-tl-card-bg': a.cardBg,
                '--bkbg-tl-card-border': a.cardBorderColor,
                '--bkbg-tl-card-border-w': a.cardBorderWidth + 'px',
                '--bkbg-tl-card-radius': a.cardBorderRadius + 'px',
                '--bkbg-tl-card-padding': a.cardPadding + 'px',
                '--bkbg-tl-date-color': a.dateColor,
                '--bkbg-tl-title-color': a.titleColor,
                '--bkbg-tl-content-color': a.contentColor,
                '--bkbg-tl-spacing': a.itemSpacing + 'px'
            };
            Object.assign(wrapStyle, _tvf(a.dateTypo, '--bktl-dt-'), _tvf(a.titleTypo, '--bktl-tt-'), _tvf(a.contentTypo, '--bktl-ct-'));

            // Render full icon picker popover with tabs
            function renderIconPicker(index) {
                var currentIcon = parseIcon(a.items[index].icon);
                var stop = function (e) {
                    if (e && e.stopPropagation) e.stopPropagation();
                };

                return el(Popover, {
                    position: 'bottom center',
                    className: 'bkbg-tl-icon-popover',
                    onClose: function () { 
                        setOpenIconPicker(null);
                        setIconSearch('');
                    }
                },
                    el('div', { className: 'bkbg-tl-icon-picker-modal', onMouseDown: stop, onClick: stop },
                        el('div', { className: 'bkbg-tl-icon-picker-header' },
                            el('h3', {}, __('Choose Icon', 'blockenberg')),
                            el(Button, {
                                icon: 'no-alt',
                                onClick: function() { 
                                    setOpenIconPicker(null);
                                    setIconSearch('');
                                },
                                className: 'bkbg-tl-icon-picker-close'
                            })
                        ),
                        el(TabPanel, {
                            className: 'bkbg-tl-icon-tabs',
                            activeClass: 'is-active',
                            tabs: [
                                { name: 'dashicons', title: __('Dashicons', 'blockenberg'), className: 'bkbg-tl-tab-dashicons' },
                                { name: 'media', title: __('Media Library', 'blockenberg'), className: 'bkbg-tl-tab-media' }
                            ]
                        }, function(tab) {
                            if (tab.name === 'dashicons') {
                                return el('div', { className: 'bkbg-tl-dashicons-panel' },
                                    // Search box
                                    el(TextControl, {
                                        placeholder: __('Search icons...', 'blockenberg'),
                                        value: iconSearch,
                                        onChange: setIconSearch,
                                        className: 'bkbg-tl-icon-search'
                                    }),
                                    // Icons grid
                                    el('div', { className: 'bkbg-tl-dashicons-grid' },
                                        filteredDashicons.length === 0 
                                            ? el('p', { className: 'bkbg-tl-no-icons' }, __('No icons found', 'blockenberg'))
                                            : filteredDashicons.map(function(icon) {
                                                var isActive = currentIcon.type === 'dashicon' && currentIcon.value === icon;
                                                return el('button', {
                                                    key: icon,
                                                    type: 'button',
                                                    className: 'bkbg-tl-dashicon-btn' + (isActive ? ' is-active' : ''),
                                                    onClick: function() {
                                                        updateItem(index, 'icon', { type: 'dashicon', value: icon });
                                                        setOpenIconPicker(null);
                                                        setIconSearch('');
                                                    },
                                                    title: icon
                                                },
                                                    el('span', { className: 'dashicons dashicons-' + icon })
                                                );
                                            })
                                    )
                                );
                            } else {
                                // Media Library tab - use wp.media directly for reliability
                                var openMediaLibrary = function() {
                                    var frame = wp.media({
                                        title: __('Select Icon Image', 'blockenberg'),
                                        button: { text: __('Use as Icon', 'blockenberg') },
                                        multiple: false,
                                        library: { type: 'image' }
                                    });
                                    frame.on('select', function() {
                                        var attachment = frame.state().get('selection').first().toJSON();
                                        updateItem(index, 'icon', { type: 'custom', value: attachment.url });
                                        setOpenIconPicker(null);
                                    });
                                    frame.open();
                                };

                                return el('div', { className: 'bkbg-tl-media-panel' },
                                    // Current custom icon preview
                                    currentIcon.type === 'custom' && el('div', { className: 'bkbg-tl-current-media' },
                                        el('img', { src: currentIcon.value, alt: '' }),
                                        el(Button, {
                                            isDestructive: true,
                                            variant: 'secondary',
                                            onClick: function() {
                                                updateItem(index, 'icon', { type: 'dashicon', value: 'flag' });
                                            }
                                        }, __('Remove', 'blockenberg'))
                                    ),
                                    // Media upload button using wp.media directly
                                    el(Button, {
                                        variant: 'secondary',
                                        onClick: openMediaLibrary,
                                        className: 'bkbg-tl-media-btn'
                                    },
                                        el('span', { className: 'dashicons dashicons-upload' }),
                                        ' ',
                                        __('Select from Media Library', 'blockenberg')
                                    ),
                                    el('p', { className: 'bkbg-tl-media-hint' },
                                        __('Upload or select an image to use as a custom marker icon.', 'blockenberg')
                                    )
                                );
                            }
                        })
                    )
                );
            }

            // Render marker content (icon, number, or dot)
            function renderMarkerContent(item, index) {
                if (a.markerStyle === 'number') {
                    return el('span', { className: 'bkbg-tl-marker-number' }, index + 1);
                } else if (a.markerStyle === 'dot') {
                    return el('span', { className: 'bkbg-tl-marker-dot' });
                } else {
                    var iconData = parseIcon(item.icon);
                    if (iconData.type === 'custom') {
                        return el('img', {
                            src: iconData.value,
                            alt: '',
                            className: 'bkbg-tl-marker-img'
                        });
                    } else {
                        // Dashicon
                        return el('span', {
                            className: 'dashicons dashicons-' + iconData.value + ' bkbg-tl-marker-dashicon'
                        });
                    }
                }
            }

            // Render items
            var itemsEl = a.items.map(function (item, index) {
                var isHovered = hoveredItem === index;
                var itemClass = 'bkbg-tl-item';
                if (a.alternating && a.layoutStyle === 'vertical') {
                    itemClass += index % 2 === 0 ? ' bkbg-tl-item-left' : ' bkbg-tl-item-right';
                }
                if (isHovered) itemClass += ' bkbg-tl-item-hovered';

                // Marker is clickable when icon style
                var markerProps = {
                    className: 'bkbg-tl-marker' + (a.markerStyle === 'icon' ? ' bkbg-tl-marker-clickable' : '')
                };
                if (a.markerStyle === 'icon') {
                    markerProps.onClick = function (e) {
                        e.stopPropagation();
                        setOpenIconPicker(openIconPicker === index ? null : index);
                    };
                    markerProps.title = __('Click to change icon', 'blockenberg');
                }

                return el('div', {
                    key: index,
                    className: itemClass,
                    onMouseEnter: function () { setHoveredItem(index); },
                    onMouseLeave: function () { setHoveredItem(null); }
                },
                    // Marker
                    el('div', markerProps,
                        renderMarkerContent(item, index),
                        openIconPicker === index && renderIconPicker(index)
                    ),

                    // Card
                    el('div', {
                        className: 'bkbg-tl-card' + (a.cardShadow ? ' has-shadow' : '')
                    },
                        // Date
                        el(RichText, {
                            tagName: 'div',
                            className: 'bkbg-tl-date',
                            value: item.date,
                            onChange: function (v) { updateItem(index, 'date', v); },
                            placeholder: __('Date...', 'blockenberg')
                        }),

                        // Title
                        el(RichText, {
                            tagName: 'h4',
                            className: 'bkbg-tl-title',
                            value: item.title,
                            onChange: function (v) { updateItem(index, 'title', v); },
                            placeholder: __('Title...', 'blockenberg')
                        }),

                        // Content
                        el(RichText, {
                            tagName: 'p',
                            className: 'bkbg-tl-content',
                            value: item.content,
                            onChange: function (v) { updateItem(index, 'content', v); },
                            placeholder: __('Content...', 'blockenberg')
                        })
                    ),

                    // Item actions - icons change based on layout
                    el('div', { className: 'bkbg-tl-item-actions' },
                        el(Button, {
                            icon: a.layoutStyle === 'horizontal' ? 'arrow-left-alt2' : 'arrow-up-alt2',
                            label: a.layoutStyle === 'horizontal' ? __('Move Left', 'blockenberg') : __('Move Up', 'blockenberg'),
                            onClick: function () { moveItem(index, -1); },
                            disabled: index === 0,
                            size: 'small'
                        }),
                        el(Button, {
                            icon: a.layoutStyle === 'horizontal' ? 'arrow-right-alt2' : 'arrow-down-alt2',
                            label: a.layoutStyle === 'horizontal' ? __('Move Right', 'blockenberg') : __('Move Down', 'blockenberg'),
                            onClick: function () { moveItem(index, 1); },
                            disabled: index === a.items.length - 1,
                            size: 'small'
                        }),
                        el(Button, {
                            icon: 'plus-alt2',
                            label: __('Insert After', 'blockenberg'),
                            onClick: function () { insertItemAfter(index); },
                            size: 'small'
                        }),
                        el(Button, {
                            icon: 'admin-page',
                            label: __('Duplicate', 'blockenberg'),
                            onClick: function () { duplicateItem(index); },
                            size: 'small'
                        }),
                        el(Button, {
                            icon: 'trash',
                            label: __('Remove', 'blockenberg'),
                            onClick: function () { removeItem(index); },
                            isDestructive: true,
                            disabled: a.items.length <= 1,
                            size: 'small'
                        })
                    )
                );
            });

            // Block props
            var wrapClass = 'bkbg-tl-wrap bkbg-editor-wrap';
            wrapClass += ' bkbg-tl-' + a.layoutStyle;
            if (a.alternating && a.layoutStyle === 'vertical') wrapClass += ' bkbg-tl-alternating';
            if (a.showConnector) wrapClass += ' bkbg-tl-has-connector bkbg-tl-connector-' + a.connectorStyle;

            var blockProps = useBlockProps({
                className: wrapClass,
                style: wrapStyle,
                'data-block-label': 'Timeline'
            });

            // Toolbar
            var toolbar = el(BlockControls, {},
                el(ToolbarGroup, {},
                    el(ToolbarButton, {
                        icon: 'plus-alt2',
                        label: __('Add Item', 'blockenberg'),
                        onClick: addItem
                    })
                )
            );

            return el('div', blockProps,
                inspector,
                toolbar,
                el('div', { className: 'bkbg-tl-items' },
                    itemsEl
                ),
                el('div', { className: 'bkbg-editor-actions' },
                    el(Button, {
                        variant: 'secondary',
                        onClick: addItem,
                        icon: 'plus-alt2'
                    }, __('Add Event', 'blockenberg'))
                )
            );
        },

        save: function (props) {
            var a = props.attributes;

            var wrapStyle = {
                '--bkbg-tl-line-color': a.lineColor,
                '--bkbg-tl-line-width': a.lineWidth + 'px',
                '--bkbg-tl-marker-bg': a.markerBg,
                '--bkbg-tl-marker-color': a.markerColor,
                '--bkbg-tl-marker-size': a.markerSize + 'px',
                '--bkbg-tl-card-bg': a.cardBg,
                '--bkbg-tl-card-border': a.cardBorderColor,
                '--bkbg-tl-card-border-w': a.cardBorderWidth + 'px',
                '--bkbg-tl-card-radius': a.cardBorderRadius + 'px',
                '--bkbg-tl-card-padding': a.cardPadding + 'px',
                '--bkbg-tl-date-color': a.dateColor,
                '--bkbg-tl-title-color': a.titleColor,
                '--bkbg-tl-content-color': a.contentColor,
                '--bkbg-tl-spacing': a.itemSpacing + 'px'
            };
            Object.assign(wrapStyle, _tvf(a.dateTypo, '--bktl-dt-'), _tvf(a.titleTypo, '--bktl-tt-'), _tvf(a.contentTypo, '--bktl-ct-'));

            // Helper: parse icon value (supports legacy string or new object format)
            function parseSaveIcon(iconValue) {
                if (!iconValue) return { type: 'dashicon', value: 'flag' };
                if (typeof iconValue === 'string') {
                    return { type: 'dashicon', value: iconValue };
                }
                return iconValue;
            }

            var el = wp.element.createElement;

            var wrapClass = 'bkbg-tl-wrap';
            wrapClass += ' bkbg-tl-' + a.layoutStyle;
            if (a.alternating && a.layoutStyle === 'vertical') wrapClass += ' bkbg-tl-alternating';
            if (a.showConnector) wrapClass += ' bkbg-tl-has-connector bkbg-tl-connector-' + a.connectorStyle;

            var itemsEl = a.items.map(function (item, index) {
                var itemClass = 'bkbg-tl-item';
                if (a.alternating && a.layoutStyle === 'vertical') {
                    itemClass += index % 2 === 0 ? ' bkbg-tl-item-left' : ' bkbg-tl-item-right';
                }

                var markerContent;
                if (a.markerStyle === 'number') {
                    markerContent = el('span', { className: 'bkbg-tl-marker-number' }, index + 1);
                } else if (a.markerStyle === 'dot') {
                    markerContent = el('span', { className: 'bkbg-tl-marker-dot' });
                } else {
                    var iconData = parseSaveIcon(item.icon);
                    if (iconData.type === 'custom') {
                        markerContent = el('img', {
                            src: iconData.value,
                            alt: '',
                            className: 'bkbg-tl-marker-img'
                        });
                    } else {
                        // Dashicon
                        markerContent = el('span', {
                            className: 'dashicons dashicons-' + iconData.value + ' bkbg-tl-marker-dashicon'
                        });
                    }
                }

                return el('div', { key: index, className: itemClass },
                    el('div', { className: 'bkbg-tl-marker' }, markerContent),
                    el('div', { className: 'bkbg-tl-card' + (a.cardShadow ? ' has-shadow' : '') },
                        el('div', { className: 'bkbg-tl-date' }, item.date),
                        el('h4', { className: 'bkbg-tl-title' }, item.title),
                        el('p', { className: 'bkbg-tl-content' }, item.content)
                    )
                );
            });

            return el('div', {
                className: wrapClass,
                style: wrapStyle,
                'data-animate': a.animateOnScroll ? '1' : '0',
                'data-animation': a.animationType
            },
                el('div', { className: 'bkbg-tl-items' }, itemsEl)
            );
        }
    });
}() );

```
