@sentropic/design-system-svelte 0.34.0 → 0.34.21

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 (112) hide show
  1. package/dist/AppChrome.svelte +660 -0
  2. package/dist/AppChrome.svelte.d.ts +74 -0
  3. package/dist/AppChrome.svelte.d.ts.map +1 -0
  4. package/dist/AppChrome.test.d.ts +2 -0
  5. package/dist/AppChrome.test.d.ts.map +1 -0
  6. package/dist/AppChrome.test.js +122 -0
  7. package/dist/AppHeader.svelte +159 -1
  8. package/dist/AppHeader.svelte.d.ts +18 -1
  9. package/dist/AppHeader.svelte.d.ts.map +1 -1
  10. package/dist/ArcDiagramChart.svelte +380 -0
  11. package/dist/ArcDiagramChart.svelte.d.ts +43 -0
  12. package/dist/ArcDiagramChart.svelte.d.ts.map +1 -0
  13. package/dist/AreaRangeChart.svelte +487 -0
  14. package/dist/AreaRangeChart.svelte.d.ts +38 -0
  15. package/dist/AreaRangeChart.svelte.d.ts.map +1 -0
  16. package/dist/AreaSplineRangeChart.svelte +478 -0
  17. package/dist/AreaSplineRangeChart.svelte.d.ts +37 -0
  18. package/dist/AreaSplineRangeChart.svelte.d.ts.map +1 -0
  19. package/dist/BellCurveChart.svelte +487 -0
  20. package/dist/BellCurveChart.svelte.d.ts +40 -0
  21. package/dist/BellCurveChart.svelte.d.ts.map +1 -0
  22. package/dist/Calendar.svelte +11 -0
  23. package/dist/ChatThread.svelte +32 -1
  24. package/dist/ChatThread.svelte.d.ts +14 -0
  25. package/dist/ChatThread.svelte.d.ts.map +1 -1
  26. package/dist/ColumnPyramidChart.svelte +332 -0
  27. package/dist/ColumnPyramidChart.svelte.d.ts +35 -0
  28. package/dist/ColumnPyramidChart.svelte.d.ts.map +1 -0
  29. package/dist/ColumnRangeChart.svelte +432 -0
  30. package/dist/ColumnRangeChart.svelte.d.ts +42 -0
  31. package/dist/ColumnRangeChart.svelte.d.ts.map +1 -0
  32. package/dist/Combobox.svelte +3 -0
  33. package/dist/ConfigItemCard.svelte +303 -0
  34. package/dist/ConfigItemCard.svelte.d.ts +37 -0
  35. package/dist/ConfigItemCard.svelte.d.ts.map +1 -0
  36. package/dist/ContentSwitcher.svelte +1 -1
  37. package/dist/DatePicker.svelte +3 -0
  38. package/dist/DependencyWheelChart.svelte +413 -0
  39. package/dist/DependencyWheelChart.svelte.d.ts +42 -0
  40. package/dist/DependencyWheelChart.svelte.d.ts.map +1 -0
  41. package/dist/DumbbellChart.svelte +403 -0
  42. package/dist/DumbbellChart.svelte.d.ts +44 -0
  43. package/dist/DumbbellChart.svelte.d.ts.map +1 -0
  44. package/dist/ErrorBarChart.svelte +428 -0
  45. package/dist/ErrorBarChart.svelte.d.ts +40 -0
  46. package/dist/ErrorBarChart.svelte.d.ts.map +1 -0
  47. package/dist/FieldCard.svelte +220 -0
  48. package/dist/FieldCard.svelte.d.ts +28 -0
  49. package/dist/FieldCard.svelte.d.ts.map +1 -0
  50. package/dist/GanttChart.svelte +410 -0
  51. package/dist/GanttChart.svelte.d.ts +39 -0
  52. package/dist/GanttChart.svelte.d.ts.map +1 -0
  53. package/dist/HLCChart.svelte +330 -0
  54. package/dist/HLCChart.svelte.d.ts +32 -0
  55. package/dist/HLCChart.svelte.d.ts.map +1 -0
  56. package/dist/HeikinAshiChart.svelte +365 -0
  57. package/dist/HeikinAshiChart.svelte.d.ts +37 -0
  58. package/dist/HeikinAshiChart.svelte.d.ts.map +1 -0
  59. package/dist/HollowCandlestickChart.svelte +357 -0
  60. package/dist/HollowCandlestickChart.svelte.d.ts +34 -0
  61. package/dist/HollowCandlestickChart.svelte.d.ts.map +1 -0
  62. package/dist/Input.svelte +3 -0
  63. package/dist/ItemChart.svelte +389 -0
  64. package/dist/ItemChart.svelte.d.ts +67 -0
  65. package/dist/ItemChart.svelte.d.ts.map +1 -0
  66. package/dist/LollipopChart.svelte +1 -1
  67. package/dist/MultiSelect.svelte +3 -0
  68. package/dist/NumberInput.svelte +3 -0
  69. package/dist/OHLCChart.svelte +343 -0
  70. package/dist/OHLCChart.svelte.d.ts +33 -0
  71. package/dist/OHLCChart.svelte.d.ts.map +1 -0
  72. package/dist/OrganizationChart.svelte +284 -0
  73. package/dist/OrganizationChart.svelte.d.ts +19 -0
  74. package/dist/OrganizationChart.svelte.d.ts.map +1 -0
  75. package/dist/PasswordInput.svelte +3 -0
  76. package/dist/PolygonChart.svelte +189 -0
  77. package/dist/PolygonChart.svelte.d.ts +17 -0
  78. package/dist/PolygonChart.svelte.d.ts.map +1 -0
  79. package/dist/ScoreCard.svelte +172 -0
  80. package/dist/ScoreCard.svelte.d.ts +27 -0
  81. package/dist/ScoreCard.svelte.d.ts.map +1 -0
  82. package/dist/Search.svelte +7 -5
  83. package/dist/Select.svelte +3 -0
  84. package/dist/StreamgraphChart.svelte +283 -0
  85. package/dist/StreamgraphChart.svelte.d.ts +23 -0
  86. package/dist/StreamgraphChart.svelte.d.ts.map +1 -0
  87. package/dist/StreamingMessage.svelte +44 -2
  88. package/dist/StreamingMessage.svelte.d.ts +18 -1
  89. package/dist/StreamingMessage.svelte.d.ts.map +1 -1
  90. package/dist/TileMapChart.svelte +314 -0
  91. package/dist/TileMapChart.svelte.d.ts +45 -0
  92. package/dist/TileMapChart.svelte.d.ts.map +1 -0
  93. package/dist/TimePicker.svelte +3 -0
  94. package/dist/TimelineChart.svelte +362 -0
  95. package/dist/TimelineChart.svelte.d.ts +22 -0
  96. package/dist/TimelineChart.svelte.d.ts.map +1 -0
  97. package/dist/TreegraphChart.svelte +281 -0
  98. package/dist/TreegraphChart.svelte.d.ts +19 -0
  99. package/dist/TreegraphChart.svelte.d.ts.map +1 -0
  100. package/dist/VariablePieChart.svelte +313 -0
  101. package/dist/VariablePieChart.svelte.d.ts +52 -0
  102. package/dist/VariablePieChart.svelte.d.ts.map +1 -0
  103. package/dist/VennChart.svelte +348 -0
  104. package/dist/VennChart.svelte.d.ts +72 -0
  105. package/dist/VennChart.svelte.d.ts.map +1 -0
  106. package/dist/WordCloudChart.svelte +279 -0
  107. package/dist/WordCloudChart.svelte.d.ts +18 -0
  108. package/dist/WordCloudChart.svelte.d.ts.map +1 -0
  109. package/dist/index.d.ts +56 -0
  110. package/dist/index.d.ts.map +1 -1
  111. package/dist/index.js +28 -0
  112. package/package.json +5 -3
@@ -0,0 +1,343 @@
1
+ <script lang="ts" module>
2
+ /**
3
+ * OHLCChart - barres open/high/low/close (bâtons financiers).
4
+ * Variante du CandlestickChart : barre verticale du low au high,
5
+ * tick gauche au niveau open, tick droite au niveau close.
6
+ * API canonique (référence Svelte, React/Vue doivent s'aligner)
7
+ *
8
+ * Props obligatoires :
9
+ * data OHLCChartDatum[] - tableau {label, open, high, low, close}
10
+ * label string
11
+ *
12
+ * Props optionnelles :
13
+ * width number (défaut 480)
14
+ * height number (défaut 240)
15
+ * class string
16
+ */
17
+ export type OHLCChartDatum = {
18
+ label: string;
19
+ open: number;
20
+ high: number;
21
+ low: number;
22
+ close: number;
23
+ };
24
+ </script>
25
+
26
+ <script lang="ts">
27
+ import ChartDataList from "./ChartDataList.svelte";
28
+
29
+ type OHLCChartProps = {
30
+ data: OHLCChartDatum[];
31
+ label: string;
32
+ width?: number;
33
+ height?: number;
34
+ class?: string;
35
+ };
36
+
37
+ let {
38
+ data = [],
39
+ label,
40
+ width = 480,
41
+ height = 240,
42
+ class: className
43
+ }: OHLCChartProps = $props();
44
+
45
+ const MARGIN = { top: 12, right: 16, bottom: 32, left: 52 };
46
+
47
+ function scaleLinear(v: number, d0: number, d1: number, r0: number, r1: number) {
48
+ if (d1 === d0) return r0;
49
+ return r0 + ((v - d0) * (r1 - r0)) / (d1 - d0);
50
+ }
51
+
52
+ function niceTicks(min: number, max: number, target = 5): number[] {
53
+ if (!Number.isFinite(min) || !Number.isFinite(max) || min === max) {
54
+ const base = Number.isFinite(max) ? max : 0;
55
+ return [base];
56
+ }
57
+ const range = max - min;
58
+ const rough = range / Math.max(target - 1, 1);
59
+ const pow = Math.pow(10, Math.floor(Math.log10(rough)));
60
+ const norm = rough / pow;
61
+ let step: number;
62
+ if (norm < 1.5) step = 1 * pow;
63
+ else if (norm < 3) step = 2 * pow;
64
+ else if (norm < 7) step = 5 * pow;
65
+ else step = 10 * pow;
66
+ const start = Math.floor(min / step) * step;
67
+ const end = Math.ceil(max / step) * step;
68
+ const ticks: number[] = [];
69
+ for (let v = start; v <= end + step / 2; v += step) {
70
+ ticks.push(Number(v.toFixed(10)));
71
+ }
72
+ return ticks;
73
+ }
74
+
75
+ function formatTick(v: number): string {
76
+ if (Math.abs(v) >= 1000) return `${(v / 1000).toFixed(v % 1000 === 0 ? 0 : 1)}k`;
77
+ if (Number.isInteger(v)) return String(v);
78
+ return v.toFixed(1);
79
+ }
80
+
81
+ let hoveredIndex: number | null = $state(null);
82
+
83
+ const plotWidth = $derived(Math.max(width - MARGIN.left - MARGIN.right, 1));
84
+ const plotHeight = $derived(Math.max(height - MARGIN.top - MARGIN.bottom, 1));
85
+
86
+ // Filtrer les barres invalides AVANT le domaine
87
+ const validData = $derived(
88
+ data.filter((d) =>
89
+ Number.isFinite(d.open) &&
90
+ Number.isFinite(d.high) &&
91
+ Number.isFinite(d.low) &&
92
+ Number.isFinite(d.close)
93
+ )
94
+ );
95
+
96
+ const domainBounds = $derived.by(() => {
97
+ const allVals: number[] = [];
98
+ for (const d of validData) {
99
+ // Domaine inclut open/high/low/close (pas seulement high/low)
100
+ allVals.push(d.open, d.high, d.low, d.close);
101
+ }
102
+ if (allVals.length === 0) return { rawMin: 0, rawMax: 1 };
103
+ const rawMin = Math.min(...allVals);
104
+ const rawMax = Math.max(...allVals);
105
+ // Domaine plat → fallback range 1 pour éviter division par 0
106
+ return { rawMin, rawMax: rawMax === rawMin ? rawMin + 1 : rawMax };
107
+ });
108
+
109
+ const ticks = $derived(niceTicks(domainBounds.rawMin, domainBounds.rawMax, 5));
110
+ const domainMin = $derived(ticks[0]);
111
+ const domainMax = $derived(ticks[ticks.length - 1]);
112
+
113
+ const bars = $derived.by(() => {
114
+ if (validData.length === 0) return [];
115
+ const band = plotWidth / validData.length;
116
+ // longueur du tick open/close (de chaque côté de la barre)
117
+ const tickW = Math.min(band * 0.3, 12);
118
+
119
+ return validData.map((d, i) => {
120
+ // clamp high/low pour garantir high≥max(O,C) et low≤min(O,C)
121
+ const clampedHigh = Math.max(d.high, d.open, d.close);
122
+ const clampedLow = Math.min(d.low, d.open, d.close);
123
+
124
+ const bullish = d.close >= d.open;
125
+ const centerX = MARGIN.left + band * i + band / 2;
126
+
127
+ const highY = MARGIN.top + scaleLinear(clampedHigh, domainMin, domainMax, plotHeight, 0);
128
+ const lowY = MARGIN.top + scaleLinear(clampedLow, domainMin, domainMax, plotHeight, 0);
129
+ const openY = MARGIN.top + scaleLinear(d.open, domainMin, domainMax, plotHeight, 0);
130
+ const closeY = MARGIN.top + scaleLinear(d.close, domainMin, domainMax, plotHeight, 0);
131
+
132
+ return {
133
+ datum: d,
134
+ index: i,
135
+ bullish,
136
+ centerX,
137
+ barHighY: highY,
138
+ barLowY: lowY,
139
+ openY,
140
+ closeY,
141
+ openX: centerX - tickW,
142
+ closeX: centerX + tickW,
143
+ tooltipY: Math.min(highY, openY, closeY)
144
+ };
145
+ });
146
+ });
147
+
148
+ const dataValueItems = $derived(
149
+ validData.map((d) => `${d.label}: O ${d.open} H ${d.high} L ${d.low} C ${d.close}`)
150
+ );
151
+
152
+ function handlePointerMove(event: PointerEvent) {
153
+ const target = event.target;
154
+ if (!(target instanceof Element)) { hoveredIndex = null; return; }
155
+ const idx = Number(target.getAttribute("data-chart-index"));
156
+ hoveredIndex = Number.isInteger(idx) ? idx : null;
157
+ }
158
+
159
+ const classes = () => ["st-ohlcChart", className].filter(Boolean).join(" ");
160
+ </script>
161
+
162
+ <div class={classes()}>
163
+ <div
164
+ class="st-ohlcChart__visual"
165
+ role="img"
166
+ aria-label={label}
167
+ onpointermove={handlePointerMove}
168
+ onpointerleave={() => (hoveredIndex = null)}
169
+ >
170
+ <svg
171
+ viewBox="0 0 {width} {height}"
172
+ preserveAspectRatio="xMidYMid meet"
173
+ width="100%"
174
+ height="100%"
175
+ focusable="false"
176
+ aria-hidden="true"
177
+ >
178
+ <!-- gridlines + tick labels -->
179
+ {#each ticks as tick (tick)}
180
+ {@const ty = MARGIN.top + scaleLinear(tick, domainMin, domainMax, plotHeight, 0)}
181
+ <line class="st-ohlcChart__grid" x1={MARGIN.left} x2={width - MARGIN.right} y1={ty} y2={ty} />
182
+ <text class="st-ohlcChart__tickLabel" x={MARGIN.left - 6} y={ty} text-anchor="end" dominant-baseline="middle">
183
+ {formatTick(tick)}
184
+ </text>
185
+ {/each}
186
+
187
+ <!-- axes -->
188
+ <line class="st-ohlcChart__axis" x1={MARGIN.left} x2={MARGIN.left} y1={MARGIN.top} y2={height - MARGIN.bottom} />
189
+ <line class="st-ohlcChart__axis" x1={MARGIN.left} x2={width - MARGIN.right} y1={height - MARGIN.bottom} y2={height - MARGIN.bottom} />
190
+
191
+ <!-- clé composite pour éviter les doublons -->
192
+ {#each bars as b, i (`${i}-${b.datum.label}`)}
193
+ <g
194
+ class="st-ohlcChart__bar st-ohlcChart__bar--{b.bullish ? 'up' : 'down'}"
195
+ class:st-ohlcChart__bar--dim={hoveredIndex !== null && hoveredIndex !== i}
196
+ >
197
+ <!-- barre verticale low → high -->
198
+ <line
199
+ class="st-ohlcChart__range"
200
+ x1={b.centerX}
201
+ x2={b.centerX}
202
+ y1={b.barHighY}
203
+ y2={b.barLowY}
204
+ data-chart-index={i}
205
+ />
206
+ <!-- tick open (gauche) -->
207
+ <line
208
+ class="st-ohlcChart__open"
209
+ x1={b.openX}
210
+ x2={b.centerX}
211
+ y1={b.openY}
212
+ y2={b.openY}
213
+ data-chart-index={i}
214
+ />
215
+ <!-- tick close (droite) -->
216
+ <line
217
+ class="st-ohlcChart__close"
218
+ x1={b.centerX}
219
+ x2={b.closeX}
220
+ y1={b.closeY}
221
+ y2={b.closeY}
222
+ data-chart-index={i}
223
+ />
224
+ </g>
225
+ <!-- category label -->
226
+ <text
227
+ class="st-ohlcChart__categoryLabel"
228
+ x={b.centerX}
229
+ y={height - MARGIN.bottom + 16}
230
+ text-anchor="middle"
231
+ >
232
+ {b.datum.label}
233
+ </text>
234
+ {/each}
235
+ </svg>
236
+ </div>
237
+
238
+ <ChartDataList {label} items={dataValueItems} />
239
+
240
+ {#if hoveredIndex !== null && bars[hoveredIndex]}
241
+ {@const b = bars[hoveredIndex]}
242
+ <div
243
+ class="st-ohlcChart__tooltip"
244
+ role="presentation"
245
+ style="left: {(b.centerX / width) * 100}%; top: {(b.tooltipY / height) * 100}%"
246
+ >
247
+ <span class="st-ohlcChart__tooltipLabel">{b.datum.label}</span>
248
+ <span class="st-ohlcChart__tooltipValue">O {b.datum.open} H {b.datum.high} L {b.datum.low} C {b.datum.close}</span>
249
+ </div>
250
+ {/if}
251
+ </div>
252
+
253
+ <style>
254
+ .st-ohlcChart {
255
+ color: var(--st-semantic-text-secondary);
256
+ display: block;
257
+ font-family: inherit;
258
+ position: relative;
259
+ width: 100%;
260
+ }
261
+
262
+ .st-ohlcChart svg {
263
+ display: block;
264
+ overflow: visible;
265
+ }
266
+
267
+ .st-ohlcChart__visual {
268
+ display: block;
269
+ }
270
+
271
+ .st-ohlcChart__axis {
272
+ stroke: var(--st-semantic-border-subtle);
273
+ stroke-width: 1;
274
+ }
275
+
276
+ .st-ohlcChart__grid {
277
+ stroke: var(--st-semantic-border-subtle);
278
+ stroke-dasharray: 2 3;
279
+ stroke-width: 1;
280
+ opacity: 0.7;
281
+ }
282
+
283
+ .st-ohlcChart__bar {
284
+ cursor: pointer;
285
+ transition: opacity 120ms ease;
286
+ }
287
+
288
+ .st-ohlcChart__bar--dim {
289
+ opacity: 0.4;
290
+ }
291
+
292
+ .st-ohlcChart__range,
293
+ .st-ohlcChart__open,
294
+ .st-ohlcChart__close {
295
+ stroke-width: 1.75;
296
+ stroke-linecap: round;
297
+ }
298
+
299
+ .st-ohlcChart__bar--up :is(.st-ohlcChart__range, .st-ohlcChart__open, .st-ohlcChart__close) {
300
+ stroke: var(--st-semantic-feedback-success);
301
+ }
302
+
303
+ .st-ohlcChart__bar--down :is(.st-ohlcChart__range, .st-ohlcChart__open, .st-ohlcChart__close) {
304
+ stroke: var(--st-semantic-feedback-error);
305
+ }
306
+
307
+ @media (prefers-reduced-motion: reduce) {
308
+ .st-ohlcChart__bar {
309
+ transition: none;
310
+ }
311
+ }
312
+
313
+ .st-ohlcChart__tickLabel,
314
+ .st-ohlcChart__categoryLabel {
315
+ fill: var(--st-semantic-text-secondary);
316
+ font-size: 0.6875rem;
317
+ }
318
+
319
+ .st-ohlcChart__tooltip {
320
+ background: var(--st-semantic-surface-inverse);
321
+ border-radius: var(--st-radius-sm, 0.25rem);
322
+ color: var(--st-semantic-text-inverse);
323
+ display: inline-flex;
324
+ flex-direction: column;
325
+ font-size: 0.75rem;
326
+ gap: 0.125rem;
327
+ line-height: 1.2;
328
+ padding: 0.375rem 0.5rem;
329
+ pointer-events: none;
330
+ position: absolute;
331
+ transform: translate(-50%, calc(-100% - 8px));
332
+ white-space: nowrap;
333
+ z-index: 1;
334
+ }
335
+
336
+ .st-ohlcChart__tooltipLabel {
337
+ font-weight: 600;
338
+ }
339
+
340
+ .st-ohlcChart__tooltipValue {
341
+ opacity: 0.85;
342
+ }
343
+ </style>
@@ -0,0 +1,33 @@
1
+ /**
2
+ * OHLCChart - barres open/high/low/close (bâtons financiers).
3
+ * Variante du CandlestickChart : barre verticale du low au high,
4
+ * tick gauche au niveau open, tick droite au niveau close.
5
+ * API canonique (référence Svelte, React/Vue doivent s'aligner)
6
+ *
7
+ * Props obligatoires :
8
+ * data OHLCChartDatum[] - tableau {label, open, high, low, close}
9
+ * label string
10
+ *
11
+ * Props optionnelles :
12
+ * width number (défaut 480)
13
+ * height number (défaut 240)
14
+ * class string
15
+ */
16
+ export type OHLCChartDatum = {
17
+ label: string;
18
+ open: number;
19
+ high: number;
20
+ low: number;
21
+ close: number;
22
+ };
23
+ type OHLCChartProps = {
24
+ data: OHLCChartDatum[];
25
+ label: string;
26
+ width?: number;
27
+ height?: number;
28
+ class?: string;
29
+ };
30
+ declare const OHLCChart: import("svelte").Component<OHLCChartProps, {}, "">;
31
+ type OHLCChart = ReturnType<typeof OHLCChart>;
32
+ export default OHLCChart;
33
+ //# sourceMappingURL=OHLCChart.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"OHLCChart.svelte.d.ts","sourceRoot":"","sources":["../src/lib/OHLCChart.svelte.ts"],"names":[],"mappings":"AAGE;;;;;;;;;;;;;;GAcG;AACH,MAAM,MAAM,cAAc,GAAG;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AAMF,KAAK,cAAc,GAAG;IACpB,IAAI,EAAE,cAAc,EAAE,CAAC;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAqLJ,QAAA,MAAM,SAAS,oDAAwC,CAAC;AACxD,KAAK,SAAS,GAAG,UAAU,CAAC,OAAO,SAAS,CAAC,CAAC;AAC9C,eAAe,SAAS,CAAC"}
@@ -0,0 +1,284 @@
1
+ <script lang="ts" module>
2
+ export type OrganizationChartTone =
3
+ | "category1" | "category2" | "category3" | "category4"
4
+ | "category5" | "category6" | "category7" | "category8";
5
+
6
+ export type OrganizationChartNode = {
7
+ id: string;
8
+ parentId?: string | null;
9
+ label: string;
10
+ tone?: OrganizationChartTone;
11
+ };
12
+ </script>
13
+
14
+ <script lang="ts">
15
+ import ChartDataList from "./ChartDataList.svelte";
16
+
17
+ type OrganizationChartProps = {
18
+ /** Nœuds plats : `parentId` null/absent = racine. Plusieurs racines acceptées. */
19
+ data: OrganizationChartNode[];
20
+ width?: number;
21
+ height?: number;
22
+ label: string;
23
+ class?: string;
24
+ };
25
+
26
+ let {
27
+ data,
28
+ width = 640,
29
+ height = 320,
30
+ label,
31
+ class: className
32
+ }: OrganizationChartProps = $props();
33
+
34
+ const TONES = [
35
+ "category1", "category2", "category3", "category4",
36
+ "category5", "category6", "category7", "category8"
37
+ ] as const;
38
+
39
+ type Box = {
40
+ id: string;
41
+ label: string;
42
+ tone: OrganizationChartTone;
43
+ depth: number;
44
+ x: number; // centre X
45
+ y: number; // haut de la boîte
46
+ parentId: string | null;
47
+ };
48
+
49
+ type Link = { id: string; x1: number; y1: number; x2: number; y2: number };
50
+
51
+ const BOX_H = 36;
52
+ const GAP_X = 16; // espace horizontal entre frères (feuilles)
53
+ const PAD_X = 8; // marge latérale
54
+
55
+ type Layout = { boxes: Box[]; links: Link[]; boxW: number };
56
+
57
+ const layout = $derived.by<Layout>(() => {
58
+ if (!data || data.length === 0) return { boxes: [], links: [], boxW: 0 };
59
+
60
+ // Index + détection des racines (parentId invalide/cyclique → traité comme racine).
61
+ const byId = new Map<string, OrganizationChartNode>();
62
+ for (const n of data) if (n.id != null && !byId.has(n.id)) byId.set(n.id, n);
63
+
64
+ const validParent = (n: OrganizationChartNode): string | null => {
65
+ const p = n.parentId;
66
+ if (p == null) return null;
67
+ if (!byId.has(p) || p === n.id) return null;
68
+ // Évite les cycles : remonte la chaîne, si on revient sur n.id → racine.
69
+ let cursor: string | null = p;
70
+ const seen = new Set<string>([n.id]);
71
+ while (cursor != null) {
72
+ if (seen.has(cursor)) return null;
73
+ seen.add(cursor);
74
+ const parent: OrganizationChartNode | undefined = byId.get(cursor);
75
+ cursor = parent ? (parent.parentId ?? null) : null;
76
+ }
77
+ return p;
78
+ };
79
+
80
+ const childrenOf = new Map<string, string[]>();
81
+ const roots: string[] = [];
82
+ for (const n of byId.values()) {
83
+ const p = validParent(n);
84
+ if (p == null) {
85
+ roots.push(n.id);
86
+ } else {
87
+ const list = childrenOf.get(p) ?? [];
88
+ list.push(n.id);
89
+ childrenOf.set(p, list);
90
+ }
91
+ }
92
+
93
+ // Profondeur par nœud + position des feuilles (parcours en profondeur, ordre stable).
94
+ const depthOf = new Map<string, number>();
95
+ const leafOrder = new Map<string, number>();
96
+ let leafCounter = 0;
97
+ let maxDepth = 0;
98
+
99
+ const visit = (id: string, depth: number) => {
100
+ depthOf.set(id, depth);
101
+ if (depth > maxDepth) maxDepth = depth;
102
+ const kids = childrenOf.get(id) ?? [];
103
+ if (kids.length === 0) {
104
+ leafOrder.set(id, leafCounter);
105
+ leafCounter += 1;
106
+ return;
107
+ }
108
+ for (const k of kids) visit(k, depth + 1);
109
+ };
110
+ for (const r of roots) visit(r, 0);
111
+
112
+ const leafCount = Math.max(leafCounter, 1);
113
+
114
+ // Largeur de boîte : on répartit `leafCount` colonnes sur la largeur utile.
115
+ const usable = Math.max(width - PAD_X * 2, 1);
116
+ const colW = usable / leafCount;
117
+ const boxW = Math.max(colW - GAP_X, 24);
118
+
119
+ // X (centre) : feuille = sa colonne ; parent = moyenne des enfants.
120
+ const centerX = new Map<string, number>();
121
+ const computeX = (id: string): number => {
122
+ const cached = centerX.get(id);
123
+ if (cached != null) return cached;
124
+ const kids = childrenOf.get(id) ?? [];
125
+ let cx: number;
126
+ if (kids.length === 0) {
127
+ const col = leafOrder.get(id) ?? 0;
128
+ cx = PAD_X + col * colW + colW / 2;
129
+ } else {
130
+ const xs = kids.map((k) => computeX(k));
131
+ cx = xs.reduce((s, v) => s + v, 0) / xs.length;
132
+ }
133
+ centerX.set(id, cx);
134
+ return cx;
135
+ };
136
+ for (const r of roots) computeX(r);
137
+
138
+ // Y : une rangée par niveau, répartie verticalement.
139
+ const levels = maxDepth + 1;
140
+ const usableY = Math.max(height - BOX_H, 1);
141
+ const rowGap = levels > 1 ? usableY / (levels - 1) : 0;
142
+ const yForDepth = (d: number) => (levels > 1 ? d * rowGap : (height - BOX_H) / 2);
143
+
144
+ const boxes: Box[] = [];
145
+ let toneIdx = 0;
146
+ // Ordre : profondeur croissante puis ordre d'insertion → rendu stable.
147
+ const ordered = [...byId.values()].filter((n) => depthOf.has(n.id));
148
+ for (const n of ordered) {
149
+ const depth = depthOf.get(n.id) ?? 0;
150
+ const tone = n.tone ?? TONES[toneIdx % TONES.length];
151
+ if (!n.tone) toneIdx += 1;
152
+ boxes.push({
153
+ id: n.id,
154
+ label: n.label,
155
+ tone,
156
+ depth,
157
+ x: centerX.get(n.id) ?? width / 2,
158
+ y: yForDepth(depth),
159
+ parentId: validParent(n)
160
+ });
161
+ }
162
+
163
+ const boxById = new Map(boxes.map((b) => [b.id, b]));
164
+ const links: Link[] = [];
165
+ for (const b of boxes) {
166
+ if (b.parentId == null) continue;
167
+ const parent = boxById.get(b.parentId);
168
+ if (!parent) continue;
169
+ links.push({
170
+ id: b.id,
171
+ x1: parent.x,
172
+ y1: parent.y + BOX_H,
173
+ x2: b.x,
174
+ y2: b.y
175
+ });
176
+ }
177
+
178
+ return { boxes, links, boxW };
179
+ });
180
+
181
+ // Police adaptée à la largeur de boîte (plus petite si étroite).
182
+ const fontSize = $derived(layout.boxW < 56 ? 9 : layout.boxW < 88 ? 10 : 12);
183
+ // Nombre de caractères affichables avant ellipsis (approx. largeur de glyphe).
184
+ const maxChars = $derived(Math.max(2, Math.floor((layout.boxW - 10) / (fontSize * 0.58))));
185
+
186
+ const clip = (s: string, n: number) => (s.length > n ? `${s.slice(0, Math.max(1, n - 1))}…` : s);
187
+
188
+ const dataValueItems = $derived(
189
+ layout.boxes.map((b) => {
190
+ const parent = b.parentId != null ? layout.boxes.find((p) => p.id === b.parentId) : undefined;
191
+ return parent ? `${b.label} (${b.id}) → ${parent.label}` : `${b.label} (${b.id})`;
192
+ })
193
+ );
194
+
195
+ const classes = () => ["st-organizationChart", className].filter(Boolean).join(" ");
196
+ </script>
197
+
198
+ <div class={classes()}>
199
+ <div class="st-organizationChart__visual" role="img" aria-label={label}>
200
+ <svg
201
+ viewBox="0 0 {width} {height}"
202
+ preserveAspectRatio="xMidYMid meet"
203
+ width="100%"
204
+ height="100%"
205
+ focusable="false"
206
+ aria-hidden="true"
207
+ >
208
+ {#each layout.links as link (link.id)}
209
+ {@const midY = (link.y1 + link.y2) / 2}
210
+ <path
211
+ class="st-organizationChart__link"
212
+ d="M {link.x1} {link.y1} V {midY} H {link.x2} V {link.y2}"
213
+ fill="none"
214
+ />
215
+ {/each}
216
+
217
+ {#each layout.boxes as box (box.id)}
218
+ <g class="st-organizationChart__node">
219
+ <rect
220
+ class="st-organizationChart__box st-organizationChart__box--{box.tone}"
221
+ x={box.x - layout.boxW / 2}
222
+ y={box.y}
223
+ width={layout.boxW}
224
+ height={BOX_H}
225
+ rx="6"
226
+ />
227
+ <text
228
+ class="st-organizationChart__label"
229
+ x={box.x}
230
+ y={box.y + BOX_H / 2}
231
+ text-anchor="middle"
232
+ dominant-baseline="central"
233
+ style="font-size: {fontSize}px"
234
+ >
235
+ {clip(box.label, maxChars)}
236
+ </text>
237
+ </g>
238
+ {/each}
239
+ </svg>
240
+ </div>
241
+
242
+ <ChartDataList {label} items={dataValueItems} />
243
+ </div>
244
+
245
+ <style>
246
+ .st-organizationChart {
247
+ color: var(--st-semantic-text-secondary);
248
+ display: block;
249
+ font-family: inherit;
250
+ position: relative;
251
+ width: 100%;
252
+ }
253
+
254
+ .st-organizationChart svg,
255
+ .st-organizationChart__visual {
256
+ display: block;
257
+ overflow: visible;
258
+ }
259
+
260
+ .st-organizationChart__link {
261
+ stroke: var(--st-semantic-border-default, #cbd5e1);
262
+ stroke-width: 1.5;
263
+ }
264
+
265
+ .st-organizationChart__box {
266
+ stroke: var(--st-semantic-surface-default, #fff);
267
+ stroke-width: 1.5;
268
+ }
269
+
270
+ .st-organizationChart__box--category1 { fill: var(--st-semantic-data-category1); }
271
+ .st-organizationChart__box--category2 { fill: var(--st-semantic-data-category2); }
272
+ .st-organizationChart__box--category3 { fill: var(--st-semantic-data-category3); }
273
+ .st-organizationChart__box--category4 { fill: var(--st-semantic-data-category4); }
274
+ .st-organizationChart__box--category5 { fill: var(--st-semantic-data-category5); }
275
+ .st-organizationChart__box--category6 { fill: var(--st-semantic-data-category6); }
276
+ .st-organizationChart__box--category7 { fill: var(--st-semantic-data-category7); }
277
+ .st-organizationChart__box--category8 { fill: var(--st-semantic-data-category8); }
278
+
279
+ .st-organizationChart__label {
280
+ fill: var(--st-semantic-text-inverse, #fff);
281
+ font-weight: 600;
282
+ pointer-events: none;
283
+ }
284
+ </style>
@@ -0,0 +1,19 @@
1
+ export type OrganizationChartTone = "category1" | "category2" | "category3" | "category4" | "category5" | "category6" | "category7" | "category8";
2
+ export type OrganizationChartNode = {
3
+ id: string;
4
+ parentId?: string | null;
5
+ label: string;
6
+ tone?: OrganizationChartTone;
7
+ };
8
+ type OrganizationChartProps = {
9
+ /** Nœuds plats : `parentId` null/absent = racine. Plusieurs racines acceptées. */
10
+ data: OrganizationChartNode[];
11
+ width?: number;
12
+ height?: number;
13
+ label: string;
14
+ class?: string;
15
+ };
16
+ declare const OrganizationChart: import("svelte").Component<OrganizationChartProps, {}, "">;
17
+ type OrganizationChart = ReturnType<typeof OrganizationChart>;
18
+ export default OrganizationChart;
19
+ //# sourceMappingURL=OrganizationChart.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"OrganizationChart.svelte.d.ts","sourceRoot":"","sources":["../src/lib/OrganizationChart.svelte.ts"],"names":[],"mappings":"AAGE,MAAM,MAAM,qBAAqB,GAC7B,WAAW,GAAG,WAAW,GAAG,WAAW,GAAG,WAAW,GACrD,WAAW,GAAG,WAAW,GAAG,WAAW,GAAG,WAAW,CAAC;AAE1D,MAAM,MAAM,qBAAqB,GAAG;IAClC,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,qBAAqB,CAAC;CAC9B,CAAC;AAMF,KAAK,sBAAsB,GAAG;IAC5B,kFAAkF;IAClF,IAAI,EAAE,qBAAqB,EAAE,CAAC;IAC9B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAgNJ,QAAA,MAAM,iBAAiB,4DAAwC,CAAC;AAChE,KAAK,iBAAiB,GAAG,UAAU,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAC9D,eAAe,iBAAiB,CAAC"}
@@ -130,14 +130,17 @@
130
130
 
131
131
  .st-passwordInput--sm {
132
132
  min-height: var(--st-component-control-smHeight, 2rem);
133
+ font-size: 0.8125rem;
133
134
  }
134
135
 
135
136
  .st-passwordInput--md {
136
137
  min-height: var(--st-component-control-mdHeight, 2.5rem);
138
+ font-size: 0.875rem;
137
139
  }
138
140
 
139
141
  .st-passwordInput--lg {
140
142
  min-height: var(--st-component-control-lgHeight, 3rem);
143
+ font-size: 1rem;
141
144
  }
142
145
 
143
146
  .st-passwordInput:hover:not(:has(input:disabled)) {