import Node from './shared/Node.js'; import Attribute from './Attribute.js'; import map_children from './shared/map_children.js'; import Binding from './Binding.js'; import EventHandler from './EventHandler.js'; import Expression from './shared/Expression.js'; import compiler_errors from '../compiler_errors.js'; import { regex_only_whitespaces } from '../../utils/patterns.js'; /** @extends Node<'InlineComponent'> */ export default class InlineComponent extends Node { /** @type {string} */ name; /** @type {import('./shared/Expression.js').default} */ expression; /** @type {import('./Binding.js').default[]} */ bindings = []; /** @type {import('./EventHandler.js').default[]} */ handlers = []; /** @type {import('./Attribute.js').default[]} */ css_custom_properties = []; /** @type {import('./interfaces.js').INode[]} */ children; /** @type {import('./shared/TemplateScope.js').default} */ scope; /** @type {string} */ namespace; /** @type {Attribute[]} */ let_attributes; /** * @param {import('../Component.js').default} component * @param {import('./shared/Node.js').default} parent * @param {import('./shared/TemplateScope.js').default} scope * @param {import('../../interfaces.js').TemplateNode} info */ constructor(component, parent, scope, info) { super(component, parent, scope, info); this.cannot_use_innerhtml(); this.not_static_content(); if (info.name !== 'svelte:component' && info.name !== 'svelte:self') { const name = info.name.split('.')[0]; // accommodate namespaces component.warn_if_undefined(name, info, scope); component.add_reference(/** @type {any} */ (this), name); } this.name = info.name; this.namespace = get_namespace(parent, component.namespace); this.expression = this.name === 'svelte:component' ? new Expression(component, this, scope, info.expression) : null; const let_attributes = (this.let_attributes = []); info.attributes.forEach( /** @param {import('../../interfaces.js').BaseDirective | import('../../interfaces.js').Attribute | import('../../interfaces.js').SpreadAttribute} node */ ( node ) => { /* eslint-disable no-fallthrough */ switch (node.type) { case 'Action': return component.error(node, compiler_errors.invalid_action); case 'Attribute': if (node.name.startsWith('--')) { this.css_custom_properties.push(new Attribute(component, this, scope, node)); break; } // fallthrough case 'Spread': this.attributes.push(new Attribute(component, this, scope, node)); break; case 'Binding': this.bindings.push(new Binding(component, this, scope, node)); break; case 'Class': return component.error(node, compiler_errors.invalid_class); case 'EventHandler': this.handlers.push(new EventHandler(component, this, scope, node)); break; case 'Let': let_attributes.push(node); break; case 'Transition': return component.error(node, compiler_errors.invalid_transition); case 'StyleDirective': return component.error(node, compiler_errors.invalid_component_style_directive); case 'Animation': return component.error(node, compiler_errors.invalid_animation); default: throw new Error(`Not implemented: ${node.type}`); } /* eslint-enable no-fallthrough */ } ); this.scope = scope; this.handlers.forEach((handler) => { handler.modifiers.forEach((modifier) => { if (modifier !== 'once') { return component.error(handler, compiler_errors.invalid_event_modifier_component); } }); }); const children = []; for (let i = info.children.length - 1; i >= 0; i--) { const child = info.children[i]; if (child.type === 'SlotTemplate') { children.push(child); info.children.splice(i, 1); } else if ( (child.type === 'Element' || child.type === 'InlineComponent' || child.type === 'Slot') && child.attributes.find((attribute) => attribute.name === 'slot') ) { const slot_template = { start: child.start, end: child.end, type: 'SlotTemplate', name: 'svelte:fragment', attributes: [], children: [child] }; // transfer attributes for (let i = child.attributes.length - 1; i >= 0; i--) { const attribute = child.attributes[i]; if (attribute.type === 'Let') { slot_template.attributes.push(attribute); child.attributes.splice(i, 1); } else if (attribute.type === 'Attribute' && attribute.name === 'slot') { slot_template.attributes.push(attribute); } } // transfer const for (let i = child.children.length - 1; i >= 0; i--) { const child_child = child.children[i]; if (child_child.type === 'ConstTag') { slot_template.children.push(child_child); child.children.splice(i, 1); } } children.push(slot_template); info.children.splice(i, 1); } else if (child.type === 'Comment' && children.length > 0) { children[children.length - 1].children.unshift(child); } } if (info.children.some((node) => not_whitespace_text(node))) { children.push({ start: info.start, end: info.end, type: 'SlotTemplate', name: 'svelte:fragment', attributes: [], children: info.children }); } if (let_attributes.length) { // copy let: attribute from to // as they are for `slot="default"` only children.forEach((child) => { const slot = child.attributes.find((attribute) => attribute.name === 'slot'); if (!slot || slot.value[0].data === 'default') { child.attributes.push(...let_attributes); } }); } this.children = map_children(component, this, this.scope, children); } get slot_template_name() { return /** @type {string} */ ( this.attributes.find((attribute) => attribute.name === 'slot').get_static_value() ); } } /** @param {any} node */ function not_whitespace_text(node) { return !(node.type === 'Text' && regex_only_whitespaces.test(node.data)); } /** * @param {import('./shared/Node.js').default} parent * @param {string} explicit_namespace */ function get_namespace(parent, explicit_namespace) { const parent_element = parent.find_nearest(/^Element/); if (!parent_element) { return explicit_namespace; } return parent_element.namespace; }