@urbicon-ui/blocks 6.35.0 → 6.37.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (124) hide show
  1. package/dist/components/Chat/Chat/Chat.svelte +53 -0
  2. package/dist/components/Chat/Chat/Chat.svelte.d.ts +4 -0
  3. package/dist/components/Chat/Chat/chat.variants.d.ts +20 -0
  4. package/dist/components/Chat/Chat/chat.variants.js +15 -0
  5. package/dist/components/Chat/Chat/index.d.ts +47 -0
  6. package/dist/components/Chat/Chat/index.js +2 -0
  7. package/dist/components/Chat/ChatMessage/ChatMessage.svelte +342 -0
  8. package/dist/components/Chat/ChatMessage/ChatMessage.svelte.d.ts +4 -0
  9. package/dist/components/Chat/ChatMessage/chat-message.variants.d.ts +138 -0
  10. package/dist/components/Chat/ChatMessage/chat-message.variants.js +118 -0
  11. package/dist/components/Chat/ChatMessage/index.d.ts +102 -0
  12. package/dist/components/Chat/ChatMessage/index.js +2 -0
  13. package/dist/components/Chat/ChatMessageList/ChatMessageList.svelte +301 -0
  14. package/dist/components/Chat/ChatMessageList/ChatMessageList.svelte.d.ts +6 -0
  15. package/dist/components/Chat/ChatMessageList/chat-message-list.variants.d.ts +23 -0
  16. package/dist/components/Chat/ChatMessageList/chat-message-list.variants.js +32 -0
  17. package/dist/components/Chat/ChatMessageList/chat-scroll.d.ts +45 -0
  18. package/dist/components/Chat/ChatMessageList/chat-scroll.js +52 -0
  19. package/dist/components/Chat/ChatMessageList/index.d.ts +93 -0
  20. package/dist/components/Chat/ChatMessageList/index.js +3 -0
  21. package/dist/components/Chat/CitationChip/CitationChip.svelte +107 -0
  22. package/dist/components/Chat/CitationChip/CitationChip.svelte.d.ts +4 -0
  23. package/dist/components/Chat/CitationChip/citation-chip.variants.d.ts +40 -0
  24. package/dist/components/Chat/CitationChip/citation-chip.variants.js +42 -0
  25. package/dist/components/Chat/CitationChip/index.d.ts +69 -0
  26. package/dist/components/Chat/CitationChip/index.js +2 -0
  27. package/dist/components/Chat/CodeBlock/CodeBlock.svelte +120 -0
  28. package/dist/components/Chat/CodeBlock/CodeBlock.svelte.d.ts +4 -0
  29. package/dist/components/Chat/CodeBlock/code-block.variants.d.ts +40 -0
  30. package/dist/components/Chat/CodeBlock/code-block.variants.js +51 -0
  31. package/dist/components/Chat/CodeBlock/index.d.ts +55 -0
  32. package/dist/components/Chat/CodeBlock/index.js +2 -0
  33. package/dist/components/Chat/PromptInput/PromptInput.svelte +432 -0
  34. package/dist/components/Chat/PromptInput/PromptInput.svelte.d.ts +4 -0
  35. package/dist/components/Chat/PromptInput/index.d.ts +165 -0
  36. package/dist/components/Chat/PromptInput/index.js +2 -0
  37. package/dist/components/Chat/PromptInput/prompt-input.variants.d.ts +90 -0
  38. package/dist/components/Chat/PromptInput/prompt-input.variants.js +102 -0
  39. package/dist/components/Chat/ReasoningDisclosure/ReasoningDisclosure.svelte +107 -0
  40. package/dist/components/Chat/ReasoningDisclosure/ReasoningDisclosure.svelte.d.ts +4 -0
  41. package/dist/components/Chat/ReasoningDisclosure/index.d.ts +85 -0
  42. package/dist/components/Chat/ReasoningDisclosure/index.js +2 -0
  43. package/dist/components/Chat/ReasoningDisclosure/reasoning-disclosure.variants.d.ts +20 -0
  44. package/dist/components/Chat/ReasoningDisclosure/reasoning-disclosure.variants.js +31 -0
  45. package/dist/components/Chat/StreamingMarkdown/MdBlock.svelte +99 -0
  46. package/dist/components/Chat/StreamingMarkdown/MdBlock.svelte.d.ts +9 -0
  47. package/dist/components/Chat/StreamingMarkdown/MdInline.svelte +82 -0
  48. package/dist/components/Chat/StreamingMarkdown/MdInline.svelte.d.ts +9 -0
  49. package/dist/components/Chat/StreamingMarkdown/StreamingMarkdown.svelte +118 -0
  50. package/dist/components/Chat/StreamingMarkdown/StreamingMarkdown.svelte.d.ts +4 -0
  51. package/dist/components/Chat/StreamingMarkdown/index.d.ts +86 -0
  52. package/dist/components/Chat/StreamingMarkdown/index.js +2 -0
  53. package/dist/components/Chat/StreamingMarkdown/md-context.d.ts +58 -0
  54. package/dist/components/Chat/StreamingMarkdown/md-context.js +1 -0
  55. package/dist/components/Chat/StreamingMarkdown/streaming-markdown.variants.d.ts +145 -0
  56. package/dist/components/Chat/StreamingMarkdown/streaming-markdown.variants.js +73 -0
  57. package/dist/components/Chat/ToolCallCard/ToolCallCard.svelte +174 -0
  58. package/dist/components/Chat/ToolCallCard/ToolCallCard.svelte.d.ts +4 -0
  59. package/dist/components/Chat/ToolCallCard/index.d.ts +84 -0
  60. package/dist/components/Chat/ToolCallCard/index.js +2 -0
  61. package/dist/components/Chat/ToolCallCard/tool-call-card.variants.d.ts +38 -0
  62. package/dist/components/Chat/ToolCallCard/tool-call-card.variants.js +38 -0
  63. package/dist/components/Chat/chat.types.d.ts +82 -0
  64. package/dist/components/Chat/chat.types.js +7 -0
  65. package/dist/components/Chat/index.d.ts +22 -0
  66. package/dist/components/Chat/index.js +14 -0
  67. package/dist/components/Chat/markdown/blocks.d.ts +54 -0
  68. package/dist/components/Chat/markdown/blocks.js +462 -0
  69. package/dist/components/Chat/markdown/inline.d.ts +31 -0
  70. package/dist/components/Chat/markdown/inline.js +446 -0
  71. package/dist/components/Chat/markdown/repair.d.ts +23 -0
  72. package/dist/components/Chat/markdown/repair.js +337 -0
  73. package/dist/components/Chat/markdown/streaming-harness.d.ts +23 -0
  74. package/dist/components/Chat/markdown/streaming-harness.js +62 -0
  75. package/dist/components/Chat/markdown/types.d.ts +139 -0
  76. package/dist/components/Chat/markdown/types.js +13 -0
  77. package/dist/components/Chat/markdown/url-policy.d.ts +33 -0
  78. package/dist/components/Chat/markdown/url-policy.js +109 -0
  79. package/dist/components/FileUpload/FileUpload.svelte +35 -123
  80. package/dist/components/FileUpload/FileUpload.svelte.d.ts +1 -1
  81. package/dist/components/FileUpload/index.d.ts +2 -28
  82. package/dist/components/PinInput/PinInput.svelte +321 -0
  83. package/dist/components/PinInput/PinInput.svelte.d.ts +4 -0
  84. package/dist/components/PinInput/index.d.ts +98 -0
  85. package/dist/components/PinInput/index.js +2 -0
  86. package/dist/components/PinInput/pin-input.variants.d.ts +96 -0
  87. package/dist/components/PinInput/pin-input.variants.js +111 -0
  88. package/dist/components/QRCode/QRCode.svelte +107 -0
  89. package/dist/components/QRCode/QRCode.svelte.d.ts +4 -0
  90. package/dist/components/QRCode/index.d.ts +74 -0
  91. package/dist/components/QRCode/index.js +3 -0
  92. package/dist/components/QRCode/qr-code.variants.d.ts +25 -0
  93. package/dist/components/QRCode/qr-code.variants.js +24 -0
  94. package/dist/components/QRCode/qr-encode.d.ts +22 -0
  95. package/dist/components/QRCode/qr-encode.js +528 -0
  96. package/dist/components/TimeInput/TimeInput.svelte +521 -0
  97. package/dist/components/TimeInput/TimeInput.svelte.d.ts +4 -0
  98. package/dist/components/TimeInput/index.d.ts +93 -0
  99. package/dist/components/TimeInput/index.js +2 -0
  100. package/dist/components/TimeInput/time-input.variants.d.ts +131 -0
  101. package/dist/components/TimeInput/time-input.variants.js +120 -0
  102. package/dist/components/index.d.ts +7 -0
  103. package/dist/components/index.js +4 -0
  104. package/dist/i18n/index.d.ts +18 -0
  105. package/dist/primitives/Collapsible/Collapsible.svelte +5 -0
  106. package/dist/primitives/SplitPane/SplitPane.svelte +293 -0
  107. package/dist/primitives/SplitPane/SplitPane.svelte.d.ts +4 -0
  108. package/dist/primitives/SplitPane/index.d.ts +88 -0
  109. package/dist/primitives/SplitPane/index.js +2 -0
  110. package/dist/primitives/SplitPane/split-pane.utils.d.ts +73 -0
  111. package/dist/primitives/SplitPane/split-pane.utils.js +94 -0
  112. package/dist/primitives/SplitPane/split-pane.variants.d.ts +40 -0
  113. package/dist/primitives/SplitPane/split-pane.variants.js +56 -0
  114. package/dist/primitives/index.d.ts +2 -0
  115. package/dist/primitives/index.js +1 -0
  116. package/dist/translations/de.d.ts +6 -0
  117. package/dist/translations/de.js +6 -0
  118. package/dist/translations/en.d.ts +6 -0
  119. package/dist/translations/en.js +6 -0
  120. package/dist/utils/file-intake.d.ts +78 -0
  121. package/dist/utils/file-intake.js +144 -0
  122. package/dist/utils/index.d.ts +1 -0
  123. package/dist/utils/index.js +1 -0
  124. package/package.json +3 -3
@@ -0,0 +1,107 @@
1
+ <script lang="ts">
2
+ import { Popover } from '../../../primitives';
3
+ import { getBlocksConfig, resolveSlotClasses } from '../../../provider';
4
+ import { resolveIcon } from '../../../icons';
5
+ import LinkIconDefault from '../../../icons/LinkIcon.svelte';
6
+ import { checkLinkUrl } from '../markdown/url-policy.js';
7
+ import { citationChipVariants } from './citation-chip.variants';
8
+ import type { CitationChipProps } from './index';
9
+
10
+ const LinkIcon = resolveIcon('link', LinkIconDefault);
11
+
12
+ let {
13
+ source,
14
+ index,
15
+ citationStyle = 'numeric',
16
+ urlPolicy,
17
+ openLabel = 'Open source',
18
+ label,
19
+ class: className,
20
+ unstyled: unstyledProp = false,
21
+ slotClasses: slotClassesProp = {},
22
+ preset,
23
+ ...restProps
24
+ }: CitationChipProps = $props();
25
+
26
+ const blocksConfig = getBlocksConfig();
27
+ const unstyled = $derived(unstyledProp || blocksConfig?.unstyled || false);
28
+
29
+ const variantProps = $derived({ citationStyle });
30
+ const styles = $derived(citationChipVariants(variantProps));
31
+ const slotClasses = $derived(
32
+ resolveSlotClasses(blocksConfig, 'CitationChip', preset, variantProps, slotClassesProp)
33
+ );
34
+
35
+ // Numeric markers show the 1-based ordinal, falling back to the raw id when
36
+ // no index is supplied (standalone reference lists). Label markers show the
37
+ // (truncated) title.
38
+ const displayLabel = $derived(
39
+ citationStyle === 'label' ? source.title : index != null ? String(index) : source.id
40
+ );
41
+
42
+ const ariaLabel = $derived(
43
+ label ?? (index != null ? `Source ${index}: ${source.title}` : `Source: ${source.title}`)
44
+ );
45
+
46
+ // Run the same strict URL policy as the streaming-markdown engine: a blocked
47
+ // or absent URL yields no link, only the title/snippet.
48
+ const linkCheck = $derived(
49
+ source.url !== undefined ? checkLinkUrl(source.url, urlPolicy) : ({ ok: false } as const)
50
+ );
51
+ const linkHref = $derived(linkCheck.ok ? linkCheck.href : undefined);
52
+ </script>
53
+
54
+ <!-- The aria-label on Popover lands on the panel via its restProps spread, so
55
+ the opened popover is not an unnamed dialog (axe: aria-dialog-name). -->
56
+ <Popover placement="bottom-start" aria-label={ariaLabel}>
57
+ {#snippet trigger()}
58
+ <button
59
+ type="button"
60
+ class={unstyled
61
+ ? [slotClasses?.trigger, className].filter(Boolean).join(' ')
62
+ : styles.trigger({ class: [slotClasses?.trigger, className] })}
63
+ aria-label={ariaLabel}
64
+ {...restProps}
65
+ >
66
+ <!-- truncate must sit on an inner block-ish span: on the inline-flex
67
+ trigger itself the ellipsis never renders and justify-center clips
68
+ the START of long labels. -->
69
+ <span class="min-w-0 truncate">{displayLabel}</span>
70
+ </button>
71
+ {/snippet}
72
+ <div
73
+ class={unstyled
74
+ ? (slotClasses?.popover ?? '')
75
+ : styles.popover({ class: slotClasses?.popover })}
76
+ >
77
+ <span
78
+ class={unstyled ? (slotClasses?.title ?? '') : styles.title({ class: slotClasses?.title })}
79
+ >
80
+ {source.title}
81
+ </span>
82
+ {#if source.snippet}
83
+ <p
84
+ class={unstyled
85
+ ? (slotClasses?.snippet ?? '')
86
+ : styles.snippet({ class: slotClasses?.snippet })}
87
+ >
88
+ {source.snippet}
89
+ </p>
90
+ {/if}
91
+ {#if linkHref}
92
+ <a
93
+ href={linkHref}
94
+ target="_blank"
95
+ rel="noopener noreferrer"
96
+ class={unstyled ? (slotClasses?.link ?? '') : styles.link({ class: slotClasses?.link })}
97
+ >
98
+ <LinkIcon
99
+ class={unstyled
100
+ ? (slotClasses?.linkIcon ?? '')
101
+ : styles.linkIcon({ class: slotClasses?.linkIcon })}
102
+ />
103
+ {openLabel}
104
+ </a>
105
+ {/if}
106
+ </div>
107
+ </Popover>
@@ -0,0 +1,4 @@
1
+ import type { CitationChipProps } from './index.js';
2
+ declare const CitationChip: import("svelte").Component<CitationChipProps, {}, "">;
3
+ type CitationChip = ReturnType<typeof CitationChip>;
4
+ export default CitationChip;
@@ -0,0 +1,40 @@
1
+ import { type SlotNames, type VariantProps } from '../../../utils/variants.js';
2
+ export declare const citationChipVariants: ((props?: {
3
+ citationStyle?: "numeric" | "label" | undefined;
4
+ } | undefined) => {
5
+ trigger: (props?: ({
6
+ citationStyle?: "numeric" | "label" | undefined;
7
+ } & {
8
+ class?: string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | /*elided*/ any | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined;
9
+ }) | undefined) => string;
10
+ popover: (props?: ({
11
+ citationStyle?: "numeric" | "label" | undefined;
12
+ } & {
13
+ class?: string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | /*elided*/ any | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined;
14
+ }) | undefined) => string;
15
+ title: (props?: ({
16
+ citationStyle?: "numeric" | "label" | undefined;
17
+ } & {
18
+ class?: string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | /*elided*/ any | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined;
19
+ }) | undefined) => string;
20
+ snippet: (props?: ({
21
+ citationStyle?: "numeric" | "label" | undefined;
22
+ } & {
23
+ class?: string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | /*elided*/ any | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined;
24
+ }) | undefined) => string;
25
+ link: (props?: ({
26
+ citationStyle?: "numeric" | "label" | undefined;
27
+ } & {
28
+ class?: string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | /*elided*/ any | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined;
29
+ }) | undefined) => string;
30
+ linkIcon: (props?: ({
31
+ citationStyle?: "numeric" | "label" | undefined;
32
+ } & {
33
+ class?: string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | /*elided*/ any | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined;
34
+ }) | undefined) => string;
35
+ }) & {
36
+ readonly config: import("../../../utils/variants.js").TVConfig;
37
+ };
38
+ export type CitationChipVariants = VariantProps<typeof citationChipVariants>;
39
+ /** Slot names derived from the tv() config — single source of truth for slotClasses. */
40
+ export type CitationChipSlots = SlotNames<typeof citationChipVariants>;
@@ -0,0 +1,42 @@
1
+ import { tv } from '../../../utils/variants.js';
2
+ export const citationChipVariants = tv({
3
+ slots: {
4
+ // Compact inline chip in the Badge soft-primary idiom. `align-baseline`
5
+ // + `leading-none` keep it seated on the text baseline so an inline
6
+ // citation marker never bumps the surrounding line height.
7
+ trigger: [
8
+ 'inline-flex items-center justify-center align-baseline',
9
+ 'bg-primary-subtle text-primary-emphasis',
10
+ 'text-xs font-medium leading-none tabular-nums',
11
+ 'rounded-full px-1.5 py-0.5 cursor-pointer',
12
+ 'transition-colors duration-[var(--blocks-duration-fast)] ease-out',
13
+ 'hover:bg-primary/15',
14
+ 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary'
15
+ ],
16
+ // Content wrapper INSIDE the Popover panel — the panel itself already
17
+ // supplies surface / border / shadow / padding, so this only lays out.
18
+ popover: 'flex flex-col gap-1.5 max-w-xs',
19
+ title: 'font-medium text-text-primary text-sm',
20
+ snippet: 'text-text-secondary text-sm line-clamp-3',
21
+ link: [
22
+ 'inline-flex items-center gap-1 self-start',
23
+ 'text-sm text-primary rounded-modify',
24
+ 'hover:underline',
25
+ 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary'
26
+ ],
27
+ linkIcon: 'h-3.5 w-3.5 shrink-0'
28
+ },
29
+ variants: {
30
+ citationStyle: {
31
+ // Numeric marker — a fixed-width numeric pill; no truncation.
32
+ numeric: {},
33
+ // Label marker — carries the source title, capped so a long title
34
+ // can't stretch the chip across the line. The ellipsis itself lives on
35
+ // an inner span (truncate doesn't render on the inline-flex trigger).
36
+ label: { trigger: 'max-w-[16ch]' }
37
+ }
38
+ },
39
+ defaultVariants: {
40
+ citationStyle: 'numeric'
41
+ }
42
+ });
@@ -0,0 +1,69 @@
1
+ import type { HTMLButtonAttributes } from 'svelte/elements';
2
+ import type { MarkdownUrlPolicy } from '../markdown/types.js';
3
+ import type { CitationChipSlots } from './citation-chip.variants.js';
4
+ /**
5
+ * A cited source surfaced behind a `[id]` citation marker. `id` keys the
6
+ * source to its marker; `title` is always shown, `url` / `snippet` are optional
7
+ * and only render when present (and, for `url`, when the URL policy allows it).
8
+ */
9
+ export interface CitationSource {
10
+ /** Stable id — matches the `[id]` marker in the streamed markdown. */
11
+ id: string;
12
+ /** Human-readable source title, always shown in the popover (and as the chip label under `citationStyle="label"`). */
13
+ title: string;
14
+ /** Source URL. Rendered as an outbound link only when it passes the `urlPolicy`; a blocked or absent URL shows title/snippet with no link. */
15
+ url?: string;
16
+ /** Short excerpt shown under the title, clamped to ~3 lines. */
17
+ snippet?: string;
18
+ }
19
+ /**
20
+ * @description Compact source marker rendered for a `[id]` citation. StreamingMarkdown wires it up automatically from its `sources` prop — each in-text marker becomes a CitationChip whose click opens a Popover with the source title, snippet, and a policy-checked outbound link. Also usable standalone for source footers / reference lists outside a streamed message.
21
+ * @tag ai
22
+ * @related StreamingMarkdown
23
+ * @related Badge
24
+ * @related Popover
25
+ * @stability experimental
26
+ */
27
+ export interface CitationChipProps extends Omit<HTMLButtonAttributes, 'class' | 'children'> {
28
+ /** The cited source. Required. */
29
+ source: CitationSource;
30
+ /** 1-based ordinal shown as the chip label under `citationStyle="numeric"`. Falls back to `source.id` when omitted. */
31
+ index?: number;
32
+ /**
33
+ * What the chip shows: `numeric` renders `index` (or `source.id` as a
34
+ * fallback) as a compact numeric pill; `label` renders the (truncated)
35
+ * `source.title`.
36
+ * @default 'numeric'
37
+ */
38
+ citationStyle?: 'numeric' | 'label';
39
+ /**
40
+ * URL policy applied to `source.url` before it becomes a link (same strict
41
+ * default as the streaming-markdown engine — untrusted LLM output). A blocked
42
+ * URL yields no link in the popover, only title/snippet.
43
+ */
44
+ urlPolicy?: MarkdownUrlPolicy;
45
+ /**
46
+ * Text of the outbound link in the popover.
47
+ * @default 'Open source'
48
+ */
49
+ openLabel?: string;
50
+ /**
51
+ * Override the trigger's `aria-label`. Defaults to
52
+ * `Source {index}: {title}` (or `Source: {title}` without an index).
53
+ */
54
+ label?: string;
55
+ /** Extra classes merged onto the trigger chip (the root slot). */
56
+ class?: string;
57
+ /** Strip all default tv() classes; combine with `class` / `slotClasses` for a custom look. */
58
+ unstyled?: boolean;
59
+ /** Per-slot class overrides. Slots: `trigger` (root chip), `popover` (content wrapper), `title`, `snippet`, `link`, `linkIcon`. */
60
+ slotClasses?: Partial<Record<CitationChipSlots, string>>;
61
+ /**
62
+ * Apply a named preset registered via `<BlocksProvider presets={{ CitationChip: {...} }}>`.
63
+ * Prefer this over `class` overrides when the requested look falls outside the
64
+ * semantic intent palette.
65
+ */
66
+ preset?: string;
67
+ }
68
+ export { default as CitationChip } from './CitationChip.svelte';
69
+ export { type CitationChipSlots, type CitationChipVariants, citationChipVariants } from './citation-chip.variants.js';
@@ -0,0 +1,2 @@
1
+ export { default as CitationChip } from './CitationChip.svelte';
2
+ export { citationChipVariants } from './citation-chip.variants.js';
@@ -0,0 +1,120 @@
1
+ <script lang="ts">
2
+ import { getBlocksConfig, resolveSlotClasses } from '../../../provider';
3
+ import { resolveIcon } from '../../../icons';
4
+ import CopyIconDefault from '../../../icons/CopyIcon.svelte';
5
+ import CheckIconDefault from '../../../icons/CheckIcon.svelte';
6
+ import { codeBlockVariants, type CodeBlockVariants } from './code-block.variants';
7
+ import type { CodeBlockProps } from './index';
8
+
9
+ let {
10
+ code,
11
+ lang,
12
+ showCopy = true,
13
+ wrap = false,
14
+ copyLabel = 'Copy code',
15
+ copiedLabel = 'Copied',
16
+ onCopy,
17
+ actions,
18
+ class: className,
19
+ unstyled: unstyledProp = false,
20
+ slotClasses: slotClassesProp = {},
21
+ preset,
22
+ ...restProps
23
+ }: CodeBlockProps = $props();
24
+
25
+ const blocksConfig = getBlocksConfig();
26
+ const unstyled = $derived(unstyledProp || blocksConfig?.unstyled || false);
27
+
28
+ const CopyIcon = resolveIcon('copy', CopyIconDefault);
29
+ const CheckIcon = resolveIcon('check', CheckIconDefault);
30
+
31
+ const variantProps: CodeBlockVariants = $derived({ wrap });
32
+ const styles = $derived(codeBlockVariants(variantProps));
33
+ const slotClasses = $derived(
34
+ resolveSlotClasses(blocksConfig, 'CodeBlock', preset, variantProps, slotClassesProp)
35
+ );
36
+
37
+ function cls(name: keyof typeof slotClasses, extra?: string | (string | undefined)[]) {
38
+ if (unstyled) {
39
+ const own = slotClasses?.[name];
40
+ return [own, ...(Array.isArray(extra) ? extra : [extra])].filter(Boolean).join(' ');
41
+ }
42
+ const slotFns = styles as Record<string, (args: { class?: unknown }) => string>;
43
+ return slotFns[name]({
44
+ class: [slotClasses?.[name], ...(Array.isArray(extra) ? extra : [extra])]
45
+ });
46
+ }
47
+
48
+ const showHeader = $derived(!!lang || showCopy || !!actions);
49
+ const regionLabel = $derived(lang ? `${lang} code` : 'Code');
50
+
51
+ let copied = $state(false);
52
+ let resetTimer: ReturnType<typeof setTimeout> | undefined;
53
+
54
+ async function copyCode() {
55
+ try {
56
+ await navigator.clipboard.writeText(code);
57
+ copied = true;
58
+ if (resetTimer) clearTimeout(resetTimer);
59
+ resetTimer = setTimeout(() => {
60
+ copied = false;
61
+ resetTimer = undefined;
62
+ }, 2000);
63
+ onCopy?.(code);
64
+ } catch (err) {
65
+ // Never swallow silently — but leave state untouched so the UI does not
66
+ // falsely confirm a copy that failed (e.g. denied clipboard permission).
67
+ console.error('CodeBlock: failed to copy code', err);
68
+ }
69
+ }
70
+
71
+ // Clear a pending reset on unmount so the timer can't fire into a torn-down component.
72
+ $effect(() => () => {
73
+ if (resetTimer) clearTimeout(resetTimer);
74
+ });
75
+ </script>
76
+
77
+ <div class={cls('root', className)} {...restProps}>
78
+ {#if showHeader}
79
+ <div class={cls('header')}>
80
+ <span class={cls('langLabel')}>{lang ?? ''}</span>
81
+ <div class="flex items-center gap-1">
82
+ {@render actions?.()}
83
+ {#if showCopy}
84
+ <button
85
+ type="button"
86
+ class={cls('copyButton')}
87
+ onclick={copyCode}
88
+ aria-label={copied ? copiedLabel : copyLabel}
89
+ >
90
+ {#if copied}
91
+ <CheckIcon size={14} />
92
+ {:else}
93
+ <CopyIcon size={14} />
94
+ {/if}
95
+ <span>{copied ? copiedLabel : copyLabel}</span>
96
+ </button>
97
+ {/if}
98
+ </div>
99
+ </div>
100
+ {/if}
101
+
102
+ <!--
103
+ Scrollable code region: tabindex=0 + role=region + aria-label so keyboard
104
+ users can reach and scroll the overflow (WCAG 2.1.1). Svelte flags tabindex
105
+ on a "noninteractive" element, but a focusable scroll container is the
106
+ sanctioned exception here.
107
+ -->
108
+ <!-- svelte-ignore a11y_no_noninteractive_tabindex -->
109
+ <pre class={cls('pre')} tabindex="0" role="region" aria-label={regionLabel}><code
110
+ class={cls('code')}>{code}</code
111
+ ></pre>
112
+
113
+ <!--
114
+ Copy confirmation for screen readers. A label change on the button the user
115
+ just activated is not a reliable announcement — a live status region is. It
116
+ must exist in the DOM before `copied` flips, so it always renders and only
117
+ its text content changes.
118
+ -->
119
+ <span class="sr-only" role="status">{copied ? copiedLabel : ''}</span>
120
+ </div>
@@ -0,0 +1,4 @@
1
+ import type { CodeBlockProps } from './index.js';
2
+ declare const CodeBlock: import("svelte").Component<CodeBlockProps, {}, "">;
3
+ type CodeBlock = ReturnType<typeof CodeBlock>;
4
+ export default CodeBlock;
@@ -0,0 +1,40 @@
1
+ import { type SlotNames, type VariantProps } from '../../../utils/variants.js';
2
+ export declare const codeBlockVariants: ((props?: {
3
+ wrap?: boolean | undefined;
4
+ } | undefined) => {
5
+ root: (props?: ({
6
+ wrap?: boolean | undefined;
7
+ } & {
8
+ class?: string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | /*elided*/ any | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined;
9
+ }) | undefined) => string;
10
+ header: (props?: ({
11
+ wrap?: boolean | undefined;
12
+ } & {
13
+ class?: string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | /*elided*/ any | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined;
14
+ }) | undefined) => string;
15
+ langLabel: (props?: ({
16
+ wrap?: boolean | undefined;
17
+ } & {
18
+ class?: string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | /*elided*/ any | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined;
19
+ }) | undefined) => string;
20
+ copyButton: (props?: ({
21
+ wrap?: boolean | undefined;
22
+ } & {
23
+ class?: string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | /*elided*/ any | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined;
24
+ }) | undefined) => string;
25
+ pre: (props?: ({
26
+ wrap?: boolean | undefined;
27
+ } & {
28
+ class?: string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | /*elided*/ any | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined;
29
+ }) | undefined) => string;
30
+ code: (props?: ({
31
+ wrap?: boolean | undefined;
32
+ } & {
33
+ class?: string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | (string | number | false | /*elided*/ any | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined)[] | Record<string, boolean | null | undefined> | null | undefined;
34
+ }) | undefined) => string;
35
+ }) & {
36
+ readonly config: import("../../../utils/variants.js").TVConfig;
37
+ };
38
+ export type CodeBlockVariants = VariantProps<typeof codeBlockVariants>;
39
+ /** Slot names derived from the tv() config — single source of truth for slotClasses. */
40
+ export type CodeBlockSlots = SlotNames<typeof codeBlockVariants>;
@@ -0,0 +1,51 @@
1
+ import { tv } from '../../../utils/variants.js';
2
+ export const codeBlockVariants = tv({
3
+ slots: {
4
+ // Architectural surface — an elevated card, structure radius, clipped so the
5
+ // scrollable body respects the rounded corners.
6
+ root: [
7
+ 'relative block w-full overflow-hidden box-border',
8
+ 'bg-surface-elevated border border-border-subtle rounded-contain'
9
+ ],
10
+ // Header only renders when there is something to show (lang / copy / actions).
11
+ header: [
12
+ 'flex items-center justify-between gap-2',
13
+ 'px-3 py-1.5 border-b border-border-subtle'
14
+ ],
15
+ langLabel: ['font-mono text-xs text-text-tertiary select-none'],
16
+ copyButton: [
17
+ 'inline-flex items-center gap-1.5',
18
+ 'px-2 py-1 rounded-modify',
19
+ 'font-mono text-xs text-text-secondary',
20
+ 'cursor-pointer select-none',
21
+ 'transition-[color,background-color] duration-[var(--blocks-duration-fast)] ease-out',
22
+ 'hover:bg-surface-hover hover:text-text-primary',
23
+ 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-2'
24
+ ],
25
+ // The scrollable region. Body horizontal scroll stays INSIDE the block —
26
+ // never the page. Focusable + role=region so keyboard users can reach the
27
+ // scroll (a11y for scrollable content).
28
+ pre: [
29
+ 'm-0 px-3 py-2.5',
30
+ 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-inset'
31
+ ],
32
+ code: ['font-mono text-sm text-text-primary']
33
+ },
34
+ variants: {
35
+ // false → single-line semantics, horizontal scroll inside the block.
36
+ // true → soft-wrap long lines, no horizontal scroll.
37
+ wrap: {
38
+ true: {
39
+ pre: 'whitespace-pre-wrap break-words',
40
+ code: 'whitespace-pre-wrap break-words'
41
+ },
42
+ false: {
43
+ pre: 'overflow-x-auto',
44
+ code: 'whitespace-pre'
45
+ }
46
+ }
47
+ },
48
+ defaultVariants: {
49
+ wrap: false
50
+ }
51
+ });
@@ -0,0 +1,55 @@
1
+ import type { Snippet } from 'svelte';
2
+ import type { HTMLAttributes } from 'svelte/elements';
3
+ import type { CodeBlockSlots, CodeBlockVariants } from './code-block.variants.js';
4
+ /**
5
+ * @description Read-only code display card with a one-click copy interaction: the copy
6
+ * button swaps its icon and label to a confirmation for two seconds and fires `onCopy`.
7
+ * Renders raw text only — no built-in syntax highlighting; a consumer or the
8
+ * StreamingMarkdown renderer can layer highlighting in via a snippet. Used by
9
+ * StreamingMarkdown for fenced code blocks, and standalone for any code snippet.
10
+ *
11
+ * @tag ai
12
+ * @tag display
13
+ * @related StreamingMarkdown
14
+ * @stability experimental
15
+ *
16
+ * @example
17
+ * ```svelte
18
+ * <CodeBlock lang="ts" code={`const x = 1;`} onCopy={(c) => track(c)} />
19
+ * ```
20
+ */
21
+ export interface CodeBlockProps extends CodeBlockVariants, Omit<HTMLAttributes<HTMLElement>, 'children' | 'class'> {
22
+ /** The code to display and copy. Rendered as raw text (no highlighting). */
23
+ code: string;
24
+ /** Language label shown in the header. Display-only — does not drive highlighting. */
25
+ lang?: string;
26
+ /** Show the copy button in the header. */
27
+ showCopy?: boolean;
28
+ /**
29
+ * Soft-wrap long lines. `false` (default) scrolls horizontally inside the
30
+ * block; `true` wraps with `whitespace-pre-wrap` + word breaking.
31
+ */
32
+ wrap?: boolean;
33
+ /** Accessible label / tooltip for the copy button in its idle state. */
34
+ copyLabel?: string;
35
+ /** Accessible label shown for two seconds after a successful copy. */
36
+ copiedLabel?: string;
37
+ /** Called with the copied code after a successful clipboard write. */
38
+ onCopy?: (code: string) => void;
39
+ /** Extra header actions rendered to the right, before the copy button. */
40
+ actions?: Snippet;
41
+ /** Extra classes merged onto the root element. */
42
+ class?: string;
43
+ /** Remove all default tv classes. */
44
+ unstyled?: boolean;
45
+ /** Per-slot class overrides. Slots: root | header | langLabel | copyButton | pre | code */
46
+ slotClasses?: Partial<Record<CodeBlockSlots, string>>;
47
+ /**
48
+ * Apply a named preset registered via `<BlocksProvider presets={{ CodeBlock: {...} }}>`.
49
+ * Prefer this over `class` overrides when the requested look falls outside the
50
+ * semantic intent palette.
51
+ */
52
+ preset?: string;
53
+ }
54
+ export { default as CodeBlock } from './CodeBlock.svelte';
55
+ export { type CodeBlockVariants, codeBlockVariants } from './code-block.variants.js';
@@ -0,0 +1,2 @@
1
+ export { default as CodeBlock } from './CodeBlock.svelte';
2
+ export { codeBlockVariants } from './code-block.variants.js';