import { createSignal, For, onCleanup, onMount, Show } from 'solid-js'; import { defineWebComponent } from './define'; import { Source, SourceTrigger, SourceContent, SourceList } from '../components/source'; // --- — a single citation link with hover preview --- interface SourceProps extends Record { /** The URL this citation links to (the domain also seeds the default label/favicon). */ href?: string; /** Trigger label (defaults to the domain). */ label?: string; /** Hover-card headline. Attribute: `headline` (`title` is avoided — it's a * global HTML attribute that reflects in a CE constructor and breaks it). */ headline?: string; /** Hover-card body text describing the source. */ description?: string; /** Show the source's favicon next to the trigger label. */ showFavicon?: boolean; } defineWebComponent('kc-source', { href: '', label: undefined, headline: '', description: '', showFavicon: false, }, (props, { flag }) => ( )); // --- — a wrapped list of citation links --- interface SourceItem { href: string; title?: string; description?: string; label?: string; showFavicon?: boolean; } interface SourceListProps extends Record { /** The sources to render. Set as a JS property. */ sources: SourceItem[]; /** Show favicons on all items (per-item `showFavicon` overrides). */ showFavicon?: boolean; /** * When true, each citation chip is labelled with its 1-based index in the * merged (prop + declarative-children) list (`[1]`, `[2]`, …) instead of the * per-item `label` or domain fallback. * * HTML attribute: `numbered` (boolean — bare attribute or `numbered="true"`). * JS property: `el.numbered = true`. */ numbered?: boolean; } /** Parse a single light-DOM `` element into a `SourceItem` descriptor. * Attribute mapping: * - `href` → SourceItem.href * - `label` → SourceItem.label * - `headline` → SourceItem.title (matches kc-source's prop name; "title" is a * reserved HTMLElement attribute so kc-source uses "headline") * - `description` → SourceItem.description * - `show-favicon`→ SourceItem.showFavicon (bare boolean attribute) */ export function parseKcSourceElement(n: Element): SourceItem { return { href: n.getAttribute('href') ?? '', label: n.getAttribute('label') ?? undefined, title: n.getAttribute('headline') ?? undefined, description: n.getAttribute('description') ?? undefined, showFavicon: n.hasAttribute('show-favicon') && n.getAttribute('show-favicon') !== 'false', }; } defineWebComponent('kc-sources', { sources: [], showFavicon: false, numbered: false, }, (props, { element, flag }) => { // Read declarative children from light DOM. // The shadow root has no for them, so they are invisible — pure data carriers. const [slottedSources, setSlottedSources] = createSignal([]); onMount(() => { const read = () => { const nodes = [...element.querySelectorAll('kc-source')]; setSlottedSources(nodes.map(parseKcSourceElement)); }; read(); const observer = new MutationObserver(read); observer.observe(element, { childList: true, attributes: true, subtree: true }); onCleanup(() => observer.disconnect()); }); // Prop sources take precedence; slotted children are appended after. const allSources = () => [...props.sources, ...slottedSources()]; const isNumbered = () => flag('numbered'); return ( {(s, i) => ( )} ); });