@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 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: 600,
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
- chart.applyOptions({ width: chartContainerRef.current.clientWidth });
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(