import { createSignal, onCleanup, onMount } from 'solid-js'; import { defineWebComponent } from './define'; import { MessageSkills } from '../components/message-skills'; interface Skill { /** Stable identifier for the skill. */ id: string; /** Human-readable skill name shown on the badge. */ name: string; } interface Props extends Record { /** The active skills to badge. Set as a JS property. */ skills: Skill[]; } /** * Parse a single light-DOM `` element into a `Skill` descriptor. * * Attribute / content mapping: * - `id` → Skill.id (falls back to `name` when absent) * - `textContent` → Skill.name (the human-readable badge label) * * Example: `Web Search` */ export function parseKcSkillElement(n: Element): Skill { const name = n.textContent?.trim() ?? ''; const id = n.getAttribute('id') ?? name; return { id, name }; } /** * `` — badges showing which skills were active for a * message. Data via the `skills` property **or** declarative `` * children. * * **Property API** — set a JS array on the element: * ```html * * * ``` * * **Declarative API** — compose `` children (light-DOM data * carriers hidden by Shadow DOM — no visible output of their own): * ```html * * Web Search * Code * * * ``` * * When both are provided, `skills` prop items render first and declarative * children are appended after. */ defineWebComponent('kc-skills', { skills: [], }, (props, { element }) => { // Read declarative children from light DOM. // The shadow root has no , so they are invisible — pure data carriers. const [slottedSkills, setSlottedSkills] = createSignal([]); onMount(() => { const read = () => { const nodes = [...element.querySelectorAll('kc-skill')]; setSlottedSkills(nodes.map(parseKcSkillElement)); }; read(); const observer = new MutationObserver(read); observer.observe(element, { childList: true, attributes: true, subtree: true }); onCleanup(() => observer.disconnect()); }); // Prop skills (first) merged with declarative children (after). const allSkills = () => [...(props.skills ?? []), ...slottedSkills()]; return ; });