' . \Zadex\Documents\Sanitizer::clean_svg((string) $icon['svg']) . '';
} elseif (!empty($icon['value'])) {
$icon_html = '';
}
$after = ($settings['icon_position'] ?? 'before') === 'after';
// Size preset as a zero-specificity class (style.css `:where(.zx-button--lg)`), so the Padding / Typography
// controls' generated `.zx-` rules always win (T217). Legacy padding values map to their preset.
$size_aliases = ['8px 16px' => 'sm', '12px 24px' => 'md', '16px 32px' => 'lg', '20px 40px' => 'xl'];
$size = (string) ($settings['size'] ?? 'md');
$size = $size_aliases[$size] ?? $size;
if (!in_array($size, ['sm', 'md', 'lg', 'xl'], true)) {
$size = 'md';
}
$attrs = (string) preg_replace('/class="([^"]*)"/', 'class="$1 zx-button--' . esc_attr($size) . '"', $attrs, 1);
if (!empty($settings['button_id'])) {
// The Button ID wins over a CSS ID set in the Advanced tab (never print two id attributes; mirrors Preview.vue).
$attrs = trim((string) preg_replace('/(^|\s)id="[^"]*"/', '', $attrs)) . ' id="' . esc_attr((string) $settings['button_id']) . '"';
}
$rel = [];
if (!empty($zx_link['nofollow'])) {
$rel[] = 'nofollow';
}
if (($zx_link['target'] ?? '') === '_blank') {
$rel[] = 'noopener';
}
$inner = ($after ? '' : $icon_html) . '' . esc_html($text) . '' . ($after ? $icon_html : '');
if ($href !== '') {
$zx_html = '' . $inner . "\n";
} else {
// has-* classes: themes skip their `button:not(.has-text-color)` colour rules (docs/WIDGET-GUIDE.md).
$zx_html = '\n";
}
echo wp_kses($zx_html, \Zadex\Frontend\Kses::allowed_html());