@forgecharts/sdk 1.5.68 → 1.5.70
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/core/Chart.d.ts +2 -0
- package/dist/core/Chart.d.ts.map +1 -1
- package/dist/core/__tests__/cursorTime.test.d.ts +2 -0
- package/dist/core/__tests__/cursorTime.test.d.ts.map +1 -0
- package/dist/core/cursorTime.d.ts +27 -0
- package/dist/core/cursorTime.d.ts.map +1 -0
- package/dist/datafeed/DatafeedConnector.d.ts +5 -3
- package/dist/datafeed/DatafeedConnector.d.ts.map +1 -1
- package/dist/index.js +95 -65
- package/dist/index.js.map +1 -1
- package/dist/internal.js +95 -65
- package/dist/internal.js.map +1 -1
- package/dist/pixi/PixiChart.d.ts +2 -0
- package/dist/pixi/PixiChart.d.ts.map +1 -1
- package/dist/pixi/PixiCrosshairRenderer.d.ts +9 -1
- package/dist/pixi/PixiCrosshairRenderer.d.ts.map +1 -1
- package/dist/react/index.js +95 -65
- package/dist/react/index.js.map +1 -1
- package/dist/react/internal.js +95 -65
- package/dist/react/internal.js.map +1 -1
- package/package.json +1 -1
package/dist/internal.js
CHANGED
|
@@ -100,6 +100,36 @@ function isGLBXDailyBreak(nowMs = Date.now()) {
|
|
|
100
100
|
return weekday === "Mon" || weekday === "Tue" || weekday === "Wed" || weekday === "Thu";
|
|
101
101
|
}
|
|
102
102
|
|
|
103
|
+
// src/core/cursorTime.ts
|
|
104
|
+
function cursorBucketTime(bars, transform, interval, x) {
|
|
105
|
+
const raw = transform.xToTime(x);
|
|
106
|
+
const tfSec = parseTfSeconds(interval) ?? 0;
|
|
107
|
+
const floored = tfSec > 0 ? Math.floor(raw / tfSec) * tfSec : raw;
|
|
108
|
+
if (!bars || bars.length === 0) return floored;
|
|
109
|
+
let lo = 0, hi = bars.length - 1;
|
|
110
|
+
while (lo < hi) {
|
|
111
|
+
const mid = lo + hi >>> 1;
|
|
112
|
+
if (bars[mid].time < raw) lo = mid + 1;
|
|
113
|
+
else hi = mid;
|
|
114
|
+
}
|
|
115
|
+
let nearest = lo;
|
|
116
|
+
let best = Math.abs(transform.timeToX(bars[lo].time) - x);
|
|
117
|
+
for (const i of [lo - 1, lo + 1]) {
|
|
118
|
+
if (i < 0 || i >= bars.length) continue;
|
|
119
|
+
const d = Math.abs(transform.timeToX(bars[i].time) - x);
|
|
120
|
+
if (d < best) {
|
|
121
|
+
best = d;
|
|
122
|
+
nearest = i;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
const first = bars[0].time;
|
|
126
|
+
const last = bars[bars.length - 1].time;
|
|
127
|
+
const halfCol = bars.length > 1 ? Math.abs(transform.timeToX(bars[1].time) - transform.timeToX(first)) / 2 : Infinity;
|
|
128
|
+
const edgeX = raw > last ? transform.timeToX(last) : raw < first ? transform.timeToX(first) : null;
|
|
129
|
+
if (edgeX !== null && Math.abs(x - edgeX) > halfCol) return floored;
|
|
130
|
+
return bars[nearest].time;
|
|
131
|
+
}
|
|
132
|
+
|
|
103
133
|
// src/core/CanvasLayer.ts
|
|
104
134
|
var CanvasLayer = class {
|
|
105
135
|
canvas;
|
|
@@ -4792,10 +4822,7 @@ var Chart = class {
|
|
|
4792
4822
|
onCrosshairMove: (x, y) => {
|
|
4793
4823
|
if (!this._crosshairEnabled) return;
|
|
4794
4824
|
if (this._magnetMode === "off") {
|
|
4795
|
-
|
|
4796
|
-
const rawTime = this._transform.xToTime(x);
|
|
4797
|
-
const flooredTime = Math.floor(rawTime / tfSec) * tfSec;
|
|
4798
|
-
this._crosshair.update(x, y, flooredTime);
|
|
4825
|
+
this._crosshair.update(x, y, this._cursorBucketTime(x));
|
|
4799
4826
|
} else {
|
|
4800
4827
|
const snap = this.snapXToBar(x);
|
|
4801
4828
|
const sy = this._magnetMode === "snap-xy" ? snap.closeY ?? y : y;
|
|
@@ -4836,7 +4863,8 @@ var Chart = class {
|
|
|
4836
4863
|
},
|
|
4837
4864
|
onDrawPointerMove: (x, y) => {
|
|
4838
4865
|
this._drawingCursorPt = {
|
|
4839
|
-
time:
|
|
4866
|
+
// Bucket time, not raw: the label and the stored anchor agree.
|
|
4867
|
+
time: this._cursorBucketTime(x),
|
|
4840
4868
|
price: this._transform.yToPrice(y)
|
|
4841
4869
|
};
|
|
4842
4870
|
this._dirty = true;
|
|
@@ -5109,9 +5137,19 @@ var Chart = class {
|
|
|
5109
5137
|
closeY: this._transform.priceToY(bar.close)
|
|
5110
5138
|
};
|
|
5111
5139
|
}
|
|
5112
|
-
|
|
5113
|
-
|
|
5114
|
-
|
|
5140
|
+
return { x, time: this._cursorBucketTime(x) };
|
|
5141
|
+
}
|
|
5142
|
+
/** Bucket time for a cursor x — see core/cursorTime.ts for the rule. */
|
|
5143
|
+
_cursorBucketTime(x) {
|
|
5144
|
+
let bars = null;
|
|
5145
|
+
for (const s of this._series) {
|
|
5146
|
+
const data = s.data();
|
|
5147
|
+
if (data.length > 0) {
|
|
5148
|
+
bars = data;
|
|
5149
|
+
break;
|
|
5150
|
+
}
|
|
5151
|
+
}
|
|
5152
|
+
return cursorBucketTime(bars, this._transform, this._interval, x);
|
|
5115
5153
|
}
|
|
5116
5154
|
_computeViewport() {
|
|
5117
5155
|
return {
|
|
@@ -5588,7 +5626,7 @@ var Chart = class {
|
|
|
5588
5626
|
const MAGNET_PX = 15;
|
|
5589
5627
|
const t = this._transform;
|
|
5590
5628
|
const price = t.yToPrice(y);
|
|
5591
|
-
const time =
|
|
5629
|
+
const time = this._cursorBucketTime(x);
|
|
5592
5630
|
if (this._magnetMode === "off") return { time, price };
|
|
5593
5631
|
let bestBar = null;
|
|
5594
5632
|
let bestDist = SNAP_PX;
|
|
@@ -6297,7 +6335,16 @@ var PixiCrosshairRenderer = class {
|
|
|
6297
6335
|
interval = "1h";
|
|
6298
6336
|
/** IANA timezone for the time label. Falls back to UTC. */
|
|
6299
6337
|
timezone = "UTC";
|
|
6300
|
-
|
|
6338
|
+
/**
|
|
6339
|
+
* The time the chart computed for this cursor position — nearest bar, else
|
|
6340
|
+
* floored to the timeframe grid. This renderer must never derive its own:
|
|
6341
|
+
* it once did (raw xToTime), and every pointer style's time label showed
|
|
6342
|
+
* interpolated minute noise (10:02 on an hourly chart) on any timeframe
|
|
6343
|
+
* wider than the interpolation error.
|
|
6344
|
+
*/
|
|
6345
|
+
_snapTime = null;
|
|
6346
|
+
update(x, y, snapTime) {
|
|
6347
|
+
this._snapTime = snapTime ?? null;
|
|
6301
6348
|
this._x = x;
|
|
6302
6349
|
this._y = y;
|
|
6303
6350
|
}
|
|
@@ -6339,7 +6386,7 @@ var PixiCrosshairRenderer = class {
|
|
|
6339
6386
|
this._priceLabel.text = priceText;
|
|
6340
6387
|
this._priceLabel.x = pw + 4;
|
|
6341
6388
|
this._priceLabel.y = y - this._priceLabel.height / 2;
|
|
6342
|
-
const time = transform.xToTime(x);
|
|
6389
|
+
const time = this._snapTime ?? transform.xToTime(x);
|
|
6343
6390
|
const timeText = this._formatTime(time);
|
|
6344
6391
|
const tlw = DAILY_INTERVALS.has(this.interval) ? 120 : 170;
|
|
6345
6392
|
const tlh = tsh - 4;
|
|
@@ -8736,11 +8783,11 @@ var PixiChart = class {
|
|
|
8736
8783
|
onCrosshairMove: (x, y) => {
|
|
8737
8784
|
if (!this._crosshairEnabled) return;
|
|
8738
8785
|
if (this._magnetMode === "off") {
|
|
8739
|
-
this._crosshairRenderer.update(x, y);
|
|
8786
|
+
this._crosshairRenderer.update(x, y, this._cursorBucketTime(x));
|
|
8740
8787
|
} else {
|
|
8741
8788
|
const snap = this.snapXToBar(x);
|
|
8742
8789
|
const sy = this._magnetMode === "snap-xy" ? snap.closeY ?? y : y;
|
|
8743
|
-
this._crosshairRenderer.update(snap.x, sy);
|
|
8790
|
+
this._crosshairRenderer.update(snap.x, sy, snap.time);
|
|
8744
8791
|
}
|
|
8745
8792
|
const ah = this._interaction.axisHover;
|
|
8746
8793
|
if (ah !== this._lastAxisHover) {
|
|
@@ -8786,7 +8833,9 @@ var PixiChart = class {
|
|
|
8786
8833
|
},
|
|
8787
8834
|
onDrawPointerMove: (x, y) => {
|
|
8788
8835
|
this._drawingCursorPt = {
|
|
8789
|
-
time:
|
|
8836
|
+
// Bucket time, not raw: what the crosshair label shows is what a
|
|
8837
|
+
// drawing placed here will store.
|
|
8838
|
+
time: this._cursorBucketTime(x),
|
|
8790
8839
|
price: this._transform.yToPrice(y)
|
|
8791
8840
|
};
|
|
8792
8841
|
this._layers.dirty.mark(3 /* Drawing */);
|
|
@@ -9353,6 +9402,18 @@ var PixiChart = class {
|
|
|
9353
9402
|
setNativeCursorHidden(hidden) {
|
|
9354
9403
|
this._interaction.setHideCursor(hidden);
|
|
9355
9404
|
}
|
|
9405
|
+
/** Bucket time for a cursor x — see core/cursorTime.ts for the rule. */
|
|
9406
|
+
_cursorBucketTime(x) {
|
|
9407
|
+
let bars = null;
|
|
9408
|
+
for (const s of this._series) {
|
|
9409
|
+
const data = s.data();
|
|
9410
|
+
if (data.length > 0) {
|
|
9411
|
+
bars = data;
|
|
9412
|
+
break;
|
|
9413
|
+
}
|
|
9414
|
+
}
|
|
9415
|
+
return cursorBucketTime(bars, this._transform, this._interval, x);
|
|
9416
|
+
}
|
|
9356
9417
|
snapXToBar(x) {
|
|
9357
9418
|
const SNAP_PX = 20;
|
|
9358
9419
|
let bars = null;
|
|
@@ -9396,7 +9457,7 @@ var PixiChart = class {
|
|
|
9396
9457
|
closeY: t.priceToY(bar.close)
|
|
9397
9458
|
};
|
|
9398
9459
|
}
|
|
9399
|
-
return { x, time:
|
|
9460
|
+
return { x, time: this._cursorBucketTime(x) };
|
|
9400
9461
|
}
|
|
9401
9462
|
setIndicatorsVisible(v) {
|
|
9402
9463
|
this._indicatorsVisible = v;
|
|
@@ -10369,14 +10430,13 @@ function toOHLCV(bar) {
|
|
|
10369
10430
|
function _historyWindow(tf) {
|
|
10370
10431
|
return 500 * (parseTfSeconds(tf) ?? 3600);
|
|
10371
10432
|
}
|
|
10372
|
-
var
|
|
10433
|
+
var REFETCH_ON_DOUBT_DELAY_MS = 3e3;
|
|
10373
10434
|
function tfSeconds(tf) {
|
|
10374
10435
|
const m = /^(\d+)\s*(s|m|h|D|W|M)$/.exec(tf);
|
|
10375
10436
|
if (!m) return 0;
|
|
10376
10437
|
const mult = { s: 1, m: 60, h: 3600, D: 86400, W: 604800, M: 2592e3 }[m[2]];
|
|
10377
10438
|
return parseInt(m[1], 10) * mult;
|
|
10378
10439
|
}
|
|
10379
|
-
var LIVE_GAP_REPAIR_DELAY_MS = 3e3;
|
|
10380
10440
|
function _pageSize(tf) {
|
|
10381
10441
|
return 500 * (parseTfSeconds(tf) ?? 3600);
|
|
10382
10442
|
}
|
|
@@ -10412,9 +10472,11 @@ var DatafeedConnector = class {
|
|
|
10412
10472
|
*/
|
|
10413
10473
|
_emptyPageStreak = 0;
|
|
10414
10474
|
/**
|
|
10415
|
-
*
|
|
10416
|
-
*
|
|
10417
|
-
*
|
|
10475
|
+
* Nothing arms this any more — live-tick suppression went with the rest of
|
|
10476
|
+
* the client-side stitching once history became authoritative through the
|
|
10477
|
+
* forming bar. Kept (with its clear helper) because reconnect paths call
|
|
10478
|
+
* the helper and older subclasses may too; clearing a never-set timer is
|
|
10479
|
+
* free. Remove both in the next breaking cleanup.
|
|
10418
10480
|
*/
|
|
10419
10481
|
_batchSafetyTimer = null;
|
|
10420
10482
|
constructor(datafeed, series, callbacks) {
|
|
@@ -10438,12 +10500,10 @@ var DatafeedConnector = class {
|
|
|
10438
10500
|
const to = Math.floor(Date.now() / 1e3);
|
|
10439
10501
|
const from = to - _historyWindow(timeframe);
|
|
10440
10502
|
this._clearBatchTimer();
|
|
10441
|
-
let batchPending = false;
|
|
10442
10503
|
const engine = new CandleEngine({
|
|
10443
10504
|
// Every live tick mutates the engine's bar array and the chart series.
|
|
10444
10505
|
onBarUpdated: (bar) => {
|
|
10445
10506
|
if (this._activeUID !== uid) return;
|
|
10446
|
-
if (batchPending) return;
|
|
10447
10507
|
const ohlcv = toOHLCV(bar);
|
|
10448
10508
|
this._series.update(ohlcv);
|
|
10449
10509
|
this._cb.onUpdate?.(ohlcv);
|
|
@@ -10497,54 +10557,33 @@ var DatafeedConnector = class {
|
|
|
10497
10557
|
}).catch(() => {
|
|
10498
10558
|
});
|
|
10499
10559
|
if (this._activeUID !== uid) return;
|
|
10500
|
-
if (lastBarTime != null) {
|
|
10501
|
-
batchPending = true;
|
|
10502
|
-
this._batchSafetyTimer = setTimeout(() => {
|
|
10503
|
-
batchPending = false;
|
|
10504
|
-
this._batchSafetyTimer = null;
|
|
10505
|
-
}, BATCH_SUPPRESSION_MS);
|
|
10506
|
-
}
|
|
10507
10560
|
const tfSec = tfSeconds(timeframe);
|
|
10508
10561
|
let lastSeenBarTime = lastBarTime ?? 0;
|
|
10509
|
-
let
|
|
10510
|
-
const
|
|
10511
|
-
|
|
10512
|
-
|
|
10513
|
-
|
|
10514
|
-
if (repairTimer) clearTimeout(repairTimer);
|
|
10515
|
-
repairTimer = setTimeout(() => {
|
|
10516
|
-
repairTimer = null;
|
|
10562
|
+
let refetchTimer = null;
|
|
10563
|
+
const refetchOnDoubt = (fromTime, toTime) => {
|
|
10564
|
+
if (refetchTimer) clearTimeout(refetchTimer);
|
|
10565
|
+
refetchTimer = setTimeout(() => {
|
|
10566
|
+
refetchTimer = null;
|
|
10517
10567
|
if (this._activeUID !== uid || this._destroyed) return;
|
|
10518
10568
|
void this._datafeed.getHistoricalBars(symbol, timeframe, fromTime, toTime).then(({ bars: filled }) => {
|
|
10519
|
-
if (this._activeUID !== uid) return;
|
|
10520
|
-
|
|
10521
|
-
const newest = filled
|
|
10522
|
-
|
|
10523
|
-
if (!covered && repairAttempts < REPAIR_MAX_ATTEMPTS) {
|
|
10524
|
-
repairAttempts += 1;
|
|
10525
|
-
if (repairTimer) clearTimeout(repairTimer);
|
|
10526
|
-
repairTimer = setTimeout(() => {
|
|
10527
|
-
repairTimer = null;
|
|
10528
|
-
repairGap(fromTime, toTime);
|
|
10529
|
-
}, REPAIR_RETRY_MS);
|
|
10530
|
-
} else if (covered) {
|
|
10531
|
-
repairAttempts = 0;
|
|
10532
|
-
}
|
|
10569
|
+
if (this._activeUID !== uid || filled.length === 0) return;
|
|
10570
|
+
engine.backfillGap(filled);
|
|
10571
|
+
const newest = filled[filled.length - 1].time;
|
|
10572
|
+
if (newest > lastSeenBarTime) lastSeenBarTime = newest;
|
|
10533
10573
|
}).catch(() => {
|
|
10534
10574
|
});
|
|
10535
|
-
},
|
|
10575
|
+
}, REFETCH_ON_DOUBT_DELAY_MS);
|
|
10536
10576
|
};
|
|
10537
10577
|
this._datafeed.subscribeBars(symbol, timeframe, (bar) => {
|
|
10538
10578
|
if (this._activeUID !== uid) return;
|
|
10539
10579
|
const t = bar.time;
|
|
10540
10580
|
if (tfSec > 0 && lastSeenBarTime > 0 && t > lastSeenBarTime + tfSec) {
|
|
10541
|
-
|
|
10581
|
+
refetchOnDoubt(lastSeenBarTime, t);
|
|
10542
10582
|
}
|
|
10543
10583
|
if (t > lastSeenBarTime) lastSeenBarTime = t;
|
|
10544
10584
|
engine.applyLiveUpdate(bar);
|
|
10545
10585
|
}, uid, lastBarTime, (bars) => {
|
|
10546
10586
|
this._clearBatchTimer();
|
|
10547
|
-
batchPending = false;
|
|
10548
10587
|
if (this._activeUID === uid) {
|
|
10549
10588
|
engine.backfillGap(bars);
|
|
10550
10589
|
const newest = bars.length > 0 ? bars[bars.length - 1].time : 0;
|
|
@@ -10552,16 +10591,7 @@ var DatafeedConnector = class {
|
|
|
10552
10591
|
}
|
|
10553
10592
|
fireLoaded();
|
|
10554
10593
|
});
|
|
10555
|
-
if (result.filling)
|
|
10556
|
-
setTimeout(() => {
|
|
10557
|
-
if (this._activeUID !== uid || this._destroyed) return;
|
|
10558
|
-
void this._datafeed.getHistoricalBars(symbol, timeframe, from, to).then(({ bars: refetchedBars }) => {
|
|
10559
|
-
if (this._activeUID !== uid) return;
|
|
10560
|
-
engine.backfillGap(refetchedBars);
|
|
10561
|
-
}).catch(() => {
|
|
10562
|
-
});
|
|
10563
|
-
}, 8e3);
|
|
10564
|
-
}
|
|
10594
|
+
if (result.filling) refetchOnDoubt(from, to);
|
|
10565
10595
|
}).catch((err) => {
|
|
10566
10596
|
if (this._activeUID !== uid) return;
|
|
10567
10597
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -25585,7 +25615,7 @@ var demonstrationTool = {
|
|
|
25585
25615
|
};
|
|
25586
25616
|
|
|
25587
25617
|
// src/version.ts
|
|
25588
|
-
var FORGECHARTS_VERSION = "1.5.
|
|
25618
|
+
var FORGECHARTS_VERSION = "1.5.70" ;
|
|
25589
25619
|
|
|
25590
25620
|
// src/compatibility.ts
|
|
25591
25621
|
var MIN_KIT_API = 1;
|