@trdrs/quickcharts 5.0.0 → 5.1.0

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/CHANGELOG.md CHANGED
@@ -2,6 +2,14 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 5.1.0
6
+
7
+ Charts open live subscriptions alongside history, frame the first arriving data, and retain newer streamed prices when history arrives later. A snapshot with `backfill: true` adds missing history without replacing bars already held.
8
+
9
+ ## 5.0.1
10
+
11
+ Drawing gestures respect presses handled by host overlays and leave presses outside the plot to the chart scales.
12
+
5
13
  ## 5.0.0
6
14
 
7
15
  The chart settings, one typed tree saved with the chart in place of the appearance overrides, with a dialog of five pages a host can add its own to, the price labels and scale controls they drive, the plus beside the crosshair's price and its price menu, panes that move and maximize, time-scale marks with icons and lines, the optional prices port, and extension seams for legend rows, captures and settings pages.
package/README.md CHANGED
@@ -143,6 +143,9 @@ export const myFeed: ChartDatafeed = {
143
143
  4. **Live updates mutate the last bar or append.** `subscribeBars` emits `{ kind: 'snapshot', bars }` on
144
144
  connect and after every reconnect (the self-healing re-sync), then `{ kind: 'bar', bar }` per update.
145
145
  A bar's `t` is its **bucket-open** epoch-seconds time, never the update's wall-clock time.
146
+ The chart opens the subscription alongside history. The first data frames the chart; history
147
+ arriving later adds older bars and retains newer stream readings. Mark a preliminary history
148
+ snapshot with `backfill: true`: it fills missing timestamps without replacing bars already held.
146
149
  5. **Bar time is epoch SECONDS.** Not milliseconds.
147
150
  6. **No feed for a symbol is terminal.** Throw `FeedUnavailableError` from `history` when no feed serves
148
151
  the symbol; the chart shows "market data unavailable" and stops. A transient fetch failure should
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "5.0.0",
2
+ "version": "5.1.0",
3
3
  "styles": [
4
4
  {
5
5
  "id": "candles",
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { C as ChartDatafeed, M as MarkIconId, F as FeedBar, S as SymbolRow, D as DatafeedSearchOptions, a as SearchPage, b as SearchClassNode } from './symbols-DdHLHQFC.js';
2
- export { B as BarMark, c as BarsEvent, d as DatafeedConfig, e as FeedUnavailableError, H as HistoryPage, f as MarkColor, g as MarkColorPair, h as MarkColorRole, i as MarkLine, j as MarkLineStyle, k as MarkPlacement, l as MarkShape, O as OlderPageVerdict, P as PricesHandlers, m as SubscribeHandlers, n as SymbolNames, o as SymbolPrices, T as TimescaleMark, p as olderPageVerdict, s as symbolNames } from './symbols-DdHLHQFC.js';
1
+ import { C as ChartDatafeed, M as MarkIconId, F as FeedBar, S as SymbolRow, D as DatafeedSearchOptions, a as SearchPage, b as SearchClassNode } from './symbols-CHJGWrf-.js';
2
+ export { B as BarMark, c as BarsEvent, d as DatafeedConfig, e as FeedUnavailableError, H as HistoryPage, f as MarkColor, g as MarkColorPair, h as MarkColorRole, i as MarkLine, j as MarkLineStyle, k as MarkPlacement, l as MarkShape, O as OlderPageVerdict, P as PricesHandlers, m as SubscribeHandlers, n as SymbolNames, o as SymbolPrices, T as TimescaleMark, p as olderPageVerdict, s as symbolNames } from './symbols-CHJGWrf-.js';
3
3
  import { ao as en, C as ChartMessageKey, G as GlyphSourcePort, M as MagnetMode, d as CursorMode, Q as TextEditFrame, N as TextDraft, L as LineStyle, I as IDrawing, m as DrawingCounts, D as DrawingStyle, $ as VisibilityPreset, X as ToolPreset, Y as ToolTemplate, S as SerializedDrawing, o as DrawingPreferences, l as DrawingAssetPort } from './assets-DF3nozNV.js';
4
4
  import { b as ResourceRef, C as ChartSaveLoadAdapter, q as ChartMeta, t as ChartBody, h as DrawingResourceContext, R as ResourceStore, u as DrawingsMeta, j as DrawingsBody, T as TemplateMeta, a as TemplateBody, v as LayoutMeta, w as LayoutBody, e as DrawingContextKind } from './resources-XglhzzW0.js';
5
5
  export { c as ChartLocalDrawingContext, D as DRAWING_CONTEXT_VERSION, d as DRAWING_DOCUMENT_VERSION, f as DrawingEntry, g as DrawingGroup, i as DrawingTombstone, L as LayoutSharedDrawingContext, M as MemoryResourcesOptions, x as ResourceAbortError, S as SymbolGlobalDrawingContext, y as TemplateKind, W as WriteOutcome, k as drawingContextKey, l as emptyDrawingDocument, m as liveDrawingEntries, n as liveDrawingGroups, z as memorySaveLoadAdapter, o as mergeDrawingDocuments, p as parseDrawingDocument, r as reviseDrawingDocument, s as sameDrawingContext } from './resources-XglhzzW0.js';
package/dist/index.js CHANGED
@@ -4912,6 +4912,17 @@ function layoutChoices(offered) {
4912
4912
  return (arrangementOf(offered.arrangements[0])?.count ?? 1) > 1 && offered.sync.length > 0;
4913
4913
  }
4914
4914
 
4915
+ // src/widget/barSnapshot.ts
4916
+ function mergeBarSnapshot(held, incoming, backfill = false) {
4917
+ if (backfill) {
4918
+ const merged2 = new Map(incoming.map((bar) => [bar.t, bar]));
4919
+ for (const bar of held) merged2.set(bar.t, bar);
4920
+ return [...merged2.values()].sort((a, b) => a.t - b.t);
4921
+ }
4922
+ const first = incoming[0]?.t;
4923
+ return first === void 0 ? [...incoming] : [...held.filter((bar) => bar.t < first), ...incoming];
4924
+ }
4925
+
4915
4926
  // src/widget/chart.ts
4916
4927
  import {
4917
4928
  CrosshairMode as CrosshairMode2,
@@ -7021,6 +7032,10 @@ function bindGestures(ctx) {
7021
7032
  };
7022
7033
  ctx.toolChanged = dropPreview;
7023
7034
  const onDown = (e) => {
7035
+ if (e.defaultPrevented) return;
7036
+ const press = localXY(e);
7037
+ const plot = viewport();
7038
+ if (!plot || press.x < 0 || press.x > plot.width || press.y < 0 || press.y > plot.height) return;
7024
7039
  reopenMenu();
7025
7040
  dropPreview();
7026
7041
  if (e.button === 2 && ctx.draft) {
@@ -16649,8 +16664,7 @@ function attachReplayPlane(deps) {
16649
16664
  if (master === null) return false;
16650
16665
  if (event.kind === "snapshot") {
16651
16666
  const fresh = event.bars.filter((b) => deps.visible(b.t));
16652
- const first = fresh[0]?.t;
16653
- master = first === void 0 ? fresh : [...master.filter((b) => b.t < first), ...fresh];
16667
+ master = mergeBarSnapshot(master, fresh, event.backfill);
16654
16668
  } else if (deps.visible(event.bar.t)) {
16655
16669
  const last = master[master.length - 1];
16656
16670
  if (!last || event.bar.t > last.t) master = [...master, event.bar];
@@ -21957,6 +21971,7 @@ function createChartInstance(deps) {
21957
21971
  let symbolFormatter = createPriceFormatter(UNRESOLVED_PRICE_FORMAT, { locale: i18n.tag() });
21958
21972
  let epoch = 0;
21959
21973
  let historyPainted = false;
21974
+ let initialHistoryPending = false;
21960
21975
  let pendingFrame = null;
21961
21976
  let pendingLeftEdge = null;
21962
21977
  let menuLevel = null;
@@ -22991,7 +23006,7 @@ function createChartInstance(deps) {
22991
23006
  }
22992
23007
  function maybePageBack(runway = PAGE_RUNWAY) {
22993
23008
  if (replay2.active()) return;
22994
- if (paging || noMoreHistory || bars.length === 0) return;
23009
+ if (initialHistoryPending || paging || noMoreHistory || bars.length === 0) return;
22995
23010
  const range2 = chart.timeScale().getVisibleLogicalRange();
22996
23011
  if (!range2 || range2.from > PAGE_TRIGGER_BARS) return;
22997
23012
  paging = true;
@@ -23120,6 +23135,7 @@ function createChartInstance(deps) {
23120
23135
  bars = [];
23121
23136
  paging = false;
23122
23137
  historyPainted = false;
23138
+ initialHistoryPending = true;
23123
23139
  if (holdFrame !== null) window.cancelAnimationFrame(holdFrame);
23124
23140
  holdFrame = null;
23125
23141
  mainScale().applyOptions({ autoScale: true });
@@ -23165,49 +23181,72 @@ function createChartInstance(deps) {
23165
23181
  }).catch(() => {
23166
23182
  });
23167
23183
  }
23168
- void datafeed.history(symbol, tf, { countBack: firstPage }).then((page) => {
23169
- if (disposed || myEpoch !== epoch) return;
23170
- bars = [...page.bars];
23171
- paintAll();
23172
- historyPainted = true;
23173
- const framing = pendingFrame?.epoch === myEpoch ? pendingFrame : null;
23174
- pendingFrame = null;
23175
- const edge = pendingLeftEdge?.epoch === myEpoch ? pendingLeftEdge : null;
23176
- pendingLeftEdge = null;
23177
- if (framing) {
23178
- frameRange(chart, anchor, framing.span, framing.tf);
23179
- fillSpan(framing.span, framing.tf);
23180
- } else if (edge) keepLeftEdge(edge);
23181
- else chart.timeScale().fitContent();
23182
- holdAxisAfterFrame();
23183
- refreshMarks();
23184
+ let firstFrameSize = 0;
23185
+ let frameNavigation = -1;
23186
+ const firstPaint = (complete = false) => {
23187
+ if (complete) initialHistoryPending = false;
23188
+ const grow = complete && firstFrameSize < firstPage && bars.length > firstFrameSize && frameNavigation === navigationEpoch;
23189
+ if (bars.length > 0 && (!historyPainted || grow)) {
23190
+ historyPainted = true;
23191
+ const framing = pendingFrame?.epoch === myEpoch ? pendingFrame : null;
23192
+ pendingFrame = null;
23193
+ const edge = pendingLeftEdge?.epoch === myEpoch ? pendingLeftEdge : null;
23194
+ pendingLeftEdge = null;
23195
+ if (framing) {
23196
+ frameRange(chart, anchor, framing.span, framing.tf);
23197
+ fillSpan(framing.span, framing.tf);
23198
+ } else if (edge) keepLeftEdge(edge);
23199
+ else chart.timeScale().fitContent();
23200
+ firstFrameSize = bars.length;
23201
+ frameNavigation = framing || edge ? -1 : navigationEpoch;
23202
+ holdAxisAfterFrame();
23203
+ refreshMarks();
23204
+ }
23184
23205
  if (!ready) {
23185
23206
  ready = true;
23186
23207
  deps.onReady();
23187
23208
  }
23188
- openSubscription(myEpoch);
23209
+ };
23210
+ queueMicrotask(() => openSubscription(myEpoch, firstPaint));
23211
+ void datafeed.history(symbol, tf, { countBack: firstPage }).then((page) => {
23212
+ if (disposed || myEpoch !== epoch) return;
23213
+ const merged2 = new Map(page.bars.map((bar) => [bar.t, bar]));
23214
+ for (const bar of bars) merged2.set(bar.t, bar);
23215
+ const write = () => {
23216
+ bars = [...merged2.values()].sort((a, b) => a.t - b.t);
23217
+ paintAll();
23218
+ };
23219
+ if (historyPainted) maintainTimeline(write);
23220
+ else write();
23221
+ firstPaint(true);
23189
23222
  }).catch((e) => {
23190
23223
  if (disposed || myEpoch !== epoch) return;
23191
- historyPainted = true;
23192
- pendingFrame = null;
23193
23224
  if (e instanceof FeedUnavailableError) {
23225
+ unsubscribe?.();
23226
+ unsubscribe = null;
23227
+ bars = [];
23228
+ paintAll();
23194
23229
  feedStatus = "feed_unavailable";
23195
23230
  events.emit("feedStatus", "feed_unavailable");
23196
- return;
23197
23231
  }
23198
- openSubscription(myEpoch);
23232
+ }).finally(() => {
23233
+ if (myEpoch === epoch) initialHistoryPending = false;
23199
23234
  });
23200
23235
  }
23201
- function openSubscription(myEpoch) {
23236
+ function openSubscription(myEpoch, firstPaint) {
23202
23237
  if (disposed || myEpoch !== epoch) return;
23203
- unsubscribe = datafeed.subscribeBars(symbol, tf, {
23238
+ let active = true;
23239
+ const off = datafeed.subscribeBars(symbol, tf, {
23204
23240
  onBars: (e) => {
23205
- if (disposed || myEpoch !== epoch) return;
23241
+ if (!active || disposed || myEpoch !== epoch) return;
23206
23242
  const replaying = replay2.absorb(e);
23207
23243
  if (e.kind === "snapshot") {
23208
- const first = e.bars[0]?.t;
23209
- bars = first === void 0 ? [...e.bars] : [...bars.filter((b) => b.t < first), ...e.bars];
23210
- if (!replaying) paintAll();
23244
+ const write = () => {
23245
+ bars = mergeBarSnapshot(bars, e.bars, e.backfill);
23246
+ if (!replaying) paintAll();
23247
+ };
23248
+ if (!replaying && historyPainted) maintainTimeline(write);
23249
+ else write();
23211
23250
  } else {
23212
23251
  const next = applyBar(bars, e.bar);
23213
23252
  if (next) {
@@ -23215,14 +23254,19 @@ function createChartInstance(deps) {
23215
23254
  if (!replaying) paintLast(e.bar);
23216
23255
  }
23217
23256
  }
23257
+ if (!replaying) firstPaint(e.kind === "snapshot" && !e.backfill && e.bars.length >= SNAPSHOT_BARS);
23218
23258
  },
23219
23259
  onStatus: (status2) => {
23220
- if (disposed || myEpoch !== epoch) return;
23260
+ if (!active || disposed || myEpoch !== epoch) return;
23221
23261
  feedStatus = status2;
23222
23262
  events.emit("feedStatus", status2);
23223
23263
  countdown?.refresh();
23224
23264
  }
23225
23265
  });
23266
+ unsubscribe = () => {
23267
+ active = false;
23268
+ off();
23269
+ };
23226
23270
  }
23227
23271
  const LIVE_EDGE_SLACK_BARS = 1;
23228
23272
  let awayFromLive = false;