@sentropic/design-system-svelte 0.7.0 → 0.9.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 (81) hide show
  1. package/dist/Accordion.svelte +11 -3
  2. package/dist/Accordion.svelte.d.ts.map +1 -1
  3. package/dist/Alert.svelte +1 -1
  4. package/dist/AreaChart.svelte +414 -0
  5. package/dist/AreaChart.svelte.d.ts +18 -0
  6. package/dist/AreaChart.svelte.d.ts.map +1 -0
  7. package/dist/BarChart.svelte +383 -0
  8. package/dist/BarChart.svelte.d.ts +18 -0
  9. package/dist/BarChart.svelte.d.ts.map +1 -0
  10. package/dist/ChatComposer.svelte +238 -0
  11. package/dist/ChatComposer.svelte.d.ts +57 -0
  12. package/dist/ChatComposer.svelte.d.ts.map +1 -0
  13. package/dist/ChatMessage.svelte +271 -0
  14. package/dist/ChatMessage.svelte.d.ts +19 -0
  15. package/dist/ChatMessage.svelte.d.ts.map +1 -0
  16. package/dist/ChatThread.svelte +120 -0
  17. package/dist/ChatThread.svelte.d.ts +13 -0
  18. package/dist/ChatThread.svelte.d.ts.map +1 -0
  19. package/dist/Combobox.svelte +16 -2
  20. package/dist/Combobox.svelte.d.ts.map +1 -1
  21. package/dist/CopyButton.svelte +3 -7
  22. package/dist/CopyButton.svelte.d.ts.map +1 -1
  23. package/dist/Drawer.svelte +23 -3
  24. package/dist/Drawer.svelte.d.ts +1 -1
  25. package/dist/Drawer.svelte.d.ts.map +1 -1
  26. package/dist/Dropdown.svelte +38 -2
  27. package/dist/Dropdown.svelte.d.ts.map +1 -1
  28. package/dist/FileUploader.svelte +119 -4
  29. package/dist/FileUploader.svelte.d.ts +1 -0
  30. package/dist/FileUploader.svelte.d.ts.map +1 -1
  31. package/dist/IconButton.svelte +103 -0
  32. package/dist/IconButton.svelte.d.ts +15 -0
  33. package/dist/IconButton.svelte.d.ts.map +1 -0
  34. package/dist/InlineLoading.svelte +22 -14
  35. package/dist/InlineLoading.svelte.d.ts.map +1 -1
  36. package/dist/LineChart.svelte +397 -0
  37. package/dist/LineChart.svelte.d.ts +19 -0
  38. package/dist/LineChart.svelte.d.ts.map +1 -0
  39. package/dist/Menu.svelte +164 -24
  40. package/dist/Menu.svelte.d.ts +26 -4
  41. package/dist/Menu.svelte.d.ts.map +1 -1
  42. package/dist/MenuPopover.svelte +180 -0
  43. package/dist/MenuPopover.svelte.d.ts +17 -0
  44. package/dist/MenuPopover.svelte.d.ts.map +1 -0
  45. package/dist/MenuTriggerButton.svelte +50 -0
  46. package/dist/MenuTriggerButton.svelte.d.ts +16 -0
  47. package/dist/MenuTriggerButton.svelte.d.ts.map +1 -0
  48. package/dist/MessageActions.svelte +89 -0
  49. package/dist/MessageActions.svelte.d.ts +22 -0
  50. package/dist/MessageActions.svelte.d.ts.map +1 -0
  51. package/dist/MessageStatusBadge.svelte +52 -0
  52. package/dist/MessageStatusBadge.svelte.d.ts +12 -0
  53. package/dist/MessageStatusBadge.svelte.d.ts.map +1 -0
  54. package/dist/Modal.svelte +83 -3
  55. package/dist/Modal.svelte.d.ts.map +1 -1
  56. package/dist/MultiSelect.svelte +17 -3
  57. package/dist/MultiSelect.svelte.d.ts.map +1 -1
  58. package/dist/OverflowMenu.svelte +111 -24
  59. package/dist/OverflowMenu.svelte.d.ts +21 -2
  60. package/dist/OverflowMenu.svelte.d.ts.map +1 -1
  61. package/dist/PaginationNav.svelte +6 -21
  62. package/dist/PaginationNav.svelte.d.ts.map +1 -1
  63. package/dist/PasswordInput.svelte +3 -9
  64. package/dist/PasswordInput.svelte.d.ts.map +1 -1
  65. package/dist/ProgressIndicator.svelte +3 -19
  66. package/dist/ProgressIndicator.svelte.d.ts.map +1 -1
  67. package/dist/Search.svelte +3 -6
  68. package/dist/Search.svelte.d.ts.map +1 -1
  69. package/dist/Sparkline.svelte +123 -0
  70. package/dist/Sparkline.svelte.d.ts +15 -0
  71. package/dist/Sparkline.svelte.d.ts.map +1 -0
  72. package/dist/StreamingMessage.svelte +292 -0
  73. package/dist/StreamingMessage.svelte.d.ts +51 -0
  74. package/dist/StreamingMessage.svelte.d.ts.map +1 -0
  75. package/dist/Tag.svelte +2 -1
  76. package/dist/Tag.svelte.d.ts.map +1 -1
  77. package/dist/Toast.svelte +2 -2
  78. package/dist/index.d.ts +19 -0
  79. package/dist/index.d.ts.map +1 -1
  80. package/dist/index.js +13 -0
  81. package/package.json +3 -2
@@ -0,0 +1,383 @@
1
+ <script lang="ts" module>
2
+ export type BarChartTone =
3
+ | "category1"
4
+ | "category2"
5
+ | "category3"
6
+ | "category4"
7
+ | "category5"
8
+ | "category6"
9
+ | "category7"
10
+ | "category8";
11
+
12
+ export type BarChartDatum = {
13
+ label: string;
14
+ value: number;
15
+ tone?: BarChartTone;
16
+ };
17
+ </script>
18
+
19
+ <script lang="ts">
20
+ type BarChartProps = {
21
+ data: BarChartDatum[];
22
+ width?: number;
23
+ height?: number;
24
+ orientation?: "vertical" | "horizontal";
25
+ label: string;
26
+ class?: string;
27
+ };
28
+
29
+ let {
30
+ data,
31
+ width = 480,
32
+ height = 240,
33
+ orientation = "vertical",
34
+ label,
35
+ class: className
36
+ }: BarChartProps = $props();
37
+
38
+ const MARGIN = { top: 12, right: 16, bottom: 32, left: 44 };
39
+
40
+ function niceTicks(min: number, max: number, target = 5): number[] {
41
+ if (!Number.isFinite(min) || !Number.isFinite(max) || min === max) {
42
+ const base = Number.isFinite(max) ? max : 0;
43
+ return [base];
44
+ }
45
+ const range = max - min;
46
+ const rough = range / Math.max(target - 1, 1);
47
+ const pow = Math.pow(10, Math.floor(Math.log10(rough)));
48
+ const norm = rough / pow;
49
+ let step: number;
50
+ if (norm < 1.5) step = 1 * pow;
51
+ else if (norm < 3) step = 2 * pow;
52
+ else if (norm < 7) step = 5 * pow;
53
+ else step = 10 * pow;
54
+ const start = Math.floor(min / step) * step;
55
+ const end = Math.ceil(max / step) * step;
56
+ const ticks: number[] = [];
57
+ for (let v = start; v <= end + step / 2; v += step) {
58
+ ticks.push(Number(v.toFixed(10)));
59
+ }
60
+ return ticks;
61
+ }
62
+
63
+ function scaleLinear(v: number, d0: number, d1: number, r0: number, r1: number) {
64
+ if (d1 === d0) return r0;
65
+ return r0 + ((v - d0) * (r1 - r0)) / (d1 - d0);
66
+ }
67
+
68
+ function formatTick(v: number): string {
69
+ if (Math.abs(v) >= 1000) return `${(v / 1000).toFixed(v % 1000 === 0 ? 0 : 1)}k`;
70
+ if (Number.isInteger(v)) return String(v);
71
+ return v.toFixed(1);
72
+ }
73
+
74
+ let hoveredIndex: number | null = $state(null);
75
+ let containerRect: { left: number; top: number } = $state({ left: 0, top: 0 });
76
+ let chartRoot: HTMLDivElement | null = $state(null);
77
+
78
+ const scales = $derived.by(() => {
79
+ const values = data.map((d) => d.value);
80
+ const minRaw = Math.min(0, ...values);
81
+ const maxRaw = Math.max(0, ...values);
82
+ const ticks = niceTicks(minRaw, maxRaw, 5);
83
+ const domainMin = ticks[0];
84
+ const domainMax = ticks[ticks.length - 1];
85
+ const plotWidth = Math.max(width - MARGIN.left - MARGIN.right, 1);
86
+ const plotHeight = Math.max(height - MARGIN.top - MARGIN.bottom, 1);
87
+ return { ticks, domainMin, domainMax, plotWidth, plotHeight };
88
+ });
89
+
90
+ const bars = $derived.by(() => {
91
+ const { domainMin, domainMax, plotWidth, plotHeight } = scales;
92
+ if (data.length === 0) return [];
93
+ if (orientation === "vertical") {
94
+ const band = plotWidth / data.length;
95
+ const barWidth = band * 0.62;
96
+ const zeroY = scaleLinear(0, domainMin, domainMax, plotHeight, 0);
97
+ return data.map((d, i) => {
98
+ const valueY = scaleLinear(d.value, domainMin, domainMax, plotHeight, 0);
99
+ const y = Math.min(valueY, zeroY);
100
+ const h = Math.abs(zeroY - valueY);
101
+ const x = MARGIN.left + band * i + (band - barWidth) / 2;
102
+ return {
103
+ x,
104
+ y: MARGIN.top + y,
105
+ width: barWidth,
106
+ height: Math.max(h, 0.5),
107
+ cx: MARGIN.left + band * (i + 0.5),
108
+ cy: MARGIN.top + valueY,
109
+ datum: d,
110
+ tone: d.tone ?? "category1"
111
+ };
112
+ });
113
+ }
114
+ // horizontal
115
+ const band = plotHeight / data.length;
116
+ const barHeight = band * 0.62;
117
+ const zeroX = scaleLinear(0, domainMin, domainMax, 0, plotWidth);
118
+ return data.map((d, i) => {
119
+ const valueX = scaleLinear(d.value, domainMin, domainMax, 0, plotWidth);
120
+ const x = Math.min(valueX, zeroX);
121
+ const w = Math.abs(valueX - zeroX);
122
+ const y = MARGIN.top + band * i + (band - barHeight) / 2;
123
+ return {
124
+ x: MARGIN.left + x,
125
+ y,
126
+ width: Math.max(w, 0.5),
127
+ height: barHeight,
128
+ cx: MARGIN.left + valueX,
129
+ cy: MARGIN.top + band * (i + 0.5),
130
+ datum: d,
131
+ tone: d.tone ?? "category1"
132
+ };
133
+ });
134
+ });
135
+
136
+ const valueAxisTicks = $derived.by(() => {
137
+ const { ticks, domainMin, domainMax, plotWidth, plotHeight } = scales;
138
+ if (orientation === "vertical") {
139
+ return ticks.map((tick) => ({
140
+ value: tick,
141
+ x1: MARGIN.left,
142
+ x2: MARGIN.left + plotWidth,
143
+ y: MARGIN.top + scaleLinear(tick, domainMin, domainMax, plotHeight, 0),
144
+ x: undefined,
145
+ y1: undefined,
146
+ y2: undefined
147
+ }));
148
+ }
149
+ return ticks.map((tick) => ({
150
+ value: tick,
151
+ x: MARGIN.left + scaleLinear(tick, domainMin, domainMax, 0, plotWidth),
152
+ y1: MARGIN.top,
153
+ y2: MARGIN.top + plotHeight,
154
+ x1: undefined,
155
+ x2: undefined,
156
+ y: undefined
157
+ }));
158
+ });
159
+
160
+ function handleEnter(index: number, event: Event) {
161
+ hoveredIndex = index;
162
+ if (chartRoot) {
163
+ const r = chartRoot.getBoundingClientRect();
164
+ containerRect = { left: r.left, top: r.top };
165
+ }
166
+ event.stopPropagation();
167
+ }
168
+
169
+ function handleLeave() {
170
+ hoveredIndex = null;
171
+ }
172
+
173
+ const classes = () => ["st-barChart", className].filter(Boolean).join(" ");
174
+ </script>
175
+
176
+ <div
177
+ class={classes()}
178
+ role="img"
179
+ aria-label={label}
180
+ bind:this={chartRoot}
181
+ >
182
+ <svg
183
+ viewBox="0 0 {width} {height}"
184
+ preserveAspectRatio="xMidYMid meet"
185
+ width="100%"
186
+ height="100%"
187
+ focusable="false"
188
+ aria-hidden="true"
189
+ >
190
+ <!-- gridlines + value axis ticks -->
191
+ {#if orientation === "vertical"}
192
+ {#each valueAxisTicks as tick (tick.value)}
193
+ <line
194
+ class="st-barChart__grid"
195
+ x1={tick.x1}
196
+ x2={tick.x2}
197
+ y1={tick.y}
198
+ y2={tick.y}
199
+ />
200
+ <text
201
+ class="st-barChart__tickLabel"
202
+ x={MARGIN.left - 6}
203
+ y={tick.y}
204
+ text-anchor="end"
205
+ dominant-baseline="middle"
206
+ >
207
+ {formatTick(tick.value)}
208
+ </text>
209
+ {/each}
210
+ {:else}
211
+ {#each valueAxisTicks as tick (tick.value)}
212
+ <line
213
+ class="st-barChart__grid"
214
+ x1={tick.x}
215
+ x2={tick.x}
216
+ y1={tick.y1}
217
+ y2={tick.y2}
218
+ />
219
+ <text
220
+ class="st-barChart__tickLabel"
221
+ x={tick.x}
222
+ y={height - MARGIN.bottom + 16}
223
+ text-anchor="middle"
224
+ >
225
+ {formatTick(tick.value)}
226
+ </text>
227
+ {/each}
228
+ {/if}
229
+
230
+ <!-- axes -->
231
+ <line
232
+ class="st-barChart__axis"
233
+ x1={MARGIN.left}
234
+ x2={MARGIN.left}
235
+ y1={MARGIN.top}
236
+ y2={height - MARGIN.bottom}
237
+ />
238
+ <line
239
+ class="st-barChart__axis"
240
+ x1={MARGIN.left}
241
+ x2={width - MARGIN.right}
242
+ y1={height - MARGIN.bottom}
243
+ y2={height - MARGIN.bottom}
244
+ />
245
+
246
+ <!-- category labels -->
247
+ {#each bars as bar, i (bar.datum.label)}
248
+ {#if orientation === "vertical"}
249
+ <text
250
+ class="st-barChart__categoryLabel"
251
+ x={bar.x + bar.width / 2}
252
+ y={height - MARGIN.bottom + 16}
253
+ text-anchor="middle"
254
+ >
255
+ {bar.datum.label}
256
+ </text>
257
+ {:else}
258
+ <text
259
+ class="st-barChart__categoryLabel"
260
+ x={MARGIN.left - 6}
261
+ y={bar.y + bar.height / 2}
262
+ text-anchor="end"
263
+ dominant-baseline="middle"
264
+ >
265
+ {bar.datum.label}
266
+ </text>
267
+ {/if}
268
+ {/each}
269
+
270
+ <!-- bars -->
271
+ {#each bars as bar, i (bar.datum.label)}
272
+ <rect
273
+ class="st-barChart__bar st-barChart__bar--{bar.tone}"
274
+ x={bar.x}
275
+ y={bar.y}
276
+ width={bar.width}
277
+ height={bar.height}
278
+ rx="2"
279
+ tabindex="0"
280
+ role="img"
281
+ aria-label="{bar.datum.label}: {bar.datum.value}"
282
+ onmouseenter={(e) => handleEnter(i, e)}
283
+ onmouseleave={handleLeave}
284
+ onfocus={(e) => handleEnter(i, e)}
285
+ onblur={handleLeave}
286
+ />
287
+ {/each}
288
+ </svg>
289
+
290
+ {#if hoveredIndex !== null && bars[hoveredIndex]}
291
+ {@const bar = bars[hoveredIndex]}
292
+ <div
293
+ class="st-barChart__tooltip"
294
+ role="presentation"
295
+ style="left: {(bar.cx / width) * 100}%; top: {(bar.cy / height) * 100}%"
296
+ >
297
+ <span class="st-barChart__tooltipLabel">{bar.datum.label}</span>
298
+ <span class="st-barChart__tooltipValue">{bar.datum.value}</span>
299
+ </div>
300
+ {/if}
301
+ </div>
302
+
303
+ <style>
304
+ .st-barChart {
305
+ color: var(--st-semantic-text-secondary);
306
+ display: block;
307
+ font-family: inherit;
308
+ position: relative;
309
+ width: 100%;
310
+ }
311
+
312
+ .st-barChart svg {
313
+ display: block;
314
+ overflow: visible;
315
+ }
316
+
317
+ .st-barChart__grid {
318
+ stroke: var(--st-component-barChart-gridStroke, var(--st-semantic-border-subtle));
319
+ stroke-dasharray: 2 3;
320
+ stroke-width: 1;
321
+ opacity: 0.7;
322
+ }
323
+
324
+ .st-barChart__axis {
325
+ stroke: var(--st-component-barChart-axisStroke, var(--st-semantic-border-subtle));
326
+ stroke-width: 1;
327
+ }
328
+
329
+ .st-barChart__tickLabel,
330
+ .st-barChart__categoryLabel {
331
+ fill: var(--st-component-barChart-labelColor, var(--st-semantic-text-secondary));
332
+ font-size: 0.6875rem;
333
+ }
334
+
335
+ .st-barChart__bar {
336
+ cursor: pointer;
337
+ transition: opacity 120ms ease;
338
+ }
339
+
340
+ .st-barChart__bar:hover,
341
+ .st-barChart__bar:focus-visible {
342
+ opacity: 0.82;
343
+ }
344
+
345
+ .st-barChart__bar:focus-visible {
346
+ outline: 2px solid var(--st-semantic-border-interactive);
347
+ outline-offset: 1px;
348
+ }
349
+
350
+ .st-barChart__bar--category1 { fill: var(--st-semantic-data-category1); }
351
+ .st-barChart__bar--category2 { fill: var(--st-semantic-data-category2); }
352
+ .st-barChart__bar--category3 { fill: var(--st-semantic-data-category3); }
353
+ .st-barChart__bar--category4 { fill: var(--st-semantic-data-category4); }
354
+ .st-barChart__bar--category5 { fill: var(--st-semantic-data-category5); }
355
+ .st-barChart__bar--category6 { fill: var(--st-semantic-data-category6); }
356
+ .st-barChart__bar--category7 { fill: var(--st-semantic-data-category7); }
357
+ .st-barChart__bar--category8 { fill: var(--st-semantic-data-category8); }
358
+
359
+ .st-barChart__tooltip {
360
+ background: var(--st-component-barChart-tooltipBackground, var(--st-semantic-surface-inverse));
361
+ border-radius: var(--st-radius-sm, 0.25rem);
362
+ color: var(--st-component-barChart-tooltipText, var(--st-semantic-text-inverse));
363
+ display: inline-flex;
364
+ flex-direction: column;
365
+ font-size: 0.75rem;
366
+ gap: 0.125rem;
367
+ line-height: 1.2;
368
+ padding: 0.375rem 0.5rem;
369
+ pointer-events: none;
370
+ position: absolute;
371
+ transform: translate(-50%, calc(-100% - 8px));
372
+ white-space: nowrap;
373
+ z-index: 1;
374
+ }
375
+
376
+ .st-barChart__tooltipLabel {
377
+ font-weight: 600;
378
+ }
379
+
380
+ .st-barChart__tooltipValue {
381
+ opacity: 0.85;
382
+ }
383
+ </style>
@@ -0,0 +1,18 @@
1
+ export type BarChartTone = "category1" | "category2" | "category3" | "category4" | "category5" | "category6" | "category7" | "category8";
2
+ export type BarChartDatum = {
3
+ label: string;
4
+ value: number;
5
+ tone?: BarChartTone;
6
+ };
7
+ type BarChartProps = {
8
+ data: BarChartDatum[];
9
+ width?: number;
10
+ height?: number;
11
+ orientation?: "vertical" | "horizontal";
12
+ label: string;
13
+ class?: string;
14
+ };
15
+ declare const BarChart: import("svelte").Component<BarChartProps, {}, "">;
16
+ type BarChart = ReturnType<typeof BarChart>;
17
+ export default BarChart;
18
+ //# sourceMappingURL=BarChart.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BarChart.svelte.d.ts","sourceRoot":"","sources":["../src/lib/BarChart.svelte.ts"],"names":[],"mappings":"AAGE,MAAM,MAAM,YAAY,GACpB,WAAW,GACX,WAAW,GACX,WAAW,GACX,WAAW,GACX,WAAW,GACX,WAAW,GACX,WAAW,GACX,WAAW,CAAC;AAEhB,MAAM,MAAM,aAAa,GAAG;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,YAAY,CAAC;CACrB,CAAC;AAEF,KAAK,aAAa,GAAG;IACnB,IAAI,EAAE,aAAa,EAAE,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,UAAU,GAAG,YAAY,CAAC;IACxC,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAgNJ,QAAA,MAAM,QAAQ,mDAAwC,CAAC;AACvD,KAAK,QAAQ,GAAG,UAAU,CAAC,OAAO,QAAQ,CAAC,CAAC;AAC5C,eAAe,QAAQ,CAAC"}
@@ -0,0 +1,238 @@
1
+ <script lang="ts">
2
+ import { Send, Square } from "@lucide/svelte";
3
+ import type { HTMLFormAttributes } from "svelte/elements";
4
+ import Button from "./Button.svelte";
5
+
6
+ type ChatComposerSubmitEvent = (payload: {
7
+ message: string;
8
+ source: "submit" | "keyboard";
9
+ event?: SubmitEvent | KeyboardEvent;
10
+ }) => void | Promise<void>;
11
+
12
+ type ChatComposerProps = Omit<HTMLFormAttributes, "class" | "onsubmit"> & {
13
+ value?: string;
14
+ placeholder?: string;
15
+ autosize?: boolean;
16
+ maxRows?: number;
17
+ submitDisabled?: boolean;
18
+ class?: string;
19
+ sendLabel?: string;
20
+ stopLabel?: string;
21
+ sendAriaLabel?: string;
22
+ stopAriaLabel?: string;
23
+ inputAriaLabel?: string;
24
+ disabled?: boolean;
25
+ busy?: boolean;
26
+ stoppable?: boolean;
27
+ clearOnSubmit?: boolean;
28
+ onsubmit?: ChatComposerSubmitEvent;
29
+ onstop?: () => void;
30
+ };
31
+
32
+ let {
33
+ value = $bindable(""),
34
+ placeholder = "Écrire un message...",
35
+ autosize = true,
36
+ maxRows = 8,
37
+ submitDisabled = false,
38
+ class: className,
39
+ sendLabel = "Envoyer",
40
+ stopLabel = "Arrêter",
41
+ sendAriaLabel = "Envoyer le message",
42
+ stopAriaLabel = "Arrêter l’envoi",
43
+ inputAriaLabel = "Composer de message",
44
+ disabled = false,
45
+ busy = false,
46
+ stoppable = false,
47
+ clearOnSubmit = true,
48
+ onsubmit,
49
+ onstop,
50
+ ...rest
51
+ }: ChatComposerProps = $props();
52
+
53
+ let textarea: HTMLTextAreaElement | undefined = $state();
54
+ let isSubmitting = $state(false);
55
+
56
+ const classes = () => ["st-chatComposer", className].filter(Boolean).join(" ");
57
+
58
+ const hasActivity = () => busy || isSubmitting;
59
+
60
+ const isSendable = () =>
61
+ !disabled &&
62
+ !hasActivity() &&
63
+ !submitDisabled &&
64
+ value.trim().length > 0;
65
+
66
+ $effect(() => {
67
+ if (autosize) resizeTextarea();
68
+ });
69
+
70
+ function resizeTextarea() {
71
+ const node = textarea;
72
+ if (!node || !autosize) return;
73
+ node.style.height = "auto";
74
+ const style = getComputedStyle(node);
75
+ const lineHeight = parseFloat(style.lineHeight || "20");
76
+ const maxHeight = Math.max(lineHeight * (maxRows || 1), lineHeight * 2);
77
+ const nextHeight = Math.min(node.scrollHeight, maxHeight);
78
+ node.style.height = `${Math.max(lineHeight * 1.5, nextHeight)}px`;
79
+ node.style.overflowY = node.scrollHeight > maxHeight ? "auto" : "hidden";
80
+ }
81
+
82
+ async function submitComposer(event?: SubmitEvent | KeyboardEvent, source: "submit" | "keyboard" = "submit") {
83
+ const trimmed = value.trim();
84
+ if (!isSendable() || !onsubmit || !trimmed) return;
85
+
86
+ event?.preventDefault();
87
+ isSubmitting = true;
88
+ try {
89
+ await onsubmit({ event, message: trimmed, source });
90
+ if (clearOnSubmit) value = "";
91
+ } finally {
92
+ isSubmitting = false;
93
+ resizeTextarea();
94
+ }
95
+ }
96
+
97
+ async function handleSubmit(event: SubmitEvent) {
98
+ await submitComposer(event, "submit");
99
+ }
100
+
101
+ async function handleTextareaKeydown(event: KeyboardEvent) {
102
+ if (event.key !== "Enter" || event.shiftKey || event.isComposing) return;
103
+ await submitComposer(event, "keyboard");
104
+ }
105
+
106
+ function handleStop() {
107
+ onstop?.();
108
+ }
109
+ </script>
110
+
111
+ <form
112
+ {...rest}
113
+ class={classes()}
114
+ onsubmit={handleSubmit}
115
+ novalidate
116
+ >
117
+ <div class="st-chatComposer__body">
118
+ <div class="st-chatComposer__inputShell">
119
+ <textarea
120
+ bind:this={textarea}
121
+ class="st-chatComposer__textarea"
122
+ bind:value
123
+ placeholder={placeholder}
124
+ rows="2"
125
+ aria-label={inputAriaLabel}
126
+ aria-disabled={disabled ? "true" : undefined}
127
+ disabled={disabled}
128
+ oninput={resizeTextarea}
129
+ onkeydown={handleTextareaKeydown}
130
+ ></textarea>
131
+ </div>
132
+ <slot name="attachments"></slot>
133
+ </div>
134
+
135
+ <div class="st-chatComposer__toolbar">
136
+ <div class="st-chatComposer__actions st-chatComposer__actions--left">
137
+ <slot name="actions-left"></slot>
138
+ </div>
139
+ <div class="st-chatComposer__actions st-chatComposer__actions--right">
140
+ <slot name="actions-right"></slot>
141
+ {#if hasActivity() && stoppable && onstop}
142
+ <Button type="button" variant="danger" onclick={handleStop} aria-label={stopAriaLabel}>
143
+ <Square size={16} strokeWidth={2} aria-hidden="true" />
144
+ <span>{stopLabel}</span>
145
+ </Button>
146
+ {/if}
147
+ <Button
148
+ type="submit"
149
+ aria-label={sendAriaLabel}
150
+ disabled={!isSendable()}
151
+ >
152
+ <Send size={16} strokeWidth={2} aria-hidden="true" />
153
+ {sendLabel}
154
+ </Button>
155
+ </div>
156
+ </div>
157
+ </form>
158
+
159
+ <style>
160
+ .st-chatComposer {
161
+ background: var(--st-component-chat-composerSurface, var(--st-semantic-surface-raised, #ffffff));
162
+ border: 1px solid var(--st-component-control-border, var(--st-semantic-border-subtle));
163
+ border-radius: var(--st-component-control-radius, 0.375rem);
164
+ display: grid;
165
+ gap: var(--st-spacing-3, 0.75rem);
166
+ padding: var(--st-spacing-3, 0.75rem);
167
+ width: 100%;
168
+ }
169
+
170
+ .st-chatComposer__body {
171
+ display: grid;
172
+ gap: var(--st-spacing-2, 0.5rem);
173
+ }
174
+
175
+ .st-chatComposer__inputShell {
176
+ display: grid;
177
+ gap: 0.25rem;
178
+ }
179
+
180
+ .st-chatComposer__textarea {
181
+ background: var(--st-component-control-background, var(--st-semantic-surface-default));
182
+ border: 1px solid var(--st-component-control-border, var(--st-semantic-border-subtle));
183
+ border-radius: var(--st-component-control-radius, 0.375rem);
184
+ color: var(--st-component-control-text, var(--st-semantic-text-primary));
185
+ font: inherit;
186
+ min-height: 2.5rem;
187
+ max-height: 16rem;
188
+ overflow-y: hidden;
189
+ padding: var(--st-spacing-2, 0.5rem) var(--st-spacing-3, 0.75rem);
190
+ resize: none;
191
+ width: 100%;
192
+ }
193
+
194
+ .st-chatComposer__textarea:focus-visible {
195
+ border-color: var(--st-component-control-focusRing, var(--st-semantic-border-interactive));
196
+ box-shadow: 0 0 0 2px var(--st-component-control-focusRing, var(--st-semantic-border-interactive));
197
+ outline: none;
198
+ }
199
+
200
+ .st-chatComposer__textarea[aria-disabled="true"] {
201
+ background: var(--st-component-control-disabledBackground, var(--st-semantic-surface-subtle));
202
+ color: var(--st-component-control-disabledText, var(--st-semantic-text-muted));
203
+ cursor: not-allowed;
204
+ }
205
+
206
+ .st-chatComposer__toolbar {
207
+ align-items: center;
208
+ display: flex;
209
+ gap: var(--st-spacing-2, 0.5rem);
210
+ justify-content: space-between;
211
+ }
212
+
213
+ .st-chatComposer__actions {
214
+ align-items: center;
215
+ display: flex;
216
+ gap: var(--st-spacing-2, 0.5rem);
217
+ }
218
+
219
+ .st-chatComposer__actions--right {
220
+ margin-left: auto;
221
+ }
222
+
223
+ @media (max-width: 640px) {
224
+ .st-chatComposer__toolbar {
225
+ align-items: stretch;
226
+ flex-wrap: wrap;
227
+ justify-content: flex-start;
228
+ }
229
+
230
+ .st-chatComposer__actions {
231
+ width: 100%;
232
+ }
233
+
234
+ .st-chatComposer__actions--right {
235
+ margin-left: 0;
236
+ }
237
+ }
238
+ </style>
@@ -0,0 +1,57 @@
1
+ import type { HTMLFormAttributes } from "svelte/elements";
2
+ type ChatComposerSubmitEvent = (payload: {
3
+ message: string;
4
+ source: "submit" | "keyboard";
5
+ event?: SubmitEvent | KeyboardEvent;
6
+ }) => void | Promise<void>;
7
+ type ChatComposerProps = Omit<HTMLFormAttributes, "class" | "onsubmit"> & {
8
+ value?: string;
9
+ placeholder?: string;
10
+ autosize?: boolean;
11
+ maxRows?: number;
12
+ submitDisabled?: boolean;
13
+ class?: string;
14
+ sendLabel?: string;
15
+ stopLabel?: string;
16
+ sendAriaLabel?: string;
17
+ stopAriaLabel?: string;
18
+ inputAriaLabel?: string;
19
+ disabled?: boolean;
20
+ busy?: boolean;
21
+ stoppable?: boolean;
22
+ clearOnSubmit?: boolean;
23
+ onsubmit?: ChatComposerSubmitEvent;
24
+ onstop?: () => void;
25
+ };
26
+ interface $$__sveltets_2_IsomorphicComponent<Props extends Record<string, any> = any, Events extends Record<string, any> = any, Slots extends Record<string, any> = any, Exports = {}, Bindings = string> {
27
+ new (options: import('svelte').ComponentConstructorOptions<Props>): import('svelte').SvelteComponent<Props, Events, Slots> & {
28
+ $$bindings?: Bindings;
29
+ } & Exports;
30
+ (internal: unknown, props: Props & {
31
+ $$events?: Events;
32
+ $$slots?: Slots;
33
+ }): Exports & {
34
+ $set?: any;
35
+ $on?: any;
36
+ };
37
+ z_$$bindings?: Bindings;
38
+ }
39
+ type $$__sveltets_2_PropsWithChildren<Props, Slots> = Props & (Slots extends {
40
+ default: any;
41
+ } ? Props extends Record<string, never> ? any : {
42
+ children?: any;
43
+ } : {});
44
+ declare const ChatComposer: $$__sveltets_2_IsomorphicComponent<$$__sveltets_2_PropsWithChildren<ChatComposerProps, {
45
+ attachments: {};
46
+ 'actions-left': {};
47
+ 'actions-right': {};
48
+ }>, {
49
+ [evt: string]: CustomEvent<any>;
50
+ }, {
51
+ attachments: {};
52
+ 'actions-left': {};
53
+ 'actions-right': {};
54
+ }, {}, "value">;
55
+ type ChatComposer = InstanceType<typeof ChatComposer>;
56
+ export default ChatComposer;
57
+ //# sourceMappingURL=ChatComposer.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ChatComposer.svelte.d.ts","sourceRoot":"","sources":["../src/lib/ChatComposer.svelte.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAIxD,KAAK,uBAAuB,GAAG,CAAC,OAAO,EAAE;IACvC,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,QAAQ,GAAG,UAAU,CAAC;IAC9B,KAAK,CAAC,EAAE,WAAW,GAAG,aAAa,CAAC;CACrC,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;AAE3B,KAAK,iBAAiB,GAAG,IAAI,CAAC,kBAAkB,EAAE,OAAO,GAAG,UAAU,CAAC,GAAG;IACxE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,QAAQ,CAAC,EAAE,uBAAuB,CAAC;IACnC,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;CACrB,CAAC;AAuHJ,UAAU,kCAAkC,CAAC,KAAK,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,GAAG,EAAE,MAAM,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,GAAG,EAAE,KAAK,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,GAAG,EAAE,OAAO,GAAG,EAAE,EAAE,QAAQ,GAAG,MAAM;IACpM,KAAK,OAAO,EAAE,OAAO,QAAQ,EAAE,2BAA2B,CAAC,KAAK,CAAC,GAAG,OAAO,QAAQ,EAAE,eAAe,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,CAAC,GAAG;QAAE,UAAU,CAAC,EAAE,QAAQ,CAAA;KAAE,GAAG,OAAO,CAAC;IACjK,CAAC,QAAQ,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,GAAG;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,KAAK,CAAA;KAAC,GAAG,OAAO,GAAG;QAAE,IAAI,CAAC,EAAE,GAAG,CAAC;QAAC,GAAG,CAAC,EAAE,GAAG,CAAA;KAAE,CAAC;IAC9G,YAAY,CAAC,EAAE,QAAQ,CAAC;CAC3B;AACD,KAAK,gCAAgC,CAAC,KAAK,EAAE,KAAK,IAAI,KAAK,GACvD,CAAC,KAAK,SAAS;IAAE,OAAO,EAAE,GAAG,CAAA;CAAE,GACzB,KAAK,SAAS,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,GACnC,GAAG,GACH;IAAE,QAAQ,CAAC,EAAE,GAAG,CAAA;CAAE,GAClB,EAAE,CAAC,CAAC;AAId,QAAA,MAAM,YAAY;;;;;;;;;;eAAqF,CAAC;AACtF,KAAK,YAAY,GAAG,YAAY,CAAC,OAAO,YAAY,CAAC,CAAC;AACxD,eAAe,YAAY,CAAC"}