/** Datei: plugins/rest/assets.OLD/js/generator/components/button.js **/ /** * generator/components/button.js * * Erstellt einen flexiblen Button basierend auf einer API-Config. * * @author Sparky * @copyright 2025 ARUNOX * @version 1.0.2 * * @since 1.0.0 Datei erstellt * @since 1.0.1 Bootstrap-Icons + BEM-Syntax hinzugefügt * @since 1.0.2 BEM-Klasse auf `.button` umgestellt */ import { router } from 'plugins/rest/core/router'; import { generator } from 'plugins/rest/core/generator'; import { handleAction } from 'plugins/rest/core/helpers'; generator.extend( 'components/button', async ( config, data, container ) => { if( !config.label && !config.icon ) { console.warn("⚠ Button benötigt eine `label`- oder `icon`-Eigenschaft!"); return; } const dropdown = config.dropdowns && Array.isArray( config.dropdowns ); const wrapper = document.createElement('div' ); const button = document.createElement(config.href ? 'a' : 'button' ); const label = document.createElement('span' ); const className = 'button'; const block = config.block ? `${className} ${config.block}__${className}` : className; button.classList.add( ...block.split(' ') ); if( config.title ) button.title = config.title; if( config.type ) button.type = config.type; if( config.name ) button.name = config.name; if( config.value ) button.value = config.value; if( config.color ) await generator.generate( 'helpers/colorize', {}, config.color, button ); if( config.classes ) button.classList.add( ...config.classes ); if( config.icon ) { button.classList.add( 'has--icon' ); await generator.generate( 'components/icon', { name: config.icon, block: className }, {}, button ); } if( config.label ) { button.classList.add( 'has--label' ); label.classList.add( ...(`${block}__label label`.split(' ')) ); label.textContent = config.label; button.append( label ); } if( dropdown ) { const dropdownMenu = document.createElement('div'); wrapper.classList.add( 'button--wrapper' ); dropdownMenu.classList.add( 'button__dropdown' ); for( const item of config.dropdowns ) { const buttonWrapper = document.createElement('div'); // Wrapper für korrekte BEM-Struktur buttonWrapper.classList.add('button__dropdown__item'); const dropdownButton = await generator.generate( 'components/button', Object.assign({ color : 'secondary', type : 'button', block : 'button__dropdown' }, item.config ), data, buttonWrapper ); if( item.config.color ) { await generator.generate( 'helpers/colorize', {}, { target : 'icon', color : item.config.color, }, container || {}, dropdownButton.icon ); } dropdownMenu.appendChild( buttonWrapper ); } wrapper.appendChild( button ); wrapper.appendChild( dropdownMenu ); button.addEventListener('click', () => { dropdownMenu.classList.toggle('open'); }); wrapper.addEventListener('mouseleave', () => { dropdownMenu.classList.remove('open'); }); if( container ) container.appendChild( wrapper ); return wrapper; } if( container ) container.appendChild( button ); if( config.href ) { button.href = router.parseUrl( config.href, data ); button.target = config.target || '_blank'; button.addEventListener('click', ( event ) => { event.stopPropagation(); }); return button; } if( config.action ) { button.addEventListener('click', ( event ) => { event.preventDefault(); event.stopPropagation(); if( button.name && button.value !== '' && button.value !== 'undefined' ) { handleAction( config.action, { [`${button.name}`] : button.value }, data, 'rest' ); } else handleAction( config.action, {}, data, 'rest' ); }); } return button; }); /** Datei: plugins/rest/assets.OLD/js/generator/components/tooltips.js **/ /** * plugins/rest/generator/components/tooltips.js * * Erstellt dynamische Tooltips für Elemente mit `title`- oder `data-tooltip`-Attributen. * * @author Sparky * @copyright 2025 ARUNOX * @version 1.0.0 * * @since 1.0.0 Erste Implementierung für dynamische Tooltips */ export const enableTooltips = () => { let activeTooltip = null; // Referenz auf den aktuellen Tooltip const showTooltip = (event) => { const target = event.target.closest('[title], [data-tooltip]'); if (!target) return; const parent = target.closest('dialog, body'); const tooltipText = target.getAttribute('data-tooltip') || target.getAttribute('title'); if (!tooltipText) return; // Entferne native Tooltips if (target.hasAttribute('title')) { target.setAttribute('data-tooltip', target.getAttribute('title')); target.removeAttribute('title'); } // Tooltip erstellen oder aktualisieren if (!activeTooltip) { activeTooltip = document.createElement('div'); activeTooltip.className = 'tooltip'; parent.appendChild(activeTooltip); } activeTooltip.innerHTML = tooltipText; activeTooltip.style.display = 'block'; // Temporär sichtbar machen, um die Größe zu berechnen const { top: targetTop, left: targetLeft, width: targetWidth, height: targetHeight } = target.getBoundingClientRect(); const { top: parentTop, left: parentLeft, width: parentWidth, height: parentHeight } = parent.getBoundingClientRect(); const top = targetTop - parentTop; const left = targetLeft - parentLeft; const width = targetWidth; const height = targetHeight; // console.log( top, targetTop, parentTop, 'x', left, targetLeft, parentLeft ); // Tooltip-Größe aktualisieren const tooltipHeight = activeTooltip.offsetHeight; const tooltipWidth = activeTooltip.offsetWidth; const halfHeight = height / 2; const halfWidth = width / 2; const tooltipHalfH = tooltipHeight / 2; const tooltipHalfW = tooltipWidth / 2; // Abstände definieren const tooltipMargin = 8; const viewportSpace = 8; // Verfügbarer Platz ermitteln const spaceLeft = left - viewportSpace; const spaceRight = window.innerWidth - ( left + width ) - viewportSpace; const spaceAbove = top - viewportSpace; const spaceBelow = window.innerHeight - ( top + height ) - viewportSpace; const position = target.getAttribute('data-tooltip-position') || 'vertical'; const alignment = target.getAttribute('data-tooltip-alignment') || (position === 'vertical' ? 'top center' : 'right middle'); let verticalAlign = alignment.match( /(top|middle|bottom)/ )[0]; let horizontalAlign = alignment.match( /(left|center|right)/ )[0]; let tooltipTop = 0; let tooltipLeft = 0; let positionClass = ''; let alignmentClass = ''; if( position === 'vertical' ) { // Prüfen, ob bei default Position ausreichend Platz vorhanden ist switch( verticalAlign ) { case 'top' : verticalAlign = spaceAbove > tooltipWidth ? 'top' : 'bottom'; break; case 'bottom': verticalAlign = spaceBelow > tooltipWidth ? 'bottom' : 'top'; break; } if( verticalAlign === 'top' ) { tooltipTop = top - tooltipHeight - tooltipMargin; positionClass = 'tooltip-top'; } if( verticalAlign === 'bottom' ) { tooltipTop = top + height + tooltipMargin; positionClass = 'tooltip-bottom'; } // Prüfen, ob bei default Alignment ausreichend Platz links und rechts vorhanden ist if( horizontalAlign === 'center' ) { if( spaceLeft <= tooltipHalfW || spaceRight <= tooltipHalfW ) { horizontalAlign = "left"; } } // Prüfen, ob bei default Alignment ausreichend Platz vorhanden ist switch( horizontalAlign ) { case 'left' : horizontalAlign = spaceRight > tooltipWidth ? 'left' : 'right'; break; case 'right' : horizontalAlign = spaceLeft > tooltipWidth ? 'right' : 'left'; break; } if( horizontalAlign === 'left' ) { tooltipLeft = left; alignmentClass = 'tooltip-align-left'; } if( horizontalAlign === 'center' ) { tooltipLeft = left + halfWidth - tooltipHalfW; alignmentClass = 'tooltip-align-center'; } if( horizontalAlign === 'right' ) { tooltipLeft = left + width - tooltipWidth; alignmentClass = 'tooltip-align-right'; } } if( position === 'horizontal' ) { // Prüfen, ob bei default Position ausreichend Platz vorhanden ist switch( horizontalAlign ) { case 'left' : horizontalAlign = spaceLeft > tooltipWidth ? 'left' : 'right'; break; case 'right' : horizontalAlign = spaceRight > tooltipWidth ? 'right' : 'left'; break; } if( horizontalAlign === 'left' ) { tooltipLeft = left - tooltipWidth - tooltipMargin; positionClass = 'tooltip-left'; } if( horizontalAlign === 'right' ) { tooltipLeft = left + width + tooltipMargin; positionClass = 'tooltip-right'; } // Prüfen, ob bei default Alignment ausreichend Platz oben und unten vorhanden ist if( verticalAlign === 'middle' ) { if( spaceAbove <= tooltipHalfH || spaceBelow <= tooltipHalfH ) { verticalAlign = "top"; } } // Prüfen, ob bei default Alignment ausreichend Platz vorhanden ist switch( verticalAlign ) { case 'top' : verticalAlign = spaceBelow > tooltipHeight ? 'top' : 'bottom'; break; case 'bottom' : verticalAlign = spaceAbove > tooltipHeight ? 'bottom' : 'top'; break; } if( verticalAlign === 'top' ) { tooltipTop = top; alignmentClass = 'tooltip-align-top'; } if( verticalAlign === 'middle' ) { tooltipTop = top + halfHeight - tooltipHalfH; alignmentClass = 'tooltip-align-middle'; } if( verticalAlign === 'bottom' ) { tooltipTop = top + height - tooltipHeight; alignmentClass = 'tooltip-align-bottom'; } } activeTooltip.style.top = `${window.scrollY + tooltipTop}px`; activeTooltip.style.left = `${window.scrollX + tooltipLeft}px`; // CSS-Klassen basierend auf Position und Ausrichtung setzen activeTooltip.className = `tooltip ${positionClass} ${alignmentClass}`; // Tooltip anzeigen activeTooltip.style.display = 'block'; target.classList.add( 'has-tooltip--active' ); // Tooltip bei Mausverlassen entfernen const removeTooltip = () => { if( activeTooltip ) { activeTooltip.remove(); activeTooltip = null; } target.classList.remove( 'has-tooltip--active' ); target.removeEventListener('mouseleave', removeTooltip); }; target.addEventListener('mouseleave', removeTooltip, { once: true }); }; // Event-Listener für `mouseover` document.body.addEventListener('mouseover', showTooltip); }; /** Datei: plugins/rest/assets.OLD/js/generator/components/paragraph.js **/ /** * generator/components/paragraph.js * * Erstellt einen anklickbaren Link, der Route-Änderungen auslöst. * * @author Sparky * @copyright 2025 ARUNOX * @version 0.0.1 * * @since 1.0.0 Datei erstellt */ import { generator } from 'plugins/rest/core/generator'; import { handleAction } from 'plugins/rest/core/helpers'; generator.extend("components/paragraph", async ( config, data, container ) => { const wrapper = document.createElement("p" ); const className = 'paragraph'; const block = config.block ? `${className} ${config.block}__${className}` : className; if( !config.text ) return; wrapper.classList.add( ...block.split(' ') ); wrapper.elements = {}; if( config.icon ) { wrapper.elements.icon = await generator.generate("components/icon", { name: config.icon, block: config.block }, {}, wrapper ); } wrapper.innerHTML += config.text; if( config.color ) { generator.generate( 'helpers/colorize', config.color, { target: 'icon' }, wrapper, wrapper.elements.icon ); } container.append( wrapper ); return wrapper; }); /** Datei: plugins/rest/assets.OLD/js/generator/components/breadcrumbs.js **/ /** * generator/components/breadcrumbs.js * * Erstellt Breadcrumbs basierend auf der aktuellen Route. * * @author Sparky * @copyright 2025 ARUNOX * @version 0.0.1 * * @since 1.0.0 Datei erstellt */ import { router } from 'plugins/rest/core/router'; import { generator } from 'plugins/rest/core/generator'; generator.extend( 'components/breadcrumbs', async ( config, data, container ) => { const wrapper = document.querySelector('#breadcrumbs'); if( !wrapper) return; if( location.pathname != config.path.cachekey || config.path.cachekey != config.path.history ) { window.history.pushState({}, "", config.path.history ); } const home = wrapper.querySelector( '.breadcrumbs__item--home'); const items = wrapper.querySelectorAll( ':not(.breadcrumbs__item--home) '); if( !wrapper ) return; wrapper.classList.add( 'breadcrumbs' ); items.forEach( element => element.remove() ); const segments = router.getBreadcrumbSegments( config.path.history ); for( const [ index, segment ] of segments.entries() ) { if( ( home && index === 0 ) || ( index <= segments.length - 1 ) ) { await generator.generate( 'components/breadcrumbs/separator', {}, {}, wrapper ); } await generator.generate( 'components/link', { name : 'item', block : 'breadcrumbs', path : index === segments.length - 1 ? false : segment.path, label : segment.label, active : index === segments.length - 1, }, {}, wrapper ); } }); generator.extend( 'components/breadcrumbs/separator', ( config, data, container ) => { const separator = document.createElement('div'); separator.classList.add('breadcrumbs__separator'); container.append( separator ); }); /** Datei: plugins/rest/assets.OLD/js/generator/components/headline.js **/ /** * generator/components/headline.js * * Erstellt einen anklickbaren Link, der Route-Änderungen auslöst. * * @author Sparky * @copyright 2025 ARUNOX * @version 0.0.1 * * @since 1.0.0 Datei erstellt */ import { generator } from 'plugins/rest/core/generator'; import { handleAction } from 'plugins/rest/core/helpers'; generator.extend( 'components/headline', async ( config, data, container ) => { const wrapper = document.createElement(`h${config.size || 2}` ); const className = 'headline'; const block = config.block ? `${className} ${config.block}__${className}` : className; wrapper.classList.add( ...block.split(' ') ); wrapper.elements = {}; if( config.icon ) { wrapper.elements.icon = await generator.generate("components/icon", { name: config.icon, block: block }, {}, wrapper ); } if( config.title ) { wrapper.elements.title = document.createTextNode( config.title ); wrapper.append( wrapper.elements.title ); } if( config.color ) { generator.generate( 'helpers/colorize', config.color, { target: 'icon' }, wrapper, wrapper.elements.icon ); } container.append( wrapper ); return wrapper; }); /** Datei: plugins/rest/assets.OLD/js/generator/components/icon.js **/ /** * generator/components/icon.js * * Erstellt ein Icon-Element, unterstützt BEM-Klassen. * * @author Sparky * @copyright 2025 ARUNOX * @version 0.0.1 * * @since 1.0.0 Datei erstellt */ import { generator } from 'plugins/rest/core/generator'; generator.extend( 'components/icon', async ( config, data, container ) => { const wrapper = document.createElement("i"); const className = 'icon'; const block = config.block ? `${className} ${config.block}__${className}` : className; wrapper.classList.add( ...config.name.split(' ') ); wrapper.classList.add( ...block.split(' ') ); if( config.color ) wrapper.classList.add(`text--${config.color}`); if( container ) { container.classList.add( 'has--icon' ); container.append( wrapper ); } return wrapper; }); /** Datei: plugins/rest/assets.OLD/js/generator/components/link.js **/ /** * generator/components/link.js * * Erstellt einen anklickbaren Link, der Route-Änderungen auslöst. * * @author Sparky * @copyright 2025 ARUNOX * @version 0.0.1 * * @since 1.0.0 Datei erstellt */ import { generator } from 'plugins/rest/core/generator'; import { handleAction } from 'plugins/rest/core/helpers'; generator.extend("components/link", async ( config, data, container ) => { const link = document.createElement("a"); const label = document.createElement("span"); const name = config.name || 'link'; const block = config.block ? `${config.block}__${name}` : name; // Standardklasse setzen oder `block` nutzen (z. B. "menu") link.classList.add( block ); link.elements = {}; if( config.title ) link.title = config.title; if( config.active ) link.classList.add( `${block}--active` ); if( !config.path ) link.classList.add( `${block}--inactive` ); if( config.icon ) link.elements.icon = await generator.generate("components/icon", { name: config.icon, block: config.block }, {}, link ); if( config.label ) { label.innerHTML = config.label; label.classList.add( `${block}__label` ); link.append( label ); } if( config.path && config.path != '#' ) { link.addEventListener("click", (event) => { event.preventDefault(); handleAction( config.path, {}, {}, 'rest' ); }); } if( container ) container.append( link ); return link; }); /** Datei: plugins/rest/assets.OLD/js/generator/components/notification.js **/ /** * generator/components/notification.js * * Erstellt Breadcrumbs basierend auf der aktuellen Route. * * @author Sparky * @copyright 2025 ARUNOX * @version 0.0.1 * * @since 1.0.0 Datei erstellt */ import { generator } from 'core/generator'; generator.extend( 'components/notification', async ( config, data, container ) => { const wrapper = document.createElement('div'); // console.log( config, data, container ); wrapper.classList.add( 'notification', `notification--${config.type || 'default'}`); if( config.icon ) { if( config.icon ) { await generator.generate("components/icon", { name: config.icon, block: 'notification' }, {}, wrapper ); } } if( config.message ) { const message = document.createElement('div'); message.classList.add('notification__message'); message.textContent = config.message; wrapper.appendChild( message ); } const closeButton = document.createElement('button'); closeButton.classList.add('notification__close'); closeButton.innerHTML = '×'; closeButton.addEventListener('click', () => { wrapper.classList.add('is-closing'); setTimeout(() => wrapper.remove(), 300); }); wrapper.appendChild( closeButton ); container.appendChild( wrapper ); // Automatisches Entfernen nach der Dauer (Standard: 5 Sekunden) if( config.duration === 0 ) return; const duration = config.duration || 5000; setTimeout(() => { wrapper.classList.add('is-closing'); setTimeout(() => wrapper.remove(), 300); }, duration); }); /** Datei: plugins/rest/assets.OLD/js/generator/components/badge.js **/ /** * generator/components/badge.js * * Erstellt ein flexibles Badge-Element im BEM-Style. * * @author Sparky * @copyright 2025 ARUNOX * @version 1.0.0 * * @since 1.0.0 Datei erstellt */ import { generator } from 'plugins/rest/core/generator'; generator.extend('components/badge', ( config, data, container ) => { const badge = document.createElement('span' ); badge.classList.add( 'badge', `badge--${config.color || 'primary'}` ); const text = document.createElement('span' ); text.classList.add( 'badge__text' ); text.innerHTML = config.label; badge.appendChild(text); container.appendChild(badge); return badge; });