import { devices, effectiveStyle, hoverStyle, locate, upgradeDocument, setStyle } from './editor-model.js'; export function effectiveButton(block, device, defaults) { const result = { ...defaults }; for (const name of Object.keys(devices)) { Object.assign(result, block.layout?.button?.[name]); if (name === device) break; } return result; } /** Change appearance without touching copy, URL, typography, sizing or layout placement. */ export function setButtonDesign(document, id, device, values) { const block = locate(document.blocks, id)?.block; if (block?.type !== 'button' || !Object.hasOwn(devices, device)) return; if (Object.values(values).some(value => value !== '')) upgradeDocument(document, 21); const target = ((block.layout ??= {}).button ??= {}); target[device] ??= {}; for (const [key, value] of Object.entries(values)) { if (value === '') delete target[device][key]; else target[device][key] = value; // A chosen shape/appearance must visibly take effect even on an older hand-styled button. // Other groups (font, width, padding, margins, position) remain untouched. Undo is atomic. const reset = key === 'shape' ? ['borderRadius'] : key === 'appearance' ? ['backgroundColor', 'backgroundImage', 'gradientFrom', 'gradientTo', 'color', 'borderColor', 'borderWidth', 'borderStyle'] : []; for (const property of reset) { setStyle(document, id, device, property, ''); setStyle(document, id, device, property, '', { hover: true }); } } if (!Object.keys(target[device]).length) delete target[device]; if (!Object.keys(target).length) delete block.layout.button; } const element = (tag, className, text) => { const node = document.createElement(tag); if (className) node.className = className; if (text !== undefined) node.textContent = text; return node; }; const color = (value, schema) => value?.startsWith('@') ? schema.colors.find(row => row.key === value.slice(1))?.value || '#203b34' : value; export function previewVariables(node, values, schema) { const set = (key, value) => node.style.setProperty(`--kb-${key}`, String(value)); set('fill', color(values.backgroundColor, schema)); set('ink', color(values.color, schema)); set('hover-fill', color(values.hoverBackgroundColor, schema)); set('hover-ink', color(values.hoverColor, schema)); const font = schema.fonts.find(font => font.key === values.fontFamily); set('font', font ? `${font.family ? `"${font.family}",` : ''}${font.fallback}` : 'inherit'); set('font-size', `${values.fontSize}px`); set('weight', values.fontWeight); set('tracking', `${values.letterSpacing}px`); set('transform', values.textTransform); } export function buttonSample(text, values, schema, hover = false) { const sample = element('span', `cms-button b-desktop-shape-${values.shape} b-desktop-appearance-${values.appearance} b-desktop-size-${values.size}${hover ? ' is-hover' : ''}`, text || 'Button'); previewVariables(sample, values, schema); return sample; } export function renderButtonControls(panel, { block, device, schema, apply }) { const group = element('section', 'button-designer'); group.setAttribute('aria-label', 'Button-Gestaltung'); group.append(element('h3', null, 'Button-Gestaltung')); const current = effectiveButton(block, device, schema.buttons); const previews = element('div', 'button-state-previews'); for (const [label, hover] of [['Normal', false], ['Beim Überfahren', true]]) { const state = element('div', 'button-state'); state.append(element('span', 'field-help', label)); const sample = buttonSample(block.props.text, current, schema, hover); // Preview the block's own free styling as well as its variant and site defaults. for (const [key, definition] of Object.entries(schema.properties)) { if (!definition.css || definition.target || definition.sides || definition.corners || ['position','display','width','height','minWidth','maxWidth','minHeight','maxHeight'].includes(key)) continue; let value = effectiveStyle(block, device, key).value; if (hover) for (const name of Object.keys(devices)) { value = hoverStyle(block, name, key) ?? value; if (name === device) break; } if (value !== undefined && value !== '') sample.style.setProperty(definition.css, definition.type === 'color' ? color(value, schema) : String(value)); } state.append(sample); previews.append(state); } group.append(previews); const cards = element('div', 'button-preset-grid'); cards.setAttribute('role', 'group'); cards.setAttribute('aria-label', 'Button-Vorlagen'); for (const [index, preset] of schema.buttonPresets.entries()) { const choice = element('button', 'button-preset'); choice.type = 'button'; choice.id = `button-preset-${index}`; choice.setAttribute('aria-label', `Button-Vorlage ${preset.name}`); choice.setAttribute('aria-pressed', String(current.shape === preset.shape && current.appearance === preset.appearance)); const sample = buttonSample('Button', { ...schema.buttons, ...preset, size: 'small' }, schema); sample.setAttribute('aria-hidden', 'true'); choice.append(sample, element('span', 'button-preset-label', preset.name)); choice.addEventListener('click', () => apply({ shape: preset.shape, appearance: preset.appearance }, choice.id)); cards.append(choice); } group.append(cards); for (const [key, options] of Object.entries(schema.buttonOptions)) { const id = `button-${key}`, label = element('label', null, { shape: 'Form', appearance: 'Darstellung', size: 'Button-Größe' }[key]); label.htmlFor = id; const select = element('select'); select.id = id; select.append(new Option(`Übernehmen: ${options[current[key]]}`, '')); for (const [value, name] of Object.entries(options)) select.append(new Option(name, value)); select.value = block.layout?.button?.[device]?.[key] ?? ''; select.addEventListener('change', () => apply({ [key]: select.value }, id)); group.append(label, select); } const reset = element('button', 'secondary full-width', device === 'desktop' ? 'Website-Standard übernehmen' : 'Größeres Gerät übernehmen'); reset.type = 'button'; reset.id = 'button-reset'; reset.addEventListener('click', () => apply({ shape: '', appearance: '', size: '' }, reset.id)); group.append(reset, element('p', 'field-help', `${devices[device]}: Form und Darstellung übernehmen die Website-Vorgaben, solange kein eigener Wert gesetzt ist. Vorlagen ersetzen eigene Füllung und Rahmen; Schrift, Breite und Abstände bleiben erhalten.`)); panel.append(group); }