@sentropic/design-system-svelte 0.16.0 → 0.18.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 (41) hide show
  1. package/dist/Calendar.svelte +237 -42
  2. package/dist/Calendar.svelte.d.ts.map +1 -1
  3. package/dist/ComboChart.svelte +620 -0
  4. package/dist/ComboChart.svelte.d.ts +28 -0
  5. package/dist/ComboChart.svelte.d.ts.map +1 -0
  6. package/dist/FunnelChart.svelte +358 -0
  7. package/dist/FunnelChart.svelte.d.ts +21 -0
  8. package/dist/FunnelChart.svelte.d.ts.map +1 -0
  9. package/dist/GaugeChart.svelte +300 -0
  10. package/dist/GaugeChart.svelte.d.ts +36 -0
  11. package/dist/GaugeChart.svelte.d.ts.map +1 -0
  12. package/dist/KpiCard.svelte +318 -0
  13. package/dist/KpiCard.svelte.d.ts +36 -0
  14. package/dist/KpiCard.svelte.d.ts.map +1 -0
  15. package/dist/Popper.svelte +157 -0
  16. package/dist/Popper.svelte.d.ts +17 -0
  17. package/dist/Popper.svelte.d.ts.map +1 -1
  18. package/dist/Rating.svelte +130 -35
  19. package/dist/Rating.svelte.d.ts.map +1 -1
  20. package/dist/SelectableList.svelte +60 -12
  21. package/dist/SelectableList.svelte.d.ts.map +1 -1
  22. package/dist/SelectableRow.svelte +23 -8
  23. package/dist/SelectableRow.svelte.d.ts +5 -4
  24. package/dist/SelectableRow.svelte.d.ts.map +1 -1
  25. package/dist/SlideIndicator.svelte +17 -3
  26. package/dist/SlideIndicator.svelte.d.ts.map +1 -1
  27. package/dist/TimePicker.svelte +176 -13
  28. package/dist/TimePicker.svelte.d.ts.map +1 -1
  29. package/dist/TreemapChart.svelte +448 -0
  30. package/dist/TreemapChart.svelte.d.ts +26 -0
  31. package/dist/TreemapChart.svelte.d.ts.map +1 -0
  32. package/dist/WaterfallChart.svelte +469 -0
  33. package/dist/WaterfallChart.svelte.d.ts +19 -0
  34. package/dist/WaterfallChart.svelte.d.ts.map +1 -0
  35. package/dist/chartContrast.d.ts +6 -0
  36. package/dist/chartContrast.d.ts.map +1 -0
  37. package/dist/chartContrast.js +58 -0
  38. package/dist/index.d.ts +12 -0
  39. package/dist/index.d.ts.map +1 -1
  40. package/dist/index.js +6 -0
  41. package/package.json +1 -1
@@ -0,0 +1,448 @@
1
+ <script lang="ts" module>
2
+ export type TreemapChartTone =
3
+ | "category1" | "category2" | "category3" | "category4"
4
+ | "category5" | "category6" | "category7" | "category8";
5
+
6
+ export type TreemapChartDatum = {
7
+ label: string;
8
+ value: number;
9
+ tone?: TreemapChartTone;
10
+ children?: TreemapChartDatum[];
11
+ };
12
+
13
+ export type TreemapTiling = "squarified";
14
+ </script>
15
+
16
+ <script lang="ts">
17
+ import ChartDataList from "./ChartDataList.svelte";
18
+ import { contrastTextForTone } from "./chartContrast";
19
+
20
+ type TreemapChartProps = {
21
+ /** Données hiérarchiques : 1 ou 2 niveaux. Un nœud avec `children` est subdivisé. */
22
+ data: TreemapChartDatum[];
23
+ /** Algorithme de pavage (squarified uniquement pour l'instant). */
24
+ tiling?: TreemapTiling;
25
+ /** Affiche les labels dans les rectangles suffisamment grands. */
26
+ showLabels?: boolean;
27
+ /** Affiche une légende sous le graphique. */
28
+ legend?: boolean;
29
+ width?: number;
30
+ height?: number;
31
+ label: string;
32
+ class?: string;
33
+ };
34
+
35
+ let {
36
+ data,
37
+ tiling = "squarified",
38
+ showLabels = true,
39
+ legend = false,
40
+ width = 480,
41
+ height = 300,
42
+ label,
43
+ class: className
44
+ }: TreemapChartProps = $props();
45
+
46
+ const TONES = [
47
+ "category1", "category2", "category3", "category4",
48
+ "category5", "category6", "category7", "category8"
49
+ ] as const;
50
+
51
+ const PADDING = 2;
52
+
53
+ type Rect = { x: number; y: number; w: number; h: number };
54
+ type Cell = {
55
+ datum: TreemapChartDatum;
56
+ value: number;
57
+ tone: TreemapChartTone;
58
+ textColor: string;
59
+ rect: Rect;
60
+ parentLabel?: string;
61
+ depth: number;
62
+ };
63
+
64
+ /** Valeur d'une feuille : seulement les nombres finis et positifs comptent. */
65
+ const leafValue = (v: number): number => (Number.isFinite(v) && v > 0 ? v : 0);
66
+
67
+ const sumValue = (d: TreemapChartDatum): number => {
68
+ if (d.children && d.children.length > 0) {
69
+ return d.children.reduce((s, c) => s + sumValue(c), 0);
70
+ }
71
+ return leafValue(d.value);
72
+ };
73
+
74
+ // Squarified treemap (Bruls, Huizing, van Wijk 2000).
75
+ // Pave `rect` avec des nœuds proportionnels à leur valeur en minimisant
76
+ // le ratio d'aspect des rectangles produits.
77
+ function squarify(
78
+ nodes: Array<{ datum: TreemapChartDatum; value: number }>,
79
+ rect: Rect
80
+ ): Array<{ datum: TreemapChartDatum; value: number; rect: Rect }> {
81
+ const out: Array<{ datum: TreemapChartDatum; value: number; rect: Rect }> = [];
82
+ const total = nodes.reduce((s, n) => s + n.value, 0);
83
+ if (total <= 0 || nodes.length === 0) return out;
84
+
85
+ // Échelle valeur → aire disponible.
86
+ const area = rect.w * rect.h;
87
+ const scale = area / total;
88
+ const items = nodes.map((n) => ({ datum: n.datum, value: n.value, area: n.value * scale }));
89
+
90
+ let free: Rect = { ...rect };
91
+ let row: typeof items = [];
92
+
93
+ const worst = (r: typeof items, side: number): number => {
94
+ if (r.length === 0 || side <= 0) return Infinity;
95
+ const s = r.reduce((acc, it) => acc + it.area, 0);
96
+ let max = -Infinity;
97
+ let min = Infinity;
98
+ for (const it of r) {
99
+ if (it.area > max) max = it.area;
100
+ if (it.area < min) min = it.area;
101
+ }
102
+ const s2 = s * s;
103
+ const side2 = side * side;
104
+ return Math.max((side2 * max) / s2, s2 / (side2 * min));
105
+ };
106
+
107
+ const layoutRow = (r: typeof items, side: number, area2: Rect): Rect => {
108
+ const s = r.reduce((acc, it) => acc + it.area, 0);
109
+ if (side <= 0) return area2;
110
+ // Largeur (ou hauteur) de la bande consommée.
111
+ const thickness = s / side;
112
+ if (area2.w >= area2.h) {
113
+ // Bande verticale à gauche, items empilés verticalement.
114
+ let oy = area2.y;
115
+ for (const it of r) {
116
+ const h = it.area / thickness;
117
+ out.push({ datum: it.datum, value: it.value, rect: { x: area2.x, y: oy, w: thickness, h } });
118
+ oy += h;
119
+ }
120
+ return { x: area2.x + thickness, y: area2.y, w: area2.w - thickness, h: area2.h };
121
+ } else {
122
+ // Bande horizontale en haut, items côte à côte.
123
+ let ox = area2.x;
124
+ for (const it of r) {
125
+ const w = it.area / thickness;
126
+ out.push({ datum: it.datum, value: it.value, rect: { x: ox, y: area2.y, w, h: thickness } });
127
+ ox += w;
128
+ }
129
+ return { x: area2.x, y: area2.y + thickness, w: area2.w, h: area2.h - thickness };
130
+ }
131
+ };
132
+
133
+ for (const it of items) {
134
+ const side = Math.min(free.w, free.h);
135
+ const next = [...row, it];
136
+ if (row.length === 0 || worst(next, side) <= worst(row, side)) {
137
+ row = next;
138
+ } else {
139
+ free = layoutRow(row, side, free);
140
+ row = [it];
141
+ }
142
+ }
143
+ if (row.length > 0) {
144
+ free = layoutRow(row, Math.min(free.w, free.h), free);
145
+ }
146
+ return out;
147
+ }
148
+
149
+ function inset(r: Rect, pad: number): Rect {
150
+ const w = Math.max(r.w - pad * 2, 0);
151
+ const h = Math.max(r.h - pad * 2, 0);
152
+ return { x: r.x + pad, y: r.y + pad, w, h };
153
+ }
154
+
155
+ const cells = $derived.by<Cell[]>(() => {
156
+ if (!data || data.length === 0) return [];
157
+ // Squarify suppose une entrée triée par valeur décroissante pour produire
158
+ // des ratios d'aspect corrects.
159
+ const roots = data
160
+ .map((d, i) => ({ datum: d, value: sumValue(d), tone: d.tone ?? TONES[i % TONES.length] }))
161
+ .filter((n) => n.value > 0)
162
+ .sort((a, b) => b.value - a.value);
163
+ if (roots.length === 0) return [];
164
+
165
+ const topRects = squarify(
166
+ roots.map((r) => ({ datum: r.datum, value: r.value })),
167
+ { x: 0, y: 0, w: width, h: height }
168
+ );
169
+
170
+ const result: Cell[] = [];
171
+ topRects.forEach((tr) => {
172
+ const root = roots.find((r) => r.datum === tr.datum)!;
173
+ const children = (tr.datum.children ?? [])
174
+ .filter((c) => leafValue(c.value) > 0)
175
+ .sort((a, b) => leafValue(b.value) - leafValue(a.value));
176
+ if (children.length > 0) {
177
+ // Niveau 2 : subdiviser l'intérieur du rectangle parent.
178
+ const innerRect = inset(tr.rect, PADDING);
179
+ const childRects = squarify(
180
+ children.map((c) => ({ datum: c, value: leafValue(c.value) })),
181
+ innerRect
182
+ );
183
+ childRects.forEach((cr, ci) => {
184
+ const tone = cr.datum.tone ?? root.tone ?? TONES[ci % TONES.length];
185
+ result.push({
186
+ datum: cr.datum,
187
+ value: cr.value,
188
+ tone,
189
+ textColor: contrastTextForTone(tone),
190
+ rect: inset(cr.rect, PADDING / 2),
191
+ parentLabel: tr.datum.label,
192
+ depth: 1
193
+ });
194
+ });
195
+ } else {
196
+ result.push({
197
+ datum: tr.datum,
198
+ value: tr.value,
199
+ tone: root.tone,
200
+ textColor: contrastTextForTone(root.tone),
201
+ rect: inset(tr.rect, PADDING / 2),
202
+ depth: 0
203
+ });
204
+ }
205
+ });
206
+ return result;
207
+ });
208
+
209
+ // Légende : un swatch par catégorie de premier niveau.
210
+ const legendItems = $derived.by(() => {
211
+ if (!data) return [] as Array<{ label: string; tone: TreemapChartTone }>;
212
+ return data
213
+ .map((d, i) => ({ label: d.label, tone: d.tone ?? TONES[i % TONES.length] }))
214
+ .filter((_, i) => sumValue(data[i]) > 0);
215
+ });
216
+
217
+ const dataValueItems = $derived(
218
+ cells.map((c) =>
219
+ c.parentLabel ? `${c.parentLabel}, ${c.datum.label}: ${c.value}` : `${c.datum.label}: ${c.value}`
220
+ )
221
+ );
222
+
223
+ // Seuils (en unités SVG) pour décider quand afficher un label.
224
+ const LABEL_MIN_W = 44;
225
+ const LABEL_MIN_H = 22;
226
+ const VALUE_MIN_H = 38;
227
+
228
+ // Préfixe d'id unique pour les clip-paths (évite les collisions entre instances).
229
+ const clipPrefix = `st-treemap-clip-${Math.random().toString(36).slice(2, 9)}`;
230
+
231
+ let hoveredIndex: number | null = $state(null);
232
+
233
+ function handleVisualPointerMove(event: PointerEvent) {
234
+ const target = event.target;
235
+ if (!(target instanceof Element)) {
236
+ hoveredIndex = null;
237
+ return;
238
+ }
239
+ const index = Number(target.getAttribute("data-chart-index"));
240
+ hoveredIndex = Number.isInteger(index) ? index : null;
241
+ }
242
+
243
+ const classes = () => ["st-treemapChart", className].filter(Boolean).join(" ");
244
+ </script>
245
+
246
+ <div class={classes()}>
247
+ <div
248
+ class="st-treemapChart__visual"
249
+ role="img"
250
+ aria-label={label}
251
+ onpointermove={handleVisualPointerMove}
252
+ onpointerleave={() => (hoveredIndex = null)}
253
+ >
254
+ <svg
255
+ viewBox="0 0 {width} {height}"
256
+ preserveAspectRatio="xMidYMid meet"
257
+ width="100%"
258
+ height="100%"
259
+ focusable="false"
260
+ aria-hidden="true"
261
+ >
262
+ <defs>
263
+ {#each cells as cell, i (cell.parentLabel ? `${cell.parentLabel}/${cell.datum.label}` : cell.datum.label)}
264
+ <clipPath id="{clipPrefix}-{i}">
265
+ <rect x={cell.rect.x} y={cell.rect.y} width={cell.rect.w} height={cell.rect.h} rx="2" />
266
+ </clipPath>
267
+ {/each}
268
+ </defs>
269
+
270
+ {#each cells as cell, i (cell.parentLabel ? `${cell.parentLabel}/${cell.datum.label}` : cell.datum.label)}
271
+ <g class="st-treemapChart__cell" data-chart-index={i}>
272
+ <rect
273
+ class="st-treemapChart__rect st-treemapChart__rect--{cell.tone}"
274
+ class:st-treemapChart__rect--dim={hoveredIndex !== null && hoveredIndex !== i}
275
+ x={cell.rect.x}
276
+ y={cell.rect.y}
277
+ width={cell.rect.w}
278
+ height={cell.rect.h}
279
+ rx="2"
280
+ data-chart-index={i}
281
+ />
282
+ {#if showLabels && cell.rect.w >= LABEL_MIN_W && cell.rect.h >= LABEL_MIN_H}
283
+ <g clip-path="url(#{clipPrefix}-{i})">
284
+ <text
285
+ class="st-treemapChart__label"
286
+ x={cell.rect.x + 6}
287
+ y={cell.rect.y + 15}
288
+ data-chart-index={i}
289
+ style="fill: {cell.textColor}"
290
+ >
291
+ {cell.datum.label}
292
+ </text>
293
+ {#if cell.rect.h >= VALUE_MIN_H}
294
+ <text
295
+ class="st-treemapChart__value"
296
+ x={cell.rect.x + 6}
297
+ y={cell.rect.y + 30}
298
+ data-chart-index={i}
299
+ style="fill: {cell.textColor}"
300
+ >
301
+ {cell.value}
302
+ </text>
303
+ {/if}
304
+ </g>
305
+ {/if}
306
+ </g>
307
+ {/each}
308
+ </svg>
309
+ </div>
310
+
311
+ <ChartDataList {label} items={dataValueItems} />
312
+
313
+ {#if hoveredIndex !== null && cells[hoveredIndex]}
314
+ {@const cell = cells[hoveredIndex]}
315
+ <div
316
+ class="st-treemapChart__tooltip"
317
+ role="presentation"
318
+ style="left: {((cell.rect.x + cell.rect.w / 2) / width) * 100}%; top: {(cell.rect.y / height) * 100}%"
319
+ >
320
+ <span class="st-treemapChart__tooltipLabel">
321
+ {cell.parentLabel ? `${cell.parentLabel} · ${cell.datum.label}` : cell.datum.label}
322
+ </span>
323
+ <span class="st-treemapChart__tooltipValue">{cell.value}</span>
324
+ </div>
325
+ {/if}
326
+
327
+ {#if legend && legendItems.length > 0}
328
+ <ul class="st-treemapChart__legend" aria-hidden="true">
329
+ {#each legendItems as item (item.label)}
330
+ <li class="st-treemapChart__legendItem">
331
+ <span
332
+ class="st-treemapChart__legendSwatch st-treemapChart__legendSwatch--{item.tone}"
333
+ aria-hidden="true"
334
+ ></span>
335
+ {item.label}
336
+ </li>
337
+ {/each}
338
+ </ul>
339
+ {/if}
340
+ </div>
341
+
342
+ <style>
343
+ .st-treemapChart {
344
+ color: var(--st-semantic-text-secondary);
345
+ display: block;
346
+ font-family: inherit;
347
+ position: relative;
348
+ width: 100%;
349
+ }
350
+
351
+ .st-treemapChart svg,
352
+ .st-treemapChart__visual {
353
+ display: block;
354
+ overflow: visible;
355
+ }
356
+
357
+ .st-treemapChart__rect {
358
+ cursor: pointer;
359
+ stroke: var(--st-semantic-surface-default, #fff);
360
+ stroke-width: 1.5;
361
+ transition: opacity 120ms ease;
362
+ }
363
+
364
+ .st-treemapChart__rect--dim {
365
+ opacity: 0.45;
366
+ }
367
+
368
+ @media (prefers-reduced-motion: reduce) {
369
+ .st-treemapChart__rect {
370
+ transition: none;
371
+ }
372
+ }
373
+
374
+ .st-treemapChart__rect--category1 { fill: var(--st-semantic-data-category1); }
375
+ .st-treemapChart__rect--category2 { fill: var(--st-semantic-data-category2); }
376
+ .st-treemapChart__rect--category3 { fill: var(--st-semantic-data-category3); }
377
+ .st-treemapChart__rect--category4 { fill: var(--st-semantic-data-category4); }
378
+ .st-treemapChart__rect--category5 { fill: var(--st-semantic-data-category5); }
379
+ .st-treemapChart__rect--category6 { fill: var(--st-semantic-data-category6); }
380
+ .st-treemapChart__rect--category7 { fill: var(--st-semantic-data-category7); }
381
+ .st-treemapChart__rect--category8 { fill: var(--st-semantic-data-category8); }
382
+
383
+ .st-treemapChart__label {
384
+ fill: var(--st-component-treemapChart-labelColor, #fff);
385
+ font-size: 0.75rem;
386
+ font-weight: 600;
387
+ pointer-events: none;
388
+ }
389
+
390
+ .st-treemapChart__value {
391
+ fill: var(--st-component-treemapChart-valueColor, #fff);
392
+ font-size: 0.6875rem;
393
+ opacity: 0.85;
394
+ pointer-events: none;
395
+ }
396
+
397
+ .st-treemapChart__tooltip {
398
+ background: var(--st-semantic-surface-inverse);
399
+ border-radius: var(--st-radius-sm, 0.25rem);
400
+ color: var(--st-semantic-text-inverse);
401
+ display: inline-flex;
402
+ flex-direction: column;
403
+ font-size: 0.75rem;
404
+ gap: 0.125rem;
405
+ line-height: 1.2;
406
+ padding: 0.375rem 0.5rem;
407
+ pointer-events: none;
408
+ position: absolute;
409
+ transform: translate(-50%, calc(-100% - 8px));
410
+ white-space: nowrap;
411
+ z-index: 1;
412
+ }
413
+
414
+ .st-treemapChart__tooltipLabel { font-weight: 600; }
415
+ .st-treemapChart__tooltipValue { opacity: 0.85; }
416
+
417
+ .st-treemapChart__legend {
418
+ display: flex;
419
+ flex-wrap: wrap;
420
+ gap: var(--st-spacing-3, 0.75rem);
421
+ list-style: none;
422
+ margin: var(--st-spacing-2, 0.5rem) 0 0;
423
+ padding: 0;
424
+ }
425
+
426
+ .st-treemapChart__legendItem {
427
+ align-items: center;
428
+ color: var(--st-semantic-text-secondary);
429
+ display: inline-flex;
430
+ font-size: 0.75rem;
431
+ gap: var(--st-spacing-1, 0.25rem);
432
+ }
433
+
434
+ .st-treemapChart__legendSwatch {
435
+ border-radius: 2px;
436
+ height: 0.7rem;
437
+ width: 0.7rem;
438
+ }
439
+
440
+ .st-treemapChart__legendSwatch--category1 { background: var(--st-semantic-data-category1); }
441
+ .st-treemapChart__legendSwatch--category2 { background: var(--st-semantic-data-category2); }
442
+ .st-treemapChart__legendSwatch--category3 { background: var(--st-semantic-data-category3); }
443
+ .st-treemapChart__legendSwatch--category4 { background: var(--st-semantic-data-category4); }
444
+ .st-treemapChart__legendSwatch--category5 { background: var(--st-semantic-data-category5); }
445
+ .st-treemapChart__legendSwatch--category6 { background: var(--st-semantic-data-category6); }
446
+ .st-treemapChart__legendSwatch--category7 { background: var(--st-semantic-data-category7); }
447
+ .st-treemapChart__legendSwatch--category8 { background: var(--st-semantic-data-category8); }
448
+ </style>
@@ -0,0 +1,26 @@
1
+ export type TreemapChartTone = "category1" | "category2" | "category3" | "category4" | "category5" | "category6" | "category7" | "category8";
2
+ export type TreemapChartDatum = {
3
+ label: string;
4
+ value: number;
5
+ tone?: TreemapChartTone;
6
+ children?: TreemapChartDatum[];
7
+ };
8
+ export type TreemapTiling = "squarified";
9
+ type TreemapChartProps = {
10
+ /** Données hiérarchiques : 1 ou 2 niveaux. Un nœud avec `children` est subdivisé. */
11
+ data: TreemapChartDatum[];
12
+ /** Algorithme de pavage (squarified uniquement pour l'instant). */
13
+ tiling?: TreemapTiling;
14
+ /** Affiche les labels dans les rectangles suffisamment grands. */
15
+ showLabels?: boolean;
16
+ /** Affiche une légende sous le graphique. */
17
+ legend?: boolean;
18
+ width?: number;
19
+ height?: number;
20
+ label: string;
21
+ class?: string;
22
+ };
23
+ declare const TreemapChart: import("svelte").Component<TreemapChartProps, {}, "">;
24
+ type TreemapChart = ReturnType<typeof TreemapChart>;
25
+ export default TreemapChart;
26
+ //# sourceMappingURL=TreemapChart.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TreemapChart.svelte.d.ts","sourceRoot":"","sources":["../src/lib/TreemapChart.svelte.ts"],"names":[],"mappings":"AAGE,MAAM,MAAM,gBAAgB,GACxB,WAAW,GAAG,WAAW,GAAG,WAAW,GAAG,WAAW,GACrD,WAAW,GAAG,WAAW,GAAG,WAAW,GAAG,WAAW,CAAC;AAE1D,MAAM,MAAM,iBAAiB,GAAG;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,gBAAgB,CAAC;IACxB,QAAQ,CAAC,EAAE,iBAAiB,EAAE,CAAC;CAChC,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG,YAAY,CAAC;AAOzC,KAAK,iBAAiB,GAAG;IACvB,qFAAqF;IACrF,IAAI,EAAE,iBAAiB,EAAE,CAAC;IAC1B,mEAAmE;IACnE,MAAM,CAAC,EAAE,aAAa,CAAC;IACvB,kEAAkE;IAClE,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,6CAA6C;IAC7C,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAwRJ,QAAA,MAAM,YAAY,uDAAwC,CAAC;AAC3D,KAAK,YAAY,GAAG,UAAU,CAAC,OAAO,YAAY,CAAC,CAAC;AACpD,eAAe,YAAY,CAAC"}