@sentropic/design-system-svelte 0.34.28 → 0.34.32
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/CandlestickChart.svelte +286 -11
- package/dist/CandlestickChart.svelte.d.ts +17 -3
- package/dist/CandlestickChart.svelte.d.ts.map +1 -1
- package/dist/CellDecorationIcon.svelte +39 -0
- package/dist/CellDecorationIcon.svelte.d.ts +7 -0
- package/dist/CellDecorationIcon.svelte.d.ts.map +1 -0
- package/dist/ComboChart.svelte +333 -2
- package/dist/ComboChart.svelte.d.ts +34 -0
- package/dist/ComboChart.svelte.d.ts.map +1 -1
- package/dist/DataTable.svelte +91 -2
- package/dist/DataTable.svelte.d.ts +12 -0
- package/dist/DataTable.svelte.d.ts.map +1 -1
- package/dist/KpiCard.svelte +66 -1
- package/dist/KpiCard.svelte.d.ts +7 -0
- package/dist/KpiCard.svelte.d.ts.map +1 -1
- package/dist/OHLCChart.svelte +286 -11
- package/dist/OHLCChart.svelte.d.ts +17 -3
- package/dist/OHLCChart.svelte.d.ts.map +1 -1
- package/dist/ScatterPlot.svelte +260 -6
- package/dist/ScatterPlot.svelte.d.ts +25 -0
- package/dist/ScatterPlot.svelte.d.ts.map +1 -1
- package/dist/cellDecoration.d.ts +36 -0
- package/dist/cellDecoration.d.ts.map +1 -0
- package/dist/cellDecoration.js +71 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/KpiCard.svelte
CHANGED
|
@@ -12,11 +12,19 @@
|
|
|
12
12
|
| "category6"
|
|
13
13
|
| "category7"
|
|
14
14
|
| "category8";
|
|
15
|
+
|
|
16
|
+
export type { CellDecoration, CellDecorationIntent } from "./cellDecoration.js";
|
|
15
17
|
</script>
|
|
16
18
|
|
|
17
19
|
<script lang="ts">
|
|
18
20
|
import type { HTMLAttributes } from "svelte/elements";
|
|
19
21
|
import Sparkline from "./Sparkline.svelte";
|
|
22
|
+
import {
|
|
23
|
+
type CellDecoration,
|
|
24
|
+
cellDecorationClass,
|
|
25
|
+
cellDecorationLabel,
|
|
26
|
+
} from "./cellDecoration.js";
|
|
27
|
+
import CellDecorationIcon from "./CellDecorationIcon.svelte";
|
|
20
28
|
|
|
21
29
|
type KpiCardProps = Omit<HTMLAttributes<HTMLElement>, "class"> & {
|
|
22
30
|
/** Valeur principale affichée en grand. */
|
|
@@ -42,6 +50,11 @@
|
|
|
42
50
|
size?: KpiCardSize;
|
|
43
51
|
/** Couleur catégorielle pour l'accent (bordure de gauche). */
|
|
44
52
|
tone?: KpiCardTone;
|
|
53
|
+
/**
|
|
54
|
+
* Conditional formatting : décoration sémantique de la carte (intent → token
|
|
55
|
+
* feedback en fond teinté accessible + icône lucide optionnelle).
|
|
56
|
+
*/
|
|
57
|
+
decoration?: CellDecoration;
|
|
45
58
|
class?: string;
|
|
46
59
|
};
|
|
47
60
|
|
|
@@ -58,6 +71,7 @@
|
|
|
58
71
|
sparkline,
|
|
59
72
|
size = "md",
|
|
60
73
|
tone,
|
|
74
|
+
decoration,
|
|
61
75
|
class: className,
|
|
62
76
|
...rest
|
|
63
77
|
}: KpiCardProps = $props();
|
|
@@ -116,7 +130,13 @@
|
|
|
116
130
|
);
|
|
117
131
|
|
|
118
132
|
const ariaLabel = $derived(
|
|
119
|
-
[
|
|
133
|
+
[
|
|
134
|
+
label,
|
|
135
|
+
formattedValue,
|
|
136
|
+
unit,
|
|
137
|
+
formattedDelta && `${formattedDelta} ${trendLabel ?? ""}`.trim(),
|
|
138
|
+
decoration && cellDecorationLabel[decoration.intent]
|
|
139
|
+
]
|
|
120
140
|
.filter(Boolean)
|
|
121
141
|
.join(", ")
|
|
122
142
|
);
|
|
@@ -127,6 +147,8 @@
|
|
|
127
147
|
`st-kpiCard--${size}`,
|
|
128
148
|
tone && `st-kpiCard--${tone}`,
|
|
129
149
|
tone && "st-kpiCard--toned",
|
|
150
|
+
decoration && "st-cell",
|
|
151
|
+
decoration && cellDecorationClass(decoration.intent),
|
|
130
152
|
className
|
|
131
153
|
]
|
|
132
154
|
.filter(Boolean)
|
|
@@ -138,6 +160,9 @@
|
|
|
138
160
|
<p class="st-kpiCard__label">{label}</p>
|
|
139
161
|
|
|
140
162
|
<p class="st-kpiCard__value">
|
|
163
|
+
{#if decoration}
|
|
164
|
+
<CellDecorationIcon icon={decoration.icon} />
|
|
165
|
+
{/if}
|
|
141
166
|
<span class="st-kpiCard__number">{formattedValue}</span>
|
|
142
167
|
{#if unit}
|
|
143
168
|
<span class="st-kpiCard__unit">{unit}</span>
|
|
@@ -235,6 +260,46 @@
|
|
|
235
260
|
margin: 0;
|
|
236
261
|
}
|
|
237
262
|
|
|
263
|
+
/* Conditional formatting (« classe Power-BI ») : fond teinté accessible
|
|
264
|
+
(color-mix 14% sur token feedback, comme Badge/Tag) + icône alignée sur la
|
|
265
|
+
valeur. Le fond n'est jamais la seule indication (icône + texte SR). */
|
|
266
|
+
.st-kpiCard.st-cell {
|
|
267
|
+
padding: var(--st-spacing-4, 1rem);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
.st-kpiCard.st-cell .st-kpiCard__value {
|
|
271
|
+
align-items: center;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
.st-kpiCard.st-cell .st-kpiCard__number {
|
|
275
|
+
color: inherit;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
.st-cell--intent-positive {
|
|
279
|
+
background: color-mix(in srgb, var(--st-semantic-feedback-success) 14%, white);
|
|
280
|
+
color: var(--st-semantic-feedback-success);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
.st-cell--intent-negative {
|
|
284
|
+
background: color-mix(in srgb, var(--st-semantic-feedback-error) 14%, white);
|
|
285
|
+
color: var(--st-semantic-feedback-error);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
.st-cell--intent-warning {
|
|
289
|
+
background: color-mix(in srgb, var(--st-semantic-feedback-warning) 14%, white);
|
|
290
|
+
color: var(--st-semantic-feedback-warning);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
.st-cell--intent-info {
|
|
294
|
+
background: color-mix(in srgb, var(--st-semantic-feedback-info) 14%, white);
|
|
295
|
+
color: var(--st-semantic-feedback-info);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
.st-cell--intent-neutral {
|
|
299
|
+
background: var(--st-semantic-surface-subtle);
|
|
300
|
+
color: var(--st-semantic-text-secondary);
|
|
301
|
+
}
|
|
302
|
+
|
|
238
303
|
.st-kpiCard__number {
|
|
239
304
|
font-size: 1.5rem;
|
|
240
305
|
font-weight: 700;
|
package/dist/KpiCard.svelte.d.ts
CHANGED
|
@@ -3,7 +3,9 @@ export type KpiCardTrend = "up" | "down" | "flat";
|
|
|
3
3
|
export type KpiCardFormat = "number" | "currency" | "percent";
|
|
4
4
|
export type KpiCardDeltaFormat = "percent" | "absolute";
|
|
5
5
|
export type KpiCardTone = "category1" | "category2" | "category3" | "category4" | "category5" | "category6" | "category7" | "category8";
|
|
6
|
+
export type { CellDecoration, CellDecorationIntent } from "./cellDecoration.js";
|
|
6
7
|
import type { HTMLAttributes } from "svelte/elements";
|
|
8
|
+
import { type CellDecoration } from "./cellDecoration.js";
|
|
7
9
|
type KpiCardProps = Omit<HTMLAttributes<HTMLElement>, "class"> & {
|
|
8
10
|
/** Valeur principale affichée en grand. */
|
|
9
11
|
value: number | string;
|
|
@@ -28,6 +30,11 @@ type KpiCardProps = Omit<HTMLAttributes<HTMLElement>, "class"> & {
|
|
|
28
30
|
size?: KpiCardSize;
|
|
29
31
|
/** Couleur catégorielle pour l'accent (bordure de gauche). */
|
|
30
32
|
tone?: KpiCardTone;
|
|
33
|
+
/**
|
|
34
|
+
* Conditional formatting : décoration sémantique de la carte (intent → token
|
|
35
|
+
* feedback en fond teinté accessible + icône lucide optionnelle).
|
|
36
|
+
*/
|
|
37
|
+
decoration?: CellDecoration;
|
|
31
38
|
class?: string;
|
|
32
39
|
};
|
|
33
40
|
declare const KpiCard: import("svelte").Component<KpiCardProps, {}, "">;
|
|
@@ -1 +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;
|
|
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;AAEhB,YAAY,EAAE,cAAc,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAGlF,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAEtD,OAAO,EACH,KAAK,cAAc,EAGpB,MAAM,qBAAqB,CAAC;AAI7B,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;;;OAGG;IACH,UAAU,CAAC,EAAE,cAAc,CAAC;IAC5B,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AA+IJ,QAAA,MAAM,OAAO,kDAAwC,CAAC;AACtD,KAAK,OAAO,GAAG,UAAU,CAAC,OAAO,OAAO,CAAC,CAAC;AAC1C,eAAe,OAAO,CAAC"}
|
package/dist/OHLCChart.svelte
CHANGED
|
@@ -10,9 +10,15 @@
|
|
|
10
10
|
* label string
|
|
11
11
|
*
|
|
12
12
|
* Props optionnelles :
|
|
13
|
-
* width
|
|
14
|
-
* height
|
|
15
|
-
*
|
|
13
|
+
* width number (défaut 480)
|
|
14
|
+
* height number (défaut 240)
|
|
15
|
+
* annotations ChartAnnotation[] - overlay support/résistance/zones/events
|
|
16
|
+
* dataLabels boolean | { format?, position? } - étiquette close par bâton
|
|
17
|
+
* hoverKey string | null - crosshair contrôlé (clé = date/catégorie)
|
|
18
|
+
* onHoverKeyChange (key) => void - émet la date survolée (ou null)
|
|
19
|
+
* keyboardNav boolean - navigation clavier (roving tabindex)
|
|
20
|
+
* onSelectKey (key) => void - sélection clavier (Enter/Space) ; null = Escape
|
|
21
|
+
* class string
|
|
16
22
|
*/
|
|
17
23
|
export type OHLCChartDatum = {
|
|
18
24
|
label: string;
|
|
@@ -25,12 +31,27 @@
|
|
|
25
31
|
|
|
26
32
|
<script lang="ts">
|
|
27
33
|
import ChartDataList from "./ChartDataList.svelte";
|
|
34
|
+
import {
|
|
35
|
+
resolveAnnotations,
|
|
36
|
+
annotationDataListItems,
|
|
37
|
+
polygonPoints,
|
|
38
|
+
type ChartAnnotation
|
|
39
|
+
} from "./chartAnnotations.js";
|
|
40
|
+
import { formatDataLabel, normalizeDataLabels, type DataLabelsProp } from "./chartDataLabels.js";
|
|
41
|
+
import { resolveActiveIndex } from "./chartCrosshair.js";
|
|
42
|
+
import { datapointAriaLabel, datapointNavAction, rovingTabIndex } from "./chartKeyboardNav.js";
|
|
28
43
|
|
|
29
44
|
type OHLCChartProps = {
|
|
30
45
|
data: OHLCChartDatum[];
|
|
31
46
|
label: string;
|
|
32
47
|
width?: number;
|
|
33
48
|
height?: number;
|
|
49
|
+
annotations?: ChartAnnotation[];
|
|
50
|
+
dataLabels?: DataLabelsProp;
|
|
51
|
+
hoverKey?: string | null;
|
|
52
|
+
onHoverKeyChange?: (key: string | null) => void;
|
|
53
|
+
keyboardNav?: boolean;
|
|
54
|
+
onSelectKey?: (key: string | null) => void;
|
|
34
55
|
class?: string;
|
|
35
56
|
};
|
|
36
57
|
|
|
@@ -39,6 +60,12 @@
|
|
|
39
60
|
label,
|
|
40
61
|
width = 480,
|
|
41
62
|
height = 240,
|
|
63
|
+
annotations,
|
|
64
|
+
dataLabels,
|
|
65
|
+
hoverKey,
|
|
66
|
+
onHoverKeyChange,
|
|
67
|
+
keyboardNav,
|
|
68
|
+
onSelectKey,
|
|
42
69
|
class: className
|
|
43
70
|
}: OHLCChartProps = $props();
|
|
44
71
|
|
|
@@ -79,6 +106,9 @@
|
|
|
79
106
|
}
|
|
80
107
|
|
|
81
108
|
let hoveredIndex: number | null = $state(null);
|
|
109
|
+
// FR-5 — roving keyboard focus over the data points (separate from hover).
|
|
110
|
+
let focusedIndex: number = $state(-1);
|
|
111
|
+
let datapointRefs: Array<SVGRectElement | null> = [];
|
|
82
112
|
|
|
83
113
|
const plotWidth = $derived(Math.max(width - MARGIN.left - MARGIN.right, 1));
|
|
84
114
|
const plotHeight = $derived(Math.max(height - MARGIN.top - MARGIN.bottom, 1));
|
|
@@ -134,6 +164,7 @@
|
|
|
134
164
|
index: i,
|
|
135
165
|
bullish,
|
|
136
166
|
centerX,
|
|
167
|
+
band,
|
|
137
168
|
barHighY: highY,
|
|
138
169
|
barLowY: lowY,
|
|
139
170
|
openY,
|
|
@@ -145,15 +176,103 @@
|
|
|
145
176
|
});
|
|
146
177
|
});
|
|
147
178
|
|
|
148
|
-
|
|
149
|
-
|
|
179
|
+
// --- Annotation overlay ---------------------------------------------------
|
|
180
|
+
// The x coordinate is CATEGORICAL (a bar `label` → centre of band); the y
|
|
181
|
+
// coordinate is a price-axis number. Regions render behind the bars, every
|
|
182
|
+
// other kind above. The resolver maps each x via `xScale` (category → pixel)
|
|
183
|
+
// and each y via `yScale` (price → pixel), relative to the plot origin.
|
|
184
|
+
const priceY = $derived((v: number): number | null => {
|
|
185
|
+
if (!Number.isFinite(v)) return null;
|
|
186
|
+
return scaleLinear(v, domainMin, domainMax, plotHeight, 0);
|
|
187
|
+
});
|
|
188
|
+
const categoryPixel = $derived((v: number | string): number | null => {
|
|
189
|
+
const bar = bars.find((b) => b.datum.label === String(v));
|
|
190
|
+
if (!bar) return null;
|
|
191
|
+
return bar.centerX - MARGIN.left;
|
|
192
|
+
});
|
|
193
|
+
const resolvedAnnotations = $derived(
|
|
194
|
+
resolveAnnotations(annotations, {
|
|
195
|
+
xScale: categoryPixel,
|
|
196
|
+
yScale: priceY,
|
|
197
|
+
plotLeft: MARGIN.left,
|
|
198
|
+
plotTop: MARGIN.top,
|
|
199
|
+
plotWidth,
|
|
200
|
+
plotHeight
|
|
201
|
+
})
|
|
150
202
|
);
|
|
203
|
+
const annotationRegions = $derived(resolvedAnnotations.filter((a) => a.kind === "region"));
|
|
204
|
+
const annotationAbove = $derived(resolvedAnnotations.filter((a) => a.kind !== "region"));
|
|
205
|
+
|
|
206
|
+
// --- Data labels ----------------------------------------------------------
|
|
207
|
+
// One `close` value label per bar, placed just above it. aria-hidden.
|
|
208
|
+
const dataLabelOpts = $derived(normalizeDataLabels(dataLabels));
|
|
209
|
+
const dataLabelItems = $derived.by(() => {
|
|
210
|
+
if (!dataLabelOpts.enabled) return [] as { key: string; x: number; y: number; text: string }[];
|
|
211
|
+
return bars.map((bar) => ({
|
|
212
|
+
key: bar.datum.label,
|
|
213
|
+
x: bar.centerX,
|
|
214
|
+
y: bar.barHighY - 6,
|
|
215
|
+
text: formatDataLabel(bar.datum.close, dataLabelOpts, formatTick)
|
|
216
|
+
}));
|
|
217
|
+
});
|
|
151
218
|
|
|
219
|
+
const dataValueItems = $derived([
|
|
220
|
+
...validData.map((d) => `${d.label}: O ${d.open} H ${d.high} L ${d.low} C ${d.close}`),
|
|
221
|
+
...annotationDataListItems(annotations)
|
|
222
|
+
]);
|
|
223
|
+
|
|
224
|
+
// Stable key per bar (FR-3): its `label`. Resolves a controlled `hoverKey` to
|
|
225
|
+
// an index and feeds `onHoverKeyChange` from pointer events.
|
|
226
|
+
const hoverKeys = $derived(bars.map((b) => b.datum.label));
|
|
227
|
+
function emitHoverKey(index: number | null) {
|
|
228
|
+
onHoverKeyChange?.(index == null ? null : hoverKeys[index] ?? null);
|
|
229
|
+
}
|
|
230
|
+
function handleLeave() {
|
|
231
|
+
hoveredIndex = null;
|
|
232
|
+
emitHoverKey(null);
|
|
233
|
+
}
|
|
152
234
|
function handlePointerMove(event: PointerEvent) {
|
|
153
235
|
const target = event.target;
|
|
154
|
-
if (!(target instanceof Element)) {
|
|
155
|
-
|
|
156
|
-
|
|
236
|
+
if (!(target instanceof Element)) {
|
|
237
|
+
hoveredIndex = null;
|
|
238
|
+
emitHoverKey(null);
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
const raw = Number(target.getAttribute("data-chart-index"));
|
|
242
|
+
const index = Number.isInteger(raw) ? raw : null;
|
|
243
|
+
hoveredIndex = index;
|
|
244
|
+
emitHoverKey(index);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// Index whose crosshair/tooltip is DISPLAYED: the controlled `hoverKey` when
|
|
248
|
+
// provided (resolved against `hoverKeys`), else the internal pointer index.
|
|
249
|
+
const activeIndex = $derived(resolveActiveIndex(hoverKey, hoveredIndex, hoverKeys));
|
|
250
|
+
|
|
251
|
+
// --- Keyboard navigation (FR-5) ------------------------------------------
|
|
252
|
+
// Active when wired explicitly (`keyboardNav`) or implicitly (`onSelectKey`).
|
|
253
|
+
const navEnabled = $derived((keyboardNav === true || onSelectKey !== undefined) && bars.length > 0);
|
|
254
|
+
function focusDatum(index: number) {
|
|
255
|
+
focusedIndex = index;
|
|
256
|
+
datapointRefs[index]?.focus();
|
|
257
|
+
emitHoverKey(index);
|
|
258
|
+
}
|
|
259
|
+
function handleDatapointKeyDown(event: KeyboardEvent, index: number) {
|
|
260
|
+
const action = datapointNavAction(event.key, index, bars.length);
|
|
261
|
+
if (!action) return;
|
|
262
|
+
event.preventDefault();
|
|
263
|
+
if (action.kind === "move") {
|
|
264
|
+
focusDatum(action.index);
|
|
265
|
+
} else if (action.kind === "select") {
|
|
266
|
+
onSelectKey?.(bars[index].datum.label);
|
|
267
|
+
} else {
|
|
268
|
+
focusedIndex = -1;
|
|
269
|
+
emitHoverKey(null);
|
|
270
|
+
onSelectKey?.(null);
|
|
271
|
+
(event.currentTarget as SVGElement).blur();
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
function ohlcAriaLabel(d: OHLCChartDatum): string {
|
|
275
|
+
return datapointAriaLabel(d.label, `O ${d.open} H ${d.high} L ${d.low} C ${d.close}`);
|
|
157
276
|
}
|
|
158
277
|
|
|
159
278
|
const classes = () => ["st-ohlcChart", className].filter(Boolean).join(" ");
|
|
@@ -165,7 +284,7 @@
|
|
|
165
284
|
role="img"
|
|
166
285
|
aria-label={label}
|
|
167
286
|
onpointermove={handlePointerMove}
|
|
168
|
-
onpointerleave={
|
|
287
|
+
onpointerleave={handleLeave}
|
|
169
288
|
>
|
|
170
289
|
<svg
|
|
171
290
|
viewBox="0 0 {width} {height}"
|
|
@@ -188,6 +307,20 @@
|
|
|
188
307
|
<line class="st-ohlcChart__axis" x1={MARGIN.left} x2={MARGIN.left} y1={MARGIN.top} y2={height - MARGIN.bottom} />
|
|
189
308
|
<line class="st-ohlcChart__axis" x1={MARGIN.left} x2={width - MARGIN.right} y1={height - MARGIN.bottom} y2={height - MARGIN.bottom} />
|
|
190
309
|
|
|
310
|
+
<!-- Annotation regions sit BEHIND the bars (filled bands). -->
|
|
311
|
+
{#if annotationRegions.length > 0}
|
|
312
|
+
<g class="st-ohlcChart__annotations st-ohlcChart__annotations--behind">
|
|
313
|
+
{#each annotationRegions as a (a.key)}
|
|
314
|
+
{#if a.kind === "region"}
|
|
315
|
+
<rect class="st-ohlcChart__annotationRegion" x={a.x} y={a.y} width={a.width} height={a.height} />
|
|
316
|
+
{#if a.label}
|
|
317
|
+
<text class="st-ohlcChart__annotationLabel" x={a.x + 4} y={a.y + 11}>{a.label}</text>
|
|
318
|
+
{/if}
|
|
319
|
+
{/if}
|
|
320
|
+
{/each}
|
|
321
|
+
</g>
|
|
322
|
+
{/if}
|
|
323
|
+
|
|
191
324
|
<!-- clé composite pour éviter les doublons -->
|
|
192
325
|
{#each bars as b, i (`${i}-${b.datum.label}`)}
|
|
193
326
|
<g
|
|
@@ -232,13 +365,95 @@
|
|
|
232
365
|
{b.datum.label}
|
|
233
366
|
</text>
|
|
234
367
|
{/each}
|
|
368
|
+
|
|
369
|
+
<!-- Annotations ABOVE the bars: lines, shapes, points, labels. -->
|
|
370
|
+
{#if annotationAbove.length > 0}
|
|
371
|
+
<g class="st-ohlcChart__annotations st-ohlcChart__annotations--above">
|
|
372
|
+
{#each annotationAbove as a (a.key)}
|
|
373
|
+
{#if a.kind === "line"}
|
|
374
|
+
<line class="st-ohlcChart__annotationLine" x1={a.x1} y1={a.y1} x2={a.x2} y2={a.y2} />
|
|
375
|
+
{#if a.label}
|
|
376
|
+
<text
|
|
377
|
+
class="st-ohlcChart__annotationLabel"
|
|
378
|
+
x={a.axis === "x" ? a.x1 + 4 : MARGIN.left + plotWidth - 4}
|
|
379
|
+
y={a.axis === "x" ? MARGIN.top + 11 : a.y1 - 4}
|
|
380
|
+
text-anchor={a.axis === "x" ? "start" : "end"}
|
|
381
|
+
>{a.label}</text>
|
|
382
|
+
{/if}
|
|
383
|
+
{:else if a.kind === "shape"}
|
|
384
|
+
<polygon class="st-ohlcChart__annotationShape" points={polygonPoints(a.points)} />
|
|
385
|
+
{#if a.label}
|
|
386
|
+
<text class="st-ohlcChart__annotationLabel" x={a.labelX} y={a.labelY} text-anchor="middle">{a.label}</text>
|
|
387
|
+
{/if}
|
|
388
|
+
{:else if a.kind === "point"}
|
|
389
|
+
<circle class="st-ohlcChart__annotationPoint" cx={a.x} cy={a.y} r="4.5" />
|
|
390
|
+
{#if a.label}
|
|
391
|
+
<text class="st-ohlcChart__annotationLabel" x={a.x} y={a.y - 8} text-anchor="middle">{a.label}</text>
|
|
392
|
+
{/if}
|
|
393
|
+
{:else}
|
|
394
|
+
<text class="st-ohlcChart__annotationText" x={a.x} y={a.y} text-anchor={a.anchor}>{a.text}</text>
|
|
395
|
+
{/if}
|
|
396
|
+
{/each}
|
|
397
|
+
</g>
|
|
398
|
+
{/if}
|
|
399
|
+
|
|
400
|
+
<!-- Data labels — one close value per bar, drawn on top. aria-hidden. -->
|
|
401
|
+
{#if dataLabelItems.length > 0}
|
|
402
|
+
<g class="st-ohlcChart__dataLabels" aria-hidden="true">
|
|
403
|
+
{#each dataLabelItems as d (d.key)}
|
|
404
|
+
<text class="st-ohlcChart__dataLabel" x={d.x} y={d.y} text-anchor="middle" dominant-baseline="auto">{d.text}</text>
|
|
405
|
+
{/each}
|
|
406
|
+
</g>
|
|
407
|
+
{/if}
|
|
408
|
+
|
|
409
|
+
<!-- Crosshair (FR-3) — a tokenised dashed vertical line at the active bar.
|
|
410
|
+
Decorative (aria-hidden); the value is in the tooltip + ChartDataList. -->
|
|
411
|
+
{#if activeIndex >= 0 && bars[activeIndex]}
|
|
412
|
+
{@const cb = bars[activeIndex]}
|
|
413
|
+
<g class="st-ohlcChart__crosshair" aria-hidden="true">
|
|
414
|
+
<line class="st-ohlcChart__crosshairLine" x1={cb.centerX} x2={cb.centerX} y1={MARGIN.top} y2={MARGIN.top + plotHeight} />
|
|
415
|
+
</g>
|
|
416
|
+
{/if}
|
|
235
417
|
</svg>
|
|
418
|
+
|
|
419
|
+
<!-- Keyboard navigation overlay (FR-5) — a focusable, transparent hit layer
|
|
420
|
+
over the bars. NOT aria-hidden: it is the accessible roving cursor. -->
|
|
421
|
+
{#if navEnabled}
|
|
422
|
+
<svg
|
|
423
|
+
class="st-ohlcChart__navLayer"
|
|
424
|
+
viewBox="0 0 {width} {height}"
|
|
425
|
+
preserveAspectRatio="xMidYMid meet"
|
|
426
|
+
width="100%"
|
|
427
|
+
height="100%"
|
|
428
|
+
role="group"
|
|
429
|
+
aria-label={`${label} — points de données`}
|
|
430
|
+
>
|
|
431
|
+
{#each bars as bar, i (`${i}-${bar.datum.label}`)}
|
|
432
|
+
<rect
|
|
433
|
+
bind:this={datapointRefs[i]}
|
|
434
|
+
class="st-ohlcChart__navDatum"
|
|
435
|
+
x={bar.centerX - bar.band / 2}
|
|
436
|
+
y={MARGIN.top}
|
|
437
|
+
width={bar.band}
|
|
438
|
+
height={plotHeight}
|
|
439
|
+
role="img"
|
|
440
|
+
tabindex={rovingTabIndex(i, focusedIndex, bars.length)}
|
|
441
|
+
aria-label={ohlcAriaLabel(bar.datum)}
|
|
442
|
+
onkeydown={(event) => handleDatapointKeyDown(event, i)}
|
|
443
|
+
onfocus={() => {
|
|
444
|
+
focusedIndex = i;
|
|
445
|
+
emitHoverKey(i);
|
|
446
|
+
}}
|
|
447
|
+
/>
|
|
448
|
+
{/each}
|
|
449
|
+
</svg>
|
|
450
|
+
{/if}
|
|
236
451
|
</div>
|
|
237
452
|
|
|
238
453
|
<ChartDataList {label} items={dataValueItems} />
|
|
239
454
|
|
|
240
|
-
{#if
|
|
241
|
-
{@const b = bars[
|
|
455
|
+
{#if activeIndex >= 0 && bars[activeIndex]}
|
|
456
|
+
{@const b = bars[activeIndex]}
|
|
242
457
|
<div
|
|
243
458
|
class="st-ohlcChart__tooltip"
|
|
244
459
|
role="presentation"
|
|
@@ -266,6 +481,7 @@
|
|
|
266
481
|
|
|
267
482
|
.st-ohlcChart__visual {
|
|
268
483
|
display: block;
|
|
484
|
+
position: relative;
|
|
269
485
|
}
|
|
270
486
|
|
|
271
487
|
.st-ohlcChart__axis {
|
|
@@ -304,6 +520,65 @@
|
|
|
304
520
|
stroke: var(--st-semantic-feedback-error);
|
|
305
521
|
}
|
|
306
522
|
|
|
523
|
+
/* --- Annotation layer ----------------------------------------------------
|
|
524
|
+
Regions render BEHIND the bars; lines/shapes/points/labels render ABOVE. */
|
|
525
|
+
.st-ohlcChart__annotationRegion {
|
|
526
|
+
fill: color-mix(in srgb, var(--st-semantic-feedback-info) 12%, transparent);
|
|
527
|
+
stroke: none;
|
|
528
|
+
}
|
|
529
|
+
.st-ohlcChart__annotationLine {
|
|
530
|
+
stroke: var(--st-semantic-feedback-info);
|
|
531
|
+
stroke-width: 1.5;
|
|
532
|
+
stroke-dasharray: 4 3;
|
|
533
|
+
}
|
|
534
|
+
.st-ohlcChart__annotationShape {
|
|
535
|
+
fill: color-mix(in srgb, var(--st-semantic-feedback-info) 14%, transparent);
|
|
536
|
+
stroke: var(--st-semantic-feedback-info);
|
|
537
|
+
stroke-width: 1.5;
|
|
538
|
+
}
|
|
539
|
+
.st-ohlcChart__annotationPoint {
|
|
540
|
+
fill: var(--st-semantic-feedback-info);
|
|
541
|
+
stroke: var(--st-semantic-surface-default);
|
|
542
|
+
stroke-width: 1.5;
|
|
543
|
+
}
|
|
544
|
+
.st-ohlcChart__annotationLabel,
|
|
545
|
+
.st-ohlcChart__annotationText {
|
|
546
|
+
fill: var(--st-semantic-text-primary);
|
|
547
|
+
font-size: 0.625rem;
|
|
548
|
+
font-weight: 600;
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
/* Data labels — per-bar close value, drawn on top. Token-only colour. */
|
|
552
|
+
.st-ohlcChart__dataLabel {
|
|
553
|
+
fill: var(--st-semantic-text-primary);
|
|
554
|
+
font-size: 0.6875rem;
|
|
555
|
+
font-weight: 600;
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
/* Crosshair (FR-3) — a tokenised dashed vertical line at the active bar. */
|
|
559
|
+
.st-ohlcChart__crosshairLine {
|
|
560
|
+
stroke: var(--st-semantic-border-strong);
|
|
561
|
+
stroke-width: 1;
|
|
562
|
+
stroke-dasharray: 3 3;
|
|
563
|
+
opacity: 0.7;
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
/* Keyboard navigation layer (FR-5) — a focusable, transparent overlay of one
|
|
567
|
+
hit-rect per bar. Carries the roving tab stop; the focus ring is tokenised. */
|
|
568
|
+
.st-ohlcChart__navLayer {
|
|
569
|
+
inset: 0;
|
|
570
|
+
position: absolute;
|
|
571
|
+
}
|
|
572
|
+
.st-ohlcChart__navDatum {
|
|
573
|
+
fill: transparent;
|
|
574
|
+
outline: none;
|
|
575
|
+
}
|
|
576
|
+
.st-ohlcChart__navDatum:focus-visible {
|
|
577
|
+
fill: color-mix(in srgb, var(--st-semantic-border-interactive) 12%, transparent);
|
|
578
|
+
outline: 2px solid var(--st-semantic-border-interactive);
|
|
579
|
+
outline-offset: 1px;
|
|
580
|
+
}
|
|
581
|
+
|
|
307
582
|
@media (prefers-reduced-motion: reduce) {
|
|
308
583
|
.st-ohlcChart__bar {
|
|
309
584
|
transition: none;
|
|
@@ -9,9 +9,15 @@
|
|
|
9
9
|
* label string
|
|
10
10
|
*
|
|
11
11
|
* Props optionnelles :
|
|
12
|
-
* width
|
|
13
|
-
* height
|
|
14
|
-
*
|
|
12
|
+
* width number (défaut 480)
|
|
13
|
+
* height number (défaut 240)
|
|
14
|
+
* annotations ChartAnnotation[] - overlay support/résistance/zones/events
|
|
15
|
+
* dataLabels boolean | { format?, position? } - étiquette close par bâton
|
|
16
|
+
* hoverKey string | null - crosshair contrôlé (clé = date/catégorie)
|
|
17
|
+
* onHoverKeyChange (key) => void - émet la date survolée (ou null)
|
|
18
|
+
* keyboardNav boolean - navigation clavier (roving tabindex)
|
|
19
|
+
* onSelectKey (key) => void - sélection clavier (Enter/Space) ; null = Escape
|
|
20
|
+
* class string
|
|
15
21
|
*/
|
|
16
22
|
export type OHLCChartDatum = {
|
|
17
23
|
label: string;
|
|
@@ -20,11 +26,19 @@ export type OHLCChartDatum = {
|
|
|
20
26
|
low: number;
|
|
21
27
|
close: number;
|
|
22
28
|
};
|
|
29
|
+
import { type ChartAnnotation } from "./chartAnnotations.js";
|
|
30
|
+
import { type DataLabelsProp } from "./chartDataLabels.js";
|
|
23
31
|
type OHLCChartProps = {
|
|
24
32
|
data: OHLCChartDatum[];
|
|
25
33
|
label: string;
|
|
26
34
|
width?: number;
|
|
27
35
|
height?: number;
|
|
36
|
+
annotations?: ChartAnnotation[];
|
|
37
|
+
dataLabels?: DataLabelsProp;
|
|
38
|
+
hoverKey?: string | null;
|
|
39
|
+
onHoverKeyChange?: (key: string | null) => void;
|
|
40
|
+
keyboardNav?: boolean;
|
|
41
|
+
onSelectKey?: (key: string | null) => void;
|
|
28
42
|
class?: string;
|
|
29
43
|
};
|
|
30
44
|
declare const OHLCChart: import("svelte").Component<OHLCChartProps, {}, "">;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"OHLCChart.svelte.d.ts","sourceRoot":"","sources":["../src/lib/OHLCChart.svelte.ts"],"names":[],"mappings":"AAGE
|
|
1
|
+
{"version":3,"file":"OHLCChart.svelte.d.ts","sourceRoot":"","sources":["../src/lib/OHLCChart.svelte.ts"],"names":[],"mappings":"AAGE;;;;;;;;;;;;;;;;;;;;GAoBG;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;AAIJ,OAAO,EAIH,KAAK,eAAe,EACrB,MAAM,uBAAuB,CAAC;AACjC,OAAO,EAAwC,KAAK,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAK/F,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,WAAW,CAAC,EAAE,eAAe,EAAE,CAAC;IAChC,UAAU,CAAC,EAAE,cAAc,CAAC;IAC5B,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,gBAAgB,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAC;IAChD,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,WAAW,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAC;IAC3C,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAgWJ,QAAA,MAAM,SAAS,oDAAwC,CAAC;AACxD,KAAK,SAAS,GAAG,UAAU,CAAC,OAAO,SAAS,CAAC,CAAC;AAC9C,eAAe,SAAS,CAAC"}
|