@luxalgo/vela 0.8.1 → 0.8.3

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.
Files changed (49) hide show
  1. package/README.md +1 -1
  2. package/dist/{DataProvider-DwFIoV4r.d.ts → DataProvider-oH78tYXL.d.ts} +1 -1
  3. package/dist/{DataProvider-BrlxntPN.d.cts → DataProvider-wbtJ3-Sa.d.cts} +1 -1
  4. package/dist/{chunk-DBDXKZUF.js → chunk-2IPXYM4B.js} +306 -177
  5. package/dist/{chunk-NELQJCGK.js → chunk-G7B7ZCBF.js} +167 -10
  6. package/dist/{chunk-ATGTCWHJ.js → chunk-HMHA733L.js} +473 -196
  7. package/dist/{chunk-EZ5FWVLA.js → chunk-KWI3YDBW.js} +343 -17
  8. package/dist/{chunk-LMBYEGUR.js → chunk-TO3GIK7Z.js} +71 -6
  9. package/dist/{chunk-BZQM2XO7.js → chunk-VHGACEHO.js} +62 -1
  10. package/dist/{contributions-tjNExo1o.d.ts → contributions-BhOBRenr.d.cts} +79 -12
  11. package/dist/{contributions-CtlptxR8.d.cts → contributions-HGqnNb_P.d.ts} +79 -12
  12. package/dist/index.cjs +733 -113
  13. package/dist/index.d.cts +34 -14
  14. package/dist/index.d.ts +34 -14
  15. package/dist/index.js +5 -5
  16. package/dist/{options-BnEPUuVV.d.ts → options-lMUwmRwh.d.cts} +69 -4
  17. package/dist/{options-BnEPUuVV.d.cts → options-lMUwmRwh.d.ts} +69 -4
  18. package/dist/{plugin-e2WUEtiI.d.ts → plugin-Cr2Pj8q8.d.ts} +4 -4
  19. package/dist/{plugin-BWxJRKzS.d.cts → plugin-Do5J5xuF.d.cts} +4 -4
  20. package/dist/plugin.cjs +38 -6
  21. package/dist/plugin.d.cts +5 -5
  22. package/dist/plugin.d.ts +5 -5
  23. package/dist/plugin.js +3 -3
  24. package/dist/providers/binance.d.cts +2 -2
  25. package/dist/providers/binance.d.ts +2 -2
  26. package/dist/providers/coinbase.d.cts +2 -2
  27. package/dist/providers/coinbase.d.ts +2 -2
  28. package/dist/providers/hyperliquid.d.cts +2 -2
  29. package/dist/providers/hyperliquid.d.ts +2 -2
  30. package/dist/{side-panel-HF0IAzwf.d.cts → side-panel-_2UK1gSm.d.cts} +58 -3
  31. package/dist/{side-panel-HF0IAzwf.d.ts → side-panel-_2UK1gSm.d.ts} +58 -3
  32. package/dist/{statusline-model-DFY7tuHj.d.cts → statusline-model-B-2xGlB_.d.cts} +3 -3
  33. package/dist/{statusline-model-zKXtGIjj.d.ts → statusline-model-CY1wzxHg.d.ts} +3 -3
  34. package/dist/ui.cjs +298 -13
  35. package/dist/ui.d.cts +132 -74
  36. package/dist/ui.d.ts +132 -74
  37. package/dist/ui.js +3 -3
  38. package/dist/vela.global.js +734 -114
  39. package/dist/vela.global.min.js +86 -52
  40. package/dist/widget.cjs +1227 -211
  41. package/dist/widget.d.cts +36 -12
  42. package/dist/widget.d.ts +36 -12
  43. package/dist/widget.js +7 -7
  44. package/dist/workspace.cjs +1226 -211
  45. package/dist/workspace.d.cts +47 -20
  46. package/dist/workspace.d.ts +47 -20
  47. package/dist/workspace.js +6 -6
  48. package/package.json +2 -2
  49. /package/dist/{chunk-JU4CWEBF.js → chunk-W5YPYZ2P.js} +0 -0
@@ -1,4 +1,4 @@
1
- import { svg24, NEUTRAL, BEARISH, WARNING, BULLISH, INFO, ACCENT_BRIGHT, VALID, INVALID, MARKER, ACCENT, injectStyles, iconEl } from './chunk-BZQM2XO7.js';
1
+ import { svg24, NEUTRAL, BEARISH, WARNING, BULLISH, INFO, ACCENT_BRIGHT, VALID, INVALID, MARKER, ACCENT, injectStyles, iconEl, announceSurface, holdForExit } from './chunk-VHGACEHO.js';
2
2
 
3
3
  // src/core/model/series.ts
4
4
  function isLineLikeSeries(spec) {
@@ -2778,6 +2778,10 @@ var FibRatios = class extends Drawing {
2778
2778
 
2779
2779
  // src/core/drawings/types/FibLevels.ts
2780
2780
  var FibLevels = class extends FibRatios {
2781
+ /** Price of a level for the two anchor prices; subclasses override to change which anchor ratio 0 sits on. */
2782
+ levelPrice(ratio, p1, p2) {
2783
+ return p1 + ratio * (p2 - p1);
2784
+ }
2781
2785
  /** Per-level pixel line + price for the ENABLED levels, spanning the anchors' time range. */
2782
2786
  levelLines(proj) {
2783
2787
  const a = this.anchors[0];
@@ -2787,11 +2791,10 @@ var FibLevels = class extends FibRatios {
2787
2791
  const xb = proj.xOf(b.time);
2788
2792
  const x1 = Math.min(xa, xb);
2789
2793
  const x2 = Math.max(xa, xb);
2790
- const delta = b.price - a.price;
2791
2794
  const out = [];
2792
2795
  for (const lv of this.levels) {
2793
2796
  if (!lv.enabled) continue;
2794
- const price = a.price + lv.ratio * delta;
2797
+ const price = this.levelPrice(lv.ratio, a.price, b.price);
2795
2798
  const y = proj.yOf(price, this.paneId);
2796
2799
  if (y == null) continue;
2797
2800
  out.push({ ratio: lv.ratio, color: lv.color, label: lv.label, price, x1, x2, y });
@@ -2831,8 +2834,7 @@ var FibLevels = class extends FibRatios {
2831
2834
  const a = this.anchors[0];
2832
2835
  const b = this.anchors[1];
2833
2836
  if (!a || !b) return null;
2834
- const delta = b.price - a.price;
2835
- const prices = this.levels.filter((l) => l.enabled).map((l) => a.price + l.ratio * delta);
2837
+ const prices = this.levels.filter((l) => l.enabled).map((l) => this.levelPrice(l.ratio, a.price, b.price));
2836
2838
  if (prices.length === 0) return null;
2837
2839
  return { min: Math.min(...prices), max: Math.max(...prices) };
2838
2840
  }
@@ -2918,13 +2920,24 @@ function cycleLevels(count, enabledCount = count) {
2918
2920
  // src/core/drawings/types/FibRetracement.ts
2919
2921
  var LEVELS = fibLevels([0, 0.236, 0.382, 0.5, 0.618, 0.786, 1]);
2920
2922
  var FibRetracement = class extends FibLevels {
2921
- constructor() {
2922
- super(...arguments);
2923
+ constructor(init) {
2924
+ super(init);
2923
2925
  this.type = "fibretracement";
2926
+ if (this.reverse === void 0) this.reverse = false;
2924
2927
  }
2925
2928
  defaultLevels() {
2926
2929
  return LEVELS;
2927
2930
  }
2931
+ levelPrice(ratio, p1, p2) {
2932
+ return this.reverse ? p1 + ratio * (p2 - p1) : p2 + ratio * (p1 - p2);
2933
+ }
2934
+ writeProps() {
2935
+ return { ...super.writeProps(), reverse: this.reverse };
2936
+ }
2937
+ readProps(props) {
2938
+ super.readProps(props);
2939
+ this.reverse = typeof props.reverse === "boolean" ? props.reverse : true;
2940
+ }
2928
2941
  };
2929
2942
 
2930
2943
  // src/core/drawings/types/FibExtension.ts
@@ -5892,6 +5905,100 @@ function inputDeltas(schema, values) {
5892
5905
  return Object.keys(out).length > 0 ? out : void 0;
5893
5906
  }
5894
5907
 
5908
+ // src/core/timezones.ts
5909
+ var TIMEZONES = [
5910
+ { value: "Etc/UTC", label: "UTC" },
5911
+ { value: "Etc/GMT+12", label: "International Date Line West" },
5912
+ { value: "Pacific/Pago_Pago", label: "Pago Pago" },
5913
+ { value: "Pacific/Honolulu", label: "Honolulu" },
5914
+ { value: "Pacific/Marquesas", label: "Marquesas Islands" },
5915
+ { value: "America/Anchorage", label: "Anchorage" },
5916
+ { value: "America/Los_Angeles", label: "Los Angeles" },
5917
+ { value: "America/Phoenix", label: "Phoenix" },
5918
+ { value: "America/Denver", label: "Denver" },
5919
+ { value: "America/Chicago", label: "Chicago" },
5920
+ { value: "America/Mexico_City", label: "Mexico City" },
5921
+ { value: "America/New_York", label: "New York" },
5922
+ { value: "America/Bogota", label: "Bogot\xE1" },
5923
+ { value: "America/Caracas", label: "Caracas" },
5924
+ { value: "America/Santiago", label: "Santiago" },
5925
+ { value: "America/St_Johns", label: "St. John's" },
5926
+ { value: "America/Sao_Paulo", label: "S\xE3o Paulo" },
5927
+ { value: "America/Argentina/Buenos_Aires", label: "Buenos Aires" },
5928
+ { value: "America/Noronha", label: "Fernando de Noronha" },
5929
+ { value: "Atlantic/Azores", label: "Azores" },
5930
+ { value: "Atlantic/Reykjavik", label: "Reykjavik" },
5931
+ { value: "Europe/London", label: "London" },
5932
+ { value: "Europe/Paris", label: "Paris" },
5933
+ { value: "Europe/Berlin", label: "Berlin" },
5934
+ { value: "Europe/Athens", label: "Athens" },
5935
+ { value: "Africa/Cairo", label: "Cairo" },
5936
+ { value: "Africa/Johannesburg", label: "Johannesburg" },
5937
+ { value: "Europe/Moscow", label: "Moscow" },
5938
+ { value: "Europe/Istanbul", label: "Istanbul" },
5939
+ { value: "Asia/Tehran", label: "Tehran" },
5940
+ { value: "Asia/Dubai", label: "Dubai" },
5941
+ { value: "Asia/Kabul", label: "Kabul" },
5942
+ { value: "Asia/Karachi", label: "Karachi" },
5943
+ { value: "Asia/Kolkata", label: "Mumbai" },
5944
+ { value: "Asia/Kathmandu", label: "Kathmandu" },
5945
+ { value: "Asia/Dhaka", label: "Dhaka" },
5946
+ { value: "Asia/Yangon", label: "Yangon" },
5947
+ { value: "Asia/Bangkok", label: "Bangkok" },
5948
+ { value: "Asia/Shanghai", label: "Shanghai" },
5949
+ { value: "Asia/Hong_Kong", label: "Hong Kong" },
5950
+ { value: "Asia/Singapore", label: "Singapore" },
5951
+ { value: "Australia/Eucla", label: "Eucla" },
5952
+ { value: "Asia/Tokyo", label: "Tokyo" },
5953
+ { value: "Asia/Seoul", label: "Seoul" },
5954
+ { value: "Australia/Adelaide", label: "Adelaide" },
5955
+ { value: "Australia/Sydney", label: "Sydney" },
5956
+ { value: "Australia/Lord_Howe", label: "Lord Howe Island" },
5957
+ { value: "Pacific/Noumea", label: "Noum\xE9a" },
5958
+ { value: "Pacific/Auckland", label: "Auckland" },
5959
+ { value: "Pacific/Chatham", label: "Chatham Islands" },
5960
+ { value: "Pacific/Apia", label: "Apia" },
5961
+ { value: "Pacific/Kiritimati", label: "Kiritimati" }
5962
+ ];
5963
+ var EXCHANGE_TIMEZONE = "exchange";
5964
+ function isExchangeTimezone(zone) {
5965
+ return zone === EXCHANGE_TIMEZONE;
5966
+ }
5967
+ function resolveTimezone(zone, exchangeZone) {
5968
+ if (!isExchangeTimezone(zone)) return zone;
5969
+ return exchangeZone && exchangeZone !== "" ? exchangeZone : "Etc/UTC";
5970
+ }
5971
+ function normalizeTimezone(zone) {
5972
+ return zone === "UTC" || zone === "Etc/UTC" || zone === "Etc/GMT" ? "Etc/UTC" : zone;
5973
+ }
5974
+ function timezoneMenuRows(current) {
5975
+ const active = normalizeTimezone(current);
5976
+ const [utc, ...zones] = TIMEZONES.map((t) => ({ value: t.value, label: tzMenuLabel(t.value, t.label), checked: t.value === active }));
5977
+ return [utc, { value: EXCHANGE_TIMEZONE, label: "Exchange", checked: isExchangeTimezone(current) }, ...zones];
5978
+ }
5979
+ function tzOffset(zone, date = /* @__PURE__ */ new Date()) {
5980
+ try {
5981
+ const parts = new Intl.DateTimeFormat("en-US", { timeZone: zone, timeZoneName: "shortOffset" }).formatToParts(date);
5982
+ const raw = parts.find((p) => p.type === "timeZoneName")?.value ?? "";
5983
+ const m = raw.match(/GMT(?:(\+|-)(\d{1,2})(?::(\d{2}))?)?/);
5984
+ if (!m || !m[1]) return "UTC";
5985
+ const sign = m[1] === "-" ? "-" : "+";
5986
+ const hrs = m[2] ?? "0";
5987
+ const mins = m[3] ?? "";
5988
+ return mins ? `UTC${sign}${hrs}:${mins}` : `UTC${sign}${hrs}`;
5989
+ } catch {
5990
+ return "UTC";
5991
+ }
5992
+ }
5993
+ function tzMenuLabel(zone, location) {
5994
+ if (normalizeTimezone(zone) === "Etc/UTC") return location;
5995
+ return `(${tzOffset(zone)}) ${location}`;
5996
+ }
5997
+ function tzButtonLabel(zone) {
5998
+ if (normalizeTimezone(zone) === "Etc/UTC") return "UTC";
5999
+ return tzOffset(zone);
6000
+ }
6001
+
5895
6002
  // src/renderers/native/layers.ts
5896
6003
  function foldBaseModulation(acc, next) {
5897
6004
  if (next == null) return acc;
@@ -6023,10 +6130,12 @@ function legendActions() {
6023
6130
  }
6024
6131
  function legendActionsProviderFor(chart, context) {
6025
6132
  return (indicatorId) => {
6026
- const handle = chart.indicators().find((h) => h.id === indicatorId);
6133
+ const find = () => chart.indicators().find((h) => h.id === indicatorId);
6134
+ const infoOf = (h) => ({ id: h.id, title: h.title, ...h.source !== void 0 ? { source: h.source } : {} });
6135
+ const handle = find();
6027
6136
  if (!handle) return [];
6028
- const info = { id: handle.id, title: handle.title, ...handle.source !== void 0 ? { source: handle.source } : {} };
6029
- return legendActions().filter((d) => !d.when || d.when(info)).map((d) => ({ id: d.id, icon: d.icon, tooltip: d.tooltip, run: () => d.run(context(), info) }));
6137
+ const info = infoOf(handle);
6138
+ return legendActions().filter((d) => !d.when || d.when(info)).map((d) => ({ id: d.id, icon: d.icon, tooltip: d.tooltip, run: () => d.run(context(), infoOf(find() ?? handle)) }));
6030
6139
  };
6031
6140
  }
6032
6141
  var calloutRegistry = /* @__PURE__ */ new Map();
@@ -6113,6 +6222,127 @@ function resolveEngines(overrides) {
6113
6222
  return { ...Object.fromEntries(defaultEngines), ...overrides };
6114
6223
  }
6115
6224
 
6225
+ // src/widget/context-menu-model.ts
6226
+ var SETTINGS_SECTION = {
6227
+ body: "Canvas",
6228
+ "price-axis": "Scales and lines",
6229
+ "time-axis": "Scales and lines"
6230
+ };
6231
+ function settingsItem(zone, label, icon) {
6232
+ return { id: `settings:${SETTINGS_SECTION[zone]}`, label, ...icon ? { icon } : {}, separatorBefore: true };
6233
+ }
6234
+ function settingsSectionOf(id) {
6235
+ return id.slice("settings:".length) || void 0;
6236
+ }
6237
+ var SCALE_CHOICES = [
6238
+ ["regular", "Regular"],
6239
+ ["percent", "Percent"],
6240
+ ["indexed", "Indexed to 100"],
6241
+ ["log", "Logarithmic"]
6242
+ ];
6243
+ function paneScaleAt(panes, y) {
6244
+ return panes.find((p) => y >= p.top && y < p.top + p.height) ?? panes[0] ?? null;
6245
+ }
6246
+ function scaleChoiceOf(scale) {
6247
+ if (scale.log) return "log";
6248
+ if (scale.mode === "percent") return "percent";
6249
+ if (scale.mode === "indexed") return "indexed";
6250
+ return "regular";
6251
+ }
6252
+ function mainScale(pane) {
6253
+ return pane === null || pane.kind === "price";
6254
+ }
6255
+ function scaleWrites(choice, pane) {
6256
+ const mode = choice === "percent" ? "percent" : choice === "indexed" ? "indexed" : "price";
6257
+ const log = choice === "log";
6258
+ if (mainScale(pane)) {
6259
+ return [
6260
+ ["scaleMode", mode],
6261
+ ["logScale", log]
6262
+ ];
6263
+ }
6264
+ return [
6265
+ ["scaleMode", { pane: pane.id, mode }],
6266
+ ["logScale", { pane: pane.id, value: log }]
6267
+ ];
6268
+ }
6269
+ function invertWrite(next, pane) {
6270
+ return mainScale(pane) ? ["invertScale", next] : ["invertScale", { pane: pane.id, value: next }];
6271
+ }
6272
+ function priceAxisItems(s) {
6273
+ return [
6274
+ { id: "auto", label: "Auto (fits data to screen)", checked: s.auto },
6275
+ { id: "invert", label: "Invert scale", checked: s.invert },
6276
+ ...SCALE_CHOICES.map(([choice, label], i) => ({
6277
+ id: `scale:${choice}`,
6278
+ label,
6279
+ checked: s.choice === choice,
6280
+ separatorBefore: i === 0
6281
+ })),
6282
+ {
6283
+ id: "labels",
6284
+ label: "Labels",
6285
+ separatorBefore: true,
6286
+ submenu: [
6287
+ { id: "toggle:axisLabels", label: "Price axis labels", checked: s.axisLabels },
6288
+ { id: "toggle:priceLabel", label: "Last price label", checked: s.priceLabel },
6289
+ { id: "toggle:countdown", label: "Countdown to bar close", checked: s.countdown }
6290
+ ]
6291
+ },
6292
+ {
6293
+ id: "levels",
6294
+ label: "Levels",
6295
+ submenu: [{ id: "toggle:currentPriceLine", label: "Last Price Line", checked: s.priceLine }]
6296
+ },
6297
+ settingsItem("price-axis", "More settings\u2026")
6298
+ ];
6299
+ }
6300
+ function timeAxisItems(timezone) {
6301
+ return [
6302
+ {
6303
+ id: "timezone",
6304
+ label: "Time zone",
6305
+ submenu: timezoneMenuRows(timezone).map((r) => ({ id: `tz:${r.value}`, label: r.label, checked: r.checked }))
6306
+ },
6307
+ settingsItem("time-axis", "More settings\u2026")
6308
+ ];
6309
+ }
6310
+ function bodyItems(counts) {
6311
+ return [
6312
+ { id: "reset-view", label: "Reset chart view", icon: "reset" },
6313
+ { id: "remove-drawings", label: "Remove drawings", icon: "eraser", disabled: counts.drawings === 0, separatorBefore: true },
6314
+ { id: "remove-indicators", label: "Remove indicators", icon: "indicators", disabled: counts.indicators === 0 },
6315
+ settingsItem("body", "Settings\u2026", "gear")
6316
+ ];
6317
+ }
6318
+ var CONTEXT_MENU_BUILTIN_ORDER = {
6319
+ body: { "reset-view": -30, "remove-drawings": -20, "remove-indicators": -10, settings: 1e3 },
6320
+ "price-axis": {
6321
+ auto: -80,
6322
+ invert: -70,
6323
+ "scale:regular": -60,
6324
+ "scale:percent": -50,
6325
+ "scale:indexed": -40,
6326
+ "scale:log": -30,
6327
+ labels: -20,
6328
+ levels: -10,
6329
+ settings: 1e3
6330
+ },
6331
+ "time-axis": { timezone: -10, settings: 1e3 }
6332
+ };
6333
+ function composeMenu(zone, builtin, contributed) {
6334
+ const ranks = CONTEXT_MENU_BUILTIN_ORDER[zone];
6335
+ const rows = [];
6336
+ let group = 0;
6337
+ for (const item of builtin) {
6338
+ if (item.separatorBefore) group += 1;
6339
+ rows.push({ item, rank: ranks[item.id.startsWith("settings") ? "settings" : item.id] ?? 0, group });
6340
+ }
6341
+ for (const { item, order } of contributed) rows.push({ item, rank: order ?? 0, group: -1 });
6342
+ rows.sort((a, b) => a.rank - b.rank);
6343
+ return rows.map(({ item, group: g }, i) => ({ ...item, separatorBefore: i > 0 && rows[i - 1].group !== g }));
6344
+ }
6345
+
6116
6346
  // src/widget/side-panel.ts
6117
6347
  var STYLE_ID = "vela-widget-sidepanel";
6118
6348
  var DEFAULT_PANEL_WIDTH = 280;
@@ -6177,6 +6407,32 @@ var CSS = `
6177
6407
  .vela-panel-pin .vela-icon { width: 15px; height: 15px; }
6178
6408
  .vela-panel-pin:hover { background: var(--vela-hover); color: var(--vela-fg-bright); }
6179
6409
  .vela-panel-pin[data-on='1'] { color: var(--vela-fg-bright); }
6410
+ .vela-panel-max {
6411
+ all: unset;
6412
+ cursor: pointer;
6413
+ width: 26px;
6414
+ height: 26px;
6415
+ flex: none;
6416
+ display: inline-flex;
6417
+ align-items: center;
6418
+ justify-content: center;
6419
+ border-radius: 4px;
6420
+ color: var(--vela-fg-muted);
6421
+ }
6422
+ .vela-panel-max .vela-icon { width: 15px; height: 15px; }
6423
+ .vela-panel-max:hover { background: var(--vela-hover); color: var(--vela-fg-bright); }
6424
+ /* MAXIMIZED: the panel covers the whole dock host \u2014 every chart of the shell \u2014 whatever
6425
+ its placement; the width handle and the pin have nothing to do meanwhile. */
6426
+ .vela-panel[data-maximized] {
6427
+ position: absolute;
6428
+ inset: 0;
6429
+ width: auto;
6430
+ max-width: none;
6431
+ z-index: 26;
6432
+ border-left: none;
6433
+ }
6434
+ .vela-panel[data-maximized] .vela-panel-resizer,
6435
+ .vela-panel[data-maximized] .vela-panel-pin { display: none; }
6180
6436
  .vela-panel-body { flex: 1; overflow: auto; padding: 8px; }
6181
6437
  .vela-panel-body::-webkit-scrollbar { width: 8px; }
6182
6438
  .vela-panel-body::-webkit-scrollbar-thumb { background: var(--vela-scroll); border-radius: 4px; border: 2px solid transparent; background-clip: padding-box; }
@@ -6229,6 +6485,7 @@ var CSS = `
6229
6485
  border-left: none;
6230
6486
  }
6231
6487
  [data-layout='mobile'] .vela-panel-resizer { display: none; }
6488
+ [data-layout='mobile'] .vela-panel-max { display: none; }
6232
6489
  `;
6233
6490
  function clampPanelWidth(px, min = DEFAULT_PANEL_MIN_WIDTH, max = DEFAULT_PANEL_MAX_WIDTH) {
6234
6491
  const lo = Number.isFinite(min) && min > 0 ? min : DEFAULT_PANEL_MIN_WIDTH;
@@ -6252,6 +6509,15 @@ var SidePanel = class {
6252
6509
  * never on a programmatic {@link setOverlay}, for the same reason as widths. */
6253
6510
  this.onPlacementChange = null;
6254
6511
  this.pin = null;
6512
+ this.maxButton = null;
6513
+ this.maximizedOn = false;
6514
+ /** Set while the close is announced, so a listener's own close does not re-enter. */
6515
+ this.closing = false;
6516
+ /** Open as far as the dock and the host are concerned — a closed panel can still be on
6517
+ * screen while its exit animation runs. */
6518
+ this.isOpen = false;
6519
+ /** The exit animation of a close; a reopen cancels it. */
6520
+ this.exit = null;
6255
6521
  const doc = host.ownerDocument;
6256
6522
  injectStyles(STYLE_ID, CSS, doc);
6257
6523
  this.minWidth = opts.resizable ? opts.minWidth ?? DEFAULT_PANEL_MIN_WIDTH : 1;
@@ -6286,7 +6552,13 @@ var SidePanel = class {
6286
6552
  });
6287
6553
  this.refreshPin();
6288
6554
  }
6289
- header.append(this.heading, this.slot, ...this.pin ? [this.pin] : [], close);
6555
+ if (opts.maximizable) {
6556
+ this.maxButton = doc.createElement("button");
6557
+ this.maxButton.className = "vela-panel-max";
6558
+ this.maxButton.addEventListener("click", () => this.setMaximized(!this.maximizedOn));
6559
+ this.refreshMaxButton();
6560
+ }
6561
+ header.append(this.heading, this.slot, ...this.pin ? [this.pin] : [], ...this.maxButton ? [this.maxButton] : [], close);
6290
6562
  this.body = doc.createElement("div");
6291
6563
  this.body.className = "vela-panel-body";
6292
6564
  this.el.append(header, this.body);
@@ -6294,13 +6566,65 @@ var SidePanel = class {
6294
6566
  host.appendChild(this.el);
6295
6567
  }
6296
6568
  get open() {
6297
- return !this.el.hidden;
6569
+ return this.isOpen;
6298
6570
  }
6299
- /** Open/close the panel — a bare call flips it. */
6300
- toggle(open = this.el.hidden) {
6301
- if (open === !this.el.hidden) return;
6302
- this.el.hidden = !open;
6571
+ /**
6572
+ * Open/close the panel — a bare call flips it. The panel reports closed at once, but a
6573
+ * close keeps it on screen, marked `data-closing`, while a host exit animation runs.
6574
+ * `instant` skips that, and also cuts short the exit of a panel already closing: a panel
6575
+ * handing the dock to another leaves at once.
6576
+ */
6577
+ toggle(open = !this.isOpen, instant = false) {
6578
+ if (open === this.isOpen) {
6579
+ if (!open && instant) this.exit?.finish();
6580
+ return;
6581
+ }
6582
+ if (!open && this.closing) return;
6583
+ if (!open) {
6584
+ this.closing = true;
6585
+ announceSurface(this.el, false, "panel");
6586
+ this.closing = false;
6587
+ }
6588
+ this.isOpen = open;
6589
+ if (open) {
6590
+ if (this.exit) {
6591
+ this.exit.cancel();
6592
+ this.exit = null;
6593
+ this.setMaximized(false);
6594
+ }
6595
+ this.el.hidden = false;
6596
+ } else {
6597
+ const hide = () => {
6598
+ this.exit = null;
6599
+ this.el.hidden = true;
6600
+ this.setMaximized(false);
6601
+ };
6602
+ if (instant) hide();
6603
+ else this.exit = holdForExit(this.el, hide);
6604
+ }
6303
6605
  this.onOpenChange?.(open);
6606
+ if (open && this.isOpen) announceSurface(this.el, true, "panel");
6607
+ }
6608
+ /** Whether the panel covers every chart right now. */
6609
+ get maximized() {
6610
+ return this.maximizedOn;
6611
+ }
6612
+ /** Maximize the panel over the shell's charts, or restore it. A no-op on a panel not
6613
+ * declared `maximizable`. */
6614
+ setMaximized(maximized) {
6615
+ if (!this.maxButton || maximized === this.maximizedOn) return;
6616
+ this.maximizedOn = maximized;
6617
+ if (maximized) this.el.dataset.maximized = "1";
6618
+ else delete this.el.dataset.maximized;
6619
+ this.refreshMaxButton();
6620
+ }
6621
+ refreshMaxButton() {
6622
+ if (!this.maxButton) return;
6623
+ const doc = this.maxButton.ownerDocument;
6624
+ this.maxButton.replaceChildren(iconEl(this.maximizedOn ? "restore" : "maximize", doc));
6625
+ this.maxButton.title = this.maximizedOn ? "Restore" : "Maximize";
6626
+ this.maxButton.setAttribute("aria-label", this.maxButton.title);
6627
+ this.maxButton.setAttribute("aria-pressed", this.maximizedOn ? "true" : "false");
6304
6628
  }
6305
6629
  /** The scrolling body, for a panel filled from OUTSIDE the class — a contributed panel's
6306
6630
  * `mount` receives exactly this element. Subclasses use the protected `body`. */
@@ -6352,6 +6676,8 @@ var SidePanel = class {
6352
6676
  this.pin.setAttribute("aria-pressed", pinned ? "true" : "false");
6353
6677
  }
6354
6678
  destroy() {
6679
+ if (this.open) announceSurface(this.el, false, "panel");
6680
+ this.exit?.finish();
6355
6681
  this.el.remove();
6356
6682
  }
6357
6683
  /** The drag handle on the panel's inner (left) edge — the panel is docked right, so dragging
@@ -6396,4 +6722,4 @@ var SidePanel = class {
6396
6722
  }
6397
6723
  };
6398
6724
 
6399
- export { AnchoredVwap, ArrowMark, Callout, CalloutBase, Comment, DEDEKIND_CURVATURE_OPTIONS, DEFAULT_DRAWING_COLOR, DEFAULT_PANEL_MAX_WIDTH, DEFAULT_PANEL_MIN_WIDTH, DEFAULT_PANEL_ORDER, DEFAULT_PANEL_WIDTH, DIRECTION_OPTIONS, DedekindTessellation, Drawing, FibRatios, FibSpiral, FixedRangeVolumeProfile, GANN_SQUARE_ARCS, GLYPH_OPTIONS, GannSquare, GlyphStamp, LINE_STYLE_OPTIONS, MACH_NUMBER_OPTIONS, MACH_WAVE_COUNT_OPTIONS, MAGNIFIER_TIMEFRAME_OPTIONS, MachFigure, Magnifier, MeasureBox, Note, OVERRIDABLE_TOPBAR_IDS, PatternDrawing, PositionTool, PriceLabel, PriceNote, RadialFib, RegressionChannel, STAMP_SIZE_OPTIONS, SegmentDrawing, SidePanel, Signpost, TEXT_SIZE_OPTIONS, TOPBAR_BUILTIN_IDS, TOPBAR_DEFAULT_LEFT, TOPBAR_DEFAULT_RIGHT, TextLabel, chartType, chartTypes, clampPanelWidth, createDrawing, deserializeDrawing, drawingTypes, foldBaseModulation, formatDuration, getDrawingType, getNativeIndicator, inputDeltas, inputVisible, isLineLikeSeries, legendActions, legendActionsProviderFor, legendCallouts, legendCalloutsProviderFor, lineSegmentIntersection, magnifierTimeframeLabel, mobilePlacement, nativeIndicatorDescriptors, nativeIndicatorTypes, nativeInstanceChannel, normalizeSettingsRow, pinnedTopbarActionIds, registerChartType, registerDefaultEngine, registerDrawingType, registerLegendAction, registerLegendCallout, registerNativeIndicator, registerRendererDefaults, registerRendererLayer, registerSidePanel, registerStatePersistence, registerSymbolRanking, registerWidgetAction, registerWidgetAttachment, rendererDefaults, rendererLayers, resetDrawingSettings, resolveEngines, resolveTopbarComposition, seriesInScale, seriesShownOn, settingsRowValueKeys, settingsRowVisible, sidePanels, stableSeriesId, statePersistenceHandlers, symbolRanking, tickerModifierIds, topbarActionOverride, topbarHas, unregisterChartType, unregisterDefaultEngine, unregisterLegendAction, unregisterLegendCallout, unregisterNativeIndicator, unregisterRendererDefaults, unregisterRendererLayer, unregisterSidePanel, unregisterStatePersistence, unregisterWidgetAction, unregisterWidgetAttachment, widgetActions, widgetAttachments };
6725
+ export { AnchoredVwap, ArrowMark, CONTEXT_MENU_BUILTIN_ORDER, Callout, CalloutBase, Comment, DEDEKIND_CURVATURE_OPTIONS, DEFAULT_DRAWING_COLOR, DEFAULT_PANEL_MAX_WIDTH, DEFAULT_PANEL_MIN_WIDTH, DEFAULT_PANEL_ORDER, DEFAULT_PANEL_WIDTH, DIRECTION_OPTIONS, DedekindTessellation, Drawing, FibRatios, FibRetracement, FibSpiral, FixedRangeVolumeProfile, GANN_SQUARE_ARCS, GLYPH_OPTIONS, GannSquare, GlyphStamp, LINE_STYLE_OPTIONS, MACH_NUMBER_OPTIONS, MACH_WAVE_COUNT_OPTIONS, MAGNIFIER_TIMEFRAME_OPTIONS, MachFigure, Magnifier, MeasureBox, Note, OVERRIDABLE_TOPBAR_IDS, PatternDrawing, PositionTool, PriceLabel, PriceNote, RadialFib, RegressionChannel, STAMP_SIZE_OPTIONS, SegmentDrawing, SidePanel, Signpost, TEXT_SIZE_OPTIONS, TIMEZONES, TOPBAR_BUILTIN_IDS, TOPBAR_DEFAULT_LEFT, TOPBAR_DEFAULT_RIGHT, TextLabel, bodyItems, chartType, chartTypes, clampPanelWidth, composeMenu, createDrawing, deserializeDrawing, drawingTypes, foldBaseModulation, formatDuration, getDrawingType, getNativeIndicator, inputDeltas, inputVisible, invertWrite, isExchangeTimezone, isLineLikeSeries, legendActions, legendActionsProviderFor, legendCallouts, legendCalloutsProviderFor, lineSegmentIntersection, magnifierTimeframeLabel, mobilePlacement, nativeIndicatorDescriptors, nativeIndicatorTypes, nativeInstanceChannel, normalizeSettingsRow, normalizeTimezone, paneScaleAt, pinnedTopbarActionIds, priceAxisItems, registerChartType, registerDefaultEngine, registerDrawingType, registerLegendAction, registerLegendCallout, registerNativeIndicator, registerRendererDefaults, registerRendererLayer, registerSidePanel, registerStatePersistence, registerSymbolRanking, registerWidgetAction, registerWidgetAttachment, rendererDefaults, rendererLayers, resetDrawingSettings, resolveEngines, resolveTimezone, resolveTopbarComposition, scaleChoiceOf, scaleWrites, seriesInScale, seriesShownOn, settingsRowValueKeys, settingsRowVisible, settingsSectionOf, sidePanels, stableSeriesId, statePersistenceHandlers, symbolRanking, tickerModifierIds, timeAxisItems, timezoneMenuRows, topbarActionOverride, topbarHas, tzButtonLabel, tzMenuLabel, tzOffset, unregisterChartType, unregisterDefaultEngine, unregisterLegendAction, unregisterLegendCallout, unregisterNativeIndicator, unregisterRendererDefaults, unregisterRendererLayer, unregisterSidePanel, unregisterStatePersistence, unregisterWidgetAction, unregisterWidgetAttachment, widgetActions, widgetAttachments };
@@ -1,5 +1,5 @@
1
- import { normalizeProps, nextUid, runMachine, spreadProps } from './chunk-NELQJCGK.js';
2
- import { injectStyles } from './chunk-BZQM2XO7.js';
1
+ import { normalizeProps, nextUid, runMachine, spreadProps } from './chunk-G7B7ZCBF.js';
2
+ import { injectStyles, holdForExit, announceSurface } from './chunk-VHGACEHO.js';
3
3
  import * as tooltip from '@zag-js/tooltip';
4
4
  import * as dialog from '@zag-js/dialog';
5
5
 
@@ -343,6 +343,11 @@ function swipeDirection(dx, dy) {
343
343
  }
344
344
  var Drawer = class {
345
345
  constructor(opts = {}) {
346
+ this.opener = null;
347
+ this.wasOpen = false;
348
+ /** The sheet and scrim's exit animation after a close; a reopen cancels it. */
349
+ this.exit = null;
350
+ this.destroyed = false;
346
351
  const doc = (opts.host ?? document.body).ownerDocument;
347
352
  injectStyles(DRAWER_STYLE_ID, DRAWER_CSS, doc);
348
353
  const host = opts.host ?? doc.body;
@@ -370,12 +375,24 @@ var Drawer = class {
370
375
  const mid = String(this.ctrl.props.id);
371
376
  this.handle = runMachine(this.ctrl.machine, this.ctrl.props, (service) => {
372
377
  const api = this.ctrl.connect(service);
378
+ const closing = this.wasOpen && !api.open;
379
+ if (closing) this.announce(false);
373
380
  spreadProps(this.backdrop, api.getBackdropProps(), mid);
374
381
  spreadProps(this.positioner, api.getPositionerProps(), mid);
375
382
  spreadProps(this.panel, api.getContentProps(), mid);
376
383
  spreadProps(this.titleEl, api.getTitleProps(), mid);
377
- this.backdrop.style.display = api.open ? "" : "none";
378
- this.positioner.style.display = api.open ? "" : "none";
384
+ if (api.open) {
385
+ this.exit?.cancel();
386
+ this.exit = null;
387
+ this.setShown(true);
388
+ } else if (closing) {
389
+ this.holdExit();
390
+ } else if (!this.exit) {
391
+ this.setShown(false);
392
+ }
393
+ if (api.open && !this.wasOpen) this.announce(true);
394
+ if (this.wasOpen && !api.open) this.restoreOpener();
395
+ this.wasOpen = api.open;
379
396
  });
380
397
  }
381
398
  /** Any element between `from` and the panel that has already been scrolled down —
@@ -484,19 +501,67 @@ var Drawer = class {
484
501
  setTitle(title) {
485
502
  this.titleEl.textContent = title;
486
503
  }
504
+ setShown(shown) {
505
+ this.backdrop.style.display = shown ? "" : "none";
506
+ this.positioner.style.display = shown ? "" : "none";
507
+ }
508
+ /** Keep the just-closed sheet up through its exit animation, then hide it — or
509
+ * remove it, when it was destroyed meanwhile. `spreadProps` re-applies the close
510
+ * props' `hidden` only when its value changes, so lifting it keeps the sheet and
511
+ * scrim up until the exit ends. The positioner spans the host: inert, it lets
512
+ * clicks through to the page while the sheet animates out. */
513
+ holdExit() {
514
+ const held = [this.panel, this.backdrop];
515
+ for (const el of held) el.hidden = false;
516
+ this.exit = holdForExit(
517
+ held,
518
+ () => {
519
+ this.exit = null;
520
+ if (this.destroyed) {
521
+ this.detach();
522
+ } else if (!this.open) {
523
+ for (const el of held) el.hidden = true;
524
+ this.setShown(false);
525
+ }
526
+ },
527
+ { inert: [this.positioner] }
528
+ );
529
+ }
530
+ detach() {
531
+ this.backdrop.remove();
532
+ this.positioner.remove();
533
+ }
534
+ /** Closing hands focus back to whatever opened it (Escape, the ✕, hide(), or a
535
+ * teardown that outruns the machine's close notification). */
536
+ restoreOpener() {
537
+ const opener = this.opener;
538
+ this.opener = null;
539
+ if (opener?.isConnected) opener.focus({ preventScroll: true });
540
+ }
541
+ announce(open) {
542
+ const opener = this.opener === this.panel.ownerDocument.body ? null : this.opener;
543
+ announceSurface(this.panel, open, "drawer", opener);
544
+ }
487
545
  get open() {
488
546
  return this.ctrl.connect(this.handle.service).open;
489
547
  }
490
548
  show() {
549
+ this.opener = this.panel.ownerDocument.activeElement;
491
550
  this.ctrl.connect(this.handle.service).setOpen(true);
492
551
  }
493
552
  hide() {
494
553
  this.ctrl.connect(this.handle.service).setOpen(false);
495
554
  }
496
555
  destroy() {
556
+ if (this.wasOpen && !this.open) this.handle.flush();
497
557
  this.handle.stop();
498
- this.backdrop.remove();
499
- this.positioner.remove();
558
+ this.destroyed = true;
559
+ if (this.wasOpen) {
560
+ this.wasOpen = false;
561
+ this.announce(false);
562
+ }
563
+ this.restoreOpener();
564
+ if (!this.exit) this.detach();
500
565
  }
501
566
  };
502
567
 
@@ -146,6 +146,7 @@ registerIcon("expand", S('<rect x="2.2" y="2.2" width="11.6" height="11.6" rx="1
146
146
  registerIcon("plus", S('<path d="M8 2.8v10.4M2.8 8h10.4"/>'));
147
147
  registerIcon("minus", S('<path d="M2.8 8h10.4"/>'));
148
148
  registerIcon("maximize", S('<path d="M2.5 6V3a.5.5 0 0 1 .5-.5h3M10 2.5h3a.5.5 0 0 1 .5.5v3M13.5 10v3a.5.5 0 0 1-.5.5h-3M6 13.5H3a.5.5 0 0 1-.5-.5v-3"/>'));
149
+ registerIcon("restore", S('<path d="M6 2.5v3a.5.5 0 0 1-.5.5h-3M13.5 6h-3a.5.5 0 0 1-.5-.5v-3M10 13.5v-3a.5.5 0 0 1 .5-.5h3M2.5 10h3a.5.5 0 0 1 .5.5v3"/>'));
149
150
  registerIcon("restore", S('<path d="M6.2 2.5v3.7H2.5M9.8 13.5V9.8h3.7M13.5 6.2H9.8V2.5M2.5 9.8h3.7v3.7"/>'));
150
151
  registerIcon("star", S('<path d="M8 2.2l1.75 3.55 3.9.55-2.8 2.75.65 3.9L8 11.1l-3.5 1.85.65-3.9-2.8-2.75 3.9-.55z"/>'));
151
152
  registerIcon("star-filled", S('<path d="M8 2.2l1.75 3.55 3.9.55-2.8 2.75.65 3.9L8 11.1l-3.5 1.85.65-3.9-2.8-2.75 3.9-.55z"/>', 'fill="currentColor"'));
@@ -270,4 +271,64 @@ function iconEl(id, doc = document) {
270
271
  return span;
271
272
  }
272
273
 
273
- export { ACCENT, ACCENT_BRIGHT, BEARISH, BULLISH, CATEGORICAL, CHIP_PLATE, CROSSHAIR, FIELD_FOCUS_CSS, FIELD_FOCUS_RING, HIGHLIGHT, INFO, INVALID, MARKER, NEUTRAL, SERIES_LINE, SESSION_OFF, SESSION_POST, SESSION_PRE, SLATE, SLATE_DEEP, TRADE_EXIT, TRADE_LONG, TRADE_SHORT, VALID, WARNING, categoricalColor, icon, iconAt, iconEl, iconMarkup, injectStyles, isDarkColor, mix, overlayScrollbarCss, registerIcon, svg16, svg24, svg24Solid, withAlpha };
274
+ // src/ui/surface-events.ts
275
+ var SURFACE_OPEN_EVENT = "vela:surface-open";
276
+ var SURFACE_CLOSE_EVENT = "vela:surface-close";
277
+ function announceSurface(el, open, kind, trigger = null) {
278
+ const View = el.ownerDocument.defaultView;
279
+ if (!View) return;
280
+ el.dispatchEvent(
281
+ new View.CustomEvent(open ? SURFACE_OPEN_EVENT : SURFACE_CLOSE_EVENT, { bubbles: true, composed: true, detail: { kind, trigger } })
282
+ );
283
+ }
284
+
285
+ // src/ui/surface-exit.ts
286
+ var CLOSING_ATTR = "data-closing";
287
+ var SURFACE_EXIT_CAP_MS = 1e3;
288
+ function animationsOf(els) {
289
+ return els.flatMap((el) => el.getAnimations({ subtree: true }));
290
+ }
291
+ function ends(a) {
292
+ return Number.isFinite(a.effect?.getComputedTiming().endTime ?? Number.POSITIVE_INFINITY);
293
+ }
294
+ function holdForExit(els, done, opts = {}) {
295
+ const list = Array.isArray(els) ? els : [els];
296
+ if (list.length === 0 || !list.every((el) => typeof el.getAnimations === "function")) {
297
+ done();
298
+ return null;
299
+ }
300
+ const before = new Set(animationsOf(list));
301
+ for (const el of list) el.setAttribute(CLOSING_ATTR, "");
302
+ const exit = animationsOf(list).filter((a) => !before.has(a) && ends(a));
303
+ if (exit.length === 0) {
304
+ for (const el of list) el.removeAttribute(CLOSING_ATTR);
305
+ done();
306
+ return null;
307
+ }
308
+ const frozen = [...list, ...opts.inert ?? []];
309
+ const wasInert = frozen.map((el) => el.inert);
310
+ for (const el of frozen) el.inert = true;
311
+ let settled = false;
312
+ const settle = (complete) => {
313
+ if (settled) return;
314
+ settled = true;
315
+ clearTimeout(cap);
316
+ for (const el of list) el.removeAttribute(CLOSING_ATTR);
317
+ frozen.forEach((el, i) => {
318
+ el.inert = wasInert[i];
319
+ });
320
+ if (complete) done();
321
+ };
322
+ const cap = setTimeout(() => settle(true), SURFACE_EXIT_CAP_MS);
323
+ let pending = exit.length;
324
+ const one = () => {
325
+ if (--pending === 0) settle(true);
326
+ };
327
+ for (const a of exit) a.finished.then(one, one);
328
+ return {
329
+ cancel: () => settle(false),
330
+ finish: () => settle(true)
331
+ };
332
+ }
333
+
334
+ export { ACCENT, ACCENT_BRIGHT, BEARISH, BULLISH, CATEGORICAL, CHIP_PLATE, CROSSHAIR, FIELD_FOCUS_CSS, FIELD_FOCUS_RING, HIGHLIGHT, INFO, INVALID, MARKER, NEUTRAL, SERIES_LINE, SESSION_OFF, SESSION_POST, SESSION_PRE, SLATE, SLATE_DEEP, SURFACE_CLOSE_EVENT, SURFACE_OPEN_EVENT, TRADE_EXIT, TRADE_LONG, TRADE_SHORT, VALID, WARNING, announceSurface, categoricalColor, holdForExit, icon, iconAt, iconEl, iconMarkup, injectStyles, isDarkColor, mix, overlayScrollbarCss, registerIcon, svg16, svg24, svg24Solid, withAlpha };