@adrienhobbs/candlekit 0.2.0 → 0.2.2
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/index.d.ts +12 -1
- package/dist/index.js +70 -4
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -166,8 +166,19 @@ interface ChartComponentProps {
|
|
|
166
166
|
trades?: ChartTrade[];
|
|
167
167
|
selectedTradeId?: string | null;
|
|
168
168
|
renderTradePopup?: (trade: ChartTrade) => ReactNode;
|
|
169
|
+
/**
|
|
170
|
+
* Fixed chart height in px. When omitted, the chart fills its container's
|
|
171
|
+
* height (observed via ResizeObserver) instead of a fixed default — so a
|
|
172
|
+
* flex/percent-sized parent gets a full-height chart.
|
|
173
|
+
*/
|
|
174
|
+
height?: number;
|
|
175
|
+
/**
|
|
176
|
+
* When set, recenter the time scale on this trade (by id) so it scrolls into
|
|
177
|
+
* view. Pairs with `selectedTradeId` to drive both selection and focus.
|
|
178
|
+
*/
|
|
179
|
+
focusTradeId?: string | null;
|
|
169
180
|
}
|
|
170
|
-
declare function ChartComponent({ bars, onLoadMoreData, indicators, lines, onBarUpdate, onNewBar, onDeleteLine, onAddLine, onClearAllLines, enableBarSelection, onBarClick, trades, selectedTradeId, renderTradePopup, }: ChartComponentProps): react_jsx_runtime.JSX.Element;
|
|
181
|
+
declare function ChartComponent({ bars, onLoadMoreData, indicators, lines, onBarUpdate, onNewBar, onDeleteLine, onAddLine, onClearAllLines, enableBarSelection, onBarClick, trades, selectedTradeId, renderTradePopup, height, focusTradeId, }: ChartComponentProps): react_jsx_runtime.JSX.Element;
|
|
171
182
|
|
|
172
183
|
interface IndicatorBrowserProps {
|
|
173
184
|
isOpen: boolean;
|
package/dist/index.js
CHANGED
|
@@ -299,7 +299,9 @@ function ChartComponent({
|
|
|
299
299
|
onBarClick,
|
|
300
300
|
trades = [],
|
|
301
301
|
selectedTradeId = null,
|
|
302
|
-
renderTradePopup
|
|
302
|
+
renderTradePopup,
|
|
303
|
+
height,
|
|
304
|
+
focusTradeId = null
|
|
303
305
|
}) {
|
|
304
306
|
const chartContainerRef = useRef(null);
|
|
305
307
|
const chartRef = useRef(null);
|
|
@@ -323,6 +325,10 @@ function ChartComponent({
|
|
|
323
325
|
const barsRef = useRef(bars);
|
|
324
326
|
const isDraggingRef = useRef(false);
|
|
325
327
|
const mouseDownPosRef = useRef(null);
|
|
328
|
+
const heightRef = useRef(height);
|
|
329
|
+
heightRef.current = height;
|
|
330
|
+
const lineEditEnabledRef = useRef(false);
|
|
331
|
+
lineEditEnabledRef.current = Boolean(onAddLine || onClearAllLines);
|
|
326
332
|
useEffect(() => {
|
|
327
333
|
if (!chartContainerRef.current) return;
|
|
328
334
|
chartContainerRef.current.style.position = "relative";
|
|
@@ -341,7 +347,10 @@ function ChartComponent({
|
|
|
341
347
|
horzLines: { color: "#1e293b" }
|
|
342
348
|
},
|
|
343
349
|
width: chartContainerRef.current.clientWidth,
|
|
344
|
-
height
|
|
350
|
+
// Auto-fill the container's height unless an explicit `height` is given.
|
|
351
|
+
// Fall back to 600 only when the container hasn't been laid out yet (a
|
|
352
|
+
// ResizeObserver below corrects it on first measure).
|
|
353
|
+
height: height ?? (chartContainerRef.current.clientHeight || 600),
|
|
345
354
|
timeScale: {
|
|
346
355
|
timeVisible: true,
|
|
347
356
|
secondsVisible: true,
|
|
@@ -396,10 +405,20 @@ function ChartComponent({
|
|
|
396
405
|
});
|
|
397
406
|
const handleResize = () => {
|
|
398
407
|
if (chartContainerRef.current && chart) {
|
|
399
|
-
|
|
408
|
+
const nextHeight = heightRef.current ?? chartContainerRef.current.clientHeight;
|
|
409
|
+
chart.applyOptions({
|
|
410
|
+
width: chartContainerRef.current.clientWidth,
|
|
411
|
+
// Only drive height when auto-filling and the container has a real
|
|
412
|
+
// measured height; otherwise leave the current height untouched.
|
|
413
|
+
...heightRef.current === void 0 && nextHeight > 0 ? { height: nextHeight } : {},
|
|
414
|
+
...heightRef.current !== void 0 ? { height: heightRef.current } : {}
|
|
415
|
+
});
|
|
400
416
|
}
|
|
401
417
|
};
|
|
418
|
+
const resizeObserver = new ResizeObserver(handleResize);
|
|
419
|
+
resizeObserver.observe(chartContainerRef.current);
|
|
402
420
|
const handleContextMenu = (e) => {
|
|
421
|
+
if (!lineEditEnabledRef.current) return;
|
|
403
422
|
e.preventDefault();
|
|
404
423
|
e.stopPropagation();
|
|
405
424
|
if (!candlestickSeriesRef.current) {
|
|
@@ -490,6 +509,7 @@ function ChartComponent({
|
|
|
490
509
|
const container = chartContainerRef.current;
|
|
491
510
|
return () => {
|
|
492
511
|
window.removeEventListener("resize", handleResize);
|
|
512
|
+
resizeObserver.disconnect();
|
|
493
513
|
container.removeEventListener("contextmenu", handleContextMenu, true);
|
|
494
514
|
container.removeEventListener("mousedown", handleMouseDown, true);
|
|
495
515
|
container.removeEventListener("mousemove", handleMouseMove, true);
|
|
@@ -501,6 +521,52 @@ function ChartComponent({
|
|
|
501
521
|
useEffect(() => {
|
|
502
522
|
barsRef.current = bars;
|
|
503
523
|
}, [bars]);
|
|
524
|
+
useEffect(() => {
|
|
525
|
+
if (chartRef.current && height !== void 0) {
|
|
526
|
+
chartRef.current.applyOptions({ height });
|
|
527
|
+
}
|
|
528
|
+
}, [height]);
|
|
529
|
+
useEffect(() => {
|
|
530
|
+
if (!chartRef.current || !focusTradeId) return;
|
|
531
|
+
const trade = trades.find((t) => t.id === focusTradeId);
|
|
532
|
+
if (!trade) return;
|
|
533
|
+
const seen = /* @__PURE__ */ new Set();
|
|
534
|
+
const series = [];
|
|
535
|
+
for (const b of [...bars].sort((a, b2) => a.timestamp - b2.timestamp)) {
|
|
536
|
+
if (seen.has(b.timestamp)) continue;
|
|
537
|
+
seen.add(b.timestamp);
|
|
538
|
+
series.push(b.timestamp);
|
|
539
|
+
}
|
|
540
|
+
if (series.length === 0) return;
|
|
541
|
+
const nearestIdx = (ms) => {
|
|
542
|
+
let lo = 0;
|
|
543
|
+
let hi = series.length - 1;
|
|
544
|
+
let idx = series.length - 1;
|
|
545
|
+
while (lo <= hi) {
|
|
546
|
+
const mid = lo + hi >> 1;
|
|
547
|
+
if (series[mid] >= ms) {
|
|
548
|
+
idx = mid;
|
|
549
|
+
hi = mid - 1;
|
|
550
|
+
} else {
|
|
551
|
+
lo = mid + 1;
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
return idx;
|
|
555
|
+
};
|
|
556
|
+
const entryIdx = nearestIdx(trade.entryTime);
|
|
557
|
+
const exitIdx = Math.max(entryIdx, nearestIdx(trade.exitTime));
|
|
558
|
+
const PAD = 15;
|
|
559
|
+
const raf = requestAnimationFrame(() => {
|
|
560
|
+
try {
|
|
561
|
+
chartRef.current?.timeScale().setVisibleLogicalRange({
|
|
562
|
+
from: entryIdx - PAD,
|
|
563
|
+
to: exitIdx + PAD
|
|
564
|
+
});
|
|
565
|
+
} catch {
|
|
566
|
+
}
|
|
567
|
+
});
|
|
568
|
+
return () => cancelAnimationFrame(raf);
|
|
569
|
+
}, [focusTradeId, trades, bars]);
|
|
504
570
|
useEffect(() => {
|
|
505
571
|
if (!candlestickSeriesRef.current || !volumeSeriesRef.current) return;
|
|
506
572
|
const sortedBars = [...bars].sort((a, b) => a.timestamp - b.timestamp);
|
|
@@ -836,7 +902,7 @@ function ChartComponent({
|
|
|
836
902
|
}
|
|
837
903
|
)
|
|
838
904
|
] }),
|
|
839
|
-
chartContainerRef.current && lines.map((line) => {
|
|
905
|
+
onDeleteLine && chartContainerRef.current && lines.map((line) => {
|
|
840
906
|
const pos = linePositions.get(line.id);
|
|
841
907
|
if (!pos) return null;
|
|
842
908
|
return createPortal(
|