hyperprop-charting-library 0.1.33 → 0.1.34

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.
@@ -821,10 +821,21 @@ function createChart(element, options = {}) {
821
821
  let xSpan = 60;
822
822
  let followLatest = true;
823
823
  let followingLatestChangeHandler = null;
824
+ let viewportChangeHandler = null;
825
+ let viewportEmitScheduled = false;
826
+ const emitViewportChange = () => {
827
+ if (!viewportChangeHandler || viewportEmitScheduled) return;
828
+ viewportEmitScheduled = true;
829
+ queueMicrotask(() => {
830
+ viewportEmitScheduled = false;
831
+ viewportChangeHandler?.(getViewport());
832
+ });
833
+ };
824
834
  const updateFollowLatest = (next) => {
825
835
  if (followLatest === next) return;
826
836
  followLatest = next;
827
837
  followingLatestChangeHandler?.(next);
838
+ emitViewportChange();
828
839
  };
829
840
  let yMinOverride = null;
830
841
  let yMaxOverride = null;
@@ -2158,6 +2169,7 @@ function createChart(element, options = {}) {
2158
2169
  xCenter = nextStart + nextSpan / 2;
2159
2170
  clampXViewport();
2160
2171
  updateFollowLatest(false);
2172
+ emitViewportChange();
2161
2173
  draw();
2162
2174
  };
2163
2175
  const zoomXToLatest = (factor) => {
@@ -2172,6 +2184,7 @@ function createChart(element, options = {}) {
2172
2184
  xSpan = nextSpan;
2173
2185
  xCenter = nextStart + nextSpan / 2;
2174
2186
  clampXViewport();
2187
+ emitViewportChange();
2175
2188
  draw();
2176
2189
  };
2177
2190
  const zoomY = (factor, anchorY) => {
@@ -2192,6 +2205,7 @@ function createChart(element, options = {}) {
2192
2205
  const clamped = clampYRange(nextMin, nextMax);
2193
2206
  yMinOverride = clamped.min;
2194
2207
  yMaxOverride = clamped.max;
2208
+ emitViewportChange();
2195
2209
  draw();
2196
2210
  };
2197
2211
  const pan = (deltaX, deltaY, allowX, allowY) => {
@@ -2215,6 +2229,9 @@ function createChart(element, options = {}) {
2215
2229
  yMinOverride = clamped.min;
2216
2230
  yMaxOverride = clamped.max;
2217
2231
  }
2232
+ if (allowX || allowY) {
2233
+ emitViewportChange();
2234
+ }
2218
2235
  draw();
2219
2236
  };
2220
2237
  const resetYViewport = () => {
@@ -2254,6 +2271,7 @@ function createChart(element, options = {}) {
2254
2271
  xCenter += bars;
2255
2272
  clampXViewport();
2256
2273
  updateFollowLatest(false);
2274
+ emitViewportChange();
2257
2275
  draw();
2258
2276
  };
2259
2277
  const panY = (priceDelta) => {
@@ -2265,17 +2283,20 @@ function createChart(element, options = {}) {
2265
2283
  const clamped = clampYRange(currentMin + priceDelta, currentMax + priceDelta);
2266
2284
  yMinOverride = clamped.min;
2267
2285
  yMaxOverride = clamped.max;
2286
+ emitViewportChange();
2268
2287
  draw();
2269
2288
  };
2270
2289
  const fitContent = () => {
2271
2290
  fitXViewport();
2272
2291
  updateFollowLatest(true);
2292
+ emitViewportChange();
2273
2293
  draw();
2274
2294
  };
2275
2295
  const resetViewport = () => {
2276
2296
  fitXViewport();
2277
2297
  resetYViewport();
2278
2298
  updateFollowLatest(true);
2299
+ emitViewportChange();
2279
2300
  draw();
2280
2301
  };
2281
2302
  const isFollowingLatest = () => followLatest;
@@ -2292,6 +2313,64 @@ function createChart(element, options = {}) {
2292
2313
  const onFollowingLatestChange = (handler) => {
2293
2314
  followingLatestChangeHandler = handler;
2294
2315
  };
2316
+ const getViewport = () => {
2317
+ let centerTimeMs = null;
2318
+ if (data.length > 0) {
2319
+ const centerRounded = Math.round(xCenter);
2320
+ if (centerRounded >= 0 && centerRounded < data.length) {
2321
+ centerTimeMs = data[centerRounded]?.time.getTime() ?? null;
2322
+ }
2323
+ }
2324
+ return {
2325
+ xSpan,
2326
+ followingLatest: followLatest,
2327
+ centerTimeMs,
2328
+ yMin: yMinOverride,
2329
+ yMax: yMaxOverride
2330
+ };
2331
+ };
2332
+ const setViewport = (viewport) => {
2333
+ let changed = false;
2334
+ if (typeof viewport.xSpan === "number" && Number.isFinite(viewport.xSpan)) {
2335
+ const minSpan = minVisibleBars;
2336
+ const maxSpan = Math.min(maxVisibleBars, Math.max(minSpan, data.length + maxPanBars * 2));
2337
+ const nextSpan = clamp(viewport.xSpan, minSpan, maxSpan);
2338
+ if (nextSpan !== xSpan) {
2339
+ xSpan = nextSpan;
2340
+ changed = true;
2341
+ }
2342
+ }
2343
+ if (typeof viewport.centerTimeMs === "number" && data.length > 0) {
2344
+ const nextCenter = findNearestIndexForTimeMs(viewport.centerTimeMs);
2345
+ if (nextCenter !== null) {
2346
+ xCenter = nextCenter;
2347
+ changed = true;
2348
+ }
2349
+ }
2350
+ if (viewport.yMin !== void 0) {
2351
+ yMinOverride = viewport.yMin;
2352
+ changed = true;
2353
+ }
2354
+ if (viewport.yMax !== void 0) {
2355
+ yMaxOverride = viewport.yMax;
2356
+ changed = true;
2357
+ }
2358
+ if (typeof viewport.followingLatest === "boolean") {
2359
+ if (followLatest !== viewport.followingLatest) {
2360
+ followLatest = viewport.followingLatest;
2361
+ followingLatestChangeHandler?.(followLatest);
2362
+ changed = true;
2363
+ }
2364
+ }
2365
+ if (changed) {
2366
+ clampXViewport();
2367
+ draw();
2368
+ emitViewportChange();
2369
+ }
2370
+ };
2371
+ const onViewportChange = (handler) => {
2372
+ viewportChangeHandler = handler;
2373
+ };
2295
2374
  const getCanvasPoint = (event) => {
2296
2375
  const rect = canvas.getBoundingClientRect();
2297
2376
  return {
@@ -2907,6 +2986,9 @@ function createChart(element, options = {}) {
2907
2986
  isFollowingLatest,
2908
2987
  setFollowingLatest,
2909
2988
  onFollowingLatestChange,
2989
+ getViewport,
2990
+ setViewport,
2991
+ onViewportChange,
2910
2992
  setDoubleClickEnabled,
2911
2993
  setDoubleClickAction,
2912
2994
  registerIndicator,
@@ -284,6 +284,9 @@ interface ChartInstance {
284
284
  isFollowingLatest: () => boolean;
285
285
  setFollowingLatest: (follow: boolean) => void;
286
286
  onFollowingLatestChange: (handler: ((following: boolean) => void) | null) => void;
287
+ getViewport: () => ViewportState;
288
+ setViewport: (viewport: Partial<ViewportState>) => void;
289
+ onViewportChange: (handler: ((viewport: ViewportState) => void) | null) => void;
287
290
  setDoubleClickEnabled: (enabled: boolean) => void;
288
291
  setDoubleClickAction: (action: "reset" | "placeLimitOrder") => void;
289
292
  registerIndicator: (plugin: IndicatorPlugin<any>) => void;
@@ -305,6 +308,18 @@ interface OhlcDataPoint {
305
308
  c: number;
306
309
  v?: number;
307
310
  }
311
+ interface ViewportState {
312
+ /** Number of bars visible horizontally. */
313
+ xSpan: number;
314
+ /** Whether the chart is auto-following the latest candle. */
315
+ followingLatest: boolean;
316
+ /** Timestamp (ms) at the horizontal center of the viewport. null when data is empty. */
317
+ centerTimeMs: number | null;
318
+ /** Manual Y-axis min override (null = auto-scale). */
319
+ yMin: number | null;
320
+ /** Manual Y-axis max override (null = auto-scale). */
321
+ yMax: number | null;
322
+ }
308
323
  declare function createChart(element: HTMLElement, options?: ChartOptions): ChartInstance;
309
324
 
310
- export { type AxisOptions, type BuiltInIndicatorInfo, type ChartClickEvent, type ChartInstance, type ChartOptions, type CrosshairMoveEvent, type CrosshairOptions, type CrosshairPriceActionEvent, type DashPatternOptions, type GridOptions, type IndicatorInstanceOptions, type IndicatorPane, type IndicatorPlugin, type IndicatorRenderContext, type OhlcDataPoint, type OrderActionButton, type OrderActionEvent, type OrderLineOptions, type PriceLineOptions, type TickerLineOptions, type WatermarkOptions, createChart };
325
+ export { type AxisOptions, type BuiltInIndicatorInfo, type ChartClickEvent, type ChartInstance, type ChartOptions, type CrosshairMoveEvent, type CrosshairOptions, type CrosshairPriceActionEvent, type DashPatternOptions, type GridOptions, type IndicatorInstanceOptions, type IndicatorPane, type IndicatorPlugin, type IndicatorRenderContext, type OhlcDataPoint, type OrderActionButton, type OrderActionEvent, type OrderLineOptions, type PriceLineOptions, type TickerLineOptions, type ViewportState, type WatermarkOptions, createChart };
@@ -797,10 +797,21 @@ function createChart(element, options = {}) {
797
797
  let xSpan = 60;
798
798
  let followLatest = true;
799
799
  let followingLatestChangeHandler = null;
800
+ let viewportChangeHandler = null;
801
+ let viewportEmitScheduled = false;
802
+ const emitViewportChange = () => {
803
+ if (!viewportChangeHandler || viewportEmitScheduled) return;
804
+ viewportEmitScheduled = true;
805
+ queueMicrotask(() => {
806
+ viewportEmitScheduled = false;
807
+ viewportChangeHandler?.(getViewport());
808
+ });
809
+ };
800
810
  const updateFollowLatest = (next) => {
801
811
  if (followLatest === next) return;
802
812
  followLatest = next;
803
813
  followingLatestChangeHandler?.(next);
814
+ emitViewportChange();
804
815
  };
805
816
  let yMinOverride = null;
806
817
  let yMaxOverride = null;
@@ -2134,6 +2145,7 @@ function createChart(element, options = {}) {
2134
2145
  xCenter = nextStart + nextSpan / 2;
2135
2146
  clampXViewport();
2136
2147
  updateFollowLatest(false);
2148
+ emitViewportChange();
2137
2149
  draw();
2138
2150
  };
2139
2151
  const zoomXToLatest = (factor) => {
@@ -2148,6 +2160,7 @@ function createChart(element, options = {}) {
2148
2160
  xSpan = nextSpan;
2149
2161
  xCenter = nextStart + nextSpan / 2;
2150
2162
  clampXViewport();
2163
+ emitViewportChange();
2151
2164
  draw();
2152
2165
  };
2153
2166
  const zoomY = (factor, anchorY) => {
@@ -2168,6 +2181,7 @@ function createChart(element, options = {}) {
2168
2181
  const clamped = clampYRange(nextMin, nextMax);
2169
2182
  yMinOverride = clamped.min;
2170
2183
  yMaxOverride = clamped.max;
2184
+ emitViewportChange();
2171
2185
  draw();
2172
2186
  };
2173
2187
  const pan = (deltaX, deltaY, allowX, allowY) => {
@@ -2191,6 +2205,9 @@ function createChart(element, options = {}) {
2191
2205
  yMinOverride = clamped.min;
2192
2206
  yMaxOverride = clamped.max;
2193
2207
  }
2208
+ if (allowX || allowY) {
2209
+ emitViewportChange();
2210
+ }
2194
2211
  draw();
2195
2212
  };
2196
2213
  const resetYViewport = () => {
@@ -2230,6 +2247,7 @@ function createChart(element, options = {}) {
2230
2247
  xCenter += bars;
2231
2248
  clampXViewport();
2232
2249
  updateFollowLatest(false);
2250
+ emitViewportChange();
2233
2251
  draw();
2234
2252
  };
2235
2253
  const panY = (priceDelta) => {
@@ -2241,17 +2259,20 @@ function createChart(element, options = {}) {
2241
2259
  const clamped = clampYRange(currentMin + priceDelta, currentMax + priceDelta);
2242
2260
  yMinOverride = clamped.min;
2243
2261
  yMaxOverride = clamped.max;
2262
+ emitViewportChange();
2244
2263
  draw();
2245
2264
  };
2246
2265
  const fitContent = () => {
2247
2266
  fitXViewport();
2248
2267
  updateFollowLatest(true);
2268
+ emitViewportChange();
2249
2269
  draw();
2250
2270
  };
2251
2271
  const resetViewport = () => {
2252
2272
  fitXViewport();
2253
2273
  resetYViewport();
2254
2274
  updateFollowLatest(true);
2275
+ emitViewportChange();
2255
2276
  draw();
2256
2277
  };
2257
2278
  const isFollowingLatest = () => followLatest;
@@ -2268,6 +2289,64 @@ function createChart(element, options = {}) {
2268
2289
  const onFollowingLatestChange = (handler) => {
2269
2290
  followingLatestChangeHandler = handler;
2270
2291
  };
2292
+ const getViewport = () => {
2293
+ let centerTimeMs = null;
2294
+ if (data.length > 0) {
2295
+ const centerRounded = Math.round(xCenter);
2296
+ if (centerRounded >= 0 && centerRounded < data.length) {
2297
+ centerTimeMs = data[centerRounded]?.time.getTime() ?? null;
2298
+ }
2299
+ }
2300
+ return {
2301
+ xSpan,
2302
+ followingLatest: followLatest,
2303
+ centerTimeMs,
2304
+ yMin: yMinOverride,
2305
+ yMax: yMaxOverride
2306
+ };
2307
+ };
2308
+ const setViewport = (viewport) => {
2309
+ let changed = false;
2310
+ if (typeof viewport.xSpan === "number" && Number.isFinite(viewport.xSpan)) {
2311
+ const minSpan = minVisibleBars;
2312
+ const maxSpan = Math.min(maxVisibleBars, Math.max(minSpan, data.length + maxPanBars * 2));
2313
+ const nextSpan = clamp(viewport.xSpan, minSpan, maxSpan);
2314
+ if (nextSpan !== xSpan) {
2315
+ xSpan = nextSpan;
2316
+ changed = true;
2317
+ }
2318
+ }
2319
+ if (typeof viewport.centerTimeMs === "number" && data.length > 0) {
2320
+ const nextCenter = findNearestIndexForTimeMs(viewport.centerTimeMs);
2321
+ if (nextCenter !== null) {
2322
+ xCenter = nextCenter;
2323
+ changed = true;
2324
+ }
2325
+ }
2326
+ if (viewport.yMin !== void 0) {
2327
+ yMinOverride = viewport.yMin;
2328
+ changed = true;
2329
+ }
2330
+ if (viewport.yMax !== void 0) {
2331
+ yMaxOverride = viewport.yMax;
2332
+ changed = true;
2333
+ }
2334
+ if (typeof viewport.followingLatest === "boolean") {
2335
+ if (followLatest !== viewport.followingLatest) {
2336
+ followLatest = viewport.followingLatest;
2337
+ followingLatestChangeHandler?.(followLatest);
2338
+ changed = true;
2339
+ }
2340
+ }
2341
+ if (changed) {
2342
+ clampXViewport();
2343
+ draw();
2344
+ emitViewportChange();
2345
+ }
2346
+ };
2347
+ const onViewportChange = (handler) => {
2348
+ viewportChangeHandler = handler;
2349
+ };
2271
2350
  const getCanvasPoint = (event) => {
2272
2351
  const rect = canvas.getBoundingClientRect();
2273
2352
  return {
@@ -2883,6 +2962,9 @@ function createChart(element, options = {}) {
2883
2962
  isFollowingLatest,
2884
2963
  setFollowingLatest,
2885
2964
  onFollowingLatestChange,
2965
+ getViewport,
2966
+ setViewport,
2967
+ onViewportChange,
2886
2968
  setDoubleClickEnabled,
2887
2969
  setDoubleClickAction,
2888
2970
  registerIndicator,
package/dist/index.cjs CHANGED
@@ -821,10 +821,21 @@ function createChart(element, options = {}) {
821
821
  let xSpan = 60;
822
822
  let followLatest = true;
823
823
  let followingLatestChangeHandler = null;
824
+ let viewportChangeHandler = null;
825
+ let viewportEmitScheduled = false;
826
+ const emitViewportChange = () => {
827
+ if (!viewportChangeHandler || viewportEmitScheduled) return;
828
+ viewportEmitScheduled = true;
829
+ queueMicrotask(() => {
830
+ viewportEmitScheduled = false;
831
+ viewportChangeHandler?.(getViewport());
832
+ });
833
+ };
824
834
  const updateFollowLatest = (next) => {
825
835
  if (followLatest === next) return;
826
836
  followLatest = next;
827
837
  followingLatestChangeHandler?.(next);
838
+ emitViewportChange();
828
839
  };
829
840
  let yMinOverride = null;
830
841
  let yMaxOverride = null;
@@ -2158,6 +2169,7 @@ function createChart(element, options = {}) {
2158
2169
  xCenter = nextStart + nextSpan / 2;
2159
2170
  clampXViewport();
2160
2171
  updateFollowLatest(false);
2172
+ emitViewportChange();
2161
2173
  draw();
2162
2174
  };
2163
2175
  const zoomXToLatest = (factor) => {
@@ -2172,6 +2184,7 @@ function createChart(element, options = {}) {
2172
2184
  xSpan = nextSpan;
2173
2185
  xCenter = nextStart + nextSpan / 2;
2174
2186
  clampXViewport();
2187
+ emitViewportChange();
2175
2188
  draw();
2176
2189
  };
2177
2190
  const zoomY = (factor, anchorY) => {
@@ -2192,6 +2205,7 @@ function createChart(element, options = {}) {
2192
2205
  const clamped = clampYRange(nextMin, nextMax);
2193
2206
  yMinOverride = clamped.min;
2194
2207
  yMaxOverride = clamped.max;
2208
+ emitViewportChange();
2195
2209
  draw();
2196
2210
  };
2197
2211
  const pan = (deltaX, deltaY, allowX, allowY) => {
@@ -2215,6 +2229,9 @@ function createChart(element, options = {}) {
2215
2229
  yMinOverride = clamped.min;
2216
2230
  yMaxOverride = clamped.max;
2217
2231
  }
2232
+ if (allowX || allowY) {
2233
+ emitViewportChange();
2234
+ }
2218
2235
  draw();
2219
2236
  };
2220
2237
  const resetYViewport = () => {
@@ -2254,6 +2271,7 @@ function createChart(element, options = {}) {
2254
2271
  xCenter += bars;
2255
2272
  clampXViewport();
2256
2273
  updateFollowLatest(false);
2274
+ emitViewportChange();
2257
2275
  draw();
2258
2276
  };
2259
2277
  const panY = (priceDelta) => {
@@ -2265,17 +2283,20 @@ function createChart(element, options = {}) {
2265
2283
  const clamped = clampYRange(currentMin + priceDelta, currentMax + priceDelta);
2266
2284
  yMinOverride = clamped.min;
2267
2285
  yMaxOverride = clamped.max;
2286
+ emitViewportChange();
2268
2287
  draw();
2269
2288
  };
2270
2289
  const fitContent = () => {
2271
2290
  fitXViewport();
2272
2291
  updateFollowLatest(true);
2292
+ emitViewportChange();
2273
2293
  draw();
2274
2294
  };
2275
2295
  const resetViewport = () => {
2276
2296
  fitXViewport();
2277
2297
  resetYViewport();
2278
2298
  updateFollowLatest(true);
2299
+ emitViewportChange();
2279
2300
  draw();
2280
2301
  };
2281
2302
  const isFollowingLatest = () => followLatest;
@@ -2292,6 +2313,64 @@ function createChart(element, options = {}) {
2292
2313
  const onFollowingLatestChange = (handler) => {
2293
2314
  followingLatestChangeHandler = handler;
2294
2315
  };
2316
+ const getViewport = () => {
2317
+ let centerTimeMs = null;
2318
+ if (data.length > 0) {
2319
+ const centerRounded = Math.round(xCenter);
2320
+ if (centerRounded >= 0 && centerRounded < data.length) {
2321
+ centerTimeMs = data[centerRounded]?.time.getTime() ?? null;
2322
+ }
2323
+ }
2324
+ return {
2325
+ xSpan,
2326
+ followingLatest: followLatest,
2327
+ centerTimeMs,
2328
+ yMin: yMinOverride,
2329
+ yMax: yMaxOverride
2330
+ };
2331
+ };
2332
+ const setViewport = (viewport) => {
2333
+ let changed = false;
2334
+ if (typeof viewport.xSpan === "number" && Number.isFinite(viewport.xSpan)) {
2335
+ const minSpan = minVisibleBars;
2336
+ const maxSpan = Math.min(maxVisibleBars, Math.max(minSpan, data.length + maxPanBars * 2));
2337
+ const nextSpan = clamp(viewport.xSpan, minSpan, maxSpan);
2338
+ if (nextSpan !== xSpan) {
2339
+ xSpan = nextSpan;
2340
+ changed = true;
2341
+ }
2342
+ }
2343
+ if (typeof viewport.centerTimeMs === "number" && data.length > 0) {
2344
+ const nextCenter = findNearestIndexForTimeMs(viewport.centerTimeMs);
2345
+ if (nextCenter !== null) {
2346
+ xCenter = nextCenter;
2347
+ changed = true;
2348
+ }
2349
+ }
2350
+ if (viewport.yMin !== void 0) {
2351
+ yMinOverride = viewport.yMin;
2352
+ changed = true;
2353
+ }
2354
+ if (viewport.yMax !== void 0) {
2355
+ yMaxOverride = viewport.yMax;
2356
+ changed = true;
2357
+ }
2358
+ if (typeof viewport.followingLatest === "boolean") {
2359
+ if (followLatest !== viewport.followingLatest) {
2360
+ followLatest = viewport.followingLatest;
2361
+ followingLatestChangeHandler?.(followLatest);
2362
+ changed = true;
2363
+ }
2364
+ }
2365
+ if (changed) {
2366
+ clampXViewport();
2367
+ draw();
2368
+ emitViewportChange();
2369
+ }
2370
+ };
2371
+ const onViewportChange = (handler) => {
2372
+ viewportChangeHandler = handler;
2373
+ };
2295
2374
  const getCanvasPoint = (event) => {
2296
2375
  const rect = canvas.getBoundingClientRect();
2297
2376
  return {
@@ -2907,6 +2986,9 @@ function createChart(element, options = {}) {
2907
2986
  isFollowingLatest,
2908
2987
  setFollowingLatest,
2909
2988
  onFollowingLatestChange,
2989
+ getViewport,
2990
+ setViewport,
2991
+ onViewportChange,
2910
2992
  setDoubleClickEnabled,
2911
2993
  setDoubleClickAction,
2912
2994
  registerIndicator,
package/dist/index.d.cts CHANGED
@@ -284,6 +284,9 @@ interface ChartInstance {
284
284
  isFollowingLatest: () => boolean;
285
285
  setFollowingLatest: (follow: boolean) => void;
286
286
  onFollowingLatestChange: (handler: ((following: boolean) => void) | null) => void;
287
+ getViewport: () => ViewportState;
288
+ setViewport: (viewport: Partial<ViewportState>) => void;
289
+ onViewportChange: (handler: ((viewport: ViewportState) => void) | null) => void;
287
290
  setDoubleClickEnabled: (enabled: boolean) => void;
288
291
  setDoubleClickAction: (action: "reset" | "placeLimitOrder") => void;
289
292
  registerIndicator: (plugin: IndicatorPlugin<any>) => void;
@@ -305,6 +308,18 @@ interface OhlcDataPoint {
305
308
  c: number;
306
309
  v?: number;
307
310
  }
311
+ interface ViewportState {
312
+ /** Number of bars visible horizontally. */
313
+ xSpan: number;
314
+ /** Whether the chart is auto-following the latest candle. */
315
+ followingLatest: boolean;
316
+ /** Timestamp (ms) at the horizontal center of the viewport. null when data is empty. */
317
+ centerTimeMs: number | null;
318
+ /** Manual Y-axis min override (null = auto-scale). */
319
+ yMin: number | null;
320
+ /** Manual Y-axis max override (null = auto-scale). */
321
+ yMax: number | null;
322
+ }
308
323
  declare function createChart(element: HTMLElement, options?: ChartOptions): ChartInstance;
309
324
 
310
- export { type AxisOptions, type BuiltInIndicatorInfo, type ChartClickEvent, type ChartInstance, type ChartOptions, type CrosshairMoveEvent, type CrosshairOptions, type CrosshairPriceActionEvent, type DashPatternOptions, type GridOptions, type IndicatorInstanceOptions, type IndicatorPane, type IndicatorPlugin, type IndicatorRenderContext, type OhlcDataPoint, type OrderActionButton, type OrderActionEvent, type OrderLineOptions, type PriceLineOptions, type TickerLineOptions, type WatermarkOptions, createChart };
325
+ export { type AxisOptions, type BuiltInIndicatorInfo, type ChartClickEvent, type ChartInstance, type ChartOptions, type CrosshairMoveEvent, type CrosshairOptions, type CrosshairPriceActionEvent, type DashPatternOptions, type GridOptions, type IndicatorInstanceOptions, type IndicatorPane, type IndicatorPlugin, type IndicatorRenderContext, type OhlcDataPoint, type OrderActionButton, type OrderActionEvent, type OrderLineOptions, type PriceLineOptions, type TickerLineOptions, type ViewportState, type WatermarkOptions, createChart };
package/dist/index.d.ts CHANGED
@@ -284,6 +284,9 @@ interface ChartInstance {
284
284
  isFollowingLatest: () => boolean;
285
285
  setFollowingLatest: (follow: boolean) => void;
286
286
  onFollowingLatestChange: (handler: ((following: boolean) => void) | null) => void;
287
+ getViewport: () => ViewportState;
288
+ setViewport: (viewport: Partial<ViewportState>) => void;
289
+ onViewportChange: (handler: ((viewport: ViewportState) => void) | null) => void;
287
290
  setDoubleClickEnabled: (enabled: boolean) => void;
288
291
  setDoubleClickAction: (action: "reset" | "placeLimitOrder") => void;
289
292
  registerIndicator: (plugin: IndicatorPlugin<any>) => void;
@@ -305,6 +308,18 @@ interface OhlcDataPoint {
305
308
  c: number;
306
309
  v?: number;
307
310
  }
311
+ interface ViewportState {
312
+ /** Number of bars visible horizontally. */
313
+ xSpan: number;
314
+ /** Whether the chart is auto-following the latest candle. */
315
+ followingLatest: boolean;
316
+ /** Timestamp (ms) at the horizontal center of the viewport. null when data is empty. */
317
+ centerTimeMs: number | null;
318
+ /** Manual Y-axis min override (null = auto-scale). */
319
+ yMin: number | null;
320
+ /** Manual Y-axis max override (null = auto-scale). */
321
+ yMax: number | null;
322
+ }
308
323
  declare function createChart(element: HTMLElement, options?: ChartOptions): ChartInstance;
309
324
 
310
- export { type AxisOptions, type BuiltInIndicatorInfo, type ChartClickEvent, type ChartInstance, type ChartOptions, type CrosshairMoveEvent, type CrosshairOptions, type CrosshairPriceActionEvent, type DashPatternOptions, type GridOptions, type IndicatorInstanceOptions, type IndicatorPane, type IndicatorPlugin, type IndicatorRenderContext, type OhlcDataPoint, type OrderActionButton, type OrderActionEvent, type OrderLineOptions, type PriceLineOptions, type TickerLineOptions, type WatermarkOptions, createChart };
325
+ export { type AxisOptions, type BuiltInIndicatorInfo, type ChartClickEvent, type ChartInstance, type ChartOptions, type CrosshairMoveEvent, type CrosshairOptions, type CrosshairPriceActionEvent, type DashPatternOptions, type GridOptions, type IndicatorInstanceOptions, type IndicatorPane, type IndicatorPlugin, type IndicatorRenderContext, type OhlcDataPoint, type OrderActionButton, type OrderActionEvent, type OrderLineOptions, type PriceLineOptions, type TickerLineOptions, type ViewportState, type WatermarkOptions, createChart };
package/dist/index.js CHANGED
@@ -797,10 +797,21 @@ function createChart(element, options = {}) {
797
797
  let xSpan = 60;
798
798
  let followLatest = true;
799
799
  let followingLatestChangeHandler = null;
800
+ let viewportChangeHandler = null;
801
+ let viewportEmitScheduled = false;
802
+ const emitViewportChange = () => {
803
+ if (!viewportChangeHandler || viewportEmitScheduled) return;
804
+ viewportEmitScheduled = true;
805
+ queueMicrotask(() => {
806
+ viewportEmitScheduled = false;
807
+ viewportChangeHandler?.(getViewport());
808
+ });
809
+ };
800
810
  const updateFollowLatest = (next) => {
801
811
  if (followLatest === next) return;
802
812
  followLatest = next;
803
813
  followingLatestChangeHandler?.(next);
814
+ emitViewportChange();
804
815
  };
805
816
  let yMinOverride = null;
806
817
  let yMaxOverride = null;
@@ -2134,6 +2145,7 @@ function createChart(element, options = {}) {
2134
2145
  xCenter = nextStart + nextSpan / 2;
2135
2146
  clampXViewport();
2136
2147
  updateFollowLatest(false);
2148
+ emitViewportChange();
2137
2149
  draw();
2138
2150
  };
2139
2151
  const zoomXToLatest = (factor) => {
@@ -2148,6 +2160,7 @@ function createChart(element, options = {}) {
2148
2160
  xSpan = nextSpan;
2149
2161
  xCenter = nextStart + nextSpan / 2;
2150
2162
  clampXViewport();
2163
+ emitViewportChange();
2151
2164
  draw();
2152
2165
  };
2153
2166
  const zoomY = (factor, anchorY) => {
@@ -2168,6 +2181,7 @@ function createChart(element, options = {}) {
2168
2181
  const clamped = clampYRange(nextMin, nextMax);
2169
2182
  yMinOverride = clamped.min;
2170
2183
  yMaxOverride = clamped.max;
2184
+ emitViewportChange();
2171
2185
  draw();
2172
2186
  };
2173
2187
  const pan = (deltaX, deltaY, allowX, allowY) => {
@@ -2191,6 +2205,9 @@ function createChart(element, options = {}) {
2191
2205
  yMinOverride = clamped.min;
2192
2206
  yMaxOverride = clamped.max;
2193
2207
  }
2208
+ if (allowX || allowY) {
2209
+ emitViewportChange();
2210
+ }
2194
2211
  draw();
2195
2212
  };
2196
2213
  const resetYViewport = () => {
@@ -2230,6 +2247,7 @@ function createChart(element, options = {}) {
2230
2247
  xCenter += bars;
2231
2248
  clampXViewport();
2232
2249
  updateFollowLatest(false);
2250
+ emitViewportChange();
2233
2251
  draw();
2234
2252
  };
2235
2253
  const panY = (priceDelta) => {
@@ -2241,17 +2259,20 @@ function createChart(element, options = {}) {
2241
2259
  const clamped = clampYRange(currentMin + priceDelta, currentMax + priceDelta);
2242
2260
  yMinOverride = clamped.min;
2243
2261
  yMaxOverride = clamped.max;
2262
+ emitViewportChange();
2244
2263
  draw();
2245
2264
  };
2246
2265
  const fitContent = () => {
2247
2266
  fitXViewport();
2248
2267
  updateFollowLatest(true);
2268
+ emitViewportChange();
2249
2269
  draw();
2250
2270
  };
2251
2271
  const resetViewport = () => {
2252
2272
  fitXViewport();
2253
2273
  resetYViewport();
2254
2274
  updateFollowLatest(true);
2275
+ emitViewportChange();
2255
2276
  draw();
2256
2277
  };
2257
2278
  const isFollowingLatest = () => followLatest;
@@ -2268,6 +2289,64 @@ function createChart(element, options = {}) {
2268
2289
  const onFollowingLatestChange = (handler) => {
2269
2290
  followingLatestChangeHandler = handler;
2270
2291
  };
2292
+ const getViewport = () => {
2293
+ let centerTimeMs = null;
2294
+ if (data.length > 0) {
2295
+ const centerRounded = Math.round(xCenter);
2296
+ if (centerRounded >= 0 && centerRounded < data.length) {
2297
+ centerTimeMs = data[centerRounded]?.time.getTime() ?? null;
2298
+ }
2299
+ }
2300
+ return {
2301
+ xSpan,
2302
+ followingLatest: followLatest,
2303
+ centerTimeMs,
2304
+ yMin: yMinOverride,
2305
+ yMax: yMaxOverride
2306
+ };
2307
+ };
2308
+ const setViewport = (viewport) => {
2309
+ let changed = false;
2310
+ if (typeof viewport.xSpan === "number" && Number.isFinite(viewport.xSpan)) {
2311
+ const minSpan = minVisibleBars;
2312
+ const maxSpan = Math.min(maxVisibleBars, Math.max(minSpan, data.length + maxPanBars * 2));
2313
+ const nextSpan = clamp(viewport.xSpan, minSpan, maxSpan);
2314
+ if (nextSpan !== xSpan) {
2315
+ xSpan = nextSpan;
2316
+ changed = true;
2317
+ }
2318
+ }
2319
+ if (typeof viewport.centerTimeMs === "number" && data.length > 0) {
2320
+ const nextCenter = findNearestIndexForTimeMs(viewport.centerTimeMs);
2321
+ if (nextCenter !== null) {
2322
+ xCenter = nextCenter;
2323
+ changed = true;
2324
+ }
2325
+ }
2326
+ if (viewport.yMin !== void 0) {
2327
+ yMinOverride = viewport.yMin;
2328
+ changed = true;
2329
+ }
2330
+ if (viewport.yMax !== void 0) {
2331
+ yMaxOverride = viewport.yMax;
2332
+ changed = true;
2333
+ }
2334
+ if (typeof viewport.followingLatest === "boolean") {
2335
+ if (followLatest !== viewport.followingLatest) {
2336
+ followLatest = viewport.followingLatest;
2337
+ followingLatestChangeHandler?.(followLatest);
2338
+ changed = true;
2339
+ }
2340
+ }
2341
+ if (changed) {
2342
+ clampXViewport();
2343
+ draw();
2344
+ emitViewportChange();
2345
+ }
2346
+ };
2347
+ const onViewportChange = (handler) => {
2348
+ viewportChangeHandler = handler;
2349
+ };
2271
2350
  const getCanvasPoint = (event) => {
2272
2351
  const rect = canvas.getBoundingClientRect();
2273
2352
  return {
@@ -2883,6 +2962,9 @@ function createChart(element, options = {}) {
2883
2962
  isFollowingLatest,
2884
2963
  setFollowingLatest,
2885
2964
  onFollowingLatestChange,
2965
+ getViewport,
2966
+ setViewport,
2967
+ onViewportChange,
2886
2968
  setDoubleClickEnabled,
2887
2969
  setDoubleClickAction,
2888
2970
  registerIndicator,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hyperprop-charting-library",
3
- "version": "0.1.33",
3
+ "version": "0.1.34",
4
4
  "description": "Lightweight TypeScript charting core",
5
5
  "type": "module",
6
6
  "main": "./dist/hyperprop-charting-library.cjs",