@sentropic/design-system-svelte 0.15.0 → 0.17.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.
- package/dist/ComboChart.svelte +620 -0
- package/dist/ComboChart.svelte.d.ts +28 -0
- package/dist/ComboChart.svelte.d.ts.map +1 -0
- package/dist/ForceGraph.svelte +239 -2
- package/dist/ForceGraph.svelte.d.ts +32 -0
- package/dist/ForceGraph.svelte.d.ts.map +1 -1
- package/dist/FunnelChart.svelte +358 -0
- package/dist/FunnelChart.svelte.d.ts +21 -0
- package/dist/FunnelChart.svelte.d.ts.map +1 -0
- package/dist/GaugeChart.svelte +300 -0
- package/dist/GaugeChart.svelte.d.ts +36 -0
- package/dist/GaugeChart.svelte.d.ts.map +1 -0
- package/dist/KpiCard.svelte +318 -0
- package/dist/KpiCard.svelte.d.ts +36 -0
- package/dist/KpiCard.svelte.d.ts.map +1 -0
- package/dist/SelectableList.svelte +186 -0
- package/dist/SelectableList.svelte.d.ts +30 -0
- package/dist/SelectableList.svelte.d.ts.map +1 -0
- package/dist/SelectableRow.svelte +291 -0
- package/dist/SelectableRow.svelte.d.ts +63 -0
- package/dist/SelectableRow.svelte.d.ts.map +1 -0
- package/dist/TreemapChart.svelte +448 -0
- package/dist/TreemapChart.svelte.d.ts +26 -0
- package/dist/TreemapChart.svelte.d.ts.map +1 -0
- package/dist/WaterfallChart.svelte +469 -0
- package/dist/WaterfallChart.svelte.d.ts +19 -0
- package/dist/WaterfallChart.svelte.d.ts.map +1 -0
- package/dist/chartContrast.d.ts +6 -0
- package/dist/chartContrast.d.ts.map +1 -0
- package/dist/chartContrast.js +58 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -0
- package/package.json +1 -1
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export type GaugeChartTone = "neutral" | "info" | "success" | "warning" | "error" | "category1" | "category2" | "category3" | "category4" | "category5" | "category6" | "category7" | "category8";
|
|
2
|
+
/**
|
|
3
|
+
* Seuil de coloration. La bande s'étend depuis `value` (ou le minimum)
|
|
4
|
+
* jusqu'au seuil suivant (ou le maximum). `tone` choisit la couleur.
|
|
5
|
+
*/
|
|
6
|
+
export type GaugeChartThreshold = {
|
|
7
|
+
value: number;
|
|
8
|
+
tone: GaugeChartTone;
|
|
9
|
+
};
|
|
10
|
+
export type GaugeChartFormat = "number" | "percent";
|
|
11
|
+
type GaugeChartProps = {
|
|
12
|
+
value: number;
|
|
13
|
+
min?: number;
|
|
14
|
+
max?: number;
|
|
15
|
+
/** Bandes colorées sur la piste. Triées par `value` croissante. */
|
|
16
|
+
thresholds?: GaugeChartThreshold[];
|
|
17
|
+
/** Libellé décrivant la jauge (a11y + texte sous la valeur). */
|
|
18
|
+
label?: string;
|
|
19
|
+
/** Format de la valeur centrale. */
|
|
20
|
+
format?: GaugeChartFormat;
|
|
21
|
+
/** Suffixe d'unité (ignoré pour `percent`). */
|
|
22
|
+
unit?: string;
|
|
23
|
+
/** Diamètre du SVG. */
|
|
24
|
+
size?: number;
|
|
25
|
+
/** Épaisseur de l'arc. */
|
|
26
|
+
thickness?: number;
|
|
27
|
+
/** Angle de départ en degrés (0 = est, sens horaire). */
|
|
28
|
+
startAngle?: number;
|
|
29
|
+
/** Angle de fin en degrés. */
|
|
30
|
+
endAngle?: number;
|
|
31
|
+
class?: string;
|
|
32
|
+
};
|
|
33
|
+
declare const GaugeChart: import("svelte").Component<GaugeChartProps, {}, "">;
|
|
34
|
+
type GaugeChart = ReturnType<typeof GaugeChart>;
|
|
35
|
+
export default GaugeChart;
|
|
36
|
+
//# sourceMappingURL=GaugeChart.svelte.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"GaugeChart.svelte.d.ts","sourceRoot":"","sources":["../src/lib/GaugeChart.svelte.ts"],"names":[],"mappings":"AAGE,MAAM,MAAM,cAAc,GACtB,SAAS,GAAG,MAAM,GAAG,SAAS,GAAG,SAAS,GAAG,OAAO,GACpD,WAAW,GAAG,WAAW,GAAG,WAAW,GAAG,WAAW,GACrD,WAAW,GAAG,WAAW,GAAG,WAAW,GAAG,WAAW,CAAC;AAE1D;;;GAGG;AACH,MAAM,MAAM,mBAAmB,GAAG;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,cAAc,CAAC;CACtB,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG,QAAQ,GAAG,SAAS,CAAC;AAMpD,KAAK,eAAe,GAAG;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,mEAAmE;IACnE,UAAU,CAAC,EAAE,mBAAmB,EAAE,CAAC;IACnC,gEAAgE;IAChE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,oCAAoC;IACpC,MAAM,CAAC,EAAE,gBAAgB,CAAC;IAC1B,+CAA+C;IAC/C,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,uBAAuB;IACvB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,0BAA0B;IAC1B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,yDAAyD;IACzD,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,8BAA8B;IAC9B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AA2JJ,QAAA,MAAM,UAAU,qDAAwC,CAAC;AACzD,KAAK,UAAU,GAAG,UAAU,CAAC,OAAO,UAAU,CAAC,CAAC;AAChD,eAAe,UAAU,CAAC"}
|
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
<script lang="ts" module>
|
|
2
|
+
export type KpiCardSize = "sm" | "md" | "lg";
|
|
3
|
+
export type KpiCardTrend = "up" | "down" | "flat";
|
|
4
|
+
export type KpiCardFormat = "number" | "currency" | "percent";
|
|
5
|
+
export type KpiCardDeltaFormat = "percent" | "absolute";
|
|
6
|
+
export type KpiCardTone =
|
|
7
|
+
| "category1"
|
|
8
|
+
| "category2"
|
|
9
|
+
| "category3"
|
|
10
|
+
| "category4"
|
|
11
|
+
| "category5"
|
|
12
|
+
| "category6"
|
|
13
|
+
| "category7"
|
|
14
|
+
| "category8";
|
|
15
|
+
</script>
|
|
16
|
+
|
|
17
|
+
<script lang="ts">
|
|
18
|
+
import type { HTMLAttributes } from "svelte/elements";
|
|
19
|
+
import Sparkline from "./Sparkline.svelte";
|
|
20
|
+
|
|
21
|
+
type KpiCardProps = Omit<HTMLAttributes<HTMLElement>, "class"> & {
|
|
22
|
+
/** Valeur principale affichée en grand. */
|
|
23
|
+
value: number | string;
|
|
24
|
+
/** Étiquette de l'indicateur (ex. « Revenu mensuel »). */
|
|
25
|
+
label: string;
|
|
26
|
+
/** Variation par rapport à la période précédente. */
|
|
27
|
+
delta?: number;
|
|
28
|
+
/** Comment exprimer le delta : en pourcentage ou en valeur absolue. */
|
|
29
|
+
deltaFormat?: KpiCardDeltaFormat;
|
|
30
|
+
/** Tendance ; déduite du signe du delta si absente. */
|
|
31
|
+
trend?: KpiCardTrend;
|
|
32
|
+
/** Formatage de la valeur principale via Intl.NumberFormat. */
|
|
33
|
+
format?: KpiCardFormat;
|
|
34
|
+
/** Unité suffixée à la valeur (ex. « ms », « €/mois »). */
|
|
35
|
+
unit?: string;
|
|
36
|
+
/** Code devise ISO 4217 pour format="currency" (défaut EUR). */
|
|
37
|
+
currency?: string;
|
|
38
|
+
/** Locale BCP 47 pour le formatage des nombres (défaut undefined = locale du runtime). */
|
|
39
|
+
locale?: string;
|
|
40
|
+
/** Mini-graphique de tendance optionnel. */
|
|
41
|
+
sparkline?: number[];
|
|
42
|
+
size?: KpiCardSize;
|
|
43
|
+
/** Couleur catégorielle pour l'accent (bordure de gauche). */
|
|
44
|
+
tone?: KpiCardTone;
|
|
45
|
+
class?: string;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
let {
|
|
49
|
+
value,
|
|
50
|
+
label,
|
|
51
|
+
delta,
|
|
52
|
+
deltaFormat = "percent",
|
|
53
|
+
trend,
|
|
54
|
+
format = "number",
|
|
55
|
+
unit,
|
|
56
|
+
currency = "EUR",
|
|
57
|
+
locale,
|
|
58
|
+
sparkline,
|
|
59
|
+
size = "md",
|
|
60
|
+
tone,
|
|
61
|
+
class: className,
|
|
62
|
+
...rest
|
|
63
|
+
}: KpiCardProps = $props();
|
|
64
|
+
|
|
65
|
+
const resolvedTrend = $derived<KpiCardTrend | undefined>(
|
|
66
|
+
trend ?? (delta == null ? undefined : delta > 0 ? "up" : delta < 0 ? "down" : "flat")
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
const formattedValue = $derived.by(() => {
|
|
70
|
+
if (typeof value === "string") {
|
|
71
|
+
return value;
|
|
72
|
+
}
|
|
73
|
+
if (format === "currency") {
|
|
74
|
+
return new Intl.NumberFormat(locale, { style: "currency", currency }).format(value);
|
|
75
|
+
}
|
|
76
|
+
if (format === "percent") {
|
|
77
|
+
return new Intl.NumberFormat(locale, { style: "percent", maximumFractionDigits: 2 }).format(
|
|
78
|
+
value
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
return new Intl.NumberFormat(locale).format(value);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
const formattedDelta = $derived.by(() => {
|
|
85
|
+
if (delta == null) {
|
|
86
|
+
return undefined;
|
|
87
|
+
}
|
|
88
|
+
const sign = delta > 0 ? "+" : "";
|
|
89
|
+
if (deltaFormat === "percent") {
|
|
90
|
+
const pct = new Intl.NumberFormat(locale, {
|
|
91
|
+
style: "percent",
|
|
92
|
+
maximumFractionDigits: 1
|
|
93
|
+
}).format(delta);
|
|
94
|
+
return `${sign}${pct}`;
|
|
95
|
+
}
|
|
96
|
+
return `${sign}${new Intl.NumberFormat(locale).format(delta)}`;
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
/** Le sparkline emprunte la couleur sémantique de la tendance. */
|
|
100
|
+
const sparklineTone = $derived(
|
|
101
|
+
resolvedTrend === "up" ? "success" : resolvedTrend === "down" ? "error" : "neutral"
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
const arrow = $derived(
|
|
105
|
+
resolvedTrend === "up" ? "M3 8.5 7 4l4 4.5" : resolvedTrend === "down" ? "M3 5.5 7 10l4-4.5" : "M3 7h8"
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
const trendLabel = $derived(
|
|
109
|
+
resolvedTrend === "up"
|
|
110
|
+
? "en hausse"
|
|
111
|
+
: resolvedTrend === "down"
|
|
112
|
+
? "en baisse"
|
|
113
|
+
: resolvedTrend === "flat"
|
|
114
|
+
? "stable"
|
|
115
|
+
: undefined
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
const ariaLabel = $derived(
|
|
119
|
+
[label, formattedValue, unit, formattedDelta && `${formattedDelta} ${trendLabel ?? ""}`.trim()]
|
|
120
|
+
.filter(Boolean)
|
|
121
|
+
.join(", ")
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
const classes = $derived(
|
|
125
|
+
[
|
|
126
|
+
"st-kpiCard",
|
|
127
|
+
`st-kpiCard--${size}`,
|
|
128
|
+
tone && `st-kpiCard--${tone}`,
|
|
129
|
+
tone && "st-kpiCard--toned",
|
|
130
|
+
className
|
|
131
|
+
]
|
|
132
|
+
.filter(Boolean)
|
|
133
|
+
.join(" ")
|
|
134
|
+
);
|
|
135
|
+
</script>
|
|
136
|
+
|
|
137
|
+
<article {...rest} class={classes} role="group" aria-label={ariaLabel}>
|
|
138
|
+
<p class="st-kpiCard__label">{label}</p>
|
|
139
|
+
|
|
140
|
+
<p class="st-kpiCard__value">
|
|
141
|
+
<span class="st-kpiCard__number">{formattedValue}</span>
|
|
142
|
+
{#if unit}
|
|
143
|
+
<span class="st-kpiCard__unit">{unit}</span>
|
|
144
|
+
{/if}
|
|
145
|
+
</p>
|
|
146
|
+
|
|
147
|
+
{#if formattedDelta || sparkline}
|
|
148
|
+
<div class="st-kpiCard__footer">
|
|
149
|
+
{#if formattedDelta}
|
|
150
|
+
<span
|
|
151
|
+
class="st-kpiCard__delta st-kpiCard__delta--{resolvedTrend ?? 'flat'}"
|
|
152
|
+
aria-hidden="true"
|
|
153
|
+
>
|
|
154
|
+
<svg
|
|
155
|
+
class="st-kpiCard__arrow"
|
|
156
|
+
width="14"
|
|
157
|
+
height="14"
|
|
158
|
+
viewBox="0 0 14 14"
|
|
159
|
+
aria-hidden="true"
|
|
160
|
+
focusable="false"
|
|
161
|
+
>
|
|
162
|
+
<path
|
|
163
|
+
d={arrow}
|
|
164
|
+
fill="none"
|
|
165
|
+
stroke="currentColor"
|
|
166
|
+
stroke-width="1.75"
|
|
167
|
+
stroke-linecap="round"
|
|
168
|
+
stroke-linejoin="round"
|
|
169
|
+
/>
|
|
170
|
+
</svg>
|
|
171
|
+
<span class="st-kpiCard__deltaValue">{formattedDelta}</span>
|
|
172
|
+
</span>
|
|
173
|
+
{/if}
|
|
174
|
+
|
|
175
|
+
{#if sparkline && sparkline.length > 0}
|
|
176
|
+
<Sparkline
|
|
177
|
+
class="st-kpiCard__sparkline"
|
|
178
|
+
data={sparkline}
|
|
179
|
+
tone={sparklineTone}
|
|
180
|
+
area
|
|
181
|
+
/>
|
|
182
|
+
{/if}
|
|
183
|
+
</div>
|
|
184
|
+
{/if}
|
|
185
|
+
</article>
|
|
186
|
+
|
|
187
|
+
<style>
|
|
188
|
+
.st-kpiCard {
|
|
189
|
+
background: var(--st-component-card-background, var(--st-semantic-surface-raised));
|
|
190
|
+
border-width: var(--st-component-card-anatomy-shape-borderWidth, 1px);
|
|
191
|
+
border-style: var(--st-component-card-anatomy-shape-borderStyle, solid);
|
|
192
|
+
border-color: var(--st-component-card-border, var(--st-semantic-border-subtle));
|
|
193
|
+
border-radius: var(--st-component-card-anatomy-shape-radius, 0.5rem);
|
|
194
|
+
box-shadow: var(--st-component-card-shadow, 0 1px 2px rgb(15 23 42 / 0.08));
|
|
195
|
+
color: var(--st-semantic-text-primary);
|
|
196
|
+
display: flex;
|
|
197
|
+
flex-direction: column;
|
|
198
|
+
gap: var(--st-spacing-2, 0.5rem);
|
|
199
|
+
padding: var(--st-spacing-4, 1rem);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
.st-kpiCard--sm {
|
|
203
|
+
gap: var(--st-spacing-1, 0.25rem);
|
|
204
|
+
padding: var(--st-spacing-3, 0.75rem);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
.st-kpiCard--lg {
|
|
208
|
+
gap: var(--st-spacing-3, 0.75rem);
|
|
209
|
+
padding: var(--st-spacing-6, 1.5rem);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/* Accent catégoriel : liseré coloré à gauche. */
|
|
213
|
+
.st-kpiCard--toned {
|
|
214
|
+
border-inline-start-width: var(--st-spacing-1, 0.25rem);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
.st-kpiCard__label {
|
|
218
|
+
color: var(--st-semantic-text-secondary);
|
|
219
|
+
font-size: 0.8125rem;
|
|
220
|
+
font-weight: 500;
|
|
221
|
+
line-height: 1.25;
|
|
222
|
+
margin: 0;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
.st-kpiCard--lg .st-kpiCard__label {
|
|
226
|
+
font-size: 0.875rem;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
.st-kpiCard__value {
|
|
230
|
+
align-items: baseline;
|
|
231
|
+
color: var(--st-semantic-text-primary);
|
|
232
|
+
display: flex;
|
|
233
|
+
flex-wrap: wrap;
|
|
234
|
+
gap: var(--st-spacing-1, 0.25rem);
|
|
235
|
+
margin: 0;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
.st-kpiCard__number {
|
|
239
|
+
font-size: 1.5rem;
|
|
240
|
+
font-weight: 700;
|
|
241
|
+
letter-spacing: -0.01em;
|
|
242
|
+
line-height: 1.1;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
.st-kpiCard--sm .st-kpiCard__number {
|
|
246
|
+
font-size: 1.25rem;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
.st-kpiCard--lg .st-kpiCard__number {
|
|
250
|
+
font-size: 2.25rem;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
.st-kpiCard__unit {
|
|
254
|
+
color: var(--st-semantic-text-secondary);
|
|
255
|
+
font-size: 0.875rem;
|
|
256
|
+
font-weight: 500;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
.st-kpiCard__footer {
|
|
260
|
+
align-items: center;
|
|
261
|
+
display: flex;
|
|
262
|
+
flex-wrap: wrap;
|
|
263
|
+
gap: var(--st-spacing-3, 0.75rem);
|
|
264
|
+
justify-content: space-between;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
.st-kpiCard__delta {
|
|
268
|
+
align-items: center;
|
|
269
|
+
display: inline-flex;
|
|
270
|
+
font-size: 0.8125rem;
|
|
271
|
+
font-weight: 600;
|
|
272
|
+
gap: var(--st-spacing-1, 0.25rem);
|
|
273
|
+
line-height: 1;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
.st-kpiCard__arrow {
|
|
277
|
+
display: block;
|
|
278
|
+
flex: 0 0 auto;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
.st-kpiCard__delta--up {
|
|
282
|
+
color: var(--st-semantic-feedback-success);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
.st-kpiCard__delta--down {
|
|
286
|
+
color: var(--st-semantic-feedback-error);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
.st-kpiCard__delta--flat {
|
|
290
|
+
color: var(--st-semantic-text-secondary);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/* Liserés catégoriels — alignés sur la palette data des autres composants. */
|
|
294
|
+
.st-kpiCard--category1 {
|
|
295
|
+
border-inline-start-color: var(--st-semantic-data-category1);
|
|
296
|
+
}
|
|
297
|
+
.st-kpiCard--category2 {
|
|
298
|
+
border-inline-start-color: var(--st-semantic-data-category2);
|
|
299
|
+
}
|
|
300
|
+
.st-kpiCard--category3 {
|
|
301
|
+
border-inline-start-color: var(--st-semantic-data-category3);
|
|
302
|
+
}
|
|
303
|
+
.st-kpiCard--category4 {
|
|
304
|
+
border-inline-start-color: var(--st-semantic-data-category4);
|
|
305
|
+
}
|
|
306
|
+
.st-kpiCard--category5 {
|
|
307
|
+
border-inline-start-color: var(--st-semantic-data-category5);
|
|
308
|
+
}
|
|
309
|
+
.st-kpiCard--category6 {
|
|
310
|
+
border-inline-start-color: var(--st-semantic-data-category6);
|
|
311
|
+
}
|
|
312
|
+
.st-kpiCard--category7 {
|
|
313
|
+
border-inline-start-color: var(--st-semantic-data-category7);
|
|
314
|
+
}
|
|
315
|
+
.st-kpiCard--category8 {
|
|
316
|
+
border-inline-start-color: var(--st-semantic-data-category8);
|
|
317
|
+
}
|
|
318
|
+
</style>
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export type KpiCardSize = "sm" | "md" | "lg";
|
|
2
|
+
export type KpiCardTrend = "up" | "down" | "flat";
|
|
3
|
+
export type KpiCardFormat = "number" | "currency" | "percent";
|
|
4
|
+
export type KpiCardDeltaFormat = "percent" | "absolute";
|
|
5
|
+
export type KpiCardTone = "category1" | "category2" | "category3" | "category4" | "category5" | "category6" | "category7" | "category8";
|
|
6
|
+
import type { HTMLAttributes } from "svelte/elements";
|
|
7
|
+
type KpiCardProps = Omit<HTMLAttributes<HTMLElement>, "class"> & {
|
|
8
|
+
/** Valeur principale affichée en grand. */
|
|
9
|
+
value: number | string;
|
|
10
|
+
/** Étiquette de l'indicateur (ex. « Revenu mensuel »). */
|
|
11
|
+
label: string;
|
|
12
|
+
/** Variation par rapport à la période précédente. */
|
|
13
|
+
delta?: number;
|
|
14
|
+
/** Comment exprimer le delta : en pourcentage ou en valeur absolue. */
|
|
15
|
+
deltaFormat?: KpiCardDeltaFormat;
|
|
16
|
+
/** Tendance ; déduite du signe du delta si absente. */
|
|
17
|
+
trend?: KpiCardTrend;
|
|
18
|
+
/** Formatage de la valeur principale via Intl.NumberFormat. */
|
|
19
|
+
format?: KpiCardFormat;
|
|
20
|
+
/** Unité suffixée à la valeur (ex. « ms », « €/mois »). */
|
|
21
|
+
unit?: string;
|
|
22
|
+
/** Code devise ISO 4217 pour format="currency" (défaut EUR). */
|
|
23
|
+
currency?: string;
|
|
24
|
+
/** Locale BCP 47 pour le formatage des nombres (défaut undefined = locale du runtime). */
|
|
25
|
+
locale?: string;
|
|
26
|
+
/** Mini-graphique de tendance optionnel. */
|
|
27
|
+
sparkline?: number[];
|
|
28
|
+
size?: KpiCardSize;
|
|
29
|
+
/** Couleur catégorielle pour l'accent (bordure de gauche). */
|
|
30
|
+
tone?: KpiCardTone;
|
|
31
|
+
class?: string;
|
|
32
|
+
};
|
|
33
|
+
declare const KpiCard: import("svelte").Component<KpiCardProps, {}, "">;
|
|
34
|
+
type KpiCard = ReturnType<typeof KpiCard>;
|
|
35
|
+
export default KpiCard;
|
|
36
|
+
//# sourceMappingURL=KpiCard.svelte.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"KpiCard.svelte.d.ts","sourceRoot":"","sources":["../src/lib/KpiCard.svelte.ts"],"names":[],"mappings":"AAGE,MAAM,MAAM,WAAW,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;AAC7C,MAAM,MAAM,YAAY,GAAG,IAAI,GAAG,MAAM,GAAG,MAAM,CAAC;AAClD,MAAM,MAAM,aAAa,GAAG,QAAQ,GAAG,UAAU,GAAG,SAAS,CAAC;AAC9D,MAAM,MAAM,kBAAkB,GAAG,SAAS,GAAG,UAAU,CAAC;AACxD,MAAM,MAAM,WAAW,GACnB,WAAW,GACX,WAAW,GACX,WAAW,GACX,WAAW,GACX,WAAW,GACX,WAAW,GACX,WAAW,GACX,WAAW,CAAC;AAGlB,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAIpD,KAAK,YAAY,GAAG,IAAI,CAAC,cAAc,CAAC,WAAW,CAAC,EAAE,OAAO,CAAC,GAAG;IAC/D,2CAA2C;IAC3C,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC;IACvB,0DAA0D;IAC1D,KAAK,EAAE,MAAM,CAAC;IACd,qDAAqD;IACrD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,uEAAuE;IACvE,WAAW,CAAC,EAAE,kBAAkB,CAAC;IACjC,uDAAuD;IACvD,KAAK,CAAC,EAAE,YAAY,CAAC;IACrB,+DAA+D;IAC/D,MAAM,CAAC,EAAE,aAAa,CAAC;IACvB,2DAA2D;IAC3D,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,gEAAgE;IAChE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,0FAA0F;IAC1F,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,4CAA4C;IAC5C,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,IAAI,CAAC,EAAE,WAAW,CAAC;IACnB,8DAA8D;IAC9D,IAAI,CAAC,EAAE,WAAW,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAiIJ,QAAA,MAAM,OAAO,kDAAwC,CAAC;AACtD,KAAK,OAAO,GAAG,UAAU,CAAC,OAAO,OAAO,CAAC,CAAC;AAC1C,eAAe,OAAO,CAAC"}
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
<script lang="ts" module>
|
|
2
|
+
import type { Snippet } from "svelte";
|
|
3
|
+
|
|
4
|
+
export type SelectableListProps = {
|
|
5
|
+
/** Accessible name for the listbox (required for SR users). */
|
|
6
|
+
label?: string;
|
|
7
|
+
/** References the id of an external visible label (alternative to `label`). */
|
|
8
|
+
labelledby?: string;
|
|
9
|
+
/**
|
|
10
|
+
* Allow more than one selected row. Adds aria-multiselectable and toggles
|
|
11
|
+
* each row independently. Defaults to false (single-select).
|
|
12
|
+
*/
|
|
13
|
+
multiple?: boolean;
|
|
14
|
+
/**
|
|
15
|
+
* Selected value(s). Controlled when provided. For single-select pass a
|
|
16
|
+
* string (or null); for multiple pass a string[]. When omitted the list is
|
|
17
|
+
* uncontrolled and keeps its own internal selection.
|
|
18
|
+
*/
|
|
19
|
+
value?: string | string[] | null;
|
|
20
|
+
/**
|
|
21
|
+
* Fired with the new selection on every change. Receives a string|null for
|
|
22
|
+
* single-select and a string[] for multiple. Required for the controlled
|
|
23
|
+
* pattern; also fires for uncontrolled lists.
|
|
24
|
+
*/
|
|
25
|
+
onchange?: (value: string | string[] | null) => void;
|
|
26
|
+
class?: string;
|
|
27
|
+
children?: Snippet;
|
|
28
|
+
};
|
|
29
|
+
</script>
|
|
30
|
+
|
|
31
|
+
<script lang="ts">
|
|
32
|
+
import { setContext, untrack } from "svelte";
|
|
33
|
+
import {
|
|
34
|
+
SELECTABLE_LIST_KEY,
|
|
35
|
+
type SelectableListContext
|
|
36
|
+
} from "./SelectableRow.svelte";
|
|
37
|
+
|
|
38
|
+
let {
|
|
39
|
+
label,
|
|
40
|
+
labelledby,
|
|
41
|
+
multiple = false,
|
|
42
|
+
value,
|
|
43
|
+
onchange,
|
|
44
|
+
class: className,
|
|
45
|
+
children
|
|
46
|
+
}: SelectableListProps = $props();
|
|
47
|
+
|
|
48
|
+
// Controlled when the consumer passes `value` (including null). Otherwise the
|
|
49
|
+
// list owns an internal selection set.
|
|
50
|
+
const controlled = $derived(value !== undefined);
|
|
51
|
+
|
|
52
|
+
function toSet(v: string | string[] | null | undefined): Set<string> {
|
|
53
|
+
if (v == null) return new Set();
|
|
54
|
+
return new Set(Array.isArray(v) ? v : [v]);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Internal selection for the uncontrolled case.
|
|
58
|
+
let internal = $state<Set<string>>(new Set());
|
|
59
|
+
const selectedValues = $derived(controlled ? toSet(value) : internal);
|
|
60
|
+
|
|
61
|
+
// --- Row registry: ordered by DOM position so arrow nav matches the visual
|
|
62
|
+
// order regardless of registration timing. -------------------------------
|
|
63
|
+
type Entry = { el: HTMLElement; value: string | undefined };
|
|
64
|
+
let entries = $state<Entry[]>([]);
|
|
65
|
+
|
|
66
|
+
// The element that currently holds the roving tab stop (tabindex 0). Null until
|
|
67
|
+
// a row is focused; until then the FIRST enabled row is the default stop.
|
|
68
|
+
let tabStopEl = $state<HTMLElement | null>(null);
|
|
69
|
+
|
|
70
|
+
function sortByDom(list: Entry[]): Entry[] {
|
|
71
|
+
return [...list].sort((a, b) => {
|
|
72
|
+
const pos = a.el.compareDocumentPosition(b.el);
|
|
73
|
+
if (pos & Node.DOCUMENT_POSITION_FOLLOWING) return -1;
|
|
74
|
+
if (pos & Node.DOCUMENT_POSITION_PRECEDING) return 1;
|
|
75
|
+
return 0;
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// register/unregister are called from each row's $effect. They read AND write
|
|
80
|
+
// `entries`, so the read must be untracked — otherwise the calling effect would
|
|
81
|
+
// subscribe to `entries`, and writing it would re-run the effect forever.
|
|
82
|
+
function register(el: HTMLElement, rowValue: string | undefined): () => void {
|
|
83
|
+
untrack(() => {
|
|
84
|
+
entries = sortByDom([...entries.filter((e) => e.el !== el), { el, value: rowValue }]);
|
|
85
|
+
});
|
|
86
|
+
return () => {
|
|
87
|
+
untrack(() => {
|
|
88
|
+
entries = entries.filter((e) => e.el !== el);
|
|
89
|
+
if (tabStopEl === el) tabStopEl = null;
|
|
90
|
+
});
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Default roving stop = first registered (DOM-ordered) row when none focused.
|
|
95
|
+
const effectiveTabStop = $derived(tabStopEl ?? entries[0]?.el ?? null);
|
|
96
|
+
|
|
97
|
+
function valueOf(el: HTMLElement): string | undefined {
|
|
98
|
+
return entries.find((e) => e.el === el)?.value;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function isSelected(el: HTMLElement): boolean {
|
|
102
|
+
const v = valueOf(el);
|
|
103
|
+
return v !== undefined && selectedValues.has(v);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function isTabStop(el: HTMLElement): boolean {
|
|
107
|
+
return el === effectiveTabStop;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function emit(next: Set<string>) {
|
|
111
|
+
if (!controlled) internal = next;
|
|
112
|
+
if (multiple) onchange?.([...next]);
|
|
113
|
+
else onchange?.(next.size ? [...next][0] : null);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function activate(el: HTMLElement) {
|
|
117
|
+
const v = valueOf(el);
|
|
118
|
+
if (v === undefined) return;
|
|
119
|
+
const current = selectedValues;
|
|
120
|
+
let next: Set<string>;
|
|
121
|
+
if (multiple) {
|
|
122
|
+
next = new Set(current);
|
|
123
|
+
if (next.has(v)) next.delete(v);
|
|
124
|
+
else next.add(v);
|
|
125
|
+
} else {
|
|
126
|
+
// Single-select toggles off when re-activating the selected row.
|
|
127
|
+
next = current.has(v) && current.size === 1 ? new Set() : new Set([v]);
|
|
128
|
+
}
|
|
129
|
+
emit(next);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function focusRow(el: HTMLElement) {
|
|
133
|
+
tabStopEl = el;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function navigate(el: HTMLElement, key: string) {
|
|
137
|
+
if (entries.length === 0) return;
|
|
138
|
+
const idx = entries.findIndex((e) => e.el === el);
|
|
139
|
+
if (idx === -1) return;
|
|
140
|
+
let targetIdx = idx;
|
|
141
|
+
if (key === "ArrowDown" || key === "ArrowRight") targetIdx = idx + 1;
|
|
142
|
+
else if (key === "ArrowUp" || key === "ArrowLeft") targetIdx = idx - 1;
|
|
143
|
+
else if (key === "Home") targetIdx = 0;
|
|
144
|
+
else if (key === "End") targetIdx = entries.length - 1;
|
|
145
|
+
// Clamp (no wrap) so Home/End and arrows stay within bounds.
|
|
146
|
+
targetIdx = Math.max(0, Math.min(entries.length - 1, targetIdx));
|
|
147
|
+
const target = entries[targetIdx]?.el;
|
|
148
|
+
if (target) {
|
|
149
|
+
tabStopEl = target;
|
|
150
|
+
target.focus();
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const context: SelectableListContext = {
|
|
155
|
+
managed: true,
|
|
156
|
+
itemRole: "option",
|
|
157
|
+
register,
|
|
158
|
+
isSelected,
|
|
159
|
+
isTabStop,
|
|
160
|
+
activate,
|
|
161
|
+
focusRow,
|
|
162
|
+
navigate
|
|
163
|
+
};
|
|
164
|
+
setContext(SELECTABLE_LIST_KEY, context);
|
|
165
|
+
|
|
166
|
+
const classes = $derived(["st-selectableList", className].filter(Boolean).join(" "));
|
|
167
|
+
</script>
|
|
168
|
+
|
|
169
|
+
<div
|
|
170
|
+
class={classes}
|
|
171
|
+
role="listbox"
|
|
172
|
+
aria-label={labelledby ? undefined : label}
|
|
173
|
+
aria-labelledby={labelledby}
|
|
174
|
+
aria-multiselectable={multiple ? "true" : undefined}
|
|
175
|
+
>
|
|
176
|
+
{@render children?.()}
|
|
177
|
+
</div>
|
|
178
|
+
|
|
179
|
+
<style>
|
|
180
|
+
.st-selectableList {
|
|
181
|
+
display: flex;
|
|
182
|
+
flex-direction: column;
|
|
183
|
+
gap: var(--st-spacing-1, 0.25rem);
|
|
184
|
+
width: 100%;
|
|
185
|
+
}
|
|
186
|
+
</style>
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { Snippet } from "svelte";
|
|
2
|
+
export type SelectableListProps = {
|
|
3
|
+
/** Accessible name for the listbox (required for SR users). */
|
|
4
|
+
label?: string;
|
|
5
|
+
/** References the id of an external visible label (alternative to `label`). */
|
|
6
|
+
labelledby?: string;
|
|
7
|
+
/**
|
|
8
|
+
* Allow more than one selected row. Adds aria-multiselectable and toggles
|
|
9
|
+
* each row independently. Defaults to false (single-select).
|
|
10
|
+
*/
|
|
11
|
+
multiple?: boolean;
|
|
12
|
+
/**
|
|
13
|
+
* Selected value(s). Controlled when provided. For single-select pass a
|
|
14
|
+
* string (or null); for multiple pass a string[]. When omitted the list is
|
|
15
|
+
* uncontrolled and keeps its own internal selection.
|
|
16
|
+
*/
|
|
17
|
+
value?: string | string[] | null;
|
|
18
|
+
/**
|
|
19
|
+
* Fired with the new selection on every change. Receives a string|null for
|
|
20
|
+
* single-select and a string[] for multiple. Required for the controlled
|
|
21
|
+
* pattern; also fires for uncontrolled lists.
|
|
22
|
+
*/
|
|
23
|
+
onchange?: (value: string | string[] | null) => void;
|
|
24
|
+
class?: string;
|
|
25
|
+
children?: Snippet;
|
|
26
|
+
};
|
|
27
|
+
declare const SelectableList: import("svelte").Component<SelectableListProps, {}, "">;
|
|
28
|
+
type SelectableList = ReturnType<typeof SelectableList>;
|
|
29
|
+
export default SelectableList;
|
|
30
|
+
//# sourceMappingURL=SelectableList.svelte.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SelectableList.svelte.d.ts","sourceRoot":"","sources":["../src/lib/SelectableList.svelte.ts"],"names":[],"mappings":"AAGE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AAEtC,MAAM,MAAM,mBAAmB,GAAG;IAChC,+DAA+D;IAC/D,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,+EAA+E;IAC/E,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;OAGG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;;;OAIG;IACH,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,IAAI,CAAC;IACjC;;;;OAIG;IACH,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,IAAI,KAAK,IAAI,CAAC;IACrD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB,CAAC;AA0JJ,QAAA,MAAM,cAAc,yDAAwC,CAAC;AAC7D,KAAK,cAAc,GAAG,UAAU,CAAC,OAAO,cAAc,CAAC,CAAC;AACxD,eAAe,cAAc,CAAC"}
|