96 lines
6.4 KiB
JavaScript
96 lines
6.4 KiB
JavaScript
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);
|
|
}
|