@delightstack/components 0.1.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 (195) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +136 -0
  3. package/SKILL.md +149 -0
  4. package/bin/agents.js +63 -0
  5. package/dist/actions/Alert.svelte +202 -0
  6. package/dist/actions/Alert.svelte.d.ts +36 -0
  7. package/dist/actions/Alert.svelte.d.ts.map +1 -0
  8. package/dist/actions/Button.svelte +1450 -0
  9. package/dist/actions/Button.svelte.d.ts +56 -0
  10. package/dist/actions/Button.svelte.d.ts.map +1 -0
  11. package/dist/actions/ButtonGroup.svelte +111 -0
  12. package/dist/actions/ButtonGroup.svelte.d.ts +41 -0
  13. package/dist/actions/ButtonGroup.svelte.d.ts.map +1 -0
  14. package/dist/actions/CommandPalette.svelte +939 -0
  15. package/dist/actions/CommandPalette.svelte.d.ts +37 -0
  16. package/dist/actions/CommandPalette.svelte.d.ts.map +1 -0
  17. package/dist/actions/ContextMenu.svelte +138 -0
  18. package/dist/actions/ContextMenu.svelte.d.ts +54 -0
  19. package/dist/actions/ContextMenu.svelte.d.ts.map +1 -0
  20. package/dist/actions/Modal.svelte +474 -0
  21. package/dist/actions/Modal.svelte.d.ts +28 -0
  22. package/dist/actions/Modal.svelte.d.ts.map +1 -0
  23. package/dist/actions/Popover.svelte +1214 -0
  24. package/dist/actions/Popover.svelte.d.ts +31 -0
  25. package/dist/actions/Popover.svelte.d.ts.map +1 -0
  26. package/dist/actions/Portal.svelte +80 -0
  27. package/dist/actions/Portal.svelte.d.ts +17 -0
  28. package/dist/actions/Portal.svelte.d.ts.map +1 -0
  29. package/dist/actions/ThemeToggle.svelte +345 -0
  30. package/dist/actions/ThemeToggle.svelte.d.ts +15 -0
  31. package/dist/actions/ThemeToggle.svelte.d.ts.map +1 -0
  32. package/dist/actions/index.d.ts +13 -0
  33. package/dist/actions/index.d.ts.map +1 -0
  34. package/dist/actions/index.js +10 -0
  35. package/dist/actions/scrollbar.d.ts +48 -0
  36. package/dist/actions/scrollbar.d.ts.map +1 -0
  37. package/dist/actions/scrollbar.js +404 -0
  38. package/dist/display/Accordion.svelte +586 -0
  39. package/dist/display/Accordion.svelte.d.ts +41 -0
  40. package/dist/display/Accordion.svelte.d.ts.map +1 -0
  41. package/dist/display/Avatar.svelte +527 -0
  42. package/dist/display/Avatar.svelte.d.ts +22 -0
  43. package/dist/display/Avatar.svelte.d.ts.map +1 -0
  44. package/dist/display/AvatarGroup.svelte +298 -0
  45. package/dist/display/AvatarGroup.svelte.d.ts +31 -0
  46. package/dist/display/AvatarGroup.svelte.d.ts.map +1 -0
  47. package/dist/display/Calendar.svelte +1366 -0
  48. package/dist/display/Calendar.svelte.d.ts +58 -0
  49. package/dist/display/Calendar.svelte.d.ts.map +1 -0
  50. package/dist/display/Chart.svelte +1426 -0
  51. package/dist/display/Chart.svelte.d.ts +35 -0
  52. package/dist/display/Chart.svelte.d.ts.map +1 -0
  53. package/dist/display/Code.svelte +780 -0
  54. package/dist/display/Code.svelte.d.ts +19 -0
  55. package/dist/display/Code.svelte.d.ts.map +1 -0
  56. package/dist/display/Comparison.svelte +686 -0
  57. package/dist/display/Comparison.svelte.d.ts +22 -0
  58. package/dist/display/Comparison.svelte.d.ts.map +1 -0
  59. package/dist/display/Counter.svelte +285 -0
  60. package/dist/display/Counter.svelte.d.ts +21 -0
  61. package/dist/display/Counter.svelte.d.ts.map +1 -0
  62. package/dist/display/Expand.svelte +48 -0
  63. package/dist/display/Expand.svelte.d.ts +9 -0
  64. package/dist/display/Expand.svelte.d.ts.map +1 -0
  65. package/dist/display/List.svelte +294 -0
  66. package/dist/display/List.svelte.d.ts +40 -0
  67. package/dist/display/List.svelte.d.ts.map +1 -0
  68. package/dist/display/ListContextReset.svelte +19 -0
  69. package/dist/display/ListContextReset.svelte.d.ts +7 -0
  70. package/dist/display/ListContextReset.svelte.d.ts.map +1 -0
  71. package/dist/display/ListItem.svelte +834 -0
  72. package/dist/display/ListItem.svelte.d.ts +22 -0
  73. package/dist/display/ListItem.svelte.d.ts.map +1 -0
  74. package/dist/display/QR.svelte +1193 -0
  75. package/dist/display/QR.svelte.d.ts +23 -0
  76. package/dist/display/QR.svelte.d.ts.map +1 -0
  77. package/dist/display/SplitPane.svelte +744 -0
  78. package/dist/display/SplitPane.svelte.d.ts +25 -0
  79. package/dist/display/SplitPane.svelte.d.ts.map +1 -0
  80. package/dist/display/Stat.svelte +439 -0
  81. package/dist/display/Stat.svelte.d.ts +24 -0
  82. package/dist/display/Stat.svelte.d.ts.map +1 -0
  83. package/dist/display/Table.svelte +4654 -0
  84. package/dist/display/Table.svelte.d.ts +249 -0
  85. package/dist/display/Table.svelte.d.ts.map +1 -0
  86. package/dist/display/TableCellEditor.svelte +935 -0
  87. package/dist/display/TableCellEditor.svelte.d.ts +58 -0
  88. package/dist/display/TableCellEditor.svelte.d.ts.map +1 -0
  89. package/dist/display/Timeline.svelte +1258 -0
  90. package/dist/display/Timeline.svelte.d.ts +43 -0
  91. package/dist/display/Timeline.svelte.d.ts.map +1 -0
  92. package/dist/display/Tree.svelte +1740 -0
  93. package/dist/display/Tree.svelte.d.ts +74 -0
  94. package/dist/display/Tree.svelte.d.ts.map +1 -0
  95. package/dist/display/Typewriter.svelte +338 -0
  96. package/dist/display/Typewriter.svelte.d.ts +22 -0
  97. package/dist/display/Typewriter.svelte.d.ts.map +1 -0
  98. package/dist/display/index.d.ts +24 -0
  99. package/dist/display/index.d.ts.map +1 -0
  100. package/dist/display/index.js +18 -0
  101. package/dist/feedback/Callout.svelte +529 -0
  102. package/dist/feedback/Callout.svelte.d.ts +24 -0
  103. package/dist/feedback/Callout.svelte.d.ts.map +1 -0
  104. package/dist/feedback/Confetti.svelte +631 -0
  105. package/dist/feedback/Confetti.svelte.d.ts +90 -0
  106. package/dist/feedback/Confetti.svelte.d.ts.map +1 -0
  107. package/dist/feedback/Progress.svelte +382 -0
  108. package/dist/feedback/Progress.svelte.d.ts +25 -0
  109. package/dist/feedback/Progress.svelte.d.ts.map +1 -0
  110. package/dist/feedback/Toast.svelte +967 -0
  111. package/dist/feedback/Toast.svelte.d.ts +54 -0
  112. package/dist/feedback/Toast.svelte.d.ts.map +1 -0
  113. package/dist/feedback/index.d.ts +7 -0
  114. package/dist/feedback/index.d.ts.map +1 -0
  115. package/dist/feedback/index.js +4 -0
  116. package/dist/form/Checkbox.svelte +449 -0
  117. package/dist/form/Checkbox.svelte.d.ts +27 -0
  118. package/dist/form/Checkbox.svelte.d.ts.map +1 -0
  119. package/dist/form/Fieldset.svelte +410 -0
  120. package/dist/form/Fieldset.svelte.d.ts +22 -0
  121. package/dist/form/Fieldset.svelte.d.ts.map +1 -0
  122. package/dist/form/FileUpload.svelte +934 -0
  123. package/dist/form/FileUpload.svelte.d.ts +41 -0
  124. package/dist/form/FileUpload.svelte.d.ts.map +1 -0
  125. package/dist/form/Form.svelte +530 -0
  126. package/dist/form/Form.svelte.d.ts +120 -0
  127. package/dist/form/Form.svelte.d.ts.map +1 -0
  128. package/dist/form/Input.svelte +2858 -0
  129. package/dist/form/Input.svelte.d.ts +66 -0
  130. package/dist/form/Input.svelte.d.ts.map +1 -0
  131. package/dist/form/Radio.svelte +507 -0
  132. package/dist/form/Radio.svelte.d.ts +39 -0
  133. package/dist/form/Radio.svelte.d.ts.map +1 -0
  134. package/dist/form/Range.svelte +912 -0
  135. package/dist/form/Range.svelte.d.ts +33 -0
  136. package/dist/form/Range.svelte.d.ts.map +1 -0
  137. package/dist/form/Rating.svelte +429 -0
  138. package/dist/form/Rating.svelte.d.ts +28 -0
  139. package/dist/form/Rating.svelte.d.ts.map +1 -0
  140. package/dist/form/Select.svelte +1933 -0
  141. package/dist/form/Select.svelte.d.ts +54 -0
  142. package/dist/form/Select.svelte.d.ts.map +1 -0
  143. package/dist/form/Toggle.svelte +645 -0
  144. package/dist/form/Toggle.svelte.d.ts +50 -0
  145. package/dist/form/Toggle.svelte.d.ts.map +1 -0
  146. package/dist/form/index.d.ts +15 -0
  147. package/dist/form/index.d.ts.map +1 -0
  148. package/dist/form/index.js +10 -0
  149. package/dist/index.d.ts +7 -0
  150. package/dist/index.d.ts.map +1 -0
  151. package/dist/index.js +6 -0
  152. package/dist/layout/README.md +172 -0
  153. package/dist/media/Carousel.svelte +2424 -0
  154. package/dist/media/Carousel.svelte.d.ts +47 -0
  155. package/dist/media/Carousel.svelte.d.ts.map +1 -0
  156. package/dist/media/Gallery.svelte +2881 -0
  157. package/dist/media/Gallery.svelte.d.ts +82 -0
  158. package/dist/media/Gallery.svelte.d.ts.map +1 -0
  159. package/dist/media/Image.svelte +389 -0
  160. package/dist/media/Image.svelte.d.ts +33 -0
  161. package/dist/media/Image.svelte.d.ts.map +1 -0
  162. package/dist/media/PDF.svelte +1793 -0
  163. package/dist/media/PDF.svelte.d.ts +44 -0
  164. package/dist/media/PDF.svelte.d.ts.map +1 -0
  165. package/dist/media/Panorama.svelte +1391 -0
  166. package/dist/media/Panorama.svelte.d.ts +47 -0
  167. package/dist/media/Panorama.svelte.d.ts.map +1 -0
  168. package/dist/media/Video.svelte +2501 -0
  169. package/dist/media/Video.svelte.d.ts +58 -0
  170. package/dist/media/Video.svelte.d.ts.map +1 -0
  171. package/dist/media/carousel.d.ts +211 -0
  172. package/dist/media/carousel.d.ts.map +1 -0
  173. package/dist/media/carousel.js +408 -0
  174. package/dist/media/index.d.ts +11 -0
  175. package/dist/media/index.d.ts.map +1 -0
  176. package/dist/media/index.js +5 -0
  177. package/dist/navigation/BottomSheet.svelte +636 -0
  178. package/dist/navigation/BottomSheet.svelte.d.ts +27 -0
  179. package/dist/navigation/BottomSheet.svelte.d.ts.map +1 -0
  180. package/dist/navigation/Breadcrumbs.svelte +611 -0
  181. package/dist/navigation/Breadcrumbs.svelte.d.ts +28 -0
  182. package/dist/navigation/Breadcrumbs.svelte.d.ts.map +1 -0
  183. package/dist/navigation/Pagination.svelte +641 -0
  184. package/dist/navigation/Pagination.svelte.d.ts +27 -0
  185. package/dist/navigation/Pagination.svelte.d.ts.map +1 -0
  186. package/dist/navigation/Steps.svelte +965 -0
  187. package/dist/navigation/Steps.svelte.d.ts +43 -0
  188. package/dist/navigation/Steps.svelte.d.ts.map +1 -0
  189. package/dist/navigation/Tabs.svelte +698 -0
  190. package/dist/navigation/Tabs.svelte.d.ts +41 -0
  191. package/dist/navigation/Tabs.svelte.d.ts.map +1 -0
  192. package/dist/navigation/index.d.ts +8 -0
  193. package/dist/navigation/index.d.ts.map +1 -0
  194. package/dist/navigation/index.js +5 -0
  195. package/package.json +139 -0
@@ -0,0 +1,780 @@
1
+ <script lang="ts">
2
+ import { scrollbar } from '../actions/scrollbar';
3
+
4
+ interface Token {
5
+ type: string;
6
+ content: string;
7
+ }
8
+
9
+ const propId = $props.id();
10
+ let {
11
+ /** Code string to display */
12
+ code,
13
+
14
+ /** Language for syntax highlighting */
15
+ language = 'plaintext',
16
+
17
+ /** Filename displayed in the header */
18
+ filename = undefined as string | undefined,
19
+
20
+ /** Whether to show line numbers */
21
+ show_line_numbers = true,
22
+
23
+ /** Whether to show the copy button */
24
+ show_copy = true,
25
+
26
+ /** Starting line number */
27
+ start_line = 1,
28
+
29
+ /** Line numbers to highlight */
30
+ highlight_lines = [] as number[],
31
+
32
+ /** Render as unified diff */
33
+ diff = false,
34
+
35
+ /** Wrap long lines instead of scrolling */
36
+ wrap = false,
37
+
38
+ /** Maximum height with overflow scroll */
39
+ max_height = undefined as string | undefined,
40
+
41
+ /** Show loading skeleton */
42
+ skeleton = false,
43
+
44
+ /** Element ID */
45
+ id = propId,
46
+
47
+ /** Additional CSS classes */
48
+ class: class_name = '',
49
+ }: {
50
+ code: string;
51
+ language?: string;
52
+ filename?: string;
53
+ show_line_numbers?: boolean;
54
+ show_copy?: boolean;
55
+ start_line?: number;
56
+ highlight_lines?: number[];
57
+ diff?: boolean;
58
+ wrap?: boolean;
59
+ max_height?: string;
60
+ skeleton?: boolean;
61
+ id?: string;
62
+ class?: string;
63
+ } = $props();
64
+
65
+ /* ------------------------------------------------------------------ */
66
+ /* Copy to clipboard */
67
+ /* ------------------------------------------------------------------ */
68
+
69
+ let copy_state = $state<'idle' | 'copied'>('idle');
70
+ let copy_timeout = 0;
71
+
72
+ function handleCopy() {
73
+ navigator.clipboard.writeText(code).then(() => {
74
+ copy_state = 'copied';
75
+ if (copy_timeout) clearTimeout(copy_timeout);
76
+ copy_timeout = setTimeout(() => {
77
+ copy_state = 'idle';
78
+ }, 2000) as unknown as number;
79
+ });
80
+ }
81
+
82
+ $effect(() => {
83
+ return () => {
84
+ if (copy_timeout) clearTimeout(copy_timeout);
85
+ };
86
+ });
87
+
88
+ /* ------------------------------------------------------------------ */
89
+ /* Built-in tokenizers */
90
+ /* ------------------------------------------------------------------ */
91
+
92
+ function tokenizePlain(line: string): Token[] {
93
+ return line ? [{ type: 'plain', content: line }] : [{ type: 'plain', content: '' }];
94
+ }
95
+
96
+ function tokenizeByPatterns(line: string, patterns: [RegExp, string][]): Token[] {
97
+ const tokens: Token[] = [];
98
+ let remaining = line;
99
+
100
+ while (remaining.length > 0) {
101
+ let earliest_match: {
102
+ index: number;
103
+ length: number;
104
+ type: string;
105
+ text: string;
106
+ } | null = null;
107
+
108
+ for (const [regex, type] of patterns) {
109
+ regex.lastIndex = 0;
110
+ const m = regex.exec(remaining);
111
+ if (m && (earliest_match === null || m.index < earliest_match.index)) {
112
+ earliest_match = { index: m.index, length: m[0].length, type, text: m[0] };
113
+ }
114
+ }
115
+
116
+ if (earliest_match === null) {
117
+ tokens.push({ type: 'plain', content: remaining });
118
+ break;
119
+ }
120
+
121
+ if (earliest_match.index > 0) {
122
+ tokens.push({ type: 'plain', content: remaining.slice(0, earliest_match.index) });
123
+ }
124
+
125
+ tokens.push({ type: earliest_match.type, content: earliest_match.text });
126
+ remaining = remaining.slice(earliest_match.index + earliest_match.length);
127
+ }
128
+
129
+ return tokens.length ? tokens : [{ type: 'plain', content: '' }];
130
+ }
131
+
132
+ const js_keywords =
133
+ /\b(abstract|arguments|async|await|boolean|break|byte|case|catch|char|class|const|continue|debugger|default|delete|do|double|else|enum|export|extends|final|finally|float|for|from|function|goto|if|implements|import|in|instanceof|int|interface|let|long|native|new|null|of|package|private|protected|public|return|short|static|super|switch|synchronized|this|throw|throws|transient|try|typeof|undefined|var|void|volatile|while|with|yield|true|false)\b/g;
134
+
135
+ const ts_keywords =
136
+ /\b(abstract|arguments|as|async|await|boolean|break|byte|case|catch|char|class|const|continue|debugger|declare|default|delete|do|double|else|enum|export|extends|final|finally|float|for|from|function|goto|if|implements|import|in|infer|instanceof|int|interface|is|keyof|let|long|module|namespace|native|never|new|null|of|package|private|protected|public|readonly|return|short|static|string|number|super|switch|synchronized|this|throw|throws|transient|try|type|typeof|undefined|unknown|var|void|volatile|while|with|yield|true|false)\b/g;
137
+
138
+ function tokenizeJS(line: string): Token[] {
139
+ return tokenizeByPatterns(line, [
140
+ [/\/\/.*$/g, 'comment'],
141
+ [/\/\*.*?\*\//g, 'comment'],
142
+ [/`(?:[^`\\]|\\.)*`/g, 'string'],
143
+ [/"(?:[^"\\]|\\.)*"/g, 'string'],
144
+ [/'(?:[^'\\]|\\.)*'/g, 'string'],
145
+ [/\b\d+\.?\d*(?:e[+-]?\d+)?\b/gi, 'number'],
146
+ [/\b[A-Za-z_$][\w$]*(?=\s*\()/g, 'function'],
147
+ [js_keywords, 'keyword'],
148
+ [/[+\-*/%=!<>&|^~?:]+/g, 'operator'],
149
+ ]);
150
+ }
151
+
152
+ function tokenizeTS(line: string): Token[] {
153
+ return tokenizeByPatterns(line, [
154
+ [/\/\/.*$/g, 'comment'],
155
+ [/\/\*.*?\*\//g, 'comment'],
156
+ [/`(?:[^`\\]|\\.)*`/g, 'string'],
157
+ [/"(?:[^"\\]|\\.)*"/g, 'string'],
158
+ [/'(?:[^'\\]|\\.)*'/g, 'string'],
159
+ [/\b\d+\.?\d*(?:e[+-]?\d+)?\b/gi, 'number'],
160
+ [/\b[A-Za-z_$][\w$]*(?=\s*\()/g, 'function'],
161
+ [ts_keywords, 'keyword'],
162
+ [/[+\-*/%=!<>&|^~?:]+/g, 'operator'],
163
+ ]);
164
+ }
165
+
166
+ function tokenizeHTML(line: string): Token[] {
167
+ return tokenizeByPatterns(line, [
168
+ [/<!--.*?-->/g, 'comment'],
169
+ [/"[^"]*"/g, 'string'],
170
+ [/'[^']*'/g, 'string'],
171
+ [/<\/?[\w-]+/g, 'tag'],
172
+ [/\/?>/g, 'tag'],
173
+ [/\b[\w-]+(?==)/g, 'attribute'],
174
+ ]);
175
+ }
176
+
177
+ function tokenizeCSS(line: string): Token[] {
178
+ return tokenizeByPatterns(line, [
179
+ [/\/\*.*?\*\//g, 'comment'],
180
+ [/\/\/.*$/g, 'comment'],
181
+ [/"(?:[^"\\]|\\.)*"/g, 'string'],
182
+ [/'(?:[^'\\]|\\.)*'/g, 'string'],
183
+ [/\b\d+\.?\d*(?:px|em|rem|%|vh|vw|vmin|vmax|deg|s|ms|fr|ch|ex)?\b/g, 'number'],
184
+ [/@[\w-]+/g, 'keyword'],
185
+ [/[.#][\w-]+/g, 'variable'],
186
+ [/[\w-]+(?=\s*:)/g, 'property'],
187
+ [/:\s*[^;{}]+/g, 'value'],
188
+ ]);
189
+ }
190
+
191
+ function tokenizeJSON(line: string): Token[] {
192
+ return tokenizeByPatterns(line, [
193
+ [/"(?:[^"\\]|\\.)*"(?=\s*:)/g, 'property'],
194
+ [/"(?:[^"\\]|\\.)*"/g, 'string'],
195
+ [/\b\d+\.?\d*(?:e[+-]?\d+)?\b/gi, 'number'],
196
+ [/\b(true|false)\b/g, 'keyword'],
197
+ [/\bnull\b/g, 'keyword'],
198
+ ]);
199
+ }
200
+
201
+ const python_keywords =
202
+ /\b(False|None|True|and|as|assert|async|await|break|class|continue|def|del|elif|else|except|finally|for|from|global|if|import|in|is|lambda|nonlocal|not|or|pass|raise|return|try|while|with|yield)\b/g;
203
+
204
+ function tokenizePython(line: string): Token[] {
205
+ return tokenizeByPatterns(line, [
206
+ [/#.*$/g, 'comment'],
207
+ [/""".*?"""/g, 'string'],
208
+ [/'''.*?'''/g, 'string'],
209
+ [/f"(?:[^"\\]|\\.)*"/g, 'string'],
210
+ [/f'(?:[^'\\]|\\.)*'/g, 'string'],
211
+ [/"(?:[^"\\]|\\.)*"/g, 'string'],
212
+ [/'(?:[^'\\]|\\.)*'/g, 'string'],
213
+ [/@[\w.]+/g, 'decorator'],
214
+ [/\b\d+\.?\d*(?:e[+-]?\d+)?j?\b/gi, 'number'],
215
+ [/\b[A-Za-z_]\w*(?=\s*\()/g, 'function'],
216
+ [python_keywords, 'keyword'],
217
+ [/[+\-*/%=!<>&|^~@:]+/g, 'operator'],
218
+ ]);
219
+ }
220
+
221
+ function tokenizeBash(line: string): Token[] {
222
+ return tokenizeByPatterns(line, [
223
+ [/#.*$/g, 'comment'],
224
+ [/"(?:[^"\\]|\\.)*"/g, 'string'],
225
+ [/'[^']*'/g, 'string'],
226
+ [/\$\{[^}]*\}/g, 'variable'],
227
+ [/\$[\w]+/g, 'variable'],
228
+ [/\b\d+\.?\d*\b/g, 'number'],
229
+ [
230
+ /\b(alias|bg|bind|break|builtin|caller|case|cd|command|compgen|complete|continue|declare|dirs|disown|do|done|echo|elif|else|enable|esac|eval|exec|exit|export|false|fc|fg|fi|for|function|getopts|hash|help|history|if|in|jobs|kill|let|local|logout|popd|printf|pushd|pwd|read|readonly|return|select|set|shift|shopt|source|suspend|test|then|time|times|trap|true|type|typeset|ulimit|umask|unalias|unset|until|wait|while)\b/g,
231
+ 'keyword',
232
+ ],
233
+ [/\b[\w./-]+(?=\s|$)/g, 'function'],
234
+ [/[|&;<>]+/g, 'operator'],
235
+ ]);
236
+ }
237
+
238
+ const sql_keywords =
239
+ /\b(ADD|ALL|ALTER|AND|ANY|AS|ASC|BACKUP|BETWEEN|BY|CASE|CHECK|COLUMN|CONSTRAINT|CREATE|DATABASE|DEFAULT|DELETE|DESC|DISTINCT|DROP|EACH|ELSE|END|EXEC|EXISTS|FOREIGN|FROM|FULL|GROUP|HAVING|IF|IN|INDEX|INNER|INSERT|INTO|IS|JOIN|KEY|LEFT|LIKE|LIMIT|NOT|NULL|OFFSET|ON|OR|ORDER|OUTER|PRIMARY|PROCEDURE|REFERENCES|REPLACE|RIGHT|ROWNUM|SELECT|SET|TABLE|THEN|TOP|TRUNCATE|UNION|UNIQUE|UPDATE|VALUES|VIEW|WHEN|WHERE|WITH)\b/gi;
240
+
241
+ function tokenizeSQL(line: string): Token[] {
242
+ return tokenizeByPatterns(line, [
243
+ [/--.*$/g, 'comment'],
244
+ [/\/\*.*?\*\//g, 'comment'],
245
+ [/'(?:[^'\\]|\\.)*'/g, 'string'],
246
+ [/"(?:[^"\\]|\\.)*"/g, 'string'],
247
+ [/\b\d+\.?\d*\b/g, 'number'],
248
+ [/\b[A-Za-z_]\w*(?=\s*\()/g, 'function'],
249
+ [sql_keywords, 'keyword'],
250
+ [/[+\-*/%=!<>&|^~]+/g, 'operator'],
251
+ ]);
252
+ }
253
+
254
+ function tokenizeSvelte(line: string): Token[] {
255
+ return tokenizeByPatterns(line, [
256
+ [/<!--.*?-->/g, 'comment'],
257
+ [/\/\/.*$/g, 'comment'],
258
+ [/\{#[\w]+/g, 'keyword'],
259
+ [/\{\/[\w]+\}/g, 'keyword'],
260
+ [/\{:[\w]+/g, 'keyword'],
261
+ [/`(?:[^`\\]|\\.)*`/g, 'string'],
262
+ [/"(?:[^"\\]|\\.)*"/g, 'string'],
263
+ [/'(?:[^'\\]|\\.)*'/g, 'string'],
264
+ [/\b\d+\.?\d*\b/g, 'number'],
265
+ [/<\/?[\w-]+/g, 'tag'],
266
+ [/\/?>/g, 'tag'],
267
+ [/\b[\w-]+(?==)/g, 'attribute'],
268
+ [/\b[A-Za-z_$][\w$]*(?=\s*\()/g, 'function'],
269
+ [js_keywords, 'keyword'],
270
+ [/[+\-*/%=!<>&|^~?:]+/g, 'operator'],
271
+ ]);
272
+ }
273
+
274
+ function tokenizeMarkdown(line: string): Token[] {
275
+ return tokenizeByPatterns(line, [
276
+ [/^#{1,6}\s+.*/g, 'heading'],
277
+ [/`[^`]+`/g, 'code'],
278
+ [/\*\*[^*]+\*\*/g, 'bold'],
279
+ [/\*[^*]+\*/g, 'italic'],
280
+ [/__[^_]+__/g, 'bold'],
281
+ [/_[^_]+_/g, 'italic'],
282
+ [/\[([^\]]+)\]\([^)]+\)/g, 'link'],
283
+ [/!\[([^\]]*)\]\([^)]+\)/g, 'link'],
284
+ ]);
285
+ }
286
+
287
+ function tokenizeLine(line: string, lang: string): Token[] {
288
+ switch (lang) {
289
+ case 'javascript':
290
+ case 'js':
291
+ case 'jsx':
292
+ return tokenizeJS(line);
293
+ case 'typescript':
294
+ case 'ts':
295
+ case 'tsx':
296
+ return tokenizeTS(line);
297
+ case 'html':
298
+ case 'xml':
299
+ return tokenizeHTML(line);
300
+ case 'css':
301
+ case 'scss':
302
+ case 'sass':
303
+ case 'less':
304
+ return tokenizeCSS(line);
305
+ case 'json':
306
+ case 'jsonc':
307
+ return tokenizeJSON(line);
308
+ case 'python':
309
+ case 'py':
310
+ return tokenizePython(line);
311
+ case 'bash':
312
+ case 'sh':
313
+ case 'shell':
314
+ case 'zsh':
315
+ return tokenizeBash(line);
316
+ case 'sql':
317
+ return tokenizeSQL(line);
318
+ case 'svelte':
319
+ return tokenizeSvelte(line);
320
+ case 'markdown':
321
+ case 'md':
322
+ return tokenizeMarkdown(line);
323
+ default:
324
+ return tokenizePlain(line);
325
+ }
326
+ }
327
+
328
+ /* ------------------------------------------------------------------ */
329
+ /* Derived state */
330
+ /* ------------------------------------------------------------------ */
331
+
332
+ const lines = $derived(code.split('\n'));
333
+
334
+ const tokenized_lines = $derived(lines.map((line) => tokenizeLine(line, language)));
335
+
336
+ const highlight_set = $derived(new Set(highlight_lines));
337
+
338
+ const show_header = $derived(!!filename || show_copy);
339
+
340
+ function getDiffClass(line: string): string {
341
+ if (line.startsWith('@@')) return 'diff-section';
342
+ if (line.startsWith('+')) return 'diff-add';
343
+ if (line.startsWith('-')) return 'diff-remove';
344
+ return '';
345
+ }
346
+ </script>
347
+
348
+ {#if skeleton}
349
+ <div
350
+ class={['code skeleton', class_name].filter(Boolean).join(' ')}
351
+ {id}
352
+ aria-hidden="true">
353
+ {#if show_header}
354
+ <div class="header">
355
+ <div class="skeleton-filename"></div>
356
+ {#if show_copy}
357
+ <div class="skeleton-copy"></div>
358
+ {/if}
359
+ </div>
360
+ {/if}
361
+ <div class="skeleton-body">
362
+ {#each { length: 5 } as _, i}
363
+ <div
364
+ class="skeleton-line"
365
+ style:width="{40 + ((i * 37) % 50)}%"
366
+ style:--shimmer-delay="{i * 120}ms">
367
+ </div>
368
+ {/each}
369
+ </div>
370
+ </div>
371
+ {:else}
372
+ <div class={['code', class_name].filter(Boolean).join(' ')} class:wrap {id}>
373
+ {#if show_header}
374
+ <div class="header">
375
+ {#if filename}
376
+ <span class="filename">{filename}</span>
377
+ {:else}
378
+ <span></span>
379
+ {/if}
380
+ {#if show_copy}
381
+ <button
382
+ type="button"
383
+ onclick={handleCopy}
384
+ aria-label={copy_state === 'copied' ? 'Copied' : 'Copy code'}>
385
+ {#if copy_state === 'copied'}
386
+ <svg
387
+ width="16"
388
+ height="16"
389
+ viewBox="0 0 16 16"
390
+ fill="none"
391
+ aria-hidden="true">
392
+ <path
393
+ d="M3 8.5L6.5 12L13 4"
394
+ stroke="currentColor"
395
+ stroke-width="1.5"
396
+ stroke-linecap="round"
397
+ stroke-linejoin="round" />
398
+ </svg>
399
+ {:else}
400
+ <svg
401
+ width="16"
402
+ height="16"
403
+ viewBox="0 0 16 16"
404
+ fill="none"
405
+ aria-hidden="true">
406
+ <rect
407
+ x="5.5"
408
+ y="5.5"
409
+ width="8"
410
+ height="8"
411
+ rx="1.5"
412
+ stroke="currentColor"
413
+ stroke-width="1.25" />
414
+ <path
415
+ d="M10.5 5.5V3.5C10.5 2.67 9.83 2 9 2H3.5C2.67 2 2 2.67 2 3.5V9C2 9.83 2.67 10.5 3.5 10.5H5.5"
416
+ stroke="currentColor"
417
+ stroke-width="1.25" />
418
+ </svg>
419
+ {/if}
420
+ </button>
421
+ {/if}
422
+ </div>
423
+ {/if}
424
+
425
+ <div class="body" style:max-height={max_height} {@attach scrollbar()}>
426
+ <pre><code>{#each tokenized_lines as tokens, i}{@const line_num =
427
+ start_line + i}{@const is_highlighted =
428
+ highlight_set.has(line_num)}{@const raw_line = lines[i]}<span
429
+ class="line"
430
+ class:highlighted={is_highlighted}
431
+ class:diff-add={diff && getDiffClass(raw_line) === 'diff-add'}
432
+ class:diff-remove={diff && getDiffClass(raw_line) === 'diff-remove'}
433
+ class:diff-section={diff &&
434
+ getDiffClass(raw_line) === 'diff-section'}>{#if show_line_numbers}<span
435
+ class="number"
436
+ class:highlighted={is_highlighted}
437
+ aria-hidden="true">{line_num}</span>{/if}<span
438
+ class="content">{#each tokens as token}<span
439
+ class="token-{token.type}">{token.content}</span>{/each}</span>
440
+ </span>{/each}</code></pre>
441
+ </div>
442
+ </div>
443
+ {/if}
444
+
445
+ <style>
446
+ /* ========== Container ========== */
447
+
448
+ .code {
449
+ border-radius: var(--radius-lg, 0.5rem);
450
+ @supports (corner-shape: squircle) {
451
+ corner-shape: squircle;
452
+ border-radius: calc(var(--radius-lg, 0.5rem) * var(--squircle-ratio, 2));
453
+ }
454
+ border: 1px solid var(--color-border, light-dark(#e2e8f0, #334155));
455
+ background: var(--color-bg-muted, light-dark(#f8fafc, #1e293b));
456
+ overflow: hidden;
457
+ font-family:
458
+ ui-monospace, 'Cascadia Code', 'Source Code Pro', Menlo, Consolas,
459
+ 'DejaVu Sans Mono', monospace;
460
+ font-size: 0.875rem;
461
+ line-height: 1.6;
462
+
463
+ &.wrap .content {
464
+ white-space: pre-wrap;
465
+ word-break: break-all;
466
+ }
467
+
468
+ &.skeleton {
469
+ pointer-events: none;
470
+ /* The %-width placeholder lines have no intrinsic width, so in a
471
+ flex/grid parent the skeleton would collapse to the filename bar —
472
+ fill the container instead (the real block's natural max). */
473
+ width: 100%;
474
+ }
475
+ }
476
+
477
+ /* ========== Header (shared by the real block and the skeleton) ========== */
478
+
479
+ .header {
480
+ display: flex;
481
+ align-items: center;
482
+ justify-content: space-between;
483
+ padding: 0.5rem 0.75rem;
484
+ border-bottom: 1px solid var(--color-border, light-dark(#e2e8f0, #334155));
485
+ background: var(--color-bg-4, light-dark(#f1f5f9, #1a2332));
486
+ min-height: 2rem;
487
+
488
+ /* The copy button — the component's only <button> */
489
+ button {
490
+ display: inline-flex;
491
+ align-items: center;
492
+ justify-content: center;
493
+ width: 1.75rem;
494
+ height: 1.75rem;
495
+ padding: 0;
496
+ border: none;
497
+ border-radius: var(--radius-lg, 0.5rem);
498
+ @supports (corner-shape: squircle) {
499
+ corner-shape: squircle;
500
+ border-radius: calc(var(--radius-lg, 0.5rem) * var(--squircle-ratio, 2));
501
+ }
502
+ background: transparent;
503
+ color: light-dark(#64748b, #94a3b8);
504
+ cursor: pointer;
505
+ flex-shrink: 0;
506
+ transition:
507
+ color 150ms ease,
508
+ background 150ms ease;
509
+
510
+ &:hover {
511
+ background: light-dark(rgb(0 0 0 / 0.06), rgb(255 255 255 / 0.08));
512
+ color: light-dark(#334155, #e2e8f0);
513
+ transition: none;
514
+ }
515
+
516
+ &:active {
517
+ background: light-dark(rgb(0 0 0 / 0.1), rgb(255 255 255 / 0.12));
518
+ }
519
+
520
+ &:focus-visible {
521
+ outline: 2px solid var(--color-action, #3b82f6);
522
+ outline-offset: -2px;
523
+ }
524
+ }
525
+ }
526
+
527
+ .filename {
528
+ font-size: 0.8125rem;
529
+ color: light-dark(#475569, #94a3b8);
530
+ font-weight: 500;
531
+ white-space: nowrap;
532
+ overflow: hidden;
533
+ text-overflow: ellipsis;
534
+ min-width: 0;
535
+ }
536
+
537
+ /* ========== Code Body ========== */
538
+
539
+ .body {
540
+ overflow: auto;
541
+
542
+ pre {
543
+ margin: 0;
544
+ padding: 0;
545
+ }
546
+
547
+ code {
548
+ display: block;
549
+ padding: 0.75rem 0;
550
+ }
551
+ }
552
+
553
+ /* ========== Lines ========== */
554
+
555
+ .line {
556
+ display: flex;
557
+ min-height: 1.6em;
558
+
559
+ /* Lines without line numbers need left padding */
560
+ &:not(:has(.number)) .content {
561
+ padding-left: 1rem;
562
+ }
563
+
564
+ &.highlighted {
565
+ background: light-dark(
566
+ rgb(from var(--color-action, #3b82f6) r g b / 0.08),
567
+ rgb(from var(--color-action, #3b82f6) r g b / 0.12)
568
+ );
569
+ }
570
+
571
+ &.diff-add {
572
+ background: light-dark(
573
+ rgb(from var(--color-success, #22c55e) r g b / 0.1),
574
+ rgb(from var(--color-success, #22c55e) r g b / 0.12)
575
+ );
576
+ }
577
+
578
+ &.diff-remove {
579
+ background: light-dark(
580
+ rgb(from var(--color-error, #ef4444) r g b / 0.1),
581
+ rgb(from var(--color-error, #ef4444) r g b / 0.12)
582
+ );
583
+ }
584
+
585
+ &.diff-section {
586
+ background: light-dark(
587
+ rgb(from var(--color-action, #3b82f6) r g b / 0.06),
588
+ rgb(from var(--color-action, #3b82f6) r g b / 0.08)
589
+ );
590
+ color: light-dark(#6b7280, #9ca3af);
591
+ font-style: italic;
592
+ }
593
+ }
594
+
595
+ .number {
596
+ display: inline-block;
597
+ width: 3.5rem;
598
+ padding-right: 1rem;
599
+ text-align: right;
600
+ color: light-dark(#94a3b8, #475569);
601
+ user-select: none;
602
+ flex-shrink: 0;
603
+ box-sizing: border-box;
604
+
605
+ &.highlighted {
606
+ color: var(--color-action, #3b82f6);
607
+ }
608
+ }
609
+
610
+ .content {
611
+ flex: 1;
612
+ min-width: 0;
613
+ padding-right: 1rem;
614
+ white-space: pre;
615
+ }
616
+
617
+ /* ========== Token Colors ========== */
618
+
619
+ .token-keyword {
620
+ color: light-dark(#7c3aed, #a78bfa);
621
+ }
622
+
623
+ .token-string {
624
+ color: light-dark(#059669, #34d399);
625
+ }
626
+
627
+ .token-comment {
628
+ color: light-dark(#6b7280, #9ca3af);
629
+ font-style: italic;
630
+ }
631
+
632
+ .token-function {
633
+ color: light-dark(#2563eb, #60a5fa);
634
+ }
635
+
636
+ .token-number {
637
+ color: light-dark(#d97706, #fbbf24);
638
+ }
639
+
640
+ .token-operator {
641
+ color: light-dark(#6b7280, #cbd5e1);
642
+ }
643
+
644
+ .token-tag {
645
+ color: light-dark(#dc2626, #f87171);
646
+ }
647
+
648
+ .token-attribute {
649
+ color: light-dark(#d97706, #fbbf24);
650
+ }
651
+
652
+ .token-property {
653
+ color: light-dark(#2563eb, #60a5fa);
654
+ }
655
+
656
+ .token-value {
657
+ color: light-dark(#059669, #34d399);
658
+ }
659
+
660
+ .token-variable {
661
+ color: light-dark(#d97706, #fbbf24);
662
+ }
663
+
664
+ .token-decorator {
665
+ color: light-dark(#d97706, #fbbf24);
666
+ font-style: italic;
667
+ }
668
+
669
+ .token-heading {
670
+ color: light-dark(#7c3aed, #a78bfa);
671
+ font-weight: 700;
672
+ }
673
+
674
+ .token-bold {
675
+ font-weight: 700;
676
+ }
677
+
678
+ .token-italic {
679
+ font-style: italic;
680
+ }
681
+
682
+ .token-code {
683
+ color: light-dark(#059669, #34d399);
684
+ background: light-dark(rgb(0 0 0 / 0.04), rgb(255 255 255 / 0.06));
685
+ border-radius: 3px;
686
+ padding: 0 0.25em;
687
+ }
688
+
689
+ .token-link {
690
+ color: light-dark(#2563eb, #60a5fa);
691
+ text-decoration: underline;
692
+ }
693
+
694
+ .token-plain {
695
+ color: inherit;
696
+ }
697
+
698
+ /* ========== Skeleton ========== */
699
+
700
+ .skeleton-filename,
701
+ .skeleton-copy,
702
+ .skeleton-line {
703
+ position: relative;
704
+ overflow: hidden;
705
+ background: var(--skeleton-bg, rgb(from var(--color-text, #888) r g b / 0.1));
706
+ border-radius: var(--radius-full, 1e5px);
707
+
708
+ &::after {
709
+ content: '';
710
+ position: absolute;
711
+ inset: 0;
712
+ transform: translateX(-100%);
713
+ background-image: linear-gradient(
714
+ 105deg,
715
+ transparent 25%,
716
+ var(--skeleton-sheen, rgb(from var(--color-text, #888) r g b / 0.12)) 50%,
717
+ transparent 75%
718
+ );
719
+ animation: delight-skeleton-shimmer var(--skeleton-duration, 2.4s) ease-in-out
720
+ infinite;
721
+ animation-delay: var(--shimmer-delay, 0s);
722
+ }
723
+ }
724
+
725
+ /* Filename text-line at the real .filename size (0.8125rem), padded
726
+ out to its 1lh line box so the bar occupies the real text's height. */
727
+ .skeleton-filename {
728
+ font-size: 0.8125rem;
729
+ height: 0.7em;
730
+ margin-block: calc((1lh - 0.7em) / 2);
731
+ width: 6rem;
732
+ }
733
+
734
+ /* Stands in for the 1.75rem copy button — it's what sets the real
735
+ header's content height. */
736
+ .skeleton-copy {
737
+ width: 1.75rem;
738
+ height: 1.75rem;
739
+ flex-shrink: 0;
740
+ border-radius: var(--radius-lg, 0.5rem);
741
+ @supports (corner-shape: squircle) {
742
+ corner-shape: squircle;
743
+ border-radius: calc(var(--radius-lg, 0.5rem) * var(--squircle-ratio, 2));
744
+ }
745
+ }
746
+
747
+ /* Matches the real code block: `code` has 0.75rem block padding, lines are
748
+ padded 1rem left and stack at 1lh (line-height 1.6 on .code). */
749
+ .skeleton-body {
750
+ padding: 0.75rem 1rem;
751
+ /* Flex column so the line margins don't collapse between bars. */
752
+ display: flex;
753
+ flex-direction: column;
754
+ }
755
+
756
+ /* Each placeholder occupies exactly one code line: the 0.7em bar is padded
757
+ out to 1lh by its margins, so five bars equal five real lines. */
758
+ .skeleton-line {
759
+ height: 0.7em;
760
+ margin-block: calc((1lh - 0.7em) / 2);
761
+ }
762
+
763
+ @keyframes -global-delight-skeleton-shimmer {
764
+ 0% {
765
+ transform: translateX(-100%);
766
+ }
767
+ 55%,
768
+ 100% {
769
+ transform: translateX(100%);
770
+ }
771
+ }
772
+
773
+ @media (prefers-reduced-motion: reduce) {
774
+ .skeleton-filename::after,
775
+ .skeleton-copy::after,
776
+ .skeleton-line::after {
777
+ animation: none;
778
+ }
779
+ }
780
+ </style>