@sentropic/design-system-svelte 0.34.37 → 0.34.39

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.
@@ -0,0 +1,45 @@
1
+ /**
2
+ * EventFeedPanel — flux d'événements datés, scrollable (façon New Relic /
3
+ * observabilité). PANNEAU (liste), pas un graphe SVG : une liste verticale
4
+ * scrollable d'événements horodatés, chacun teinté/iconé par sa SÉVÉRITÉ
5
+ * (tons sémantiques --st-semantic-feedback-*). Distinct de TimelineChart
6
+ * (axe temporel SVG) : ici la temporalité est un simple tri décroissant, le
7
+ * rendu est du DOM (rôle `feed` / `list`) défilable.
8
+ * API canonique (référence Svelte, React/Vue/Angular doivent s'aligner).
9
+ *
10
+ * Modèle : items triés par `at` DÉCROISSANT (le plus récent en tête), badge
11
+ * de sévérité (pastille + libellé), horodatage formaté (heure locale) et
12
+ * message. Défilement vertical borné par `maxHeight`/`height`. a11y :
13
+ * `role="feed"` sur la liste, `role="article"` par item.
14
+ *
15
+ * Props obligatoires :
16
+ * data EventFeedPanelEvent[] - tableau d'événements
17
+ *
18
+ * Props optionnelles :
19
+ * label string - libellé accessible du flux
20
+ * maxHeight number - hauteur max en px (déclenche le scroll)
21
+ * height number - alias de maxHeight (hauteur fixe)
22
+ * class string
23
+ */
24
+ export type EventFeedPanelSeverity = "info" | "success" | "warning" | "error" | (string & {});
25
+ export type EventFeedPanelEvent = {
26
+ /** Horodatage en millisecondes epoch (ou tout nombre croissant). */
27
+ at: number;
28
+ /** Catégorie libre de l'événement (« deploy », « alert »…). */
29
+ type: string;
30
+ /** Sévérité : pilote la couleur/pastille (sémantique feedback). */
31
+ severity: EventFeedPanelSeverity;
32
+ /** Message principal affiché. */
33
+ message: string;
34
+ };
35
+ type EventFeedPanelProps = {
36
+ data: EventFeedPanelEvent[];
37
+ label?: string;
38
+ maxHeight?: number;
39
+ height?: number;
40
+ class?: string;
41
+ };
42
+ declare const EventFeedPanel: import("svelte").Component<EventFeedPanelProps, {}, "">;
43
+ type EventFeedPanel = ReturnType<typeof EventFeedPanel>;
44
+ export default EventFeedPanel;
45
+ //# sourceMappingURL=EventFeedPanel.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"EventFeedPanel.svelte.d.ts","sourceRoot":"","sources":["../src/lib/EventFeedPanel.svelte.ts"],"names":[],"mappings":"AAGE;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,MAAM,MAAM,sBAAsB,GAC9B,MAAM,GACN,SAAS,GACT,SAAS,GACT,OAAO,GACP,CAAC,MAAM,GAAG,EAAE,CAAC,CAAC;AAElB,MAAM,MAAM,mBAAmB,GAAG;IAChC,oEAAoE;IACpE,EAAE,EAAE,MAAM,CAAC;IACX,+DAA+D;IAC/D,IAAI,EAAE,MAAM,CAAC;IACb,mEAAmE;IACnE,QAAQ,EAAE,sBAAsB,CAAC;IACjC,iCAAiC;IACjC,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,KAAK,mBAAmB,GAAG;IACzB,IAAI,EAAE,mBAAmB,EAAE,CAAC;IAC5B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AA+EJ,QAAA,MAAM,cAAc,yDAAwC,CAAC;AAC7D,KAAK,cAAc,GAAG,UAAU,CAAC,OAAO,cAAc,CAAC,CAAC;AACxD,eAAe,cAAc,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=EventFeedPanel.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"EventFeedPanel.test.d.ts","sourceRoot":"","sources":["../src/lib/EventFeedPanel.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,66 @@
1
+ import { render } from "@testing-library/svelte";
2
+ import { describe, expect, it } from "vitest";
3
+ import EventFeedPanel from "./EventFeedPanel.svelte";
4
+ const events = [
5
+ { at: 1_700_000_000_000, type: "deploy", severity: "info", message: "Deployed v1.2.0" },
6
+ { at: 1_700_000_300_000, type: "alert", severity: "error", message: "5xx spike" },
7
+ { at: 1_700_000_100_000, type: "scale", severity: "success", message: "Scaled up" },
8
+ { at: 1_700_000_200_000, type: "warn", severity: "warning", message: "High latency" }
9
+ ];
10
+ const items = (container) => Array.from(container.querySelectorAll(".st-eventFeedPanel__item"));
11
+ const structuralClass = (el) => el.className.split(/\s+/)[0];
12
+ describe("EventFeedPanel", () => {
13
+ it("renders a feed with one article per event", () => {
14
+ const { container } = render(EventFeedPanel, { props: { data: events, label: "Activité" } });
15
+ expect(container.querySelector('[role="feed"]')).toBeTruthy();
16
+ expect(items(container).length).toBe(4);
17
+ expect(container.querySelectorAll('[role="article"]').length).toBe(4);
18
+ });
19
+ it("sorts events by timestamp descending (newest first)", () => {
20
+ const { container } = render(EventFeedPanel, { props: { data: events, label: "A" } });
21
+ const messages = items(container).map((li) => li.querySelector(".st-eventFeedPanel__message")?.textContent);
22
+ expect(messages).toEqual(["5xx spike", "High latency", "Scaled up", "Deployed v1.2.0"]);
23
+ });
24
+ it("applies a severity tone class per item", () => {
25
+ const { container } = render(EventFeedPanel, { props: { data: events, label: "A" } });
26
+ expect(container.querySelector(".st-eventFeedPanel__item--info")).toBeTruthy();
27
+ expect(container.querySelector(".st-eventFeedPanel__item--success")).toBeTruthy();
28
+ expect(container.querySelector(".st-eventFeedPanel__item--warning")).toBeTruthy();
29
+ expect(container.querySelector(".st-eventFeedPanel__item--error")).toBeTruthy();
30
+ });
31
+ it("falls back to the neutral tone for unknown severities", () => {
32
+ const { container } = render(EventFeedPanel, {
33
+ props: { data: [{ at: 1, type: "x", severity: "debug", message: "noise" }], label: "A" }
34
+ });
35
+ expect(container.querySelector(".st-eventFeedPanel__item--neutral")).toBeTruthy();
36
+ });
37
+ it("drops non-finite timestamps before rendering", () => {
38
+ const { container } = render(EventFeedPanel, {
39
+ props: {
40
+ data: [
41
+ { at: Number.NaN, type: "x", severity: "info", message: "bad" },
42
+ { at: 2, type: "y", severity: "info", message: "good" }
43
+ ],
44
+ label: "A"
45
+ }
46
+ });
47
+ expect(items(container).length).toBe(1);
48
+ expect(container.querySelector(".st-eventFeedPanel__message")?.textContent).toBe("good");
49
+ });
50
+ it("constrains the scroll height when maxHeight is set", () => {
51
+ const { container } = render(EventFeedPanel, { props: { data: events, maxHeight: 180 } });
52
+ const list = container.querySelector(".st-eventFeedPanel__list");
53
+ expect(list.getAttribute("style")).toContain("max-height: 180px");
54
+ });
55
+ it("accepts height as an alias of maxHeight", () => {
56
+ const { container } = render(EventFeedPanel, { props: { data: events, height: 120 } });
57
+ const list = container.querySelector(".st-eventFeedPanel__list");
58
+ expect(list.getAttribute("style")).toContain("max-height: 120px");
59
+ });
60
+ it("merges a custom class onto the root", () => {
61
+ const { container } = render(EventFeedPanel, { props: { data: events, class: "mine" } });
62
+ const root = container.querySelector(".st-eventFeedPanel");
63
+ expect(structuralClass(root)).toBe("st-eventFeedPanel");
64
+ expect(root.classList.contains("mine")).toBe(true);
65
+ });
66
+ });
@@ -0,0 +1,347 @@
1
+ <script lang="ts" module>
2
+ /**
3
+ * VectorFieldChart — champ de vecteurs (façon Highcharts « vector »). Grille
4
+ * de flèches dont la LONGUEUR est proportionnelle à la magnitude (`length`) et
5
+ * l'ORIENTATION suit la direction (`direction`, en degrés). Axes X/Y gradués
6
+ * (mêmes « niceTicks » que les autres charts). La couleur encode la magnitude
7
+ * (échelle category1..8). a11y : `role="img"` + liste accessible des points.
8
+ * API canonique (référence Svelte, React/Vue/Angular doivent s'aligner).
9
+ *
10
+ * Convention de direction : 0° pointe vers la droite (axe +X), l'angle
11
+ * augmente dans le sens trigonométrique (90° = vers le haut). Les longueurs
12
+ * sont normalisées sur la plus grande magnitude pour rester dans la cellule.
13
+ *
14
+ * Props obligatoires :
15
+ * data VectorFieldChartDatum[] - {x, y, length, direction}
16
+ *
17
+ * Props optionnelles :
18
+ * label string
19
+ * width number (défaut 640)
20
+ * height number (défaut 320)
21
+ * size number (longueur max d'une flèche en px ; défaut 26)
22
+ * class string
23
+ */
24
+ export type VectorFieldChartTone =
25
+ | "category1" | "category2" | "category3" | "category4"
26
+ | "category5" | "category6" | "category7" | "category8";
27
+
28
+ export type VectorFieldChartDatum = {
29
+ x: number;
30
+ y: number;
31
+ /** Magnitude (≥ 0) : pilote la longueur normalisée et la couleur. */
32
+ length: number;
33
+ /** Direction en DEGRÉS (0° = +X, sens trigonométrique). */
34
+ direction: number;
35
+ };
36
+ </script>
37
+
38
+ <script lang="ts">
39
+ import ChartDataList from "./ChartDataList.svelte";
40
+
41
+ type VectorFieldChartProps = {
42
+ data: VectorFieldChartDatum[];
43
+ label?: string;
44
+ width?: number;
45
+ height?: number;
46
+ size?: number;
47
+ class?: string;
48
+ };
49
+
50
+ let {
51
+ data = [],
52
+ label,
53
+ width = 640,
54
+ height = 320,
55
+ size = 26,
56
+ class: className
57
+ }: VectorFieldChartProps = $props();
58
+
59
+ const MARGIN = { top: 16, right: 18, bottom: 36, left: 48 };
60
+
61
+ const TONES = [
62
+ "category1","category2","category3","category4",
63
+ "category5","category6","category7","category8"
64
+ ] as const;
65
+
66
+ function niceTicks(min: number, max: number, target = 5): number[] {
67
+ if (!Number.isFinite(min) || !Number.isFinite(max) || min === max) {
68
+ return [Number.isFinite(max) ? max : 0];
69
+ }
70
+ const range = max - min;
71
+ const rough = range / Math.max(target - 1, 1);
72
+ const pow = Math.pow(10, Math.floor(Math.log10(rough)));
73
+ const norm = rough / pow;
74
+ let step: number;
75
+ if (norm < 1.5) step = pow;
76
+ else if (norm < 3) step = 2 * pow;
77
+ else if (norm < 7) step = 5 * pow;
78
+ else step = 10 * pow;
79
+ const start = Math.floor(min / step) * step;
80
+ const end = Math.ceil(max / step) * step;
81
+ const ticks: number[] = [];
82
+ for (let v = start; v <= end + step / 2; v += step) ticks.push(Number(v.toFixed(10)));
83
+ return ticks;
84
+ }
85
+
86
+ function scaleLinear(v: number, d0: number, d1: number, r0: number, r1: number) {
87
+ if (d1 === d0) return r0;
88
+ return r0 + ((v - d0) * (r1 - r0)) / (d1 - d0);
89
+ }
90
+
91
+ function fmt(v: number): string {
92
+ if (Math.abs(v) >= 1000) return `${(v / 1000).toFixed(v % 1000 === 0 ? 0 : 1)}k`;
93
+ return Number.isInteger(v) ? String(v) : v.toFixed(1);
94
+ }
95
+
96
+ let hoveredKey: string | null = $state(null);
97
+
98
+ // Points valides : coordonnées finies, magnitude finie ≥ 0.
99
+ const validData = $derived(
100
+ data.filter(
101
+ (d) =>
102
+ d &&
103
+ Number.isFinite(d.x) &&
104
+ Number.isFinite(d.y) &&
105
+ Number.isFinite(d.length) &&
106
+ d.length >= 0 &&
107
+ Number.isFinite(d.direction)
108
+ )
109
+ );
110
+
111
+ const scales = $derived.by(() => {
112
+ const xs = validData.map((d) => d.x);
113
+ const ys = validData.map((d) => d.y);
114
+ const xTicks = niceTicks(Math.min(...xs), Math.max(...xs));
115
+ const yTicks = niceTicks(Math.min(...ys), Math.max(...ys));
116
+ const plotW = Math.max(width - MARGIN.left - MARGIN.right, 1);
117
+ const plotH = Math.max(height - MARGIN.top - MARGIN.bottom, 1);
118
+ return {
119
+ xTicks, yTicks,
120
+ xMin: xTicks[0], xMax: xTicks[xTicks.length - 1],
121
+ yMin: yTicks[0], yMax: yTicks[yTicks.length - 1],
122
+ plotW, plotH
123
+ };
124
+ });
125
+
126
+ const maxLength = $derived(
127
+ validData.reduce((max, d) => (d.length > max ? d.length : max), 0)
128
+ );
129
+
130
+ // Une flèche par point : segment (base → pointe) + 2 traits de pointe.
131
+ const arrows = $derived.by(() => {
132
+ const { xMin, xMax, yMin, yMax, plotW, plotH } = scales;
133
+ const max = maxLength > 0 ? maxLength : 1;
134
+ return validData.map((d, i) => {
135
+ const cx = MARGIN.left + scaleLinear(d.x, xMin, xMax, 0, plotW);
136
+ const cy = MARGIN.top + scaleLinear(d.y, yMin, yMax, plotH, 0);
137
+ // Longueur normalisée (la plus grande magnitude = `size`).
138
+ const len = (d.length / max) * size;
139
+ // Direction trigonométrique : +X à droite, +Y vers le haut (donc -sin en
140
+ // espace écran où l'axe Y descend).
141
+ const rad = (d.direction * Math.PI) / 180;
142
+ const dx = Math.cos(rad) * len;
143
+ const dy = -Math.sin(rad) * len;
144
+ // Flèche centrée sur le point : moitié de chaque côté.
145
+ const x1 = cx - dx / 2;
146
+ const y1 = cy - dy / 2;
147
+ const x2 = cx + dx / 2;
148
+ const y2 = cy + dy / 2;
149
+ // Pointe : deux petits traits en arrière de la tête.
150
+ const head = Math.min(Math.max(len * 0.28, 3), 8);
151
+ const headAngle = (28 * Math.PI) / 180;
152
+ const baseAngle = Math.atan2(y2 - y1, x2 - x1);
153
+ const hx1 = x2 - head * Math.cos(baseAngle - headAngle);
154
+ const hy1 = y2 - head * Math.sin(baseAngle - headAngle);
155
+ const hx2 = x2 - head * Math.cos(baseAngle + headAngle);
156
+ const hy2 = y2 - head * Math.sin(baseAngle + headAngle);
157
+ // Ton catégoriel par bin de magnitude (max → category8).
158
+ const bin = Math.min(Math.floor((d.length / max) * TONES.length), TONES.length - 1);
159
+ return {
160
+ key: `${i}`,
161
+ datum: d,
162
+ cx, cy, x1, y1, x2, y2, hx1, hy1, hx2, hy2,
163
+ tone: TONES[Math.max(0, bin)]
164
+ };
165
+ });
166
+ });
167
+
168
+ const dataValueItems = $derived(
169
+ validData.map(
170
+ (d) => `x ${d.x}, y ${d.y} · |v| ${fmt(d.length)} @ ${fmt(d.direction)}°`
171
+ )
172
+ );
173
+
174
+ function handlePointerMove(event: PointerEvent) {
175
+ const target = event.target;
176
+ if (!(target instanceof Element)) {
177
+ hoveredKey = null;
178
+ return;
179
+ }
180
+ hoveredKey = target.getAttribute("data-chart-key");
181
+ }
182
+
183
+ const hoveredArrow = $derived.by(() => {
184
+ if (hoveredKey === null) return null;
185
+ return arrows.find((a) => a.key === hoveredKey) ?? null;
186
+ });
187
+
188
+ const classes = () => ["st-vectorFieldChart", className].filter(Boolean).join(" ");
189
+ </script>
190
+
191
+ <div class={classes()}>
192
+ <div
193
+ class="st-vectorFieldChart__visual"
194
+ role="img"
195
+ aria-label={label}
196
+ onpointermove={handlePointerMove}
197
+ onpointerleave={() => (hoveredKey = null)}
198
+ >
199
+ <svg
200
+ viewBox="0 0 {width} {height}"
201
+ preserveAspectRatio="xMidYMid meet"
202
+ width="100%"
203
+ height="100%"
204
+ focusable="false"
205
+ aria-hidden="true"
206
+ >
207
+ <!-- gridlines + ticks Y -->
208
+ {#each scales.yTicks as t (t)}
209
+ {@const y = MARGIN.top + scaleLinear(t, scales.yMin, scales.yMax, scales.plotH, 0)}
210
+ <line class="st-vectorFieldChart__grid" x1={MARGIN.left} x2={width - MARGIN.right} y1={y} y2={y} />
211
+ <text class="st-vectorFieldChart__tick" x={MARGIN.left - 6} y={y} text-anchor="end" dominant-baseline="middle">{fmt(t)}</text>
212
+ {/each}
213
+ <!-- ticks X -->
214
+ {#each scales.xTicks as t (t)}
215
+ {@const x = MARGIN.left + scaleLinear(t, scales.xMin, scales.xMax, 0, scales.plotW)}
216
+ <text class="st-vectorFieldChart__tick" x={x} y={height - MARGIN.bottom + 16} text-anchor="middle">{fmt(t)}</text>
217
+ {/each}
218
+
219
+ <!-- axes -->
220
+ <line class="st-vectorFieldChart__axis" x1={MARGIN.left} x2={MARGIN.left} y1={MARGIN.top} y2={height - MARGIN.bottom} />
221
+ <line class="st-vectorFieldChart__axis" x1={MARGIN.left} x2={width - MARGIN.right} y1={height - MARGIN.bottom} y2={height - MARGIN.bottom} />
222
+
223
+ <!-- une flèche par point : segment + pointe -->
224
+ {#each arrows as a (a.key)}
225
+ <g
226
+ class="st-vectorFieldChart__arrow st-vectorFieldChart__arrow--{a.tone}"
227
+ class:st-vectorFieldChart__arrow--dim={hoveredKey !== null && hoveredKey !== a.key}
228
+ >
229
+ <line class="st-vectorFieldChart__shaft" x1={a.x1} y1={a.y1} x2={a.x2} y2={a.y2} data-chart-key={a.key} />
230
+ <line class="st-vectorFieldChart__head" x1={a.x2} y1={a.y2} x2={a.hx1} y2={a.hy1} />
231
+ <line class="st-vectorFieldChart__head" x1={a.x2} y1={a.y2} x2={a.hx2} y2={a.hy2} />
232
+ </g>
233
+ {/each}
234
+ </svg>
235
+ </div>
236
+
237
+ <ChartDataList label={label ?? "vector field"} items={dataValueItems} />
238
+
239
+ {#if hoveredArrow}
240
+ {@const a = hoveredArrow}
241
+ <div
242
+ class="st-vectorFieldChart__tooltip"
243
+ role="presentation"
244
+ style="left: {(a.cx / width) * 100}%; top: {(a.cy / height) * 100}%"
245
+ >
246
+ <span class="st-vectorFieldChart__tooltipLabel">x {a.datum.x} · y {a.datum.y}</span>
247
+ <span class="st-vectorFieldChart__tooltipValue">|v| {fmt(a.datum.length)} @ {fmt(a.datum.direction)}°</span>
248
+ </div>
249
+ {/if}
250
+ </div>
251
+
252
+ <style>
253
+ .st-vectorFieldChart {
254
+ color: var(--st-semantic-text-secondary);
255
+ display: block;
256
+ font-family: inherit;
257
+ position: relative;
258
+ width: 100%;
259
+ }
260
+
261
+ .st-vectorFieldChart svg {
262
+ display: block;
263
+ overflow: visible;
264
+ }
265
+
266
+ .st-vectorFieldChart__visual {
267
+ display: block;
268
+ }
269
+
270
+ .st-vectorFieldChart__grid {
271
+ opacity: 0.7;
272
+ stroke: var(--st-semantic-border-subtle);
273
+ stroke-dasharray: 2 3;
274
+ stroke-width: 1;
275
+ }
276
+
277
+ .st-vectorFieldChart__axis {
278
+ stroke: var(--st-semantic-border-subtle);
279
+ stroke-width: 1;
280
+ }
281
+
282
+ .st-vectorFieldChart__tick {
283
+ fill: var(--st-semantic-text-secondary);
284
+ font-size: 0.6875rem;
285
+ }
286
+
287
+ .st-vectorFieldChart__arrow {
288
+ transition: opacity 120ms ease;
289
+ }
290
+
291
+ .st-vectorFieldChart__arrow--dim {
292
+ opacity: 0.35;
293
+ }
294
+
295
+ .st-vectorFieldChart__shaft {
296
+ cursor: pointer;
297
+ stroke: currentColor;
298
+ stroke-linecap: round;
299
+ stroke-width: 2;
300
+ }
301
+
302
+ .st-vectorFieldChart__head {
303
+ stroke: currentColor;
304
+ stroke-linecap: round;
305
+ stroke-width: 2;
306
+ }
307
+
308
+ .st-vectorFieldChart__arrow--category1 { color: var(--st-semantic-data-category1); }
309
+ .st-vectorFieldChart__arrow--category2 { color: var(--st-semantic-data-category2); }
310
+ .st-vectorFieldChart__arrow--category3 { color: var(--st-semantic-data-category3); }
311
+ .st-vectorFieldChart__arrow--category4 { color: var(--st-semantic-data-category4); }
312
+ .st-vectorFieldChart__arrow--category5 { color: var(--st-semantic-data-category5); }
313
+ .st-vectorFieldChart__arrow--category6 { color: var(--st-semantic-data-category6); }
314
+ .st-vectorFieldChart__arrow--category7 { color: var(--st-semantic-data-category7); }
315
+ .st-vectorFieldChart__arrow--category8 { color: var(--st-semantic-data-category8); }
316
+
317
+ .st-vectorFieldChart__tooltip {
318
+ background: var(--st-semantic-surface-inverse);
319
+ border-radius: var(--st-radius-sm, 0.25rem);
320
+ color: var(--st-semantic-text-inverse);
321
+ display: inline-flex;
322
+ flex-direction: column;
323
+ font-size: 0.75rem;
324
+ gap: 0.125rem;
325
+ line-height: 1.2;
326
+ padding: 0.375rem 0.5rem;
327
+ pointer-events: none;
328
+ position: absolute;
329
+ transform: translate(-50%, calc(-100% - 8px));
330
+ white-space: nowrap;
331
+ z-index: 1;
332
+ }
333
+
334
+ .st-vectorFieldChart__tooltipLabel {
335
+ font-weight: 600;
336
+ }
337
+
338
+ .st-vectorFieldChart__tooltipValue {
339
+ opacity: 0.85;
340
+ }
341
+
342
+ @media (prefers-reduced-motion: reduce) {
343
+ .st-vectorFieldChart__arrow {
344
+ transition: none;
345
+ }
346
+ }
347
+ </style>
@@ -0,0 +1,43 @@
1
+ /**
2
+ * VectorFieldChart — champ de vecteurs (façon Highcharts « vector »). Grille
3
+ * de flèches dont la LONGUEUR est proportionnelle à la magnitude (`length`) et
4
+ * l'ORIENTATION suit la direction (`direction`, en degrés). Axes X/Y gradués
5
+ * (mêmes « niceTicks » que les autres charts). La couleur encode la magnitude
6
+ * (échelle category1..8). a11y : `role="img"` + liste accessible des points.
7
+ * API canonique (référence Svelte, React/Vue/Angular doivent s'aligner).
8
+ *
9
+ * Convention de direction : 0° pointe vers la droite (axe +X), l'angle
10
+ * augmente dans le sens trigonométrique (90° = vers le haut). Les longueurs
11
+ * sont normalisées sur la plus grande magnitude pour rester dans la cellule.
12
+ *
13
+ * Props obligatoires :
14
+ * data VectorFieldChartDatum[] - {x, y, length, direction}
15
+ *
16
+ * Props optionnelles :
17
+ * label string
18
+ * width number (défaut 640)
19
+ * height number (défaut 320)
20
+ * size number (longueur max d'une flèche en px ; défaut 26)
21
+ * class string
22
+ */
23
+ export type VectorFieldChartTone = "category1" | "category2" | "category3" | "category4" | "category5" | "category6" | "category7" | "category8";
24
+ export type VectorFieldChartDatum = {
25
+ x: number;
26
+ y: number;
27
+ /** Magnitude (≥ 0) : pilote la longueur normalisée et la couleur. */
28
+ length: number;
29
+ /** Direction en DEGRÉS (0° = +X, sens trigonométrique). */
30
+ direction: number;
31
+ };
32
+ type VectorFieldChartProps = {
33
+ data: VectorFieldChartDatum[];
34
+ label?: string;
35
+ width?: number;
36
+ height?: number;
37
+ size?: number;
38
+ class?: string;
39
+ };
40
+ declare const VectorFieldChart: import("svelte").Component<VectorFieldChartProps, {}, "">;
41
+ type VectorFieldChart = ReturnType<typeof VectorFieldChart>;
42
+ export default VectorFieldChart;
43
+ //# sourceMappingURL=VectorFieldChart.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"VectorFieldChart.svelte.d.ts","sourceRoot":"","sources":["../src/lib/VectorFieldChart.svelte.ts"],"names":[],"mappings":"AAGE;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,MAAM,oBAAoB,GAC5B,WAAW,GAAG,WAAW,GAAG,WAAW,GAAG,WAAW,GACrD,WAAW,GAAG,WAAW,GAAG,WAAW,GAAG,WAAW,CAAC;AAE1D,MAAM,MAAM,qBAAqB,GAAG;IAClC,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,qEAAqE;IACrE,MAAM,EAAE,MAAM,CAAC;IACf,2DAA2D;IAC3D,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAMF,KAAK,qBAAqB,GAAG;IAC3B,IAAI,EAAE,qBAAqB,EAAE,CAAC;IAC9B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAiMJ,QAAA,MAAM,gBAAgB,2DAAwC,CAAC;AAC/D,KAAK,gBAAgB,GAAG,UAAU,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAC5D,eAAe,gBAAgB,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=VectorFieldChart.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"VectorFieldChart.test.d.ts","sourceRoot":"","sources":["../src/lib/VectorFieldChart.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,59 @@
1
+ import { render } from "@testing-library/svelte";
2
+ import { describe, expect, it } from "vitest";
3
+ import VectorFieldChart from "./VectorFieldChart.svelte";
4
+ const field = [
5
+ { x: 0, y: 0, length: 1, direction: 0 },
6
+ { x: 1, y: 0, length: 2, direction: 90 },
7
+ { x: 0, y: 1, length: 3, direction: 180 },
8
+ { x: 1, y: 1, length: 4, direction: 270 }
9
+ ];
10
+ const arrows = (container) => Array.from(container.querySelectorAll(".st-vectorFieldChart__arrow"));
11
+ const listItems = (container) => Array.from(container.querySelectorAll(".st-chartDataList li")).map((n) => n.textContent?.trim());
12
+ const structuralClass = (el) => el.className.split(/\s+/)[0];
13
+ describe("VectorFieldChart", () => {
14
+ it("renders an img role and one arrow per datum", () => {
15
+ const { container } = render(VectorFieldChart, { props: { data: field, label: "Vent" } });
16
+ expect(container.querySelector('[role="img"]')).toBeTruthy();
17
+ expect(arrows(container).length).toBe(4);
18
+ });
19
+ it("draws a shaft and two head segments per arrow", () => {
20
+ const { container } = render(VectorFieldChart, { props: { data: field, label: "V" } });
21
+ expect(container.querySelectorAll(".st-vectorFieldChart__shaft").length).toBe(4);
22
+ expect(container.querySelectorAll(".st-vectorFieldChart__head").length).toBe(8);
23
+ });
24
+ it("colours arrows by magnitude bin (largest magnitude → category8)", () => {
25
+ const { container } = render(VectorFieldChart, { props: { data: field, label: "V" } });
26
+ const last = arrows(container).at(-1);
27
+ expect(last.classList.contains("st-vectorFieldChart__arrow--category8")).toBe(true);
28
+ });
29
+ it("renders graduated X/Y axes with nice ticks", () => {
30
+ const { container } = render(VectorFieldChart, { props: { data: field, label: "V" } });
31
+ expect(container.querySelectorAll(".st-vectorFieldChart__axis").length).toBe(2);
32
+ expect(container.querySelectorAll(".st-vectorFieldChart__tick").length).toBeGreaterThan(0);
33
+ });
34
+ it("lists every datum in the accessible data list", () => {
35
+ const { container } = render(VectorFieldChart, {
36
+ props: { data: [{ x: 2, y: 3, length: 5, direction: 45 }], label: "V" }
37
+ });
38
+ expect(listItems(container)[0]).toBe("x 2, y 3 · |v| 5 @ 45°");
39
+ });
40
+ it("drops non-finite or negative-magnitude points before rendering", () => {
41
+ const { container } = render(VectorFieldChart, {
42
+ props: {
43
+ data: [
44
+ { x: Number.NaN, y: 0, length: 1, direction: 0 },
45
+ { x: 0, y: 0, length: -1, direction: 0 },
46
+ { x: 1, y: 1, length: 2, direction: 0 }
47
+ ],
48
+ label: "V"
49
+ }
50
+ });
51
+ expect(arrows(container).length).toBe(1);
52
+ });
53
+ it("merges a custom class onto the root", () => {
54
+ const { container } = render(VectorFieldChart, { props: { data: field, class: "mine" } });
55
+ const root = container.querySelector(".st-vectorFieldChart");
56
+ expect(structuralClass(root)).toBe("st-vectorFieldChart");
57
+ expect(root.classList.contains("mine")).toBe(true);
58
+ });
59
+ });
package/dist/index.d.ts CHANGED
@@ -99,11 +99,15 @@ export { default as GaugeChart } from "./GaugeChart.svelte";
99
99
  export { default as SolidGaugeChart } from "./SolidGaugeChart.svelte";
100
100
  export { default as StateTimelineChart } from "./StateTimelineChart.svelte";
101
101
  export { default as StatusHistoryChart } from "./StatusHistoryChart.svelte";
102
+ export { default as EventFeedPanel } from "./EventFeedPanel.svelte";
103
+ export { default as VectorFieldChart } from "./VectorFieldChart.svelte";
102
104
  export { default as WaffleChart } from "./WaffleChart.svelte";
103
105
  export { default as RibbonChart } from "./RibbonChart.svelte";
104
106
  export { default as AnomalySwimLaneChart } from "./AnomalySwimLaneChart.svelte";
105
107
  export { default as FlamegraphChart } from "./FlamegraphChart.svelte";
106
108
  export { default as TraceWaterfallChart } from "./TraceWaterfallChart.svelte";
109
+ export { default as DecompositionTreeChart } from "./DecompositionTreeChart.svelte";
110
+ export { default as Density2DChart } from "./Density2DChart.svelte";
107
111
  export { default as GeoMap } from "./GeoMap.svelte";
108
112
  export { default as Header } from "./Header.svelte";
109
113
  export { default as HeatmapChart } from "./HeatmapChart.svelte";
@@ -229,11 +233,15 @@ export type { GaugeChartTone, GaugeChartThreshold, GaugeChartFormat } from "./Ga
229
233
  export type { SolidGaugeTone, SolidGaugeThreshold, SolidGaugeFormat } from "./SolidGaugeChart.svelte";
230
234
  export type { StateTimelineTone, StateTimelineSegment, StateTimelineSeries } from "./StateTimelineChart.svelte";
231
235
  export type { StatusHistoryTone, StatusHistoryBucket, StatusHistorySeries } from "./StatusHistoryChart.svelte";
236
+ export type { EventFeedPanelSeverity, EventFeedPanelEvent } from "./EventFeedPanel.svelte";
237
+ export type { VectorFieldChartTone, VectorFieldChartDatum } from "./VectorFieldChart.svelte";
232
238
  export type { WaffleTone, WaffleChartDatum } from "./WaffleChart.svelte";
233
239
  export type { RibbonChartTone, RibbonChartDatum } from "./RibbonChart.svelte";
234
240
  export type { AnomalySwimLaneTone, AnomalySwimLaneBucket, AnomalySwimLaneSeries } from "./AnomalySwimLaneChart.svelte";
235
241
  export type { FlamegraphNode } from "./FlamegraphChart.svelte";
236
242
  export type { TraceSpan } from "./TraceWaterfallChart.svelte";
243
+ export type { DecompositionTreeNode, DecompositionTreeLevel, DecompositionTreeData } from "./DecompositionTreeChart.svelte";
244
+ export type { Density2DTone, Density2DPoint } from "./Density2DChart.svelte";
237
245
  export type { FunnelChartTone, FunnelChartDatum } from "./FunnelChart.svelte";
238
246
  export type { ViolinChartDatum, ViolinChartTone } from "./ViolinChart.svelte";
239
247
  export type { WaterfallType, WaterfallChartDatum } from "./WaterfallChart.svelte";