use crate::ast::{DirectiveNode, ElementNode, ExpressionNode, IfBranchNode, PropNode};
use super::super::{
context::CodegenContext,
helpers::{camelize, capitalize_first, escape_js_string, is_valid_js_identifier},
props::{StaticMerge, generate_directive_prop_with_static, is_supported_directive},
};
use super::generate_if_branch_key;
use vize_carton::FxHashSet;
use vize_carton::String;
use vize_carton::ToCompactString;
pub(super) fn should_skip_prop_for_if(
p: &PropNode<'_>,
has_dynamic_class: bool,
has_dynamic_style: bool,
) -> bool {
match p {
PropNode::Attribute(attr) => {
if attr.name == "class" && has_dynamic_class {
return true;
}
if attr.name == "style" && has_dynamic_style {
return true;
}
false
}
PropNode::Directive(dir) => {
if dir.name == "bind"
&& let Some(ExpressionNode::Simple(arg)) = &dir.arg
&& arg.content == "key"
{
return true;
}
if matches!(dir.name.as_str(), "if" | "else-if" | "else") {
return true;
}
false
}
}
}
pub(super) fn extract_static_class_style<'a>(el: &'a ElementNode<'_>) -> StaticMerge<'a> {
StaticMerge::from_props(&el.props)
}
pub(super) fn has_dynamic_class(el: &ElementNode<'_>) -> bool {
el.props.iter().any(|p| {
if let PropNode::Directive(dir) = p
&& dir.name == "bind"
&& let Some(ExpressionNode::Simple(arg)) = &dir.arg
{
return arg.content == "class";
}
false
})
}
pub(super) fn has_dynamic_style(el: &ElementNode<'_>) -> bool {
el.props.iter().any(|p| {
if let PropNode::Directive(dir) = p
&& dir.name == "bind"
&& let Some(ExpressionNode::Simple(arg)) = &dir.arg
{
return arg.content == "style";
}
false
})
}
pub(super) fn generate_single_prop_for_if(
ctx: &mut CodegenContext,
prop: &PropNode<'_>,
static_merge: StaticMerge<'_>,
) {
match prop {
PropNode::Attribute(attr) => {
let ref_value = if attr.name == "ref" && ctx.options.inline {
attr.value.as_ref()
} else {
None
};
let ref_binding_type = ref_value.and_then(|v| {
ctx.options
.binding_metadata
.as_ref()
.and_then(|m| m.bindings.get(v.content.as_str()).copied())
});
let needs_ref_key = matches!(
ref_binding_type,
Some(
crate::options::BindingType::SetupLet
| crate::options::BindingType::SetupRef
| crate::options::BindingType::SetupMaybeRef
)
);
if let (true, Some(ref_value)) = (needs_ref_key, ref_value) {
let ref_name = &ref_value.content;
ctx.push("ref_key: \"");
ctx.push(ref_name);
ctx.push("\", ref: ");
ctx.push(ref_name);
return;
}
let needs_quotes = !is_valid_js_identifier(&attr.name);
if needs_quotes {
ctx.push("\"");
}
ctx.push(&attr.name);
if needs_quotes {
ctx.push("\"");
}
ctx.push(": ");
if let Some(value) = &attr.value {
if ref_binding_type.is_some() {
ctx.push(&value.content);
} else {
ctx.push("\"");
ctx.push(&escape_js_string(value.content.as_str()));
ctx.push("\"");
}
} else {
ctx.push("\"\"");
}
}
PropNode::Directive(dir) => {
generate_directive_prop_with_static(ctx, dir, static_merge);
}
}
}
#[allow(clippy::too_many_arguments)]
pub(super) fn generate_if_branch_props_object(
ctx: &mut CodegenContext,
el: &ElementNode<'_>,
branch: &IfBranchNode<'_>,
branch_index: usize,
static_merge: StaticMerge<'_>,
has_dynamic_class: bool,
has_dynamic_style: bool,
) {
let has_other_props = el.props.iter().any(|p| {
if let PropNode::Directive(dir) = p
&& !is_supported_directive(dir)
{
return false;
}
!should_skip_prop_for_if(p, has_dynamic_class, has_dynamic_style)
&& !is_vbind_spread_prop(p)
&& !is_von_spread_prop(p)
});
let scope_id = if ctx.skip_scope_id {
None
} else {
ctx.options.scope_id.clone()
};
let has_scope = scope_id.is_some();
if !has_other_props && !has_scope {
ctx.push("{ key: ");
generate_if_branch_key(ctx, branch, branch_index);
ctx.push(" }");
return;
}
ctx.push("{");
ctx.indent();
ctx.newline();
ctx.push("key: ");
generate_if_branch_key(ctx, branch, branch_index);
let mut seen_events: FxHashSet<String> = FxHashSet::default();
for prop in el.props.iter() {
if let PropNode::Directive(dir) = prop
&& !is_supported_directive(dir)
{
continue;
}
if should_skip_prop_for_if(prop, has_dynamic_class, has_dynamic_style) {
continue;
}
if is_vbind_spread_prop(prop) {
continue;
}
if is_von_spread_prop(prop) {
continue;
}
if let PropNode::Directive(dir) = prop
&& dir.name == "on"
&& let Some(key) = get_static_event_key(dir)
&& !seen_events.insert(key)
{
continue;
}
ctx.push(",");
ctx.newline();
generate_single_prop_for_if(ctx, prop, static_merge);
}
if let Some(ref scope_id) = scope_id {
ctx.push(",");
ctx.newline();
ctx.push("\"");
ctx.push(scope_id);
ctx.push("\": \"\"");
}
ctx.deindent();
ctx.newline();
ctx.push("}");
}
pub(super) fn has_vbind_spread(el: &ElementNode<'_>) -> bool {
el.props.iter().any(|p| is_vbind_spread_prop(p))
}
pub(super) fn is_vbind_spread_prop(prop: &PropNode<'_>) -> bool {
if let PropNode::Directive(dir) = prop {
return dir.name == "bind" && dir.arg.is_none();
}
false
}
pub(super) fn has_von_spread(el: &ElementNode<'_>) -> bool {
el.props.iter().any(|p| is_von_spread_prop(p))
}
pub(super) fn is_von_spread_prop(prop: &PropNode<'_>) -> bool {
if let PropNode::Directive(dir) = prop {
return dir.name == "on" && dir.arg.is_none();
}
false
}
fn get_static_event_key(dir: &DirectiveNode<'_>) -> Option<String> {
let arg = dir.arg.as_ref()?;
let ExpressionNode::Simple(exp) = arg else {
return None;
};
if !exp.is_static {
return None;
}
let mut event_name = exp.content.as_str();
let is_keyboard_event = matches!(event_name, "keydown" | "keyup" | "keypress");
let mut event_option_modifiers: Vec<&str> = Vec::new();
let mut system_modifiers: Vec<&str> = Vec::new();
for modifier in dir.modifiers.iter() {
let mod_name = modifier.content.as_str();
match mod_name {
"capture" | "once" | "passive" => {
event_option_modifiers.push(mod_name);
}
"left" | "right" if !is_keyboard_event => {
system_modifiers.push(mod_name);
}
"middle" => {
system_modifiers.push(mod_name);
}
_ => {}
}
}
let has_right_modifier = system_modifiers.contains(&"right");
let has_middle_modifier = system_modifiers.contains(&"middle");
if event_name == "click" && has_right_modifier {
event_name = "contextmenu";
} else if event_name == "click" && has_middle_modifier {
event_name = "mouseup";
}
let mut key = if event_name.contains(':') {
let parts: Vec<&str> = event_name.splitn(2, ':').collect();
if parts.len() == 2 {
let first_part = camelize(parts[0]);
let mut name = String::from("on");
if let Some(first) = first_part.chars().next() {
name.push_str(&first.to_uppercase().to_compact_string());
name.push_str(&first_part[first.len_utf8()..]);
}
name.push(':');
name.push_str(parts[1]);
name
} else {
String::from(event_name)
}
} else {
let camelized = camelize(event_name);
let mut name = String::from("on");
if let Some(first) = camelized.chars().next() {
name.push_str(&first.to_uppercase().to_compact_string());
name.push_str(&camelized[first.len_utf8()..]);
}
name
};
for opt_mod in &event_option_modifiers {
key.push_str(&capitalize_first(opt_mod));
}
Some(key)
}