use crate::transforms::v_slot::{collect_slots, get_slot_name, has_v_slot};
use crate::*;
use vize_carton::String;
use vize_carton::ToCompactString;
use super::super::context::CodegenContext;
use super::super::expression::generate_expression;
use super::super::helpers::{escape_js_string, is_valid_js_identifier};
use super::super::node::generate_node;
use super::detect::{
child_is_slot_template, has_conditional_or_loop_slots, has_forwarded_slot_outlet,
slot_children_have_meaningful_content,
};
use super::params::{extract_slot_params, get_slot_props, prefix_slot_defaults};
pub fn generate_slots(ctx: &mut CodegenContext, el: &ElementNode<'_>) {
let root_slot = el.props.iter().find_map(|p| {
if let PropNode::Directive(dir) = p
&& dir.name.as_str() == "slot"
{
return Some(dir.as_ref());
}
None
});
let collected_slots = collect_slots(el);
let has_forwarded_slots = has_forwarded_slot_outlet(el);
let has_dynamic_slots =
ctx.in_v_for || collected_slots.iter().any(|s| s.is_dynamic) || has_forwarded_slots;
let has_conditional_slots = has_conditional_or_loop_slots(el);
if has_conditional_slots && root_slot.is_none() {
generate_create_slots(ctx, el);
return;
}
ctx.push("{");
ctx.indent();
if let Some(slot_dir) = root_slot {
ctx.newline();
ctx.push("default: ");
ctx.use_helper(RuntimeHelper::WithCtx);
ctx.push(ctx.helper(RuntimeHelper::WithCtx));
ctx.push("(");
let params = if let Some(props_str) = get_slot_props(slot_dir) {
let processed = prefix_slot_defaults(&props_str);
ctx.push("(");
ctx.push(&processed);
ctx.push(")");
extract_slot_params(&props_str)
} else {
ctx.push("()");
vec![]
};
ctx.add_slot_params(¶ms);
ctx.push(" => [");
ctx.indent();
generate_slot_children(ctx, &el.children);
ctx.deindent();
ctx.newline();
ctx.push("])");
ctx.remove_slot_params(¶ms);
} else {
let mut has_generated_default = false;
let mut first_slot = true;
for child in &el.children {
if let TemplateChildNode::Element(template_el) = child
&& template_el.tag.as_str() == "template"
&& has_v_slot(template_el)
{
if let Some(slot_dir) = template_el.props.iter().find_map(|p| {
if let PropNode::Directive(dir) = p
&& dir.name.as_str() == "slot"
{
return Some(dir.as_ref());
}
None
}) {
if !first_slot {
ctx.push(",");
}
first_slot = false;
ctx.newline();
let slot_name = get_slot_name(slot_dir);
let is_dynamic = slot_dir
.arg
.as_ref()
.map(|arg| match arg {
ExpressionNode::Simple(exp) => !exp.is_static,
ExpressionNode::Compound(_) => true,
})
.unwrap_or(false);
if is_dynamic {
let trimmed_name = slot_name.trim();
if trimmed_name.starts_with('`') && trimmed_name.ends_with('`') {
let inner = &trimmed_name[1..trimmed_name.len() - 1];
ctx.push("[\"");
ctx.push(&escape_js_string(inner));
ctx.push("\"]");
} else {
ctx.push("[");
ctx.push("_ctx.");
ctx.push(&slot_name);
ctx.push("]");
}
} else if is_valid_js_identifier(&slot_name) {
ctx.push(&slot_name);
} else {
ctx.push("\"");
ctx.push(&escape_js_string(&slot_name));
ctx.push("\"");
}
if slot_name.as_str() == "default" {
has_generated_default = true;
}
ctx.push(": ");
ctx.use_helper(RuntimeHelper::WithCtx);
ctx.push(ctx.helper(RuntimeHelper::WithCtx));
ctx.push("(");
let params = if let Some(props_str) = get_slot_props(slot_dir) {
let processed = prefix_slot_defaults(&props_str);
ctx.push("(");
ctx.push(&processed);
ctx.push(")");
extract_slot_params(&props_str)
} else {
ctx.push("()");
vec![]
};
ctx.add_slot_params(¶ms);
ctx.push(" => [");
ctx.indent();
generate_slot_children(ctx, &template_el.children);
ctx.deindent();
ctx.newline();
ctx.push("])");
ctx.remove_slot_params(¶ms);
}
}
}
let default_children: Vec<_> = el
.children
.iter()
.filter(|child| {
if let TemplateChildNode::Element(template_el) = child {
!(template_el.tag.as_str() == "template" && has_v_slot(template_el))
} else {
true
}
})
.collect();
if !default_children.is_empty() && !has_generated_default {
if !first_slot {
ctx.push(",");
}
ctx.newline();
ctx.push("default: ");
ctx.use_helper(RuntimeHelper::WithCtx);
ctx.push(ctx.helper(RuntimeHelper::WithCtx));
ctx.push("(() => [");
ctx.indent();
for (i, child) in default_children.iter().enumerate() {
if i > 0 {
ctx.push(",");
}
ctx.newline();
generate_slot_child_node(ctx, child);
}
ctx.deindent();
ctx.newline();
ctx.push("])");
}
}
ctx.push(",");
ctx.newline();
if has_forwarded_slots {
ctx.push("_: 3 /* FORWARDED */");
} else if has_dynamic_slots {
ctx.push("_: 2 /* DYNAMIC */");
} else {
ctx.push("_: 1 /* STABLE */");
}
ctx.deindent();
ctx.newline();
ctx.push("}");
}
fn generate_create_slots(ctx: &mut CodegenContext, el: &ElementNode<'_>) {
ctx.use_helper(RuntimeHelper::CreateSlots);
ctx.push(ctx.helper(RuntimeHelper::CreateSlots));
ctx.push("(");
generate_create_slots_base(ctx, el);
ctx.push(", [");
ctx.indent();
let mut first = true;
for child in &el.children {
match child {
TemplateChildNode::If(if_node) => {
if !first {
ctx.push(",");
}
first = false;
ctx.newline();
generate_conditional_slot(ctx, if_node);
}
TemplateChildNode::For(for_node) => {
if !first {
ctx.push(",");
}
first = false;
ctx.newline();
generate_looped_slot(ctx, for_node);
}
TemplateChildNode::Element(template_el)
if template_el.tag.as_str() == "template" && has_v_slot(template_el) =>
{
if !first {
ctx.push(",");
}
first = false;
ctx.newline();
generate_static_slot_entry(ctx, template_el);
}
_ => {}
}
}
ctx.deindent();
ctx.newline();
ctx.push("])");
}
fn generate_create_slots_base(ctx: &mut CodegenContext, el: &ElementNode<'_>) {
let default_children: Vec<_> = el
.children
.iter()
.filter(|child| !child_is_slot_template(child))
.collect();
let has_default_children = slot_children_have_meaningful_content(&default_children);
if !has_default_children {
ctx.push("{ _: 2 /* DYNAMIC */ }");
return;
}
ctx.push("{");
ctx.indent();
ctx.newline();
ctx.push("default: ");
ctx.use_helper(RuntimeHelper::WithCtx);
ctx.push(ctx.helper(RuntimeHelper::WithCtx));
ctx.push("(() => [");
ctx.indent();
for (i, child) in default_children.iter().enumerate() {
if i > 0 {
ctx.push(",");
}
ctx.newline();
generate_slot_child_node(ctx, child);
}
ctx.deindent();
ctx.newline();
ctx.push("]),");
ctx.newline();
ctx.push("_: 2 /* DYNAMIC */");
ctx.deindent();
ctx.newline();
ctx.push("}");
}
fn generate_conditional_slot(ctx: &mut CodegenContext, if_node: &IfNode<'_>) {
for (i, branch) in if_node.branches.iter().enumerate() {
if i > 0 {
ctx.newline();
ctx.push(": ");
}
if let Some(condition) = &branch.condition {
ctx.push("(");
generate_expression(ctx, condition);
ctx.push(")");
ctx.indent();
ctx.newline();
ctx.push("? ");
}
let slot_template = branch.children.iter().find_map(|child| {
if let TemplateChildNode::Element(el) = child
&& el.tag.as_str() == "template"
&& has_v_slot(el)
{
return Some(el.as_ref());
}
None
});
if let Some(template_el) = slot_template {
generate_slot_object_entry(ctx, template_el, Some(i));
} else {
ctx.push("undefined");
}
if branch.condition.is_some() {
ctx.deindent();
}
}
if if_node
.branches
.last()
.is_none_or(|branch| branch.condition.is_some())
{
ctx.newline();
ctx.push(": undefined");
}
}
fn generate_looped_slot(ctx: &mut CodegenContext, for_node: &ForNode<'_>) {
ctx.use_helper(RuntimeHelper::RenderList);
ctx.push(ctx.helper(RuntimeHelper::RenderList));
ctx.push("(");
generate_expression(ctx, &for_node.source);
ctx.push(", (");
let mut callback_params: Vec<String> = Vec::new();
if let Some(value) = &for_node.value_alias {
generate_expression(ctx, value);
super::super::v_for::helpers::extract_for_params(value, &mut callback_params);
}
if let Some(key) = &for_node.key_alias {
ctx.push(", ");
generate_expression(ctx, key);
super::super::v_for::helpers::extract_for_params(key, &mut callback_params);
}
if let Some(index) = &for_node.object_index_alias {
ctx.push(", ");
generate_expression(ctx, index);
super::super::v_for::helpers::extract_for_params(index, &mut callback_params);
}
ctx.add_slot_params(&callback_params);
ctx.push(") => {");
ctx.indent();
ctx.newline();
ctx.push("return ");
let slot_template = for_node.children.iter().find_map(|child| {
if let TemplateChildNode::Element(el) = child
&& el.tag.as_str() == "template"
&& has_v_slot(el)
{
return Some(el.as_ref());
}
None
});
if let Some(template_el) = slot_template {
generate_slot_object_entry(ctx, template_el, None);
}
ctx.remove_slot_params(&callback_params);
ctx.deindent();
ctx.newline();
ctx.push("})");
}
fn generate_slot_object_entry(
ctx: &mut CodegenContext,
template_el: &ElementNode<'_>,
key_index: Option<usize>,
) {
let slot_dir = template_el.props.iter().find_map(|p| {
if let PropNode::Directive(dir) = p
&& dir.name.as_str() == "slot"
{
return Some(dir.as_ref());
}
None
});
if let Some(dir) = slot_dir {
let slot_name = get_slot_name(dir);
ctx.push("{");
ctx.indent();
ctx.newline();
ctx.push("name: \"");
ctx.push(&escape_js_string(&slot_name));
ctx.push("\",");
ctx.newline();
ctx.push("fn: ");
ctx.use_helper(RuntimeHelper::WithCtx);
ctx.push(ctx.helper(RuntimeHelper::WithCtx));
ctx.push("(");
let params = if let Some(props_str) = get_slot_props(dir) {
let processed = prefix_slot_defaults(&props_str);
ctx.push("(");
ctx.push(&processed);
ctx.push(")");
extract_slot_params(&props_str)
} else {
ctx.push("()");
vec![]
};
ctx.add_slot_params(¶ms);
ctx.push(" => [");
ctx.indent();
generate_slot_children(ctx, &template_el.children);
ctx.deindent();
ctx.newline();
ctx.push("])");
ctx.remove_slot_params(¶ms);
if let Some(key) = key_index {
ctx.push(",");
ctx.newline();
ctx.push("key: \"");
ctx.push(&key.to_compact_string());
ctx.push("\"");
}
ctx.deindent();
ctx.newline();
ctx.push("}");
}
}
fn generate_static_slot_entry(ctx: &mut CodegenContext, template_el: &ElementNode<'_>) {
generate_slot_object_entry(ctx, template_el, None);
}
fn generate_slot_children(ctx: &mut CodegenContext, children: &[TemplateChildNode<'_>]) {
let all_text_or_interp = children.iter().all(|child| {
matches!(
child,
TemplateChildNode::Text(_) | TemplateChildNode::Interpolation(_)
)
});
if all_text_or_interp && !children.is_empty() {
ctx.newline();
ctx.use_helper(RuntimeHelper::CreateText);
ctx.push(ctx.helper(RuntimeHelper::CreateText));
ctx.push("(");
let has_interpolation = children
.iter()
.any(|c| matches!(c, TemplateChildNode::Interpolation(_)));
for (i, child) in children.iter().enumerate() {
if i > 0 {
ctx.push(" + ");
}
match child {
TemplateChildNode::Text(text) => {
ctx.push("\"");
ctx.push(&super::super::helpers::escape_js_string(&text.content));
ctx.push("\"");
}
TemplateChildNode::Interpolation(interp) => {
#[cfg(feature = "legacy")]
let raw = interp.raw;
#[cfg(not(feature = "legacy"))]
let raw = false;
if raw {
generate_slot_expression(ctx, &interp.content);
} else {
ctx.use_helper(RuntimeHelper::ToDisplayString);
ctx.push(ctx.helper(RuntimeHelper::ToDisplayString));
ctx.push("(");
generate_slot_expression(ctx, &interp.content);
ctx.push(")");
}
}
_ => {}
}
}
if has_interpolation {
ctx.push(", 1 /* TEXT */)");
} else {
ctx.push(")");
}
} else {
for (i, child) in children.iter().enumerate() {
if i > 0 {
ctx.push(",");
}
ctx.newline();
generate_slot_child_node(ctx, child);
}
}
}
fn generate_slot_child_node(ctx: &mut CodegenContext, child: &TemplateChildNode<'_>) {
match child {
TemplateChildNode::Text(text) => {
ctx.use_helper(RuntimeHelper::CreateText);
ctx.push(ctx.helper(RuntimeHelper::CreateText));
ctx.push("(\"");
ctx.push(&super::super::helpers::escape_js_string(&text.content));
ctx.push("\")");
}
TemplateChildNode::Interpolation(interp) => {
ctx.use_helper(RuntimeHelper::CreateText);
ctx.push(ctx.helper(RuntimeHelper::CreateText));
ctx.push("(");
#[cfg(feature = "legacy")]
let raw = interp.raw;
#[cfg(not(feature = "legacy"))]
let raw = false;
if raw {
generate_slot_expression(ctx, &interp.content);
} else {
ctx.use_helper(RuntimeHelper::ToDisplayString);
ctx.push(ctx.helper(RuntimeHelper::ToDisplayString));
ctx.push("(");
generate_slot_expression(ctx, &interp.content);
ctx.push(")");
}
ctx.push(", 1 /* TEXT */)");
}
_ => {
generate_node(ctx, child);
}
}
}
fn generate_slot_expression(ctx: &mut CodegenContext, expr: &ExpressionNode<'_>) {
match expr {
ExpressionNode::Simple(exp) => {
if exp.is_static {
ctx.push("\"");
ctx.push(&exp.content);
ctx.push("\"");
} else {
let content = strip_ctx_prefix_for_slot_params(ctx, &exp.content);
ctx.push(&content);
}
}
ExpressionNode::Compound(comp) => {
for child in comp.children.iter() {
match child {
crate::CompoundExpressionChild::Simple(exp) => {
if exp.is_static {
ctx.push("\"");
ctx.push(&exp.content);
ctx.push("\"");
} else {
let content = strip_ctx_prefix_for_slot_params(ctx, &exp.content);
ctx.push(&content);
}
}
crate::CompoundExpressionChild::String(s) => {
ctx.push(s);
}
crate::CompoundExpressionChild::Symbol(helper) => {
ctx.push(ctx.helper(*helper));
}
_ => {}
}
}
}
}
}
fn strip_ctx_prefix_for_slot_params(ctx: &CodegenContext, content: &str) -> String {
let mut result = String::new(content);
for param in &ctx.slot_params {
let mut prefixed = String::with_capacity(5 + param.len());
prefixed.push_str("_ctx.");
prefixed.push_str(param);
let replaced = result.replace(prefixed.as_str(), param.as_str());
result = String::from(replaced);
}
result
}